#include "etable.h"
#include "logger.h"

#include "estrarray.h"
#include "earray.h"
#include "estr.h"
#include "evar.h"
#include "efile.h"
#include "eparser.h"

#include "eparserinterpreter.h"
#include "sci/ecorr.h"

#include <math.h>

void emcsv_tokenize_first(const char *str, int strlen, estrarray &tokens);
void emcsv_tokenize_next(const char *str, int strlen, estrarray &tokens);


etable etableLoadCSV2(const estr& filename)
{
  efile f;
  f.open(filename,"r");
  int i;
  estr line;
  estrarray ffields;
  etable t;

  if (!f.readln(line)) { f.close(); return(t); }

  emcsv_tokenize_first(line._str,line.len(),ffields);
  t.setfields(ffields);

  while (f.readln(line)){
    emcsv_tokenize_next(line._str,line.len(),ffields);
    for (i=0; i<t.cols.size() && i<ffields.size(); ++i)
      t.cols.values(i).add(ffields.values(i));
  }
  line.trim();
  if (line.len()){
    emcsv_tokenize_next(line._str,line.len(),ffields);
    for (i=0; i<t.cols.size() && i<ffields.size(); ++i)
      t.cols.values(i).add(ffields.values(i));
  }
  return(t);
}


void ecsv_tokenize(const estr &str, estrarray &tokens)
{
  estr tmps;

  int itoken;

  int i;

  itoken=0;

  bool inside_comma;
  bool inside_quotes;
  bool ignore_next_char;

  inside_comma=false;
  inside_quotes=false;
  ignore_next_char=false;
  for (i=0; i<str.len(); ++i){
    if (inside_quotes){
      if (ignore_next_char){
        switch(str[i]){
          case '\\': tmps+='\\'; break;
          case '"': tmps+='"'; break;
          case 'n': tmps+=(char)0x0A; break;
          case 'r': tmps+=(char)0x0D; break;
        }
        ignore_next_char=false;
      } else {
        if (str[i] == '\\') ignore_next_char=true;
        else if (str[i] == '"'){
	  if (i+1 < str.len() && str[i+1] == '"')
	    { tmps+='"'; tmps+='"'; ++i; }
	  else
            { inside_quotes=false; }
	}
        else {
          tmps+=str[i];
        }
      }
    } else if (inside_comma){
      if (str[i] == ',')
        { inside_comma=false; if (itoken>=tokens.size()) tokens.add(tmps); else tokens.values(itoken) = tmps; tmps=""; }
      else
	tmps += str[i];
    } else {
      if (str[i] == ',')
        { if (itoken >= tokens.size()) tokens.add(tmps); else tokens.values(itoken) = tmps;
	  ++itoken; tmps = ""; }
      else if (str[i] == '"')
        inside_quotes=true;
      else if (str[i] != ' ')
        { inside_comma=true; tmps+=str[i]; }
    }
  }
  if (!inside_quotes) {
    if (itoken>=tokens.size())
      tokens.add(tmps);
    else
      tokens.values(itoken) = tmps;
    ++itoken;
    tmps = "";
  }
}

void emcsv_tokenize_first(const char *str, int strlen, estrarray &tokens)
{
  estr tmps;

  int i;

  bool inside_quotes;
  bool inside_comma;
  bool ignore_next_char;

  inside_comma=false;
  inside_quotes=false;
  ignore_next_char=false;
  for (i=0; i<strlen; ++i){
    if (inside_quotes){
      if (ignore_next_char){
        switch(str[i]){
          case '\\': tmps+='\\'; break;
          case '"': tmps+='"'; break;
          case 'n': tmps+=(char)0x0A; break;
          case 'r': tmps+=(char)0x0D; break;
        }
        ignore_next_char=false;
      } else {
        if (str[i] == '\\') ignore_next_char=true;
        else if (str[i] == '"'){
	  if (i+1 < strlen && str[i+1] == '"')
	    { tmps+='"'; tmps+='"'; ++i; }
	  else
            { inside_quotes=false; }
	} else {
          tmps+=str[i];
        }
      }
    } else if (inside_comma){
      if (str[i] == ',')
        { inside_comma=false; tokens.add(tmps); tmps=""; }
      else
	tmps += str[i];
    } else {
      if (str[i] == ',')
        { tokens.add(tmps); tmps = ""; }
      else if (str[i] == '"')
        inside_quotes=true;
      else if (str[i] != ' ')
        { inside_comma=true; tmps+=str[i]; }
    }
  }
  if (!inside_quotes) { tokens.add(tmps); tmps = ""; }
}

void emcsv_tokenize_next(const char *str, int strlen, estrarray &tokens)
{
  estr *tmps;

  int itoken;

  int i;

  itoken=0;

  bool inside_quotes;
  bool inside_comma;
  bool ignore_next_char;

  inside_comma=false;
  inside_quotes=false;
  ignore_next_char=false;

  lddieif(!tokens.size(),"emcsv_tokenize_next: tokens is empty, cannot write next CSV in empty array");
  tmps = &tokens[0];
  tmps->clear();

  for (i=0; i<strlen; ++i){
    if (inside_quotes){
      if (ignore_next_char){
        switch(str[i]){
          case '\\': *tmps+='\\'; break;
          case '"': *tmps+='"'; break;
          case 'n': *tmps+=(char)0x0A; break;
          case 'r': *tmps+=(char)0x0D; break;
        }
        ignore_next_char=false;
      } else {
        switch(str[i]){
          case '\\': ignore_next_char=true; break;
          case '"':
            if (i+1 < strlen && str[i+1] == '"')
              { *tmps+='"'; *tmps+='"'; ++i; }
            else
              { inside_quotes=false; }
           break;
          default:
            *tmps+=str[i];
        }
      }
    } else if (inside_comma){
      if (str[i] == ',')
        { inside_comma=false; ++itoken; if(itoken>=tokens.size()) return; tmps=&tokens[itoken]; tmps->clear(); }
      else
	*tmps += str[i];
    } else {
      switch(str[i]){
        case ',':
          ++itoken;
          if(itoken>=tokens.size()) return;
          tmps=&tokens[itoken];
          tmps->clear();
         break;
        case '"':
          inside_quotes=true; break;
        default:
          if (str[i] != ' ')
            { inside_comma=true; *tmps+=str[i]; }
      }
    }
  }
  if (!inside_quotes) {
    ++itoken; if(itoken>=tokens.size()) return; tmps=&tokens[itoken]; tmps->clear();
  }
}

estrarrayof<etable> tablesLoad(const estr& filename,const evarhash& options)
{
  size_t b=0;
  size_t e=-1;
  size_t i;
  estrarrayof<etable> ts;
#ifdef EUTILS_HAVE_ZLIB_H
  egzfile f;
#else
  efile f;
#endif
  estr tsep=">";
  estr sep="[ \t]";
  estr tid="";
  eregisterOption(options,tsep);
  eregisterOption(options,sep);
  eregisterOption(options,b);
  eregisterOption(options,e);
  eregisterOption(options,tid);

  ldieif(tsep.len()==0,"empty column separator specified");
  bool header=false;
  eregisterOption(options,header);
//  eregexp re(sep);

  if (!f.open(filename,"r")) { lerror(filename); return(ts); }

  estr line;
  estrarray arr,headerarr;
  etable *pt=0x00;
  int tcount=0;


  if (tid.len())
    tid=tsep+tid;

  if (header) f.readarr(line,headerarr,sep);
  //    headerarr=re_explode(line,re);
  while (f.readarr(line,arr,sep)){
    if (line.len()>0 && line[0]==tsep[0]){
      if (tid.len()){
        if (tid==line){
          pt=&ts.add(line.substr(1),etable());
          pt->blocks.add(0);
        }else
          pt=0x00;
        continue;
      }

      if (tcount>=e && e>0)
        break;
      if (tcount>=b){
        pt=&ts.add(line.substr(1),etable());
        pt->blocks.add(0);
      }
      ++tcount;
      continue;
    }
    if (!pt) continue;

    if (line.len()==0){
      pt->blocks.add(pt->size());
/*
      for (i=0; i<pt->cols.size(); ++i){
        pt->cols.values(i).add(evar());
        pt->cols.values(i).add(evar());
      }
*/
      continue;
    }
//    arr=re_explode(line,re);
    for (i=0; i<arr.size(); ++i){
      if (arr[i].is_float())
        pt->add(i<headerarr.size()?headerarr[i]:estr(i),double(arr[i].f()));
      else if (arr[i].is_int())
        pt->add(i<headerarr.size()?headerarr[i]:estr(i),arr[i].i());
      else
        pt->add(i<headerarr.size()?headerarr[i]:estr(i),arr[i]);
    }
  }
  f.close();
  return(ts);
}

etable etableLoadCols(const estr& filename,const evarhash& options) //,const eregexp& re)
{
  size_t i;
  etable t;
  efile f;
  estr colsep=">";
  eregisterOption(options,colsep);

  ldieif(colsep.len()==0,"empty column separator specified");
//  estr sep="[ \t]";
//  bool header=false;
//  eregisterOption(options,sep);
//  eregisterOption(options,header);
//  eregexp re(sep);

  if (!f.open(filename,"r")) { lerror(filename); return(t); }

  estr line;
  estrarray arr,headerarr;
  evararray *pcol=0x00;
  while (!f.eof() && f.readln(line)){
    if (line.len()==0) continue;
    if (line[0]==colsep[0])
      pcol=&t.cols.add(line.substr(1),evararray());
    else if (pcol){
      if (line.is_float())
        pcol->add(double(line.f()));
      else if (line.is_int())
        pcol->add(line.i());
      else
        pcol->add(line);

    }
  }
  return(t);
}

bool etableSave(const etable& t,const estr& filename,const evarhash& options) //,const eregexp& re)
{
//  size_t b=0;
//  size_t e=-1;
  size_t i;
//#ifdef EUTILS_HAVE_ZLIB_H
//  egzfile f;
//#else
  efile f;
//#endif
  estr sep="\t";
  bool header=true;
//  eregisterOption(options,b);
//  eregisterOption(options,e);
  eregisterOption(options,sep);
  eregisterOption(options,header);
//  eregexp re(sep);

  if (!f.open(filename,"w")) { lerror(filename); return(false); }

  estr line;
  estrarray arr,headerarr;
  if (header) {
    for (i=0; i<t.cols.size(); ++i){
      f.write(t.cols.keys(i));
      if (t.cols.size()>=2 && i<t.cols.size()-1)
        f.write(sep);
    }
    f.write("\n");
  }

  for (i=0; i<t.size(); ++i){
    for (size_t j=0; j<t.cols.size(); ++j){
      f.write(t.cols.values(j).at(i).get<estr>());
      if (t.cols.size()>2 && j<t.cols.size()-1)
        f.write(sep);
    }
    f.write("\n");
  }
  f.close();
  return(true);
}


etable etableLoad(const estr& filename,const evarhash& options) //,const eregexp& re)
{
  size_t b=0;
  size_t e=-1;
  size_t i;
  etable t;
#ifdef EUTILS_HAVE_ZLIB_H
  egzfile f;
#else
  efile f;
#endif
  estr sep="[ \t]";
  bool header=false;
  eregisterOption(options,b);
  eregisterOption(options,e);
  eregisterOption(options,sep);
  eregisterOption(options,header);
//  eregexp re(sep);

  if (!f.open(filename,"r")) { lerror(filename); return(t); }

  estr line;
  estrarray arr,headerarr;
  if (header) f.readarr(line,headerarr,sep);
//    headerarr=re_explode(line,re);
//
  size_t index=0;
  t.blocks.add(0);
  while (f.readarr(line,arr,sep)){
    if (line.len()==0){
      t.blocks.add(t.size());
/*
      for (i=0; i<t.cols.size(); ++i){
        t.cols.values(i).add(evar());
        t.cols.values(i).add(evar());
      }
*/
      continue;
    }
//    arr=re_explode(line,re);
    if (e!=-1 && index>e) break;
    if (index<b) { ++index; continue; }

    for (i=0; i<arr.size(); ++i){
      if (arr[i].is_float())
        t.add(i<headerarr.size()?headerarr[i]:estr(i),double(arr[i].f()));
      else if (arr[i].is_int())
        t.add(i<headerarr.size()?headerarr[i]:estr(i),arr[i].i());
      else
        t.add(i<headerarr.size()?headerarr[i]:estr(i),arr[i]);
    }
    ++index;
  }
  return(t);
}

etable::etable()
{
}

etable::etable(const estrarray &fields)
{
  setfields(fields);
}

etable::etable(const etable &table)
{
  operator=(table);
}

etable::~etable()
{
  clear();
}

/*
void getString(const evar& vstr,estr& str){
  if (vstr.getTypeid()==typeid(double))
    str=vstr.get<double>();
  else if (vstr.getTypeid()==typeid(float))
    str=estr(vstr.get<float>());
  else if (vstr.getTypeid()==typeid(estr))
    str=vstr.get<estr>();
  else ldie("unhandled type in table key column");
}

estrarrayof<eintarray> etable::getCats(int colind)
{
  estrarrayof<eintarray> res;
  if (size()==0 || colind>=cols.size()) return(res);

  estr tmps,lasts;
  evararray& col(cols.values(colind);
  eintarray sind(iheapsort(col));

  getString(cols[sind[0]],tmps);
  eintarray *tarr=&res.add(tmps,eintarray(sind[0]));
  lasts=tmps;

  for (i=1; i<sind.size(); ++i){
    getString(cols[sind[i]],tmps);
    if (tmps!=lasts) {
      tarr=&res.add(tmps,eintarray());
      lasts=tmps;
    }
    tarr->add(sind[i]);
  }
  return(res);
}
*/

void etable::append(const etable& t)
{
  if (t.size()==0) return;

  if (cols.size()==0){
    for (int i=0; i<t.cols.size(); ++i)
      cols.add(t.cols.keys(i),evararray());
  }

  if (cols.size()>0 && t.cols.size()!=cols.size()){
    lerror("Appending tables of different column number");
    return;
  }
  for (int i=0; i<t.cols.size(); ++i){
    if (!cols.exists(t.cols.keys(i))){
      lerror("Appending tables of different column names");
      return;
    }
  }
  for (int i=0; i<cols.size(); ++i)
    cols[i]+=t.cols.values(cols.keys(i));
}

etable& etable::operator=(const etable& table)
{
  if (this==&table) return(*this);
  cols=table.cols;
  blocks=table.blocks;
  return(*this);
}

erow etable::row(size_t i)
{
  return(erow(this,i));
}

etable etable::getBlockTable(int bi)
{
  if (bi<0 || bi>=blocks.size()) return(etable());

  etable t;
  for (int i=0; i<cols.size(); ++i)
    t.addfield(cols.keys(i));
  int ei=(bi+1<blocks.size()?blocks[bi+1]:size());
  for (int j=0; j<cols.size(); ++j){
    for (int i=blocks[bi]; i<ei; ++i)
      t.cols.values(j).add(cols.values(j).at(i));
  }
  return(t);
}

etable etable::filter(const estr& expression)
{
  size_t i,j;
  etable t;
  estrhashof<evar> env;
  estrhashof<evar> tmpcols;

  estr tmpexpression=expression+";";

  env.add("row",&tmpcols);
  for (i=0; i<cols.size(); ++i){
    t.addfield(cols.keys(i));
    tmpcols.add(cols.keys(i),0x00);
  }

  for (i=0; i<size(); ++i){
    for (j=0; j<cols.size(); ++j)
      tmpcols[cols.keys(j)].set(&cols.values(j).at(i));
    evar tmpvar(code_interpret(env,tmpexpression));
//    cout << "tmpvar: " << tmpvar << endl;
    if (tmpvar.getTypeid()==typeid(bool) && tmpvar.get<bool>()==true){
      for (j=0; j<cols.size(); ++j)
        t.cols.values(j).add(cols.values(j).at(i));
    }
  }
  return(t);
}

void etable::json_serial(estr& data) const
{
  size_t i,j;
  data+="{\"fields\":[";
  for (i=0; i<cols.size(); ++i){
    cols.keys(i).json_serial(data);
    if (i+1<cols.size()) data+=",";
  }
  data+="],\"cols\":[";
  for (i=0; i<size(); ++i){
    data+="[";
    for (j=0; j<cols.size(); ++j){
      cols.values(j)[i].json_serial(data);
      if (j+1<cols.size()) data+=",";
    }
    data+="]";
    if (i+1<size()) data+=",";
  }
  data+="]}";
}

void etable::serial(estr& data) const
{
  size_t i,j;
  serialuint(cols.size(),data);
  for (i=0; i<cols.size(); ++i)
    cols.keys(i).serial(data);
  serialuint(size(),data);
  int slen=data.len();
  for (i=0; i<size(); ++i){
    for (j=0; j<cols.size(); ++j)
      cols.values(j)[i].serial(data);
    if (i==10) // preallocate buffer to speed up serialization using average of sizes of first 10 elements and using 10% overestimate
      data.reserve(slen + (data.len()-slen)*size()*1.1/10);
  }
}

size_t etable::unserial(const estr& data,size_t i)
{
  clear();
  if (i==-1) return(-1);

  unsigned int count;
  size_t j;

  estr field;
  i=unserialuint(count,data,i);
  if (i==-1) return(-1);
  while(count-- && i<data.len()){
    i=field.unserial(data,i);
    if (i==-1) return(-1);
    addfield(field);
  }
  evar var;
  i=unserialuint(count,data,i);

  for (int j=0; j<cols.size(); ++j) // preallocate arrays
    cols.values(j).reserve(count);

  if (i==-1) return(-1);
  while(count-- && i<data.len()){
    for (j=0; j<cols.size(); ++j){
      i=var.unserial(data,i);
      if (i==-1) return(-1);
      cols.values(j).add(var);
      var.clear();
    }
  }
  return(i);
}

void etable::serial(const efile& f) const
{
  size_t i,j;
  serialuint(cols.size(),f);
  for (i=0; i<cols.size(); ++i)
    cols.keys(i).serial(f);
  serialuint(size(),f);
  for (i=0; i<size(); ++i)
    for (j=0; j<cols.size(); ++j)
      cols.values(j)[i].serial(f);
}

bool etable::unserial(const efile& f)
{
  clear();

  unsigned int count;
  size_t j;

  estr field;
  if (!unserialuint(count,f)) return(false);
  while(count--){
    if (!field.unserial(f)) return(false);
    addfield(field);
  }
  evar var;
  if (!unserialuint(count,f)) return(false);
  while(count--){
    for (j=0; j<cols.size(); ++j){
      if (!var.unserial(f)) return(false);
      cols.values(j).add(var);
      var.clear();
    }
  }
  return(true);
}


void etable::add(size_t i,const evar& var)
{
  lddieif(i>=cols.size(),"adding value to non-existent column: "+estr(i));
  if (cols.values(0).size()==0 || !cols[i][cols[i].size()-1].isNull()){
//    cout << " adding row " << field << endl;
    size_t i;
    for (i=0; i<cols.size(); ++i)
      cols.values(i).add(evar());
  }
//  cout << " adding var: "<<var<<" to field: " << field << endl;
  cols[i][cols.values(0).size()-1].set(var);
}


void etable::add(const estr& field,const evar& var)
{
  if (!cols.exists(field))
    addfield(field);

  if (cols.values(0).size()==0 || !cols[field][cols[field].size()-1].isNull()){
//    cout << " adding row " << field << endl;
    size_t i;
    for (i=0; i<cols.size(); ++i)
      cols.values(i).add(evar());
  }
//  cout << " adding var: "<<var<<" to field: " << field << endl;
  cols[field][cols.values(0).size()-1].set(evarCopy(var));
}

void etable::add(const estrarray& arr)
{
  size_t i;
  if (cols.size()==0){
    for (i=0; i<arr.size(); ++i)
      addfield(arr.keys(i));
  }
  for (i=0; i<cols.size() && i<arr.size(); ++i)
    cols[arr.keys(i)].add(arr.values(i));
}

void etable::add(const evarhash& arr)
{
  size_t clen=size();
  size_t i;
  for (i=0; i<arr.size(); ++i){
    if (!cols.exists(arr.keys(i)))
      cols.add(arr.keys(i),evararray()).init(clen);
    cols[arr.keys(i)].add(arr.values(i));
  }
}

void etable::addfield(const estr& field)
{
  cols.add(field,evararray()).init(size());
}

void etable::setfields(const estrarray& fields)
{
  size_t i;
  for (i=0; i<cols.size() && i<fields.size(); ++i)
    cols.keys(i)=fields[i];
  for (; i<fields.size(); ++i)
    addfield(fields[i]);
}

evararray& etable::operator[](size_t i)
{
  return(cols[i]);
}
const evararray& etable::operator[](size_t i) const
{
  return(cols[i]);
}

evararray& etable::operator[](const estr& field)
{
  return(cols[field]);
}
const evararray& etable::operator[](const estr& field) const
{
  return(cols.values(field));
}


void etable::save(efile file) const
{
//  if (!file.open("w")) { lwarn("unable to open file: \""+file.name+"\" for writing"); return; }

  if (cols.size()==0) return;

  size_t i,j;

  for (j=0; j<cols[0].size(); ++j){
    for (i=0; i<cols.size(); ++i){
      file.write(cols[i][j].get<estr>());
      file.write(" ");
    }
    file.write("\n");
  }
  file.close();
}

void etable::load(efile file)
{
  clear();

//  if (!file.open("r")) { lwarn("unable to open file: \""+file.name+"\""); return; }

  size_t i;
  estr s;
  estrarray fields;

  if (file.readln(s)){
    fields=s.explode(" ");
    for (i=0; i<fields.size(); ++i)
      addfield(cols.size());

    for (i=0; i<cols.size() && i<fields.size(); ++i)
      cols[i].add(fields[i]);
  }
  while (file.readln(s)){
    fields=s.explode(" ");
    for (i=0; i<cols.size() && i<fields.size(); ++i)
      cols[i].add(fields[i]);
  }

  file.close();
}

void etable::clear()
{
  cols.clear();
}

erow::erow(): table(0x00),index(0) {}
erow::erow(etable *_table,int _index): table(_table),index(_index) {}

size_t erow::size() const
{
  return(table->cols.size());
}

const evar& erow::operator[](size_t i) const
{
  return(table->cols[i][index]);
}

evar& erow::operator[](size_t i)
{
  return(table->cols[i][index]);
}

const evar &erow::operator[](const estr& field) const
{
  return(table->cols[field][index]);
}

evar& erow::operator[](const estr &field)
{
  return(table->cols[field][index]);
}


ostream& operator<<(ostream& stream,const erow& row)
{
  if (row.size()==0) return(stream);

  size_t i;
  stream << row[0];
  for (i=1; i<row.size(); ++i)
    stream << " " << row[i];

  return(stream);
}

#include <sstream>



void etableTSV(const etable& t)
{
  estr res;
  for (int i=0; i<t.cols.size(); ++i){
    cout << t.cols.keys(i);
    if (i<t.cols.size()-1) cout << "\t";
  }
  cout << endl;

  for (int i=0; i<t.size(); ++i){
    for (int j=0; j<t.cols.size(); ++j){
      cout << t.cols[j][i];
      if (j<t.cols.size()-1)
        cout << "\t";
    }
    cout << endl;
  }
}

ostream& operator<<(ostream& stream,const etable& table)
{
  size_t i,c,r,m;
  eintarray maxlen;
  estr line,tmp;

  if (table.cols.size()==0){
    stream << "< empty table >" << endl;
    return(stream);
  }

  for (i=0 ; i < table.cols.size() ; i++)
    maxlen.add(table.cols.keys(i).len());

  stringstream ss;
  for (r = 0 ; r < table.size() ; r ++)
    for ( c = 0 ; c < table.cols.size() ; c++){
      ss.str("");
      ss << table[c][r];
      if ( ( m = ss.str().length())  > maxlen[c])
        maxlen[c] = m;
    }

  for( c = 0; c < table.cols.size() ; c++){
    line += "+";
    for ( i=0 ; i < maxlen[c]+2 ; i++ )
      line += "-";
  }

  line += "+\n";
  stream << line << "|";

  for ( i = 0 ; i < table.cols.size() ; i++ ){
    tmp.sprintf(" %-*s|",maxlen[i]+1,table.cols.keys(i)._str);
    stream << tmp;
  }

  stream << endl << line;

  for ( r = 0 ; r < table.size() ; r++ ) {
    stream << "|";
    for( c = 0 ; c < table.cols.size() ; c++ ) {
      ss.str("");
//      ss.clear();
      ss << table[c][r];

//      tmp.sprintf(" %-*s|",maxlen[c]+1,table[c][r]._str);
      stream << " " <<ss.str().substr(0,maxlen[c]+1);
      for (i=0; i<maxlen[c]+1-ss.str().length(); ++i) stream << " ";
      stream << "|";
    }
    stream << endl;
  }

  if ( table.size() > 0 )
    stream << line;

  return(stream);
}
