#include <windows.h>
#include <fstream.h>
#include <stdio.h>

#include "date.h"
#include "expr.h"
#include "utility.h"
#include "xml.h"

class TExpr_omnia : public TExpression
{
  enum { _date2k, _periodlastday, _recno };

  static int _curr_recno;

protected:
  virtual int parse_user_func(const char* name, int nparms) const;
  virtual void evaluate_user_func(int index, int nparms, TEval_stack& stack, TTypeexp type) const;

public:
  static void set_curr_recno(int n) { _curr_recno = n; }
  static int curr_recno() { return _curr_recno; }

  TExpr_omnia(const char* exp) { set(exp, _strexpr); }
};

int TExpr_omnia::_curr_recno = 0;

int TExpr_omnia::parse_user_func(const char* name, int nparms) const
{    
  if (strcmp(name, "DATE2K") == 0)
    return nparms == 1 ? _date2k : -1;

  if (strcmp(name, "PERIODLASTDAY") == 0)
    return nparms == 1 ? _periodlastday : -1;

  if (strcmp(name, "RECNO") == 0)
    return nparms == 0 ? _recno : -1;
  
  return -1;
}    

void TExpr_omnia::evaluate_user_func(int index, int nparms, TEval_stack& stack, TTypeexp type) const
{                 
  switch (index)
  { 
  case _date2k: // Acqua Omnia Only
    {
      TString& str = stack.peek_string();
      TToken_string tok(str, '/');
      const int d = tok.get_int(0);
      const int m = tok.get_int();
      int y = tok.get_int();
      if (d > 0 && m > 0 && y < 100)
      {
        y += 2000;
        const TDate milleniumbug(d, m, y);
        str = milleniumbug.string(full, '/');
      }
    }
    break;
  case _periodlastday: // Acqua Omnia Only
    {
      TString& period = stack.peek_string();
      char m1[4], m2[4];
      int year = 0;
      sscanf(period, "%3s-%3s/%d", m1, m2, &year);

      const TString4 strMonth = m2;
      for (int month = 12; month > 0; month--)
      {
        if (strMonth.compare(itom(month), 3, true) == 0)
          break;
      }
      if (month > 0)
      {
        TDate day(1, month, year);
        day.set_end_month();
        period = day.string(full, '/');
      }
    }
    break;
  case _recno:
    stack.push(_curr_recno);
    break;
  default:
    break;
  }
}       

///////////////////////////////////////////////////////////
// TTextRecord
///////////////////////////////////////////////////////////

class TTextRecord : public TString_array
{
  TAssoc_array* m_vars;
  const TXmlItem* m_inrec;
  const TXmlItem* m_outrec;
  int m_nLines, m_nColumns;
  TString m_str;

protected:
  char* GetLineBuffer(int i, int size);
  const TString& GetFieldValue(const TXmlItem& field) const;
  const TXmlItem* FindInputField(const TString& name) const;

public:
  void SetTrc(const TXmlItem& trc, TAssoc_array* vars = NULL);
  bool Read(istream& input);

  const TString& GetValue(const TString& name) const;
  const TString& Evaluate(TExpr_omnia& exp) const;

  TTextRecord() : m_nLines(0), m_nColumns(0), m_vars(NULL) { }
  TTextRecord(const TXmlItem& trc) : m_vars(NULL) { SetTrc(trc); }
};

void TTextRecord::SetTrc(const TXmlItem& trc, TAssoc_array* vars) 
{ 
  m_vars = vars;
  const TXmlItem& input = *trc.FindFirst("Input");
  m_nLines = input.GetIntAttr("Lines"); 
  if (m_nLines <= 0) 
    m_nLines = 1;
  
  m_nColumns = input.GetIntAttr("Columns");

  m_inrec = input.FindFirst("Record");
  CHECK(m_inrec, "Null input record");

  m_outrec = trc.FindFirst("Output")->FindFirst("Record");
  CHECK(m_outrec, "Null output record");
}

char* TTextRecord::GetLineBuffer(int i, int size) 
{
  CHECKD(i >= 0 && i < m_nLines, "Line out of range ", i);
  CHECKD(size > 0, "Bad record size ", size);
  
  TToken_string* str = (TToken_string*)objptr(i);
  if (str == NULL)
  {
    str = new TToken_string(size);
    add(str, i);
  }
  char* buff = str->get_buffer(size);
  *buff = '\0';
  return buff;
}

bool TTextRecord::Read(istream& input)
{
  bool ok = !input.eof();
  if (ok)
  {
    if (m_nLines <= 1 && m_nColumns > 0) // Record a lunghezza fissa
    {
      char* buff = GetLineBuffer(0, m_nColumns);
      input.read(buff, m_nColumns);
      buff[m_nColumns] = '\0';
      ok = *buff != '\0';
    }
    else
    {
      int size = m_nColumns; 
      if (size <= 0) 
        size = 1024*16;
      for (int i = 0; i < m_nLines; i++)
      {
        char* buff = GetLineBuffer(i, size);
        input.getline(buff, size);
        buff[size] = '\0';
      }
    }
  }
  return ok;
}

const TString& TTextRecord::GetFieldValue(const TXmlItem& field) const
{
  int y = field.GetIntAttr("Y" ) - 1;
  if (y < 0) y = 0;

  int x = field.GetIntAttr("X" ) - 1;
  if (x < 0) x = 0;

  const int l = field.GetIntAttr("Length");

  TString& str = (TString&)m_str;
  str = row(y).mid(x, l);

  const TString& strTrim = field.GetAttr("Trim");
  int nTrim = 2;
  if (strTrim.not_empty())
    nTrim = atoi(strTrim);

  switch (nTrim)
  {
  case  0: break;
  case  1: str.ltrim(); break;
  case  2: str.rtrim(); break;
  default: str.trim(); break;
  }
  return str;
}

const TXmlItem* TTextRecord::FindInputField(const TString& name) const
{
  for (int i = 0; i < m_inrec->GetChildren(); i++)
  {
    const TXmlItem* field = m_inrec->GetChild(i);
    if (field->GetAttr("Name") == name)
      return field;
  }
  return NULL;
}

const TString& TTextRecord::GetValue(const TString& name) const
{
  if (m_vars != NULL)
  {
    const TString* val = (const TString*)m_vars->objptr(name);
    if (val != NULL)
      return *val;
  }

  const TXmlItem* f = FindInputField(name);
  if (f != NULL)
    return GetFieldValue(*f);
   
  return name;
}

const TString& TTextRecord::Evaluate(TExpr_omnia& exp) const
{
  TString& str = (TString&)m_str;
  
  const int nv = exp.numvar();
  if (nv > 0 || strchr(exp.string(), '(') != NULL) // C'e' qualche variabile o funzione
  {
    for (int i = nv-1; i >= 0; i--)
    {
      const TString& name = exp.varname(i);
      const TString& value = GetValue(name);
      exp.setvar(i, value);
    }
    str = exp.as_string();  
  }
  else
  {
    str = exp.string();  // Nessuna variabile = costante!
    if (str[0] == '"')   // Togli virgolette dalle costanti
    {
      str.rtrim(1);
      str.ltrim(1);
    }
  }

  return str;
}

///////////////////////////////////////////////////////////
// TScrittore
///////////////////////////////////////////////////////////

class TScrittore : public TObject
{
private:
  ofstream* _out;
  int _recno;
  
public:
  ofstream& OutStream() { return *_out; }
  int inc_recno() { _recno++; return _recno; }
  int recno() const { return _recno; }

  TScrittore(const char* name) : _recno(0) { _out = new ofstream(name); }
  virtual~ TScrittore() { delete _out; }
};

///////////////////////////////////////////////////////////
// TCasaEditrice
///////////////////////////////////////////////////////////

enum TExportFormat { fmt_txt, fmt_slk };

class TCasaEditrice : public TAssoc_array
{
  const TXmlItem& m_trc;
  const TXmlItem* m_pRecOut;
  TExportFormat m_fmt;
  TArray _expressions;

  TString m_strPrefix, m_strExt;
  
  TExpr_omnia* m_exprSuffix;

  TString m_strRecHead, m_strRecFoot, m_strFldHead, m_strFldFoot;

protected:
  TScrittore& Scrittore(const char* suffix);
  void WriteHeader(ostream& output) const;
  void WriteFooter(ostream& output) const;
  void Translate(TString& str) const;
  ofstream& ChooseOutput(const TTextRecord& rec);
  const TString& evaluate(const TTextRecord& rec, int index) const;
  void WriteField(ostream& output, int x, int y, const char* val) const;
  
public:
  const TString& RecHead() const { return m_strRecHead; }
  const TString& RecFoot() const { return m_strRecFoot; }
  const TString& FldHead() const { return m_strFldHead; }
  const TString& FldFoot() const { return m_strFldFoot; }

  void Write(const TTextRecord& rec);

  TCasaEditrice(const TXmlItem& trc, const char* name);
  virtual ~TCasaEditrice();
};

void TCasaEditrice::Translate(TString& str) const
{
  int ampersend = str.find("&#");
  TString tmp;
  while (ampersend >= 0)
  {
    const int semicolon = str.find(';', ampersend+2);
    if (semicolon < 0)
      break;
    const int k = hex2int(str.sub(ampersend+2, semicolon));
    tmp.format("%c", k);
    tmp.insert(str.left(ampersend));
    tmp << str.mid(semicolon+1);
    str = tmp;
    ampersend = str.find("&#", ampersend+1);
  }
}

void TCasaEditrice::WriteHeader(ostream& output) const
{
  TXmlItem* pOutput = m_trc.FindFirst("Output");
  CHECK(pOutput, "NULL output file");

  TXmlItem* pHeader = pOutput->FindFirst("Header");

  TString strHeader;
  if (pHeader != NULL)
  {
    pHeader->GetEnclosedText(strHeader);
    if (strHeader.not_empty())
    {
      if (strHeader[0] == '"')
      {
        strHeader.rtrim(1);
        strHeader.ltrim(1);
      }
      Translate(strHeader);
    }
  }
  if (strHeader.empty() && m_fmt == fmt_slk)
    strHeader = "ID;PWXL;N;E\n";
  output << strHeader;

  if (pHeader != NULL && pHeader->GetIntAttr("Auto") != 0)
  {
    TXmlItem* pRecOut = pOutput->FindFirst("Record");
    CHECK(pRecOut, "NULL output record");
  
    output << m_strRecHead;
    for (int i = 0; i < pRecOut->GetChildren(); i++)
    {
      const TXmlItem* outfield = pRecOut->GetChild(i);
      WriteField(output, i, 0, outfield->GetAttr("Name"));
    }
    output << m_strRecFoot;
  }
}

void TCasaEditrice::WriteFooter(ostream& output) const
{
  TXmlItem* pOutput = m_trc.FindFirst("Output");
  CHECK(pOutput, "NULL output file");
  TXmlItem* pFooter = pOutput->FindFirst("Footer");

  TString strFooter; 
  if (pFooter != NULL)
  {
    pFooter->GetEnclosedText(strFooter);
    if (strFooter[0] == '"')
    {
      strFooter.rtrim(1);
      strFooter.ltrim(1);
    }
    Translate(strFooter);
  }
  if (strFooter.empty() && m_fmt == fmt_slk)
    strFooter = "E\n";

  output << strFooter;
}

TScrittore& TCasaEditrice::Scrittore(const char* suffix)
{
  TScrittore* s = (TScrittore*)objptr(suffix);
  if (s == NULL)
  {
    TFilename n;
    n << m_strPrefix << suffix;
    if (m_strExt.not_empty())
      n << '.' << m_strExt;
    s = new TScrittore(n);
    add(suffix, s);
    WriteHeader(s->OutStream());
  }
  return *s;
}

ofstream& TCasaEditrice::ChooseOutput(const TTextRecord& rec)
{
  TString16 strSuffix;
  if (m_exprSuffix != NULL)
    strSuffix = rec.Evaluate(*m_exprSuffix);
  
  TScrittore& s = Scrittore(strSuffix);
  TExpr_omnia::set_curr_recno(s.inc_recno());
  return s.OutStream();
}

const TString& TCasaEditrice::evaluate(const TTextRecord& rec, int index) const
{
  TExpr_omnia& exp = (TExpr_omnia&)_expressions[index];
  return rec.Evaluate(exp);
}

void TCasaEditrice::WriteField(ostream& output, int x, int y, const char* val) const
{
  switch(m_fmt)
  {
  case fmt_slk:
    output << "C;Y" << (y+1) << ";X" << (x+1) << ";K\"" << val << '"' << endl;
    break;
  default:
    output << FldHead() << val << FldFoot();
    break;
  }
}

void TCasaEditrice::Write(const TTextRecord& rec)
{
  ofstream& output = ChooseOutput(rec);
  output << RecHead();
  TString expr;
  for (int i = 0; i < m_pRecOut->GetChildren(); i++)
  {
    TXmlItem* outfield = m_pRecOut->GetChild(i);
    outfield->GetEnclosedText(expr);
    const TString& val = evaluate(rec, i);
    WriteField(output, i, TExpr_omnia::curr_recno(), val);
  }
  output << RecFoot();
}

TCasaEditrice::TCasaEditrice(const TXmlItem& trc, const char* name) : m_trc(trc), m_fmt(fmt_txt), m_exprSuffix(NULL)
{
  const TFilename path(name);
  const int dot = path.rfind('.');
  if (dot >= 0)
  {
    m_strPrefix = path.left(dot);
    m_strExt = path.mid(dot+1);
    if (m_strExt.compare("slk", -1, true) == 0 || 
        m_strExt.compare("xls", -1, true) == 0)
      m_fmt = fmt_slk;
  }
  else
  {
    m_strPrefix = path;
    m_strExt.cut(0);
  }

  const TXmlItem* pOutFile = m_trc.FindFirst("Output");
  CHECK(pOutFile, "NULL Output tag");

  const TString strSuffix = pOutFile->GetAttr("Suffix");
  if (strSuffix.not_empty())
    m_exprSuffix = new TExpr_omnia(strSuffix);

  m_pRecOut = pOutFile->FindFirst("Record");
  CHECK(m_pRecOut, "NULL output record");

  if (m_fmt != fmt_slk)
  {
    m_strRecHead = m_pRecOut->GetAttr("RecHead"); Translate(m_strRecHead);
    m_strRecFoot = m_pRecOut->GetAttr("RecFoot"); Translate(m_strRecFoot);
    m_strFldHead = m_pRecOut->GetAttr("FldHead"); Translate(m_strFldHead);
    m_strFldFoot = m_pRecOut->GetAttr("FldFoot"); Translate(m_strFldFoot);
  }

  TString expr;
  for (int i = 0; i < m_pRecOut->GetChildren(); i++)
  {
    const TXmlItem* outfield = m_pRecOut->GetChild(i);
    outfield->GetEnclosedText(expr);
    _expressions.add(new TExpr_omnia(expr));
  }
}

TCasaEditrice::~TCasaEditrice()
{
  TAssoc_array& myself = *this;
  FOR_EACH_ASSOC_OBJECT(myself, h, k, o)
  {
    TScrittore* s = (TScrittore*)o;
    WriteFooter(s->OutStream());
  }
}

///////////////////////////////////////////////////////////
// TLettore
///////////////////////////////////////////////////////////

class TLettore : public TObject
{
  TXmlItem _trc;
  TTextRecord _curr;

protected:
  bool load_trc(const char* trc);

  const TString& get_field(const TXmlItem& field) const;
  const TXmlItem* find_field(const TXmlItem& record, const TString& name) const;

public:
  int convert(const TFilename& src, const TFilename& trc, const TFilename& dst, TAssoc_array& vars);
  int convert(const char* cmd);
};

bool TLettore::load_trc(const char* t)
{
  const TFilename trc = t;
  bool ok = trc.exist();
  if (ok)
  {
    ifstream in(trc);
    ok = _trc.Read(in);
  }
  _curr.destroy();  // reset line sizes
  return ok;
}

int TLettore::convert(const TFilename& src, const TFilename& trc, const TFilename& dst, TAssoc_array& vars)
{
  if (!src.exist())
  {
    error_box("Non esiste il file di input\n%s", (const char*)src);
    return 1;
  }

  if (!load_trc(trc))
  {
    error_box("Non esiste il tracciato record\n%s", (const char*)trc);
    return 2;
  }

  if (dst.blank())
  {
    error_box("File di output non valido:\n%s", (const char*)dst);
    return 3;
  }

  const TXmlItem* infile = _trc.FindFirst("Input");
  const TXmlItem* outfile = _trc.FindFirst("Output");
  if (infile == NULL || outfile == NULL)
  {
    error_box("Tracciato record non valido:\nNon esiste il tag <Input> o <Output>");
    return 4;
  }

  const TXmlItem* recin = infile->FindFirst("Record");
  const TXmlItem* recout = outfile->FindFirst("Record");
  if (recin == NULL || recout == NULL)
  {
    error_box("Tracciato record non valido:\nNon esiste il tag <Record>");
    return 4;
  }

  TAssoc_array* varibili = (vars.items() > 0) ? &vars : NULL;
  _curr.SetTrc(_trc, varibili);
  
  const int inmode = infile->GetIntAttr("Binary") != 0 ? (ios::in|ios::binary) : ios::in;
  ifstream input(src, inmode);

  TCasaEditrice mondadori(_trc, dst);
  while (_curr.Read(input))
    mondadori.Write(_curr);
  
  return 0;
}

int TLettore::convert(const char* cmd)
{
  TToken_string str(cmd, ' ');
  str.strip_d_spaces();
  const TFilename src = str.get();
  const TFilename trc = str.get();
  const TFilename dst = str.get();

  TAssoc_array vars;
  TString varname, value;
  for (const char* t = str.get(); t; t = str.get())
  {
    varname = t;
    const int equal = varname.find('=');
    if (equal > 0)
    {
      value = varname.mid(equal+1);
      varname.cut(equal);
    }
    else
      value.cut(0);
    vars.add(varname, value);
  }

  return convert(src, trc, dst, vars);
}

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR lpCmdLine, int)
{
  TLettore app;
  const int err = app.convert(lpCmdLine);
  return err;
}