#include <diction.h>
#include <odbcrset.h>
#include <sqlset.h>
#include <textset.h>
#include <utility.h>

///////////////////////////////////////////////////////////
// TODBC_connections
///////////////////////////////////////////////////////////

class TODBC_connections
{
  TString _dsn, _usr;
  XVT_ODBC _odbc;

protected:
  void close();

public:
  XVT_ODBC get(const char* dsn, const char* usr, const char* pwd, const char* dir);
  TODBC_connections();
  ~TODBC_connections();
};

TODBC_connections _connections;

void TODBC_connections::close()
{
  if (_odbc != NULL)
  {
    xvt_odbc_free_connection(_odbc);
    _odbc = NULL;
  }
}

XVT_ODBC TODBC_connections::get(const char* dsn, const char* usr, const char* pwd, const char* dir)
{
  if (_odbc == NULL || _dsn != dsn || _usr != usr)
  {
    close();
    if (dsn && *dsn)
      _odbc = xvt_odbc_get_connection(_dsn = dsn, _usr = usr, pwd, dir);
  }
  return _odbc;
}

TODBC_connections::TODBC_connections() : _odbc(NULL)
{}

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

///////////////////////////////////////////////////////////
// TODBC_recordset
///////////////////////////////////////////////////////////

XVT_ODBC TODBC_recordset::connection() const
{ return _connections.get(_dsn, _usr, _pwd, _dir); }

bool TODBC_recordset::connect(const char* dsn, const char* usr, const char* pwd, const char* dir)
{
  _dsn = dsn; _usr = usr; _pwd = pwd; _dir = dir;
  return connection() != NULL;
}

const TString& TODBC_recordset::query_text() const
{ return _sql; }

const TString& TODBC_recordset::driver_version() const
{
  TString& tmp = get_tmp_string(50);
  xvt_odbc_driver(connection(), tmp.get_buffer(), tmp.size());
  return tmp;
}

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

int TODBC_recordset::on_get_columns(int argc, char** values, char** columns)
{
  static unsigned long _counter = 0;

  if (_column.empty())
  {
    _counter = 0;
    for (int i = 0; i < argc; i++)
    {
      const char* fldtype = NULL;
      TRecordset_column_info* info = new TRecordset_column_info;
      info->_width = 1;
      info->_type = _alfafld;

      if (columns != NULL)
      {
        info->_name = columns[i];
        fldtype = columns[argc+i];
      }
      else
        info->_name.format("FIELD%d", i+1);
      
      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);
    }
  }

  const bool processed = _counter++ < 128;
  if (processed)
  {
    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;
      }
    }
  }

  return processed ? 0 : -1;  
}

static int query_get_columns(void* jolly, int argc, char** values, char** columns)
{
  TODBC_recordset* q = (TODBC_recordset*)jolly;
  return q->on_get_columns(argc, values, columns);
}

void TODBC_recordset::requery()
{
  _items = 0;
  _current_row = -1;
  _page.destroy();
}

int TODBC_recordset::on_get_items(int argc, char** values, char** columns)
{
  if (!_columns_loaded)
    on_get_rows(argc, values, columns);
  return 0;
}
static int query_get_items(void* jolly, int argc, char** values, char** columns)
{
  TODBC_recordset* q = (TODBC_recordset*)jolly;
  return q->on_get_items(argc, values, columns);
}

TRecnotype TODBC_recordset::items() const
{
  if (_items == 0)
  {
    TString sql; parsed_text(sql);
    XVT_ODBC oc = connection();
    if (oc != NULL)
    {
      TPerformance_profiler prof("ODBC count");
      TRecnotype& i = (TRecnotype&)_items;
      if (!_columns_loaded)
      {
        TODBC_recordset* myself = (TODBC_recordset*)this;
        myself->_page.destroy();
        myself->_cursor_pos = 0;
        i = xvt_odbc_execute(oc, sql, query_get_items, (void*)this);
        myself->_columns_loaded = true;
      }
      else
        i = xvt_odbc_execute(oc, sql, NULL, NULL);
    }
  }
  return _items;
}

unsigned int TODBC_recordset::columns() const
{
  if (!_columns_loaded && _column.items() == 0)
  {
    XVT_ODBC oc = connection();
    if (oc != NULL)
    {
      TODBC_recordset* myself = (TODBC_recordset*)this;
      TString sql; parsed_text(sql);
      TPerformance_profiler prof("ODBC info");
      myself->_cursor_pos = 0;
      xvt_odbc_execute(oc, sql, query_get_columns, (void*)this);
      myself->_columns_loaded = true;
    }
  }
  return _column.items();
}
 
const TRecordset_column_info& TODBC_recordset::column_info(unsigned int c) const
{
  if (c >= columns())  // Generare column infos if needed
    c = 0;
  return (const TRecordset_column_info&)_column[c];
}

// Funzione chiamata per riempire la pagina corrente delle righe della query
int TODBC_recordset::on_get_rows(int argc, char** values, char** columns)
{
  if (!_columns_loaded)
    on_get_columns(argc, values, columns);

  if (_page.items() >= _pagesize)
    return -1;

  if (_cursor_pos++ < _first_row)
    return 0; // Ignora le righe prima del LIMIT
  
  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 0;
}

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

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

  if (n < _first_row || n >= _first_row+_page.items())
  {
    TString sql; parsed_text(sql);
    XVT_ODBC oc = connection();
    if (oc == NULL)
      return false;

    if (tot > _pagesize && 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;
    }

    TPerformance_profiler prof("ODBC query");
    _cursor_pos = 0;
    xvt_odbc_execute(oc, sql, query_get_rows, this);
    
    if (!_columns_loaded)
      _columns_loaded = true;  // Brutto posto ma necessario
  }

  return true;
}

long TODBC_recordset::exec(const char* sql)
{
  long err = -1;

  TString cmd; 

  // Se la stringa "sql" non contiene parametri di connessione ma essi sono in "_sql"
  // allora cerco di effetture la connessione in base a quella
  if (_dsn.empty() && strncmp(sql, "ODBC(", 5) != 0 && _sql.starts_with("ODBC("))
    parsed_text(cmd);

  XVT_ODBC oc = connection();
  if (oc != NULL)
  {
    set(sql);
    parsed_text(cmd);
    TPerformance_profiler prof("ODBC command");
    err = xvt_odbc_execute(oc, cmd, NULL, NULL);
  }
  
  return err;
}

TRecnotype TODBC_recordset::current_row() const
{
  return _current_row;
}

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

const TVariant& TODBC_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;
}

const TVariant& TODBC_recordset::get(const char* name) const
{
  return TRecordset::get(name);
}

void TODBC_recordset::parsed_text(TString& sql) const
{
  TRecordset::parsed_text(sql);

  if (sql.starts_with("ODBC(", true))
  {
    const int par = sql.find(')');
    if (par > 0)
    {
      TToken_string conn(sql.sub(5, par), ',');
      sql.ltrim(par+1);
      sql.trim();

      TString dsn = conn.get(); dsn.strip("\"");
      TString usr = conn.get(); usr.strip("\"");
      TString pwd = conn.get(); pwd.strip("\"");
      TString dir = conn.get(); dir.strip("\"");
      if (!((TODBC_recordset*)this)->connect(dsn, usr, pwd, dir))
        error_box(FR("Impossibile connettersi al DSN %s"), (const char*)dsn);
    }
  }
}

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

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

TODBC_recordset::~TODBC_recordset()
{ }

///////////////////////////////////////////////////////////
// Creazione "intelligente" del recordset appropriato in base alla query
///////////////////////////////////////////////////////////

TRecordset* create_recordset(const TString& sql)
{
  TRecordset* rex = NULL;
  if (sql.full())
  {
    if (sql.starts_with("US", true))
      rex = new TISAM_recordset(sql); else
    if (sql.starts_with("ODBC", true))
      rex = new TODBC_recordset(sql); else
    if (sql.starts_with("CSV", true))
      rex = new TCSV_recordset(sql); else
    if (sql.starts_with("AS400", true)) 
      rex = new TAS400_recordset(sql);
		else
      rex = new TSQL_recordset(sql);
  }
  return rex;
}