/*---------------------------------------------------------------------------
logger.c

Markus Zolliker, Sept 2004
----------------------------------------------------------------------------
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <fortify.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#include "splitter.h"
#include "logger.h"

#ifndef TECS_LOG
#include "sics.h"
#endif

#define LOGGER_NAN -999999.
#define ONE_YEAR (366*24*3600)

struct Logger {
  char *name;
  char *old;
  int oldsize;
  int period;
  time_t last;
  int lastLn; /* 0 or the number chars counted from the second
                 last newline character. */
  int overwrite; /* overwrite last line when value has not changed */
  int oldValue; /* old value to be overwritten */
  int numeric;
  int exact;
  Logger *next;
};

static char *dir = NULL;
static Logger *list;
static time_t lastLife = 0;

/*--------------------------------------------------------------------------*/
char *LoggerName(Logger *log) {
  return log->name;
}
/*--------------------------------------------------------------------------*/
void LoggerSetNumeric(Logger *log, int numeric) {
  if (log) {
    log->numeric = numeric;
  }
}
/*--------------------------------------------------------------------------*/
Logger *LoggerFind(const char *name) {
  Logger *p;
  p = list;
  while (p != NULL) {
    if (0==strcasecmp(name, p->name)) {
      return p;
    }
    p = p->next;
  }
  return NULL;
}
/*--------------------------------------------------------------------------*/
#define LASTLOGTXT "#last logging entry at:\n"

void LoggerSetDir(char *dirarg) {
  char path[256], line[32];
  FILE *fil;

  dir = dirarg;
  snprintf(path, sizeof path, "%s/lastlife.dat", dir);
  fil = fopen(path, "r");
  if (fil) {
    fgets(line, sizeof line, fil);
    if (strcmp(line, LASTLOGTXT) == 0) {
      fgets(line, sizeof line, fil);
      lastLife = atol(line);
      if (lastLife < 1000000000) {
        printf("bad lastLife %ld\n", (long)lastLife);
      }
    }
    fclose(fil);
  } else {
    perror("open read error \n");
  }
}
/*--------------------------------------------------------------------------*/
char *LoggerGetDir(void) {
  char path[256];
  FILE *fil;
  time_t now;
  static time_t last;

  now = time(NULL);
  if (now != last) {
    snprintf(path, sizeof path, "%s/lastlife.dat", dir);
    fil = fopen(path, "w");
    if (fil) {
      fprintf(fil, "%s%ld\n", LASTLOGTXT, (long)now);
      fclose(fil);
    } else {
      printf("can not open %s\n", path);
      perror("open write error \n");
    }
    last = now;
  }
  return dir;
}
/*--------------------------------------------------------------------------*/
int LoggerVarPath(char *dir, char *path, int pathLen, char *name) {
  int l;
  
  l = strlen(dir);
  if (l + strlen(name) + 2 >= pathLen) {
    path[0]='\0';
    return 0;
  }
  strcpy(path, dir);
  path[l] = '/'; l++;
  for (;*name != '\0'; name++, l++) {
    path[l] = tolower(*name);
  }
  path[l] = '/'; l++;
  path[l] = '\0';
  return l;
}
/*--------------------------------------------------------------------------*/
int LoggerWrite0(Logger *log, time_t now, int period, char *value) {
  char path[256], stim[32], buf[32], chr;
  struct tm *tm;
  int yday;
  int isdst;
  int l, ext, skip, changed;
  FILE *fil;
  long endPos, pos1, p;
  
  LoggerGetDir();
  if (dir == NULL) return 0;
  if (now == 0) {
    printf("now==0\n");
  }
  tm = localtime(&log->last);
  isdst = tm->tm_isdst;
  yday = tm->tm_yday;
  tm = localtime(&now);
  if (tm->tm_yday != yday) {
    log->period = -1;
  }
  l = LoggerVarPath(dir, path, sizeof path, log->name);

  strftime(path + l, sizeof path - l, "%m-%d.log", tm);
  strftime(stim, sizeof stim, "#%Y-%m-%d %H:%M:%S", tm);
  fil = fopen(path, "r+");
  skip = 0;
  if (fil == NULL) { /* create new file */
    fil = fopen(path, "w+");
    if (fil == NULL) return 0;
    fprintf(fil, "%s isdst %d period %d exact %d\n", stim, tm->tm_isdst, period, log->exact);
    endPos = ftell(fil);
    fseek(fil, -1, SEEK_CUR);
  } else { /* check if file is from today */
    fgets(buf, sizeof buf, fil);
    if (0 != strncmp(buf, stim, 11)) {
      fclose(fil);
      fil=fopen(path, "w+"); /* overwrite old logfile */
      if (fil == NULL) return 0;
      fputs(stim, fil);
      endPos = ftell(fil);
      fseek(fil, -1, SEEK_CUR);
      log->period = -1;
    } else {
      fseek(fil, -log->lastLn, SEEK_END); /* set position to last line */
      endPos = ftell(fil) + log->lastLn;
      if (endPos > 0 && log->lastLn == 0) {
        /* find last newline in newly opened file */
        fseek(fil, -1, SEEK_CUR);
        chr = fgetc(fil);
        while (chr != '\n' && ftell(fil) >= 2) {
          fseek(fil, -2, SEEK_CUR);
          chr = fgetc(fil);
        }
        fseek(fil, -1, SEEK_CUR);
      } else if (now != log->last) {
        skip = 1;
      }
    }
  }
  changed = (0 != strcmp(value, log->old));
  if (period != log->period) {
    log->period = period;
    if (log->numeric) {
      snprintf(buf, sizeof buf, "\t%d", period);
    } else {
      strcpy(buf, "\t0");
    }
    changed = 1;
  } else {
    buf[0]='\0';
    if (log->overwrite && !changed) {
      skip = 0;
    }
  }
  pos1 = 0;
  chr = fgetc(fil);
  while (chr != EOF) {
    if (chr == '\n') {
      skip--;
      if (skip < 0) break;
    }
    chr = fgetc(fil);
  }
  if (chr == EOF) {
    if (pos1 == 0) {
      fputs("\n#no newline found\n", fil);
    } else {
      fseek(fil, pos1, SEEK_SET);
    }
  }
  if (tm->tm_isdst != isdst) {
    fprintf(fil, "#isdst %d\n", tm->tm_isdst);
  }
  pos1 = ftell(fil);
  strftime(stim, sizeof stim,"%H:%M:%S", tm);
  fprintf(fil, "%s\t%s%s\n", stim, value, buf);
  for (p = ftell(fil); p < endPos; p++) { /* overwrite dirt after last line */
    fprintf(fil, " ");
  }
  endPos = ftell(fil);
  fclose(fil);
  log->lastLn = (endPos - pos1) + 1;
  /* overwrite next time when value has not changed */
  log->overwrite = ! changed;
  l = strlen(value);
  if (l >= log->oldsize) {
    ext = ((l - log->oldsize)/16 + 1) * 16;
    if (ext < log->oldsize / 4) ext += (log->oldsize / 64) * 16;
    log->oldsize += ext;
    free(log->old);
    log->old = calloc(1, log->oldsize);
    assert(log->old);
    assert(l < log->oldsize);
  }
  strcpy(log->old, value);
  log->old[l] = '\0';
  log->last = now;
  return 1;
}
/*--------------------------------------------------------------------------*/
int LoggerWrite(Logger *log, time_t now, int period, char *value) {
  Logger *p;
  time_t h0;
  static int yday = -1;
  struct tm *tm;

  int l;
  FILE *fil;
  char path[PATH_MAX];
  char tim[256];

  tm = localtime(&now);
  if (tm->tm_yday != yday) {
    tm->tm_hour = 0;
    tm->tm_min = 0;
    tm->tm_sec = 0;
    h0 = mktime(tm);

    yday = tm->tm_yday;

    l = LoggerVarPath(dir, path, sizeof path, "debug");
    strftime(path + l, sizeof path - l, "%m-%d.log", tm);
    fil=fopen(path, "a");
    if (fil) {
      strftime(tim, sizeof tim, "h0 %m-%d %H:%M:%S\n", localtime(&h0));
      fputs(tim, fil);
    }

    /* log old values (forced midnight log) */
    p = list;
    while (p != NULL) {
      if (fil) {
        strftime(tim, sizeof tim, "last %m-%d %H:%M:%S, ", localtime(&p->last));
        fputs(tim, fil);
        fprintf(fil, "period %d, name %s, old %s\n", p->period, p->name, p->old);
      }
      if (p->last < h0 && p->last != 0 &&
          p->period >= 0 && p->old[0] != '\0') {
        LoggerWrite0(p, h0, p->period, p->old);
        p->overwrite = 0;
      }
      p = p->next;
    }
    if (fil) fclose(fil);
  }
  return LoggerWrite0(log, now, period, value);
}
/*--------------------------------------------------------------------------*/
time_t LoggerLastTime(Logger *log) {
  if (log->last != 0 && log->period > 0) {
    return log->last;
  }
  return 0;
}
/*--------------------------------------------------------------------------*/
int LoggerGetValue(void *data, char *value, int size) {
  Logger *log = data;
  value[0]='\0';
  strncat(value, log->old, size - 1);
  return 1;
}
/*--------------------------------------------------------------------------*/
void LoggerKill(Logger *log) {
  /* we do not really free the logger, it might be reused
     for the same variable later. We set the value to undefined */
  
  if (list != NULL) { /* LoggerFreeAll not yet called */
    LoggerWrite(log, time(NULL), 0, "");
  }
}
/*--------------------------------------------------------------------------*/
Logger *LoggerMake(char *name, int period, int exact) {
  Logger *log;
  char path[256];
  struct stat st;
  int i;
  time_t t;

  LoggerGetDir();
  if (dir == NULL) return NULL;
  LoggerVarPath(dir, path, sizeof path, name);
  i = stat(path, &st);
  if (i >= 0) {
    if (((st.st_mode >> 12) & 15) != 4) { /* exists, but is no directory */
      return NULL;
    }
  } else {
    i = mkdir(path, S_IRWXU+S_IRGRP+S_IXGRP+S_IROTH+S_IXOTH);
    if (i < 0) return NULL; /* mkdir failed */
  }
  log = LoggerFind(name); /* look if logger already exists */
  if (log == NULL) {
    log = calloc(1, sizeof *log);
    if (log == NULL) return NULL;
    log->name = strdup(name);
    if (log->name == NULL) {
      free(log);
      return NULL;
    }
    log->period = -1;
    log->exact = exact;
    log->old = calloc(1,12);
    log->oldsize = 12;
    log->last = 0;
    log->lastLn = 0;
    log->numeric = 1;
    log->next = list;
    list = log;
    t = time(NULL) -1;
    if (lastLife != 0 && lastLife + period < t) {
      t = lastLife + period;
    } 
    LoggerWrite(log, t, period, "");  /* value was undefined since last life of server */
  }
  return log;
}
/*--------------------------------------------------------------------------*/
#ifndef TECS_LOG
typedef enum { NUMERIC, TEXT } CompType;

typedef struct {
  SConnection *pCon;
  char *var;
  int exact;
  CompType type;
  time_t step;
  time_t tlim;
  time_t tmin, tmax, tlast, told;
  float  ymin, ymax, ylast, yold;
  char slast[256];
  char set[256];
  int np;
} Compressor;

static char *dir2 = NULL;

static void LoggerInitC(Compressor *c, SConnection *pCon, time_t step) {
  c->pCon = pCon;
  c->step = step;
  c->tmin = -2;
  c->tmax = 0;
  c->tlast = 0;
  c->tlim = 0;
  c->told = 0;
  c->slast[0]='\0';
  c->ylast = LOGGER_NAN;
}

static void LoggerOutStr(Compressor *c, time_t t, char *str) {
  char line[256];
  
  /* printf("out %ld %g\n", t, y); */
  if (0 != strcmp(str, c->slast)) {
    snprintf(line, sizeof line, "%ld %s\n", (long)(t - c->tlast), str);
    c->tlast = t;
    c->slast[0]='\0';
    strncat(c->slast, str, sizeof c->slast - 1);
    SCWrite(c->pCon, line, eWarning);
    c->np--;
  }
}

static void LoggerOut(Compressor *c, time_t t, float y) {
  char line[80];
  
  /* printf("out %ld %g\n", t, y); */
  if (y != c->ylast) {
    c->ylast = y;
    if (y == LOGGER_NAN) {
      snprintf(line, sizeof line, "%ld\n", (long)(t - c->tlast));
    } else {
      snprintf(line, sizeof line, "%ld %g\n", (long)(t - c->tlast), y);
    }
    /* printf("-%s\n", line); */
    c->tlast = t;
    SCWrite(c->pCon, line, eWarning);
    c->np--;
  }
}

static void LoggerPut(Compressor *c, time_t t, char *value) {
  char *p;
  double y;

  if (c->var) {
    SCPrintf(c->pCon, eWarning, "*%s exact %d\n", c->var, c->exact);
    c->var = NULL;
  }
  if (c->type == NUMERIC) {
    if (t == 0) {  /* finish */
      t = c->tlim + 3 * c->step;
      y = 0;
    } else {
      y = strtod(value, &p);
      if (p == value) {
        y = LOGGER_NAN;
      } else {
        if (y == LOGGER_NAN) y *= 1.0000002;
      }
    }
    /* printf("put %ld %g\n", t, y); */
    if (c->tlim == 0) goto first;
    if (t >= c->tlim) {
      c->tlim += c->step;
      if (c->tmin > c->tmax) {
        LoggerOut(c, c->tmax, c->ymax);
        c->ymax = c->ymin;
        c->tmax = c->tmin;
      } else {
        LoggerOut(c, c->tmin, c->ymin);
        if (c->tmin == c->tmax) goto first;
        c->ymin = c->ymax;
        c->tmin = c->tmax;
      }
      if (t >= c->tlim) {
        LoggerOut(c, c->tmin, c->ymin);
        if (t >= c->tlim + c->step) {
          LoggerOut(c, c->told, c->yold);
        }
        goto first;
      }
    }  
    if (y <= c->ymin) {
      c->ymin = y;
      c->tmin = t;
    } else if (y > c->ymax) {
      c->ymax = y;
      c->tmax = t;
    }
    c->yold = y;
    c->told = t;
    return;
  first:
    c->tlim = t + 2 * c->step;
    c->tmin = t;
    c->tmax = t;
    c->ymin = y;
    c->ymax = y;
    return;
  } else if (c->type == TEXT) {
    if (t != 0) LoggerOutStr(c, t, value);
  }
}
/*--------------------------------------------------------------------------*/
int LoggerGraph(SConnection *pCon, SicsInterp *pSics, void *pData,
                        int argc, char *argv[]) {
  time_t from, to, step, xs, lastt, now;
  long lxs;
  char *p;
  int i, iarg0, l, iret, loss, np;
  int inRange;
  int yday=0;
  time_t t, startim;
  struct tm tm;
  char stim[32], path[256], line[256], lastval[256];
  char *lin, *val, *stp;
  FILE *fil;
  Compressor c;
  float yy, lasty;
  CompType type0;
  DIR *dr;
  char *opt;
  int isdst;
  int overflow;
  
  /* argtolower(argc, argv); */
  if (argc < 4) goto illarg;
  now = time(NULL);
  from = strtol(argv[1], &p, 0);  /* unix time, not year 2038 safe */
  if (p == argv[1]) goto illarg;
  to = strtol(argv[2], NULL, 0);
  if (p == argv[2]) goto illarg;
  if (from <  ONE_YEAR) {
    from += now;
  }
  if (to < ONE_YEAR) {
    to += now;
  }
  iarg0 = 4;
  if (strcasecmp(argv[3],"text") == 0) { /* non-numeric values */
    step = 1;
    type0 = TEXT;
    np = from - to + 2;
  } else if (strcasecmp(argv[3],"np") == 0) { /* max. number of points */
    type0 = NUMERIC;
    iarg0 = 5;
    np = strtol(argv[4], NULL, 0);
    if (to <= from) {
      step = 1;
    } else if (np <= 2) {
      step = to - from;
    } else {
      step = (to - from) / (np - 2) + 1;
    }
  } else {
    step = strtol(argv[3], NULL, 0);
    if (step <= 0) step = 1;
    type0 = NUMERIC;
    np = (from - to) / step + 2;
  }
  if (p == argv[3]) goto illarg;
  if (step <= 0) step = 1;
  
  snprintf(line, sizeof line, "%ld\n", (long)now);
  SCWrite(pCon, line, eWarning);

  if (dir == NULL) {
    dir = IFindOption(pSICSOptions, "LoggerDir");
    if (dir == NULL) {
      SCWrite(pCon, "LoggerDir not found", eError);
      return 0;
    }
    LoggerSetDir(dir);
  }
  if (dir2 == NULL) {
    dir2 = IFindOption(pSICSOptions, "LoggerDir2");
    if (dir2 == NULL) dir2="";
  }
  
  loss = 0;
  overflow = 0;
  for (i=iarg0; i<argc; i++) {
    startim = from;
    t = 0;
    lastt = 0;
    inRange = 0;
    xs = step;
    LoggerInitC(&c, pCon, step);
    c.var = argv[i];
    c.type = type0;
    c.np = np;
    l = LoggerVarPath(dir, path, sizeof path, c.var);
    dr = opendir(path);
    if (dr) {
      closedir(dr);
    } else {
      l = LoggerVarPath(dir2, path, sizeof path, c.var);
      dr = opendir(path);
      if (dr) {
        closedir(dr);
      }
    }

    isdst = -1;
    fil = NULL;
    while (startim <= to && c.tlast <= to) {
      tm = *localtime(&startim);
      if (tm.tm_yday != yday) {
        if (fil != NULL) {  /* close file if day changed */
          fclose(fil);
          fil=NULL;
        }
      }
      if (fil == NULL) {
        yday = tm.tm_yday;
        strftime(path + l, sizeof path - l, "%m-%d.log", &tm);
        fil = fopen(path, "r");
        if (fil != NULL) { /* check if file is from the given year */
          strftime(stim, sizeof stim, "#%Y-%m-%d", &tm);
          fgets(line, sizeof line, fil);
          if (0 != strncmp(line, stim, 11)) {
            fclose(fil);
            fil = NULL;
          } else {
            opt = strstr(line, "isdst");
            if (opt) {
              sscanf(opt, "isdst %d", &isdst);
            }
            opt = strstr(line, "exact");
            if (opt) {
              sscanf(opt, "exact %d", &c.exact);
            }
            opt = strstr(line, "period");
            if (opt) {
              sscanf(opt, "period %ld", &lxs);
              if (lxs == 0) {
                c.type = TEXT;
              } else {
                c.type = type0;
                xs = lxs;
              }
              if (xs < step) {
                loss = 1;
                xs = step;
              }
            }
          }
        }
      }
      if (fil == NULL) {
        lin = NULL;
      } else {
        do {
          lin = fgets(line, sizeof line, fil);
       /* printf("%s\n", line); */
          if (lin == NULL) break;
          if (strncmp(line, "#isdst", 6) == 0) {
            sscanf(line, "#isdst %d", &isdst);
            lin[0]='\0';
          } else if (strncmp(line, "#exact", 6) == 0) {
            sscanf(line, "#exact %d", &c.exact);
            lin[0]='\0';
          } else if (strncmp(line, "#period", 7) == 0) {
            sscanf(line, "#period %ld", &lxs);
            lin[0]='\0';
            if (lxs == 0) {
              c.type = TEXT;
            } else {
              c.type = type0;
              xs = lxs;
            }
            if (xs < step) {
              loss = 1;
              xs = step;
            }
          } else {
            p = strchr(line, '\n'); if (p) *p='\0';
            p = strchr(line, '#'); if (p) *p='\0';
          }
        } while (lin[0] == '\0');
      }
      if (lin != NULL) {
        /* printf(" %s\n", line); */
        p = strchr(line, '\t');
        if (p) {
          *p='\0';
          val = p+1;
        } else {
          val = "";
        }
        p = strchr(val, '\t');
        if (p) {
          stp = p+1;
          *p='\0';
          iret = sscanf(stp, "%ld", &lxs);
          if (iret == 1) {
            if (lxs == 0) {
              c.type = TEXT;
            } else {
              c.type = type0;
              xs = lxs;
            }
            if (xs < step) {
              loss = 1;
              xs = step;
            }
          }
        }
        iret = sscanf(line, "%d:%d:%d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
        if (iret != 3) {
          lin = NULL;
        } else {
          tm.tm_isdst = isdst;
          t=mktime(&tm);
          if (!inRange) {
            if (t < startim) {
              lastval[0]='\0';
              strncat(lastval, val, sizeof lastval - 1);
              lastt = t;
            } else {
              inRange=1;
              if (lastt != 0) {
                LoggerPut(&c, lastt, lastval);
              }
              LoggerPut(&c, t, val);
            }
          } else {
            LoggerPut(&c, t, val);
          }
        }
      }
      if (lin == NULL) {
        tm.tm_hour = 24;  /* try next day */
        tm.tm_min = 0;
        tm.tm_sec = 0;
        tm.tm_isdst = -1;
        startim=mktime(&tm);
        continue;
      }
    }
    if (!inRange) {
      if (lastt != 0) {
        LoggerPut(&c, lastt, lastval);
      }
    }
    c.ylast = LOGGER_NAN; /* force output of last value */
    LoggerPut(&c, 0, ""); /* finish */
    if (fil) {
      fclose(fil);
      fil = NULL;
    }
    if (c.np < 0) overflow = 1;
  }
  snprintf(line, sizeof line, "*%d %d\n", loss, overflow);
  SCWrite(pCon, line, eWarning);
  return 1;
illarg:
  SCWrite(pCon, "illegal argument(s)", eError);
  return 0;
}
/*--------------------------------------------------------------------------*/
void LoggerFreeAll(void *data) {
  Logger *p, *next;
  
  KillDummy(data);
  p = list;
  while (p != NULL) {
    next = p->next;
    if (p->name) free(p->name);
    if (p->old) free(p->old);
    free(p);
    p = next;
  }
  list = NULL;
}
/*--------------------------------------------------------------------------*/
void LoggerInit(void) {
  AddCommand(pServ->pSics,"Graph",LoggerGraph,LoggerFreeAll,NULL);
}
#endif /* TECS_LOG */
