/*
 * Title: symtab.c
 * Author: Jed Marti KI7NNP
 * Description: Read the file of parameter ranges and the symbol table.
 * Revision History: (Created Tue Jan 24 08:19:57 2023)
 *   Wed Jan 25 08:03:26 2023 - JBM Symbol table extensions.
 *   Sun Jan 29 12:49:46 2023 - JBM Range extensions.
 *   Mon Jan 30 15:12:26 2023 - JBM Remove lookup stuff, never used.
 *   Mon Feb 27 07:15:17 2023 - JBM Back to cubics.
 *   Sun Dec  3 11:58:37 2023 - JBM Allow generic equations (see equations.h)
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <sys/types.h>
#include <unistd.h>

#include "equations.h"
#include "indequ.h"

// The range symbol table. These must be in the same order as s_... in indequ.h. The defaults
// are probably 0 so they're all wrong and must be set by the configuration file.
range R[] = {
#define X(nme) {.name=#nme},
  VARS
#undef X
};


// For simplicity, this is a separate table. These must be in the same order as the
// symbol enumeration in indequ.h.
symtab S[] = {
  {.name="population", .descriptor=DESC_INT, .value.iv = 64},
  {.name="save", .descriptor=DESC_INT, .value.iv = 4},
  {.name="maxgenerations", .descriptor=DESC_INT, .value.iv = 100000},
  {.name="resultfile", .descriptor=DESC_STRING, .value.sv = NULL},
  {.name="processes", .descriptor=DESC_INT, .value.iv = 1},
  {.name="seed", .descriptor=DESC_LONGINT, .value.lv = 1033888277L},
  {.name="noise", .descriptor=DESC_INT, .value.iv = 1},
  {.name="minormax", .descriptor=DESC_INT, .value.iv = 8388608},
  {.name="randomminor", .descriptor=DESC_DOUBLE, .value.dv=0.33},
  {.name="tries", .descriptor=DESC_INT, .value.iv = 20},
  {.name="resultc", .descriptor=DESC_STRING, .value.sv = NULL},
  {.name="cvarname", .descriptor=DESC_STRING, .value.sv= NULL},
  {.name="pctchange", .descriptor=DESC_DOUBLE, .value.dv=0.01},
  {.name="raiselower", .descriptor=DESC_INT, .value.iv = 10},
  {.name="quitat", .descriptor=DESC_INT, .value.iv=-1},
  {.name="rangeexpand", .descriptor=DESC_INT, .value.iv=0},
  {.name="direction", .descriptor=DESC_DOUBLE, .value.dv = 0.01},
  {.name="resultja", .descriptor=DESC_STRING, .value.sv = NULL},
  {.name="javarname", .descriptor=DESC_STRING, .value.sv = NULL},
#define X(nme) {.name="best"#nme, .descriptor=DESC_DOUBLE, .value.dv = 1.0E208},
   VARS
#undef X
};



void dumpRangeTable(FILE *fh, char *msg)
/*     Used during execution to check current ranges and values. Version 19 allows ranges to be
 *   adjusted at run time.
 * Parameters:
 *   fh: File handle to write to.
 *   msg: Message to show (needs \n).
 * External References:
 *   R[]: The current range table.
 * Returned values: none
 * Errors detected: none
 */
{
  int32_t i;
  fputs(msg, fh);
  for (i = 0; i < sizeof(R) / sizeof(range); ++i)
    fprintf(fh, "%4s: %10g %10g %10g %10g %3d (%d)\n", R[i].name, R[i].low, R[i].high, R[i].major,
            R[i].minor, R[i].mcount, R[i].vcount);
}


#define fixlow(nme)  \
pct = fabs(r->nme - R[s_##nme].low) / (R[s_##nme].high - R[s_##nme].low); \
 if (pct < worstpct) { worstpct = pct;  wname = s_##nme; worst = &R[s_##nme]; }

#define fixhigh(nme) \
pct = fabs(R[s_##nme].high - r->nme) / (R[s_##nme].high - R[s_##nme].low); \
if (pct < worstpct) { worstpct = pct;  wname = s_##nme; worst = &R[s_##nme]; }

static void computeInc(void);

void fixOneRange(result *r)
{
  double pct;
  double worstpct = 1000.0;
  range  *worst;
  static char *names[] =
#define X(nme) #nme,
    { VARS };
#undef X
  int32_t wname;

#define X(nme) fixlow(nme);
  VARS
#undef X

  // If range expand is off, then punt.
  if (S[s_rangeexpand].value.iv == 0) return;
  
  if (worstpct < S[s_pctchange].value.dv)
  {
    printf("  Lowered %s from %g, %g to ", names[wname], R[wname].low, R[wname].high);
    if (worst->low < 0) worst->low += worst->low;
    else if (worst->low == 0) worst->low -= worst->high;
    else worst->low -= worst->low;
    printf(" %g, %g\n", R[wname].low, R[wname].high);
    computeInc();
  }
  
  worstpct = 1000.0;
#define X(nme) fixhigh(nme);
  VARS
#undef X
  if (worstpct < S[s_pctchange].value.dv)
  {
    printf("  Raised %s from %g, %g to ", names[wname], R[wname].low, R[wname].high);
    if (worst->high < 0) worst->high -= worst->high;
    else if (worst->high == 0) worst->high -= worst->low;
    else worst->high += worst->high;
    printf(" %g, %g\n", R[wname].low, R[wname].high);
    computeInc();
  }

}




void checkRangeTable(void)
{
  int32_t i;
  uint8_t iserr = 0;
  for (i = 0; i < sizeof(R) / sizeof(range); ++i)
    if (R[i].mcount < 1)
    {
      printf("Range table error pid %d %s has mcount of %d\n", getpid(), R[i].name, R[i].mcount);
      iserr = 1;
    }
  if (iserr) printf("Task %d abort\n", getpid());

}

      


  
void dumpSymtab(char *msg)
/*     Dump the range table and symbol table contents to stdout.
 * Parameters:
 *   msg: Message to put at top.
 * External References:
 *   R[]: The range table.
 *   S[]: The symbol table.
 * Returned values: none
 * Errors detected: none
 */
{
  int32_t i;

  printf("%s\n", msg);
  //  for (i = 0; i < sizeof(R) / sizeof(range); ++i)
  //    printf("%5s: %g -> %g (%g) %d\n", R[i].name, R[i].low, R[i].high, R[i].major, R[i].vcount);

  for (i = 0; i < sizeof(S) / sizeof(symtab); ++i)
  {
    printf("%20s: ", S[i].name);
    switch (S[i].descriptor) {
    case DESC_INT: printf("%d\n", S[i].value.iv); break;
    case DESC_DOUBLE: printf("%g\n", S[i].value.dv); break;
    case DESC_LONGINT: printf("%ld\n", S[i].value.lv); break;
    case DESC_STRING: printf("'%s'\n", S[i].value.sv); break;
    }
  }
}



static void computeInc(void)
/*     Compute the major increment count. Must be called whenever a range changes.
 * Parameters: none
 * External References:
 *   R[]: The range table, .vcount modified.
 * Returned values: none
 * Errors detected: range value to small or 0.
 */
{
  int32_t i;
  int32_t vc;

  for (i = 0; i < sizeof(R) / sizeof(range); ++i)
  {
    vc = 1 + (int32_t) ((R[i].high - R[i].low) / R[i].major);
    if (vc < 1)
    {
      fprintf(stderr, "%s (%s:%d) %s has bad resolution (%g < %g)/%g\n", __FILE__, __func__,
              __LINE__, R[i].name, R[i].low, R[i].high,R[i].major);
      exit(-1);
    }
    R[i].vcount = vc;
  }
}



void readSymtab(char *fname)
/*      Read the range and symbol table. If the first symbol on a line isn't in the range table
 *   then check the symbol table. For the symbol table, the type must match that declared or
 *   be convertable into it.
 * Parameters:
 *   fname: Fully qualified file name to read.
 * External References:
 *   RP[]: range table.
 *   S[]: scalar table.
 * Returned values: none
 * Errors deteded: Missing values, bad values, name not in table.
 */
{
  FILE    *fh;
  char    iline[256];
  char    *tok;
  int32_t lineno = 0;
  int32_t i;

  if ((fh = fopen(fname, "rb")) == NULL)
  {
    fprintf(stderr, "%s (%s:%d) Couldn't open %s for ranges\n", __FILE__, __func__, __LINE__,
            fname);
    exit(-1);
  }

 loop:
  if (fgets(iline, 256, fh) == NULL)
  {
    fclose(fh);
    computeInc();
    dumpSymtab("Startup");
    return;
  }
  ++lineno;

  // Ignore comments and blank lines.
  if (iline[0] == '/' || iline[0] == '#') goto loop;
  tok = strtok(iline, " ,\n");
  if (tok == NULL) goto loop;

  // Search for the symbol in the table.  
  for (i = 0; i < sizeof(R) / sizeof(range); ++i)
    if (strcasecmp(R[i].name, tok) == 0) break;

  // If not found, then try the other symbol table.
  if (i == sizeof(R) / sizeof(range))
  {
    for (i = 0; i < sizeof(S) / sizeof(symtab); ++i)
      if (strcasecmp(S[i].name, tok) == 0) break;
    if (i == sizeof(S) / sizeof(symtab))
    {
      fprintf(stderr, "%s (%s:%d) At line %d in %s, %s unknown symbol\n", __FILE__, __func__,
              __LINE__, lineno, fname, tok);
      fclose(fh);
      exit(-1);
    }

    tok = strtok(NULL, " \n");
    if (tok == NULL)
    {
      fprintf(stderr, "%s (%s:%d) At line %d in %s, %s has no valuel\n", __FILE__, __func__,
              __LINE__, lineno, fname, S[i].name);
      fclose(fh);
      exit(-1);
    }

    switch (S[i].descriptor) {
    case DESC_INT: S[i].value.iv = strtol(tok, NULL, 0); break;
    case DESC_DOUBLE: S[i].value.dv = atof(tok); break;
    case DESC_LONGINT: S[i].value.lv = atol(tok); break;
    case DESC_STRING: S[i].value.sv = strdup(tok); break;
    }
    goto loop;
  }

  // Get range info.
  // ======================================== LOW ========================================
  tok = strtok(NULL, " ,\n");
  if (tok == NULL)
  {
    fprintf(stderr, "%s (%s:%d) At line %d in %s, missing low value for %s\n", __FILE__, __func__,
            __LINE__, lineno, fname, R[i].name);
    fclose(fh);
    exit(-1);
  }
  R[i].low = atof(tok);

  // ======================================== HIGH =======================================
  // High value.
  tok = strtok(NULL, " ,\n");
  if (tok == NULL)
  {
    fprintf(stderr, "%s (%s:%d) At line %d in %s, missing high value for %s\n", __FILE__, __func__,
            __LINE__, lineno, fname, R[i].name);
    fclose(fh);
    exit(-1);
  }
  R[i].high = atof(tok);

  // ================================ MAJOR RESOLUTION =====================================
  // Resolution.
  tok = strtok(NULL, " ,\n");
  if (tok == NULL)
  {
    fprintf(stderr, "%s (%s:%d) At line %d in %s, missing resolution value for %s\n", __FILE__,
            __func__, __LINE__, lineno, fname, R[i].name);
    fclose(fh);
    exit(-1);
  }
  R[i].major = atof(tok);

  // ================================ MINOR RESOLUTION =====================================
  tok = strtok(NULL, " ,\n");
  if (tok == NULL)
  {
    fprintf(stderr, "%s (%s:%d) At line %d in %s, missing resolution value for %s\n", __FILE__,
            __func__, __LINE__, lineno, fname, R[i].name);
    fclose(fh);
    exit(-1);
  }
  R[i].minor = atof(tok);

  // ================================== MINOR COUNT ========================================
  tok = strtok(NULL, " ,\n");
  if (tok == NULL)
  {
    fprintf(stderr, "%s (%s:%d) At line %d in %s, missing minor count value for %s\n", __FILE__,
            __func__, __LINE__, lineno, fname, R[i].name);
    fclose(fh);
    exit(-1);
  }
  R[i].mcount = atoi(tok);
  if (R[i].mcount == 0)
  {
    fprintf(stderr, "%s (%s:%d) At line %d in %s, minor count value is 0 for %s\n", __FILE__,
            __func__, __LINE__, lineno, fname, R[i].name);
    fclose(fh);
    exit(-1);
  }

  
  goto loop;
}

// EOF
