#include <stddef.h>
#include <string.h>
#include <ctype.h>

#include "dmemory.h"
#include "cdi.h"
#include "cdi_int.h"


#include "tablepar.h"
#include "table.h"

#define MAX_TABLE  256
#define MAX_PARS   1024

typedef struct
{
  bool   used;
  int    npars;
  int    modelID;
  int    number;
  char  *name;
  param_type *pars;
}
paramtab_type;

static paramtab_type parTable[MAX_TABLE];
static int  parTableSize = MAX_TABLE;
static int  parTableNum  = 0;
static int  ParTableInit = 0;

static char *tablePath = NULL;

static void tableDefModelID(int tableID, int modelID);
static void tableDefNum(int tableID, int tablenum);

static
void tableDefEntry(int tableID, int id, int ltype, const char *name,
		   const char *longname, const char *units)
{
  if ( tableID >= 0 && tableID < MAX_TABLE && parTable[tableID].used) { } else
    Error("Invalid table ID %d", tableID);

  int item = parTable[tableID].npars++;
  parTable[tableID].pars[item].id       = id;
  parTable[tableID].pars[item].ltype    = ltype;
  parTable[tableID].pars[item].dupflags = 0;
  parTable[tableID].pars[item].name     = NULL;
  parTable[tableID].pars[item].longname = NULL;
  parTable[tableID].pars[item].units    = NULL;

  if ( name && name[0] )
    {
      parTable[tableID].pars[item].name     = strdupx(name);
      parTable[tableID].pars[item].dupflags |= TABLE_DUP_NAME;
    }
  if ( longname && longname[0] )
    {
      parTable[tableID].pars[item].longname = strdupx(longname);
      parTable[tableID].pars[item].dupflags |= TABLE_DUP_LONGNAME;
    }
  if ( units && units[0] )
    {
      parTable[tableID].pars[item].units    = strdupx(units);
      parTable[tableID].pars[item].dupflags |= TABLE_DUP_UNITS;
    }
}

void tableLink(int tableID, const param_type *pars, int npars)
{
  for ( int item = 0; item < npars; item++ )
    {
      parTable[tableID].pars[item].id       = pars[item].id;
      parTable[tableID].pars[item].ltype    = pars[item].ltype;
      parTable[tableID].pars[item].dupflags = 0;
      parTable[tableID].pars[item].name     = pars[item].name;
      parTable[tableID].pars[item].longname = pars[item].longname;
      parTable[tableID].pars[item].units    = pars[item].units;
    }

  parTable[tableID].npars = npars;
}

static void parTableInitEntry(int tableID)
{
  parTable[tableID].used    = false;
  parTable[tableID].pars    = NULL;
  parTable[tableID].npars   = 0;
  parTable[tableID].modelID = CDI_UNDEFID;
  parTable[tableID].number  = CDI_UNDEFID;
  parTable[tableID].name    = NULL;
}

static void tableGetPath(void)
{
  char *path = getenv("TABLEPATH");
  if ( path ) tablePath = strdupx(path);
  // printf("tablePath = %s\n", tablePath);
}

static void parTableFinalize(void)
{
  for (int tableID = 0; tableID < MAX_TABLE; ++tableID)
    if (parTable[tableID].used)
      {
        int npars = parTable[tableID].npars;
        for (int item = 0; item < npars; ++item)
          {
            if (parTable[tableID].pars[item].dupflags & TABLE_DUP_NAME)
              Free((void *)parTable[tableID].pars[item].name);
            if (parTable[tableID].pars[item].dupflags & TABLE_DUP_LONGNAME)
              Free((void *)parTable[tableID].pars[item].longname);
            if (parTable[tableID].pars[item].dupflags & TABLE_DUP_UNITS)
              Free((void *)parTable[tableID].pars[item].units);
          }
        Free(parTable[tableID].pars);
        Free(parTable[tableID].name);
      }
}

static void parTableInit(void)
{
  ParTableInit = 1;

  atexit(parTableFinalize);
  if ( cdiPartabIntern ) tableDefault();

  tableGetPath();
}

static int tableNewEntry()
{
  int tableID = 0;

  static int init = 0;
  if ( ! init )
    {
      for ( tableID = 0; tableID < parTableSize; tableID++ )
	parTableInitEntry(tableID);
      init = 1;
    }

  // Look for a free slot in parTable.
  for ( tableID = 0; tableID < parTableSize; tableID++ )
    {
      if ( ! parTable[tableID].used ) break;
    }

  if ( tableID == parTableSize ) Error("no more entries!");

  parTable[tableID].used = true;
  parTableNum++;

  return tableID;
}

static int
decodeForm1(char *pline, char *name, char *longname, char *units)
{
  char *pstart, *pend;

  /* FIXME: parse success isn't verified */
  /* long level =  */strtol(pline, &pline, 10);
  while ( isspace((int) *pline) ) pline++;

  pstart = pline;
  while ( ! (isspace((int) *pline) || *pline == 0) ) pline++;
  size_t len = (size_t)(pline - pstart);
  if ( len > 0 )
    {
      memcpy(name, pstart, len);
      name[len] = 0;
    }
  else
    return 0;

  if ( pline[0] == 0 ) return 0;

  /* Format 1 : code name add mult longname [units] */
  /* FIXME: successful parse isn't verified */
  /* double add  =  */strtod(pline, &pline);
  /* FIXME: successful parse isn't verified */
  /* double mult =  */strtod(pline, &pline);

  while ( isspace((int) *pline) ) pline++;

  len = strlen(pline);
  if ( len > 0 )
    {
      pstart = pline;
      pend = strrchr(pline, '[');
      if ( pend == pstart )
        len = 0;
      else
        {
          if ( pend )
            pend--;
          else
            pend = pstart + len;
          while ( isspace((int) *pend) ) pend--;
          len = (size_t)(pend - pstart + 1);
        }
      if ( len > 0 )
	{
	  memcpy(longname, pstart, len);
	  longname[len] = 0;
	}
      pstart = strrchr(pline, '[');
      if ( pstart )
	{
	  pstart++;
	  while ( isspace((int) *pstart) ) pstart++;
	  pend = strchr(pstart, ']');
	  if ( ! pend ) return 0;
	  pend--;
	  while ( isspace((int) *pend) ) pend--;
	  len = (size_t)(pend - pstart + 1);
	  if ( len > 0 )
	    {
	      memcpy(units, pstart, len);
	      units[len] = 0;
	    }
	}
    }

  return 0;
}

static int
decodeForm2(char *pline, char *name, char *longname, char *units)
{
  // Format 2 : code | name | longname | units
  char *pend;

  pline = strchr(pline, '|');
  pline++;

  while ( isspace((int) *pline) ) pline++;
  if (*pline != '|')
    {
      pend = strchr(pline, '|');
      if ( ! pend )
        {
          pend = pline;
          while ( ! isspace((int) *pend) ) pend++;
          size_t len = (size_t)(pend - pline);
          if ( len > 0 )
            {
              memcpy(name, pline, len);
              name[len] = 0;
            }
          return 0;
        }
      else
        {
          pend--;
          while ( isspace((int) *pend) ) pend--;
          size_t len = (size_t)(pend - pline + 1);
          if ( len > 0 )
            {
              memcpy(name, pline, len);
              name[len] = 0;
            }
        }
    }
  else
    name[0] = '\0';

  pline = strchr(pline, '|');
  pline++;
  while ( isspace((int) *pline) ) pline++;
  pend = strchr(pline, '|');
  if ( !pend ) pend = strchr(pline, 0);
  pend--;
  while ( isspace((int) *pend) ) pend--;
  {
    size_t len = (size_t)(pend - pline + 1);
    if ( len > 0 )
      {
        memcpy(longname, pline, len);
        longname[len] = 0;
      }
  }

  pline = strchr(pline, '|');
  if ( pline )
    {
      pline++;
      while ( isspace((int) *pline) ) pline++;
      pend = strchr(pline, '|');
      if ( !pend ) pend = strchr(pline, 0);
      pend--;
      while ( isspace((int) *pend) ) pend--;
      ptrdiff_t len = pend - pline + 1;
      if ( len < 0 ) len = 0;
      memcpy(units, pline, (size_t)len);
      units[len] = 0;
    }

  return 0;
}


int tableRead(const char *tablefile)
{
  char line[1024], *pline;
  int lnr = 0;
  char name[256], longname[256], units[256];
  int err;
  int tableID = CDI_UNDEFID;

  FILE *tablefp = fopen(tablefile, "r");
  if ( tablefp == NULL ) return tableID;

  const char *tablename = strrchr(tablefile, '/');
  if ( tablename == 0 ) tablename = tablefile;
  else                  tablename++;

  tableID = tableDef(-1, 0, tablename);

  while ( fgets(line, 1023, tablefp) )
    {
      size_t len = strlen(line);
      if ( line[len-1] == '\n' ) line[len-1] = '\0';
      lnr++;
      int id      = CDI_UNDEFID;
      int ltype   = CDI_UNDEFID;
      name[0]     = 0;
      longname[0] = 0;
      units[0]    = 0;
      if ( line[0] == '#' ) continue;
      pline = line;

      len = strlen(pline);
      if ( len < 4 ) continue;
      while ( isspace((int) *pline) ) pline++;
      id = atoi(pline);
      // if ( id > 255 ) id -= 256;
      if ( id == 0 ) continue;

      while ( isdigit((int) *pline) ) pline++;

      if ( *pline == ';' || *pline == ':' )
        {
          pline++;
          ltype = atoi(pline);
          while ( isdigit((int) *pline) ) pline++;

          if ( *pline == ';' || *pline == ':' )
            {
              pline++;
              while ( isdigit((int) *pline) ) pline++;
            }
        }

      while ( isdigit((int) *pline) ) pline++;

      if ( strchr(pline, '|') )
	err = decodeForm2(pline, name, longname, units);
      else
	err = decodeForm1(pline, name, longname, units);

      if ( err ) continue;

      if ( name[0] == 0 ) sprintf(name, "var%d", id);

      tableDefEntry(tableID, id, ltype, name, longname, units);
    }

  return tableID;
}


static int tableFromEnv(int modelID, int tablenum)
{
  char tablename[256] = {'\0'};
  size_t tablenameLen = 0;
  int instID;

  const char *name2Use;
  {
    const char *modelName, *instName;
    if ( (modelName = modelInqNamePtr(modelID)) )
      name2Use = modelName;
    else if ( (instID = modelInqInstitut(modelID)) != CDI_UNDEFID
              && (instName = institutInqNamePtr(instID)) )
      name2Use = instName;
    else
      return CDI_UNDEFID;
  }
  tablenameLen = strlen(name2Use);
  memcpy(tablename, name2Use, tablenameLen);
  if ( tablenum )
    tablenameLen
      += (size_t)(sprintf(tablename+tablenameLen, "_%03d", tablenum));
  size_t lenp = 0, lenf = tablenameLen;
  if ( tablePath )
    lenp = strlen(tablePath);
  /* if (tablePath) printf("tablePath = %s\n", tablePath); */
  /* if (tablename) printf("tableName = %s\n", tablename); */
  char *tablefile = (char *) Malloc(lenp+lenf+3);
  if ( tablePath )
    {
      strcpy(tablefile, tablePath);
      strcat(tablefile, "/");
    }
  else
    tablefile[0] = '\0';
  strcat(tablefile, tablename);
  /* if (tablefile) printf("tableFile = %s\n", tablefile); */

  int tableID = tableRead(tablefile);
  if ( tableID != CDI_UNDEFID )
    {
      tableDefModelID(tableID, modelID);
      tableDefNum(tableID, tablenum);
    }
  /* printf("tableID = %d %s\n", tableID, tablefile); */
  Free(tablefile);

  return tableID;
}

int tableInq(int modelID, int tablenum, const char *tablename)
{
  int tableID = CDI_UNDEFID;
  int modelID2 = CDI_UNDEFID;
  char tablefile[256] = {'\0'};

  if ( ! ParTableInit ) parTableInit();

  if ( tablename )
    {
      strcpy(tablefile, tablename);
      /*
      printf("tableInq: tablefile = >%s<\n", tablefile);
      */
      /* search for internal table */
      for ( tableID = 0; tableID < MAX_TABLE; tableID++ )
	{
	  if ( parTable[tableID].used && parTable[tableID].name )
	    {
	      /* len = strlen(parTable[tableID].name); */
	      size_t len = strlen(tablename);
	      if ( memcmp(parTable[tableID].name, tablename, len) == 0 ) break;
	    }
	}
      if ( tableID == MAX_TABLE ) tableID = CDI_UNDEFID;
      if ( CDI_Debug )
	Message("tableID = %d tablename = %s", tableID, tablename);
    }
  else
    {
      for ( tableID = 0; tableID < MAX_TABLE; tableID++ )
	{
	  if ( parTable[tableID].used )
	    {
	      if ( parTable[tableID].modelID == modelID &&
		   parTable[tableID].number  == tablenum ) break;
	    }
	}

      if ( tableID == MAX_TABLE ) tableID = CDI_UNDEFID;

      if ( tableID == CDI_UNDEFID )
	{
	  if ( modelID != CDI_UNDEFID )
	    {
              const char *modelName;
	      if ( (modelName = modelInqNamePtr(modelID)) )
		{
		  strcpy(tablefile, modelName);
		  size_t len = strlen(tablefile);
		  for ( size_t i = 0; i < len; i++)
		    if ( tablefile[i] == '.' ) tablefile[i] = '\0';
		  modelID2 = modelInq(-1, 0, tablefile);
		}
	    }
	  if ( modelID2 != CDI_UNDEFID )
	    for ( tableID = 0; tableID < MAX_TABLE; tableID++ )
	      {
		if ( parTable[tableID].used )
		  {
		    if ( parTable[tableID].modelID == modelID2 &&
			 parTable[tableID].number  == tablenum ) break;
		  }
	      }
	}

      if ( tableID == MAX_TABLE ) tableID = CDI_UNDEFID;

      if ( tableID == CDI_UNDEFID && modelID != CDI_UNDEFID )
	tableID = tableFromEnv(modelID, tablenum);

      if ( CDI_Debug )
	if ( tablename )
	  Message("tableID = %d tablename = %s", tableID, tablename);
    }

  return tableID;
}

int tableDef(int modelID, int tablenum, const char *tablename)
{
  int tableID = CDI_UNDEFID;

  if ( ! ParTableInit ) parTableInit();
  /*
  if ( ! (modelID == CDI_UNDEFID && tablenum == 0) )
    tableID = tableInq(modelID, tablenum, tablename);
    */
  if ( tableID == CDI_UNDEFID )
    {
      tableID = tableNewEntry();

      parTable[tableID].modelID = modelID;
      parTable[tableID].number  = tablenum;
      if ( tablename )
	parTable[tableID].name = strdupx(tablename);

      parTable[tableID].pars = (param_type *) Malloc(MAX_PARS * sizeof(param_type));
    }

  return tableID;
}

static
void tableDefModelID(int tableID, int modelID)
{
  parTable[tableID].modelID = modelID;
}

static
void tableDefNum(int tableID, int tablenum)
{
  parTable[tableID].number  = tablenum;
}


int tableInqNum(int tableID)
{
  int number = 0;

  if ( tableID >= 0 && tableID < MAX_TABLE )
    number = parTable[tableID].number;

  return number;
}


int tableInqModel(int tableID)
{
  int modelID = -1;

  if ( tableID >= 0 && tableID < MAX_TABLE )
    modelID = parTable[tableID].modelID;

  return modelID;
}


static void partabCheckID(int item)
{
  if ( item < 0 || item >= parTableSize ) Error("item %d undefined!", item);

  if ( ! parTable[item].name ) Error("item %d name undefined!", item);
}


const char *tableInqNamePtr(int tableID)
{
  const char *tablename = NULL;

  if ( CDI_Debug ) Message("tableID = %d", tableID);

  if ( ! ParTableInit ) parTableInit();

  if ( tableID >= 0 && tableID < parTableSize )
    if ( parTable[tableID].name )
      tablename = parTable[tableID].name;

  return tablename;
}


void tableWrite(const char *ptfile, int tableID)
{
  size_t maxname = 4, maxlname = 10, maxunits = 2;
  int instID = CDI_UNDEFID;
  int center = 0, subcenter = 0;
  const char *instnameptr = NULL, *modelnameptr = NULL;

  if ( CDI_Debug )
    Message("write parameter table %d to %s", tableID, ptfile);

  if ( tableID == CDI_UNDEFID )
    {
      Warning("parameter table ID undefined");
      return;
    }

  partabCheckID(tableID);

  FILE *ptfp = fopen(ptfile, "w");

  int npars = parTable[tableID].npars;

  for ( int item = 0; item < npars; item++)
    {
      if ( parTable[tableID].pars[item].name )
	{
	  size_t lenname = strlen(parTable[tableID].pars[item].name);
	  if ( lenname  > maxname )  maxname  = lenname;
	}

      if ( parTable[tableID].pars[item].longname )
	{
	  size_t lenlname = strlen(parTable[tableID].pars[item].longname);
	  if ( lenlname > maxlname ) maxlname = lenlname;
	}

      if ( parTable[tableID].pars[item].units )
	{
	  size_t lenunits = strlen(parTable[tableID].pars[item].units);
	  if ( lenunits > maxunits ) maxunits = lenunits;
	}
    }

  int tablenum = tableInqNum(tableID);
  int modelID = parTable[tableID].modelID;
  if ( modelID != CDI_UNDEFID )
    {
      modelnameptr = modelInqNamePtr(modelID);
      instID = modelInqInstitut(modelID);
    }
  if ( instID != CDI_UNDEFID )
    {
      center = institutInqCenter(instID);
      subcenter = institutInqSubcenter(instID);
      instnameptr = institutInqNamePtr(instID);
    }

  fprintf(ptfp, "# Parameter table\n");
  fprintf(ptfp, "#\n");
  if ( tablenum )
    fprintf(ptfp, "# TABLE_ID=%d\n", tablenum);
  fprintf(ptfp, "# TABLE_NAME=%s\n", parTable[tableID].name);
  if ( modelnameptr )
    fprintf(ptfp, "# TABLE_MODEL=%s\n", modelnameptr);
  if ( instnameptr )
    fprintf(ptfp, "# TABLE_INSTITUT=%s\n", instnameptr);
  if ( center )
    fprintf(ptfp, "# TABLE_CENTER=%d\n", center);
  if ( subcenter )
    fprintf(ptfp, "# TABLE_SUBCENTER=%d\n", subcenter);
  fprintf(ptfp, "#\n");
  fprintf(ptfp, "#\n");
  fprintf(ptfp, "# id       = parameter ID\n");
  fprintf(ptfp, "# name     = variable name\n");
  fprintf(ptfp, "# title    = long name (description)\n");
  fprintf(ptfp, "# units    = variable units\n");
  fprintf(ptfp, "#\n");
  fprintf(ptfp, "# The format of each record is:\n");
  fprintf(ptfp, "#\n");
  fprintf(ptfp, "# id | %-*s | %-*s | %-*s\n",
	  (int)maxname,  "name",
	  (int)maxlname, "title",
	  (int)maxunits, "units");

  for ( int item = 0; item < npars; item++)
    {
      const char *name = parTable[tableID].pars[item].name,
        *longname = parTable[tableID].pars[item].longname,
        *units = parTable[tableID].pars[item].units;
      if ( name == NULL ) name = " ";
      if ( longname == NULL ) longname = " ";
      if ( units == NULL ) units = " ";
      fprintf(ptfp, "%4d | %-*s | %-*s | %-*s\n",
	      parTable[tableID].pars[item].id,
	      (int)maxname, name,
	      (int)maxlname, longname,
	      (int)maxunits, units);
    }

  fclose(ptfp);
}


void tableFWriteC(FILE *ptfp, int tableID)
{
  const char chelp[] = "";
  size_t maxname = 0, maxlname = 0, maxunits = 0;
  char tablename[256];


  if ( tableID == CDI_UNDEFID )
    {
      Warning("parameter table ID undefined");
      return;
    }

  partabCheckID(tableID);

  int npars = parTable[tableID].npars;

  for ( int item = 0; item < npars; item++)
    {
      if ( parTable[tableID].pars[item].name )
	{
	  size_t lenname = strlen(parTable[tableID].pars[item].name);
	  if ( lenname  > maxname )  maxname  = lenname;
	}

      if ( parTable[tableID].pars[item].longname )
	{
	  size_t lenlname = strlen(parTable[tableID].pars[item].longname);
	  if ( lenlname > maxlname ) maxlname = lenlname;
	}

      if ( parTable[tableID].pars[item].units )
	{
	  size_t lenunits = strlen(parTable[tableID].pars[item].units);
	  if ( lenunits > maxunits ) maxunits = lenunits;
	}
    }

  strncpy(tablename, parTable[tableID].name, sizeof (tablename));
  tablename[sizeof (tablename) - 1] = '\0';
  {
    size_t len = strlen(tablename);
    for (size_t i = 0; i < len; i++ )
      if ( tablename[i] == '.' ) tablename[i] = '_';
  }
  fprintf(ptfp, "static const param_type %s[] = {\n", tablename);

  for ( int item = 0; item < npars; item++ )
    {
      size_t len = strlen(parTable[tableID].pars[item].name),
        llen = parTable[tableID].pars[item].longname
        ? strlen(parTable[tableID].pars[item].longname) : 0,
        ulen = parTable[tableID].pars[item].units
        ? strlen(parTable[tableID].pars[item].units) : 0;
      fprintf(ptfp, "  {%4d, -1, 0, \"%s\", %-*s%c%s%s, %-*s%c%s%s %-*s},\n",
	      parTable[tableID].pars[item].id,
	      parTable[tableID].pars[item].name, (int)(maxname-len), chelp,
              llen?'"':' ',
              llen?parTable[tableID].pars[item].longname:"NULL",
              llen?"\"":"",
              (int)(maxlname-(llen?llen:3)), chelp,
              ulen?'"':' ',
              ulen?parTable[tableID].pars[item].units:"NULL",
              ulen?"\"":"",
              (int)(maxunits-(ulen?ulen:3)), chelp);
    }

  fprintf(ptfp, "};\n\n");
}


void tableInqEntry(int tableID, int id, int ltype, char *name, char *longname, char *units)
{
  if ( ((tableID >= 0) & (tableID < MAX_TABLE)) | (tableID == CDI_UNDEFID) ) { } else
    Error("Invalid table ID %d", tableID);

  if ( tableID != CDI_UNDEFID )
    {
      int npars = parTable[tableID].npars;
      for ( int item = 0; item < npars; item++ )
	{
	  if ( parTable[tableID].pars[item].id == id &&
               (parTable[tableID].pars[item].ltype == -1 || ltype == -1 ||
                parTable[tableID].pars[item].ltype == ltype) )
	    {
	      if ( name && parTable[tableID].pars[item].name )
		strcpy(name, parTable[tableID].pars[item].name);
	      if ( longname && parTable[tableID].pars[item].longname )
		strcpy(longname, parTable[tableID].pars[item].longname);
	      if ( units && parTable[tableID].pars[item].units )
		strcpy(units, parTable[tableID].pars[item].units);

	      break;
	    }
	}
    }
}


int tableInqParCode(int tableID, char *varname, int *code)
{
  int err = 1;

  if ( tableID != CDI_UNDEFID && varname != NULL )
    {
      int npars = parTable[tableID].npars;
      for ( int item = 0; item < npars; item++ )
	{
	  if ( parTable[tableID].pars[item].name
               && strcmp(parTable[tableID].pars[item].name, varname) == 0 )
            {
              *code = parTable[tableID].pars[item].id;
              err = 0;
              break;
            }
	}
    }

  return err;
}


int tableInqNumber(void)
{
  if ( ! ParTableInit ) parTableInit();

  return parTableNum;
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
