#include <sqlset.h>

#include <diction.h>
#include <relation.h>
#include <progind.h>
#include <utility.h>

///////////////////////////////////////////////////////////
// Private interface
///////////////////////////////////////////////////////////

#include "../sqlite/sqlite3.h"

class TSQLite : public TObject
{
  sqlite3* _handle;
  TFilename _currdb;

protected:
  TVariant& get_sql_value(const TRectype& curr, const RecFieldDes& fd, TVariant& tmp) const;

  void build_curr_path(TFilename& name) const;
  void test_path();

  bool bind_record(const TRectype& rec, const RecDes& rd, sqlite3_stmt* pStatement) const;

  bool create_dbf_times();
  long get_dbf_time(const TString& table);
  bool set_dbf_time(const TString& table, long last);
  bool import(int logicnum);

public:
  sqlite3* open(const char* fname = NULL);
  bool exec(const char* sql, sqlite3_callback callback = NULL, void* jolly = NULL, bool show_error = true);

  void close();

  bool exists(const char* table);
  bool parse_select_from(const char* szSql);

  TSQLite();
  virtual ~TSQLite();
} _TheDataBase;

void get_sql_directory(TFilename& name)
{
  name = firm2dir(-1); 
  name.add("sql");
  if (!name.exist())
    make_dir(name);
}

void TSQLite::build_curr_path(TFilename& name) const
{
  TString16 firm; firm.format("%05ldA.sql", prefix().get_codditta());
  get_sql_directory(name);
  name.add(firm);
}

sqlite3* TSQLite::open(const char* fname)
{
  close();
  _currdb = fname;
  int err = sqlite3_open(_currdb, &_handle);

  if (err = SQLITE_CORRUPT)
  {
    close();
    xvt_fsys_remove_file(_currdb);
    err = sqlite3_open(_currdb, &_handle);
  }
  
  if (err == SQLITE_OK)
  {
    create_dbf_times();
  }
  else
  {
    const char* errmsg = sqlite3_errmsg(_handle); // Stringa di sitema: inutile sqlite3_free(errmsg)
    error_box(errmsg);
	}

  return _handle; 
}

void TSQLite::test_path()
{
  TFilename n;
  build_curr_path(n);
  if (n != _currdb)
    open(n);
}

bool TSQLite::exec(const char* sql, sqlite3_callback callback, void* jolly, bool show_error)
{
  if (_handle == NULL)
    test_path();

  TWait_cursor hourglass;
  char* errmsg = NULL;
  const int rc = sqlite3_exec(_handle, sql, callback, jolly, &errmsg);

  if (errmsg != NULL)
  {
    if (show_error)
    {
      TString msg;
      msg << sql;
      msg.cut(128);
      msg << '\n' << errmsg;
      error_box(msg);
    }
    sqlite3_free(errmsg);
  }
  return rc == SQLITE_OK;
}

void TSQLite::close()
{
  if (_handle != NULL)
  {
    sqlite3_close(_handle);
    _handle = NULL;
  }
}

const char* const DBF_TIMES_TABLE = "DBF_TIMES";

bool TSQLite::create_dbf_times()
{
  bool ok = exists(DBF_TIMES_TABLE);
  if (!ok)
  {
    TString sql;
    sql << "CREATE TABLE " << DBF_TIMES_TABLE << " (name TEXT,time NUMERIC);\n"
        << "CREATE UNIQUE INDEX " << DBF_TIMES_TABLE << "_1 ON " << DBF_TIMES_TABLE << " (name);";
    ok = exec(sql);
  }
  return ok;
}

bool TSQLite::set_dbf_time(const TString& table, long last)
{
  TString sql;
  sql << "REPLACE INTO " << DBF_TIMES_TABLE << " VALUES("
      << '\'' << table << "','" << last << "');";
  return exec(sql);
}

static int dbf_time_callback(void* jolly, int argc, char** argv, char** columns)
{
  long& last = *(long*)jolly;
  last = atol(argv[0]);
  return SQLITE_OK;
}

long TSQLite::get_dbf_time(const TString& table)
{
  TString sql;
  sql << "SELECT time FROM " << DBF_TIMES_TABLE << "\nWHERE name='" << table << "';";
  long last = 0;
  exec(sql, dbf_time_callback, &last);
  return last;
}

TVariant& TSQLite::get_sql_value(const TRectype& curr, const RecFieldDes& fd, TVariant& tmp) const
{
  switch (fd.TypeF)
  {
  case _realfld    : tmp.set(curr.get_real(fd.Name));  break;
  case _intfld     :
  case _longfld    :
  case _wordfld    :
  case _intzerofld :
  case _longzerofld: tmp.set(curr.get_long(fd.Name)); break;
  case _datefld    : 
    {
      const TDate date = curr.get_date(fd.Name);
      tmp.set(date.date2ansi()); 
    }
    break;
  case _boolfld    : tmp.set(curr.get_bool(fd.Name)); break;
  case _memofld:
    {
      TString memo = curr.get(fd.Name);
      memo.replace('\n', char(0xB6)); // Simbolo di paragrafo
      tmp.set(memo);
    }
    break;
  default          : tmp.set(curr.get(fd.Name)); break;
  }
  return tmp;
}

static int exists_callback(void *jolly, int argc, char **argv, char **azColName)
{
  bool& yes = *(bool*)jolly;
  yes = argc > 0;
  return SQLITE_OK;
}

bool TSQLite::exists(const char* table)
{
  TString sql; 
  sql << "SELECT name FROM sqlite_master WHERE (type='table')AND(name='" << table << "');";
  bool yes = false;
  exec(sql, exists_callback, &yes, false);
  return yes;
}

bool TSQLite::bind_record(const TRectype& rec, const RecDes& rd, sqlite3_stmt* pStatement) const
{
  int rc = SQLITE_OK;
  TVariant tmp;
  for (int i = 0; i < rd.NFields && rc==SQLITE_OK ; i++)
  {
    get_sql_value(rec, rd.Fd[i], tmp);
    const TString& val = tmp.as_string();
    rc = sqlite3_bind_text(pStatement, i+1, val, val.len(), SQLITE_TRANSIENT);
  }
  return rc == SQLITE_OK;
}

bool TSQLite::import(int logicnum)
{
  const TString& table = logic2table(logicnum);

  long last = get_dbf_time(table);
  if (logicnum >= LF_USER) // Dummy test
  {
    TLocalisamfile file(logicnum);
    if (!file.is_changed_since(last))
      return true;
  }

  const RecDes& rd = prefix().get_recdes(logicnum);

  TString sql; 
  if (exists(table))
  {
    // Drop old table
    sql.cut(0) << "DROP TABLE "<< table << ';'; 
    exec(sql);
  }

  // Create new table
  sql.cut(0) << "CREATE TABLE "<< table << "\n("; 
  int i;
  for (i = 0; i < rd.NFields; i++)
  {
    if (i > 0) sql << ',';
    sql << rd.Fd[i].Name << ' ';
    switch (rd.Fd[i].TypeF)
    {
    case _alfafld: sql << "TEXT"; break;
    case _memofld: sql << "BLOB"; break;
    case _datefld: sql << "DATE"; break;
    default      : sql << "NUMERIC"; break;
    }
  }
  sql << ");";
  if (!exec(sql))
    return false;

  TRelation rel(logicnum);
  TCursor cur(&rel);
  const TRecnotype items = cur.items();
  if (items > 0)
  {
    cur.freeze();

    TString msg;
    msg << TR("Importazione tabella") << ' ' << table;
    msg << ": " << items << ' ' << TR("righe");
    TProgind pi(items, msg, true, true);

    exec("BEGIN"); // Inizio transazione

    // Creo il comando INSERT INTO table VALUES(?,?,?,?,?,?);
    sql.cut(0) << "INSERT INTO " << table << " VALUES(";
    for (i = 0; i < rd.NFields; i++)
    {
      if (i != 0) sql << ',';
      sql << '?';
    }
    sql << ");";

    sqlite3_stmt* pStatement = NULL;
    int rc = sqlite3_prepare(_handle, sql, sql.len(), &pStatement, NULL);
  
    const TRectype& curr = rel.curr();
    for (cur = 0; cur.pos() < items; ++cur)
    {
      pi.addstatus(1);
      if (pi.iscancelled())
        break;
      bind_record(curr, rd, pStatement); // Sostituisce i ? coi veri valori
      rc = sqlite3_step(pStatement);     // Ritorna sempre 101 (SQLITE_DONE)
      rc = sqlite3_reset(pStatement);    // Azzero lo statement per ricominciare
    }
    rc = sqlite3_finalize(pStatement);
    exec("COMMIT");  // Fine transazione
  }
  
  // Creo gli indici DOPO l'importazione per maggiore velocita'
  TProgind pi(rd.NKeys, TR("Creazione indici"), false, true);
  for (int index = 0; index < rd.NKeys; index++)
  {
    pi.addstatus(1);
    sql.cut(0) << "CREATE INDEX " << table << '_' << (index+1) << " ON "<< table << "\n("; 
    const KeyDes& kd = rd.Ky[index];
    for (int k = 0; k < kd.NkFields; k++)
    {
      if (k > 0) sql << ',';
      const int ndx = kd.FieldSeq[k] % MaxFields;
      sql << rd.Fd[ndx].Name;
    }
    sql << ");";
    exec(sql);
  }

  set_dbf_time(table, last);  // Aggiorna ora di ultima modifica
 
  return true;
}

bool TSQLite::parse_select_from(const char* szSql)
{
  test_path();

  TString sql(szSql);
  sql.trim(); sql.upper();
  if (!sql.starts_with("SELECT"))
    return false;
  
  const int from = sql.find("FROM");
  if (from < 0)
    return false;

  const int where = sql.find("WHERE", from);
  TToken_string tables(sql.sub(from+5, where), ',');
  TString table;
  FOR_EACH_TOKEN(tables, tok)
  {
    table = tok;
    table.trim();
    for (int i = 0; table[i]; i++)
    {
      if (table[i] <= ' ' || table[i] == ';')
      { table.cut(i); break; }
    }

		const int logicnum = (table == "MAG") ? LF_MAG : table2logic(table);

    if (logicnum > 0)
      import(logicnum);
  }

  return true;
}

TSQLite::TSQLite() : _handle(NULL)
{ }

TSQLite::~TSQLite()
{
  close();
}

///////////////////////////////////////////////////////////
// TSQL_recordset
///////////////////////////////////////////////////////////

void TSQL_recordset::reset()
{
  _items = 0;
  _first_row = 0;
  _current_row = -1;
  _pagesize = 512;
  _page.destroy();
  _column.destroy();
}

int TSQL_recordset::on_get_items(int argc, char** values, char** columns)
{
  if (_column.items() == 0)
  {
    for (int i = 0; i < argc; i++)
    {
      TRecordset_column_info* info = new TRecordset_column_info;
      info->_name = columns[i];
      info->_width = 1;
      info->_type = _alfafld;

      const char* fldtype = columns[argc+i];
      if (fldtype != NULL)
      {
        if (xvt_str_compare_ignoring_case(fldtype, "DATE") == 0)
        {
          info->_type = _datefld; 
          info->_width = 10;
        } else
        if (xvt_str_compare_ignoring_case(fldtype, "NUMERIC") == 0)
        {
          info->_type = _realfld;
        } else
        if (xvt_str_compare_ignoring_case(fldtype, "BLOB") == 0)
        {
          info->_type = _memofld;
          info->_width = 50;
        }
      }
      _column.add(info);
    }
  }
  if (_items < _pagesize)
  {
    for (int i = 0; i < argc; i++) if (values[i] && *values[i])
    {
      TRecordset_column_info& info = (TRecordset_column_info&)_column[i];
      if (info._type == _alfafld || info._type == _realfld)
      {
        const int len = strlen(values[i]);
        if (len > info._width)
          info._width = len;
      }
    }
  }

  _items++;
  return SQLITE_OK;
}

static int query_get_items(void* jolly, int argc, char** values, char** columns)
{
  TSQL_recordset* q = (TSQL_recordset*)jolly;
  return q->on_get_items(argc, values, columns);
}

void TSQL_recordset::requery()
{
  _items = 0;
  _page.destroy();
}

TRecnotype TSQL_recordset::items() const
{
  if (_items == 0)
  {
    TString sql; parsed_text(sql);
    TPerformance_profiler prof("SQL query");
    _TheDataBase.exec("PRAGMA show_datatypes = ON;", NULL, NULL);
    _TheDataBase.exec(sql, query_get_items, (TSQL_recordset*)this);
    _TheDataBase.exec("PRAGMA show_datatypes = OFF;", NULL, NULL);
  }
  return _items;
}

unsigned int TSQL_recordset::columns() const
{
  if (_column.items() == 0)
    items();
  return _column.items();
}
 
const TRecordset_column_info& TSQL_recordset::column_info(unsigned int c) const
{
  return (const TRecordset_column_info&)_column[c];
}

// Funzione chiamata per riempire la pagina corrente delle righe della query
int TSQL_recordset::on_get_rows(int argc, char** values)
{
  TArray* a = new TArray;
  for (int c = 0; c < argc; c++)
  {
    TVariant* var = new TVariant;
    switch (column_info(c)._type)
    {
    case _alfafld: 
      var->set(values[c]);
      break;
    case _memofld: 
      if (values[c])
      {
        TFixed_string memo(values[c]);
        memo.replace(char(0xB6), '\n');
        var->set(memo); 
      }
      break;
    case _datefld: 
      var->set(TDate(values[c])); 
      break;
    default: 
      var->set(real(values[c]));
      break;
    }      
    a->add(var, c);
  }
  _page.add(a);
  return SQLITE_OK;
}

static int query_get_rows(void* jolly, int argc, char** values, char** /*columns*/)
{
  TSQL_recordset* rs = (TSQL_recordset*)jolly;
  return rs->on_get_rows(argc, values);
}

bool TSQL_recordset::move_to(TRecnotype n)
{
  _current_row = n;
  if (n < 0 || n >= items())
  {
    _page.destroy(); // Forza rilettura la prossiva volta
    _first_row = 0;
    return false;
  }

  if (n < _first_row || n >= _first_row+_page.items())
  {
    TString sql; parsed_text(sql);
    if (sql.starts_with("SELECT ") && sql.find("LIMIT ") < 0)
    {
      const int semicolon = sql.rfind(';');
      if (semicolon >= 0)
        sql.cut(semicolon);
      sql.trim();
      _page.destroy();
      if (n >= _pagesize)
        _first_row = n-32; // Prendo qualche riga dalla pagina precedente, per velocizzare il pagina su
      else
        _first_row = n;
      sql << "\nLIMIT " << _pagesize << " OFFSET " << _first_row << ';';
    }
    _TheDataBase.exec(sql, query_get_rows, this);
  }

  return true;
}

const TArray* TSQL_recordset::row(TRecnotype n)
{
  const TArray* a = NULL;
  if (move_to(n))
    a = (const TArray*)_page.objptr(n-_first_row);
  return a;
}

const TVariant& TSQL_recordset::get(unsigned int c) const
{
  const TArray* a = (const TArray*)_page.objptr(_current_row-_first_row);
  if (a != NULL)
  {
    const TVariant* s = (const TVariant*)a->objptr(c);
    if (s != NULL)
      return *s;
  }
  return NULL_VARIANT;
}

void TSQL_recordset::set(const char* sql) 
{ 
  reset(); 
  _sql = sql;
  if (_sql.find("SELECT") >= 0 || _sql.find("select") >= 0)
  {
    _TheDataBase.parse_select_from(_sql);
    find_and_reset_vars();
  }
}

const TString& TSQL_recordset::driver_version() const
{
  TString& tmp = get_tmp_string();
  tmp << "SQLite " << sqlite3_libversion();
  return tmp;
}

TSQL_recordset::TSQL_recordset(const char* sql) 
{ 
  set(sql); 
}