#include <stdlib.h>
#include <float.h>
#include <strings.h>
#include <assert.h>
#include "myc_mem.h"
#include "myc_str.h"
#include "myc_err.h"
#include "myc_time.h"
#include "coc_logfile.h"
#include "tecs_data.h"
#include "logger.h"

#define RUN_SIZE 1024
#define SET_LEN 1024

typedef struct {
  int tmin, tmax, tlim;
  float dmin, dmax;
  int reset;
} Summary;

typedef struct Run {
  struct Run *next;
  int size, step;
  int startTime, endTime;
  float data[RUN_SIZE];
} Run;

typedef struct Set {
  DataSet set;
  struct Set *next;
  int nRuns;
  int step, lifetime;
  int start, end;
  float *var;
  float undef;
  Run *runs;
  Summary sum;
  Logger *log;
} Set;

typedef struct {
  Set *head;
} Base;

typedef struct {
  Base *base;
  int tim, from, to;
  int stdStep;
} ScanData;

static Base database;

static float undef_value = DATA_UNDEF; /* additonal undef value */

void DataUndef(float undef) {
  undef_value = undef;
}

void InsSum(Summary *sum, int t, float d) {
  if (sum->tmin == 0) {
    sum->tmin = t;
    sum->tmax = t;
    sum->dmin = d;
    sum->dmax = d;
  } else {
    if (d <= sum->dmin) {
      sum->dmin = d;
      sum->tmin = t;
    } else if (d >= sum->dmax) {
      sum->dmax = d;
      sum->tmax = t;
    }
  }
}

void ResetSum(Summary *sum) {
  sum->reset = 1;
  sum->tmin = 0;
  sum->dmin = DATA_UNDEF;
  sum->dmax = DATA_UNDEF;
}

void PutSum(Summary *sum, float *data) {
  if (sum->tmin == 0) {
    data[1] = sum->dmin;
    data[0] = sum->dmax;
  } else if (sum->tmin > sum->tmax) {
    data[1] = sum->dmin;
    data[0] = sum->dmax;
    sum->dmin = sum->dmax;
  } else if (sum->tmin < sum->tmax) {
    data[1] = sum->dmax;
    data[0] = sum->dmin;
    sum->dmax = sum->dmin;
  } else {
    data[1] = sum->dmin;
    data[0] = sum->dmax;
  }
  sum->tmin = 0;
  if (sum->reset) {
    sum->dmin = DATA_UNDEF;
    sum->dmax = DATA_UNDEF;
    sum->reset = 0;
  }
}  

Set *FindSet(Base *base, char *name) {
  Set *s;
  int total;

  total=0;
  s = base->head;
  while (s!=NULL && 0!=strcasecmp(name, s->set.name)) {
    s = s->next;
  }
  return s;
}

Set *CreateSet(Base *base, char *name, float *var, int step, int lifetime, int start) {
  Set *s;
  char *nam;
  
  s = FindSet(base, name);
  if (s != NULL) ERR_MSG("dataset exists");
  NEW(s, Set);
  s->next = base->head;
  base->head = s;
  NEW_STR(nam, name);
  s->set.name = nam;
  s->runs = NULL;
  s->nRuns = 0;
  s->lifetime = lifetime;
  s->start = start;
  s->step = step;
  s->var = var;
  s->undef = undef_value;
  s->log = NULL;
  ResetSum(&s->sum);
  return s;
  OnError:
    return NULL;
}
  
DataSet *DataFindSet(DataBase *dBase, char *name) {
  Base *base;
  if (dBase==NULL) {
    base=&database;
  } else {
    base=(Base *)dBase;
  }
  return (DataSet *) FindSet(base, name);
}

DataSet *DataCreateSet(DataBase *dBase, char *name, float *var, int step, int lifetime, int start) {
  Base *base;
  if (dBase==NULL) {
    base=&database;
  } else {
    base=(Base *)dBase;
  }
  return (DataSet *) CreateSet(base, name, var, step, lifetime, start);
}
  
int DataSetStep(DataSet *set, int step) {
  Set *s;

  ERR_P(set);
  ERR_I(step-1); /* step > 0 */
  s = (Set *)set;
  s->step = step;
  return 0;
  OnError: return -1;
}

int DataSetLifetime(DataSet *set, int lifetime) {
  Set *s;

  ERR_P(set);
  s = (Set *)set;
  s->lifetime = lifetime;
  return 0;
  OnError: return -1;
}

Run *InsertRun(Set *s, int time) {
  Run *r, *next, *last;

  NEW(r, Run);
  r->next = s->runs;
  s->runs = r;
  r->step = s->step;
  s->nRuns++;
  if (s->nRuns * s->step > s->lifetime) {
    next=r->next;
    last=r;
    while (next!=NULL && next->endTime > time - s->lifetime) {
      last = next;
      next = last->next;
    }
    while (next!=NULL) {
      last->next = next->next;
      FREE(next);
      s->nRuns--;
      next = last->next;
    }
  }
  return r;
  OnError:
    return NULL;
}

int Put(Set *set, int time, float value) {
  Run *r;
  int t;
  int size;
  float last;
  static int errcnt=0;

  ERR_P(set);
  if (value == DATA_UNDEF) return 0;
  r = set->runs;
  if (r == NULL || r->step != set->step || r->size+3 > RUN_SIZE) {
    ERR_P(r = InsertRun(set, time));
    r->startTime = time;
    r->endTime = time - r->step;
    size = 0;
    t = 1;
  } else {
    size = r->size;
    t = (time - r->endTime) / r->step;
    last = r->data[size-1];
  }
  if (t<0) {
    if (t<-1) {
      if (errcnt<4) {
        errcnt++;
        logfileOut(LOG_MAIN, "%s %d <? %d\n", set->set.name, time, r->endTime);
      }
      ERR_MSG("back in time not possible");
    }
    if (errcnt>0) errcnt--;
    t=0;
  }     
  if (t == 0) {
    InsSum(&set->sum, time, value);
  } else {
    if (size % 2) {
      PutSum(&set->sum, r->data+size-1); size++;
      r->endTime += r->step;
      t--;
    }
    if (t > 2) {
      r->data[size]=t-1; size++;
      r->data[size]=DATA_UNDEF; size++;
      set->sum.reset = 1;
      r->endTime += r->step * (t-1);
      t=1;
    }
    InsSum(&set->sum, time, value);
    if (t % 2) {
      r->data[size]=value; size++; /* put a provisory value */
      r->endTime += r->step;
    } else if (t == 2) {
      PutSum(&set->sum, r->data+size); size+=2;
      r->endTime += r->step * 2;
    }
  }
  r->size = size;
  return 0;
  OnError:
    return -1;
}

int DataPut(DataSet *set, int time, float value) {
  return Put((Set *)set, time, value);
}

int DataPutAll(DataBase *base, int mytime) {
  Set *s;
  char value[32];
  char tname[32];
  static time_t lastOpen = 0;
  float val;
  
  if (base == NULL) {
    s=database.head;
  } else {
    s=((Base *)base)->head;
  }
  while (s!=NULL) {
    if (s->var!=NULL) {
      val = *s->var;
      if (val == s->undef) {
        val = DATA_UNDEF;
      }
      ERR_I(Put(s, mytime, val));
      if (s->log == NULL && val != DATA_UNDEF && time(NULL) > lastOpen + 300) {
        /* try to make logger if not yet tried in the last 5 min. */ 
        snprintf(tname, sizeof tname, "tt.%s", s->set.name);
        s->log = LoggerMake(tname, s->step, 0);
        if (s->log == NULL) {
          lastOpen = time(NULL);
        }
      }
      if (s->log != NULL) {
        if (val == DATA_UNDEF) {
          value[0]='\0';
        } else {
          snprintf(value, sizeof value, "%.5g", val);
        }
        LoggerWrite(s->log, mycUnixTime(mytime), s->step, value);
      }
    }
    s=s->next;
  }
  return 0;
  OnError:
    return -1;
}

int Start(Set *s) {
  Run *r;
  int t1, t2;
  
  r = s->runs;
  t1 = s->start;
  if (r != NULL) {
    t2 = r->endTime - s->lifetime;
    if (t1 < t2) {
      while (r != NULL) {
        t1 = r->startTime;
        r = r->next;
      }
      if (t1 > t2) {
        t1 = t2;
      }
    }
  }
  return t1;
}

void FreeBase(Base *base) {
  Set *s, *sFree;
  Run *r, *rFree;

  s = base->head;
  while (s != NULL) {
    r = s->runs;
    while (r != NULL) {
      rFree = r;
      r = r->next;
      FREE(rFree);
    }
    sFree=s;
    s = s->next;
    FREE(sFree);
  }
}

int GetSet(Set *s, int startTime, int endTime, int size, float *data) {
  Run *r;
  int t, tlim;
  float d;
  int i, idx;
  Summary sum;

  if (size<=0 || size%2 != 0) ERR_MSG("size must be even and positive");
  ERR_P(s);
  ERR_P(data);
  
  r = s->runs;

  ResetSum(&sum);
  idx = size - 2;
  tlim = (endTime - startTime) * idx / size + startTime;

  while (r != NULL) {
    if (startTime > r->endTime) break;
    if (endTime >= r->startTime) {
      t = r->endTime;
      d = 0;
      for (i = r->size - 1; i>=0; i--, t-=r->step) {
        d = r->data[i];
        if (d == DATA_UNDEF) {
          i--;
          t -= (int)(r->data[i] + 0.5) * r->step;
          sum.reset = 1;
        } else {
          if (t < startTime) break;
          if (t < endTime) {
            while (t < tlim) {
              PutSum(&sum, data+idx); idx-=2;
              tlim = (endTime - startTime) * idx / size + startTime;
            }
            InsSum(&sum, t, d);
          }
        }
      }
    }
    r = r->next;
  }
  sum.reset = 1;
  while (idx >= 0) {
    PutSum(&sum, data+idx); idx-=2;
  }
  return 0;
  OnError:
    return -1;
}

void Scan(void *sData, char *line) {
  Set *set;
  Run *run;
  Base *base;
  char str[64], nam[64];
  int l, iret, hh, mm, ss;
  float num;
  int tim, tshift, t0;

  base=((ScanData *)sData)->base;
  tim=((ScanData *)sData)->tim;
  
  /* read time */
  l=0; iret=sscanf(line, "%d:%d:%d%n", &hh, &mm, &ss, &l); line+=l;
  if (iret!=3) return;
  tim+=hh*3600+mm*60+ss;
  if (tim < ((ScanData *)sData)->from || tim > ((ScanData *)sData)->to) return;

  set=NULL;
  l=0; iret=sscanf(line, "%64s%n", str, &l); line+=l;
  while (iret==1) {
    if (str[0] >= 'A') {
      str_copy(nam, str);
      l=0; iret=sscanf(line, "%64s%n", str, &l); line+=l;
      if (iret==1) {
        iret=sscanf(str, "%f", &num);
        if (iret==1) {
          tshift = 0;
          if (0 != strcmp(nam, "_")) {
            set=FindSet(base, nam);
            if (set!=NULL) tshift = 1;
          }
          if (set!=NULL && tim < set->end) {
            if (tshift) {
              t0 = tim - ((ScanData *)sData)->stdStep*2;
              run = set->runs;
              if (run != NULL && run->endTime >= t0) {
                t0 = run->endTime;
              }
              t0 += set->step;
              if (t0 > tim) t0 = tim;
              Put(set, t0, num);
            } else {
              Put(set, tim, num);
            }
          }
        }
      }
    } else {
      l=0; iret=sscanf(line, "%64s%n", str, &l); line+=l;
    }
  }
}

void Load(Base *base, int from, int to, int stdStep) {
  ScanData scanData;
  int t;

  scanData.base = base;
  scanData.stdStep = stdStep;
  scanData.from = from;
  scanData.to = to;

  /* round down "from" and "to" to 00:00 local time */
  from = from - (from % (24*3600));
  to = to - (to % (24*3600));

  for (t=from; t<=to; t+=24*3600) {
    scanData.tim=t;
    logfileScan(mycDate(t), Scan, &scanData);
  }
}

int DataGetMult(char *names, int startTime, int endTime, int step, int stdStep, float *data, int data_len) {
  Base base;
  Set *set, *s;
  int stp, minStep, period, s1;
  char *nams, nam[32];
  int p, l, n, i, j, siz1, siz2, halfsiz, start;

  base.head = NULL;
  period = endTime - startTime;
  if (period<=0) period=1;
  n=0; nams = names;
  while (nams != NULL) { /* count names */
    nams = str_split(nam, nams, ' ');
    if (nam[0] != '\0') n++;
  }

  stp=step;
  if (stp<stdStep) stp=stdStep;
  nams=names;
  while (nams!=NULL) { /* create sets for data not in memory */
    nams=str_split(nam, nams, ' ');
    if (nam[0]!='\0') {
      set=FindSet(&database, nam);
      if (set != NULL) {
        start = Start(set);
      } else {
        start = endTime;
      }
      if (set == NULL || start > startTime) {
        set=CreateSet(&base, nam, NULL, stp, period, startTime);
        if (set != NULL) set->end = start;
      }
    }
  }
  if (base.head != NULL) { /* load if needed */
    Load(&base, startTime, endTime, stdStep);
  }
  p=0;
  nams=names;
  while (nams!=NULL) {
    nams=str_split(nam, nams, ' ');
    if (nam[0]!='\0') {
      s = FindSet(&database, nam);
      set = FindSet(&base, nam);
      s1 = startTime;
      if (s == NULL) {
        if (set == NULL) {
          s1 = 0;
        } else {
          s = set;
        }
      } else {
        minStep = s->step;
        if (set != NULL) {
          s1 = Start(s);
          if (s1 >= endTime) {
            s = set;
            s1 = startTime;
          } else if (s1 < startTime || s1 <= Start(set)) {
            s1 = startTime;
          }
        }
      }
      if (s1 == 0) { /* empty array */
        data[p++]=0;
      } else {
        minStep = s->step;
        stp = step;
        halfsiz = period / (2 * stp) + 1;
        if (startTime + minStep * halfsiz * 2 > endTime) {
          if (stp < minStep) stp = minStep;
          halfsiz = period / (2 * stp) + 1;
          if (halfsiz * 2 + n > data_len) { /* check if enough space */
            halfsiz = ((data_len - p) / n - 1) / 2;
          }
        }
        siz1 = (s1 - startTime) * halfsiz / period * 2;
        siz2 = halfsiz * 2 - siz1;
        s1 = startTime + (siz1 * period + halfsiz) / halfsiz / 2;
        p++;
        if (siz1 > 0) {
          halfsiz = (s1 - startTime) / set->step / 2;
          if (halfsiz * 2 < siz1 && halfsiz > 0) {
            ERR_I(GetSet(set, startTime, s1, halfsiz*2, data+p));
            j = siz1 - 1;
            for (i = halfsiz * 2-1; i >= 0; i--) { /* expand data */
              while (j > siz1 / 2 * i / halfsiz) {
                data[p+j]=DATA_GAP; j--;
              }
              data[p+j]=data[p+i]; j--;
            }
          } else {
            ERR_I(GetSet(set, startTime, s1, siz1, data+p));
          }
          l=siz1;
        } else {
          l=0;
        }
        if (siz2 > 0) {
          ERR_I(GetSet(s, s1, endTime, siz2, data+p+siz1));
          l+=siz2;
        }
        data[p-1]=l;
        p+=l;
      }
      n--;
    }
  }
  FreeBase(&base);
  return p;
  OnError:
    return -1;
}
