#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include "coc_logfile.h"
#include "myc_time.h"
#include "myc_err.h"
#include "myc_str.h"

static FILE *fil=NULL;
static char lnam[224]="";
static char filnam[256]="";
static char ebuf[20000]="";
static char *statusBuf=NULL;
static int statusSize;
static char *eptr=&ebuf[0];
static int dated=0;
static int logMask=0;
static int wrtMask=0;
static int newLine=1;
static int logfileStd;
static int dirty, writeAll;
static int lastline=1;
static long int lastpos=0;
static int lastStamp=0;
static int openTime;
static int openDate;

void (*logfileOutRtn)(int, char *)=NULL;

void logfileOpen(int append) {
  int year;

  if (logfileStd) {
    fil=stdout;
    str_copy(filnam, "<stdout>");
    return;
  }
  assert(fil==NULL);
  openTime = mycNow();
  openDate = mycDate(openTime); /* date in yyyymmdd decimal encoding */
  openTime = openTime % (24*3600); /* seconds since midnight */
  if (dated) {
    sprintf(filnam, "%s%02d-%02d.log", lnam, openDate % 10000 / 100, openDate % 100);
  } else {
    if (lnam[0]=='\0') {
      str_copy(filnam, "test.log");
    } else {
      str_copy(filnam, lnam);
      str_append(filnam, ".log");
    }
  }
  if (dated) {
    fil=fopen(filnam, "a+");
    if (fil != NULL) {
      fseek(fil, 0, SEEK_SET); /* set position to start */
      year=0;
      fscanf(fil, "%4d", &year);
      if (year != openDate / 10000) {
        fclose(fil);
        fil=fopen(filnam, "w+"); /* overwrite old logfile */
      } else {
        fseek(fil, 0, SEEK_END); /* set position to end */
      }
    }
  } else {
    fil=fopen(filnam, "w+"); /* overwrite last logfile */
  }
  if (fil==NULL) {
    printf("Can not open %s\n", filnam);
    fil=stdout;
    str_copy(filnam, "<stdout>");
    return;
  }
  ErrSetOutFile(fil);
  fprintf(fil, "%04d-%02d-%02d %02d:%02d:%02d opened logfile\n"
    , openDate / 10000, openDate % 10000 / 100, openDate % 100
    , openTime / 3600,  openTime / 60 % 60,     openTime % 60);
  lastStamp = openTime / 60;
}

void logfileStatusBuffer(char *buffer, int bufsize) {
  statusBuf=buffer;
  statusSize=bufsize-1;
}

char *logfileInit(char *path, int nodate, int use_stdout, int write_all) {
  str_copy(lnam, path);
  logfileStd=use_stdout;
  writeAll=write_all;
  dated=!nodate;
  logfileOpen(1);
  return(filnam);
}

void logfileStamp(char *text) {
  int time, date, stamp;

  time = mycNow();
  date = mycDate(time); /* date in yyyymmdd decimal encoding */
  time = time % (24*3600); /* seconds since midnight */

  stamp=time / 60;
  if (date != openDate ) { /* day has changed -> new logfile */
    if (fil!=NULL) { fclose(fil); fil=NULL; }
    lastpos=0;
    lastline=1;
    logfileOpen(1);
  }
  if (text==NULL) {
    if (stamp>lastStamp+1) {
      fprintf(fil, "---\t%02d:%02d:%02d\n", stamp / 60, stamp % 60, time % 60);
    }
  } else {
    fprintf(fil, "\t%02d:%02d:%02d%s", stamp / 60, stamp % 60, time % 60, text);
  }
  dirty=0;
  lastStamp=stamp;      
}

void logfileOut(int mask, const char *fmt, ...)
{ va_list ap;
  char buf[8192], *p;
  int l;

  va_start(ap, fmt);

  if (mask & LOG_STAT && statusBuf!=NULL) {
    if (newLine) {
      statusBuf[0]='\0';
      newLine=0;
    }
    buf[statusSize]='\0';
    vsprintf(buf, fmt, ap);
    assert(buf[statusSize]=='\0'); /* check for overflow */
    if (buf[0]=='@') {
      if (buf[1]==' ') {
        str_ncat(statusBuf, buf+2, statusSize);
      } else {
        str_ncat(statusBuf, buf+1, statusSize);
      }
    } else {
      str_ncat(statusBuf, buf, statusSize);
    }
    p=strchr(statusBuf, '\n');
    if (p!=NULL) {
      newLine=1;
      *p='\0';
    }
  }
  if (logfileOutRtn!=NULL) {
    vsprintf(buf, fmt, ap); /* does not check for overflow! */
    logfileOutRtn(mask, buf);
  }
  if (writeAll) {
    vfprintf(fil, fmt, ap);
    wrtMask=LOG_ALL;
  } else if ((mask | LOG_NET) != LOG_NET) { /* do not store LOG_NET info */
    if (eptr!=NULL) {
      if (eptr-ebuf > sizeof(ebuf)-512) {
        sprintf(eptr, "... buffer full ... \1\1");
        eptr=NULL;
      } else {
        vsprintf(eptr, fmt, ap);
        p=strchr(eptr, '\1');
        if (p==NULL) {
          l=strlen(eptr);
          if (l>256) {
            sprintf(buf, "unusual long output %.32s... (%d chars)\n", eptr, l);
            logfileStamp(buf);
          }
          eptr+=l;
        } else {
          eptr=p; /* is in fact an error */
        }
        eptr[0]='\1'; /* put \1 as separator between blocks */
        eptr[1]=mask & 31;
        eptr[2]='\0';
        eptr+=2;
      }
    }
    wrtMask=wrtMask | mask;
  }
  va_end(ap);
}

void logfileMask(int mask) {
  logMask=logMask | mask;
}

void logfileWrite0(int mask) {
  char *s, *next;

  logMask=logMask | mask;
  if (dirty) logfileStamp(NULL); /* there was something written since last time */

  s=ebuf;
  if (writeAll || (*s!='\0' && (wrtMask & logMask))) {
    next=strchr(s, '\1');
    while (next!=NULL) {
      *next='\0';
      next++;
      if (*next & logMask) {
        if (*s=='@') { /* write out time */
          logfileStamp(s+1);
        } else {
          logfileStamp(NULL); /* write stamp before write something */
          fprintf(fil, "%s", s);
          dirty=1;
        }
      }
      s=next+1;
      next=strchr(s, '\1');
    }
  }
  ebuf[0]='\0';
  eptr=&ebuf[0];
  logMask=0;
  wrtMask=0;
}

void logfileScan(int date, void (*scanLine)(void*, char*), void *arg) {
  FILE *sFile;
  char buf[256], *res;

  if (date == openDate) {
    sFile=fil;
    rewind(sFile);
  } else {
    sprintf(buf, "%s%02d-%02d.log", lnam, date % 10000 / 100, date % 100);
    sFile=fopen(buf, "r+");
    if (sFile==NULL) return;
  }
  res=fgets(buf, sizeof(buf), sFile);
  while (res!=NULL) {
    if (res[0]=='\t') {
      scanLine(arg, res+1);
    }
    res=fgets(buf, sizeof(buf), sFile);
  }
  if (sFile==fil) {
    fseek(fil, 0, SEEK_END);
  } else {
    fclose(sFile);
  }
}

void logfileWrite(int mask) {
  logfileWrite0(mask);
  fflush(fil);
}

void logfileShowErr(char *text)
{
  logfileWrite0(LOG_ALL); /* write all */
  ErrShow(text);
  fflush(fil);
}

void logfileClose()
{
  logfileWrite0(LOG_MAIN+LOG_INFO);
  lastStamp=-2;
  logfileOut(LOG_MAIN, "closed\n");
  logfileStamp(NULL);
  if (fil!=NULL) { fclose(fil); fil=NULL; }
  filnam[0]='\0';
}

void logfileOutBuf(int mask, StrBuf *buf)
{ char str[256];
  int rd0;
  char *ret;

  rd0=buf->rdpos;
  if (buf->rdpos < 0 || buf->rdpos >= buf->dsize
    || buf->wrpos < 0 || buf->wrpos >= buf->dsize) {
    logfileOut(mask, "<buffer corrupt>"); return;
  }
  while (!StrEnd(buf)) {
    ret=StrGet(buf, str, '\0');
    if (ret==NULL) {
      ret="...";
      buf->rdpos=buf->wrpos;
    }
    if (ret[0]<32 && ret[0]>0) {
      logfileOut(mask, "[%d]%s;", ret[0], ret+1);
    } else {
      logfileOut(mask, "%s;", ret);
    }
  }
  buf->rdpos=rd0;
}

int logfileLength(void) {
  char lin[256];
  int l, ll;

  if (logfileStd || fil==NULL) return 0;
  fseek(fil, lastpos, SEEK_SET);
  ll=lastpos;
  l=lastline;
  while (NULL!=fgets(lin, sizeof(lin), fil)) {
    lastpos=ll;
    ll=ftell(fil);
    lastline=l;
    l++;
  }
  return(lastline);
}

long int logfilePos(int n) {
  /* set file to the start of n-th last line n, if n<0 then position to the -n-th line  */
  int i;
  char lin[256];

  if (logfileStd || fil==NULL) return 0;
  if (n>0) {
    n=logfileLength()-n+1;
  } else {
    n=-n;
  }
  fseek(fil, 0, SEEK_SET);
  for (i=1; i<n; i++) {
    if (NULL==fgets(lin, sizeof(lin), fil)) return ftell(fil);
  }
  return ftell(fil);
}


long int logfileGetLines(long int pos, int linmax, char *line, int len_line) {
  int l,i;
  if (logfileStd || fil==NULL) {
    line[0]='\0';
    return 0;
  }
  l=0;
  fseek(fil, pos, SEEK_SET);
  for (i=0; i<linmax; i++) {
    if (NULL==fgets(line+l, len_line-l, fil)) {
      line[l]='\0';
      return ftell(fil);
    }
    l=strlen(line);
  }
  pos=ftell(fil);
  fseek(fil, 0, SEEK_END);
  return pos;
}
