#include "wxinc.h"
#include "xvt.h"

#include "../wxSqlite3/wxSqlite3.h"

class XVT_SQLDataBase
{
public:
  virtual bool Open(const char* dsn, const char* usr, const char* pwd, const char* dir) = 0;
  virtual bool Close() = 0 { return false; }
  virtual bool IsOk() const = 0 { return false; }
  virtual ULONG Execute(const char* sql, ODBC_CALLBACK cb, void* jolly) = 0;
  virtual SLIST ListFields(const char* table) const = 0;
  virtual SLIST ListTables() const = 0;
  virtual bool TableExists(const char* name) const;

  virtual bool Begin() const    { return false; }
  virtual bool Commit() const   { return false; }
  virtual bool Rollback() const { return false; }
  
  virtual ~XVT_SQLDataBase() { Close(); }
};

bool XVT_SQLDataBase::TableExists(const char* name) const
{
  bool yes = false;
  SLIST list = ListTables();
  for (SLIST_ELT e = xvt_slist_get_first(list); e != NULL && !yes; e = xvt_slist_get_next(list, e))
  {
    const char* table = xvt_slist_get(list, e, NULL);
    yes = xvt_str_same(name, table) != 0;
  }
  xvt_slist_destroy(list);

  return yes;
}

class XVT_SQLDB_SQLite3 : public XVT_SQLDataBase
{
  wxSQLite3Database* m_pDB;

protected:
  virtual bool Open(const char* dsn, const char* usr, const char* pwd, const char* dir);
  virtual bool Close();
  virtual bool IsOk() const { return m_pDB != NULL && m_pDB->IsOpen(); }
  virtual ULONG Execute(const char* sql, ODBC_CALLBACK cb, void* jolly);
  virtual SLIST ListFields(const char* table) const;
  virtual SLIST ListTables() const;
  virtual bool TableExists(const char* name) const;

public:
  virtual bool Begin() const;
  virtual bool Commit() const;
  virtual bool Rollback() const;

  XVT_SQLDB_SQLite3() : m_pDB(NULL) {}
};

bool XVT_SQLDB_SQLite3::Open(const char* dsn, const char*, const char*, const char*)
{
  Close();
  m_pDB = new wxSQLite3Database;
  if (dsn == NULL || *dsn <= ' ')
    dsn = ":memory:";
  try
  {
    m_pDB->Open(dsn);
  }
  catch(wxSQLite3Exception& e)
  {
    xvt_dm_post_error(e.GetMessage());
    return false;
  }

  return true;
}

bool XVT_SQLDB_SQLite3::Close()
{
  if (IsOk())
  {
    delete m_pDB;
    m_pDB = NULL;
  }
  return true;
}

bool XVT_SQLDB_SQLite3::Begin() const
{
  bool bDone = IsOk();
  if (bDone)
  {
    try { m_pDB->Begin(); }
    catch(wxSQLite3Exception& e)
    {
      xvt_dm_post_error(e.GetMessage());
      bDone = false;
    }
  }
  return bDone;
}

bool XVT_SQLDB_SQLite3::Commit() const
{
  bool bDone = IsOk();
  if (bDone)
  {
    try { m_pDB->Commit(); }
    catch(wxSQLite3Exception& e)
    {
      xvt_dm_post_error(e.GetMessage());
      bDone = false;
    }
  }
  return bDone;
}

bool XVT_SQLDB_SQLite3::Rollback() const
{
  bool bDone = IsOk();
  if (bDone)
  {
    try { m_pDB->Rollback(); }
    catch(wxSQLite3Exception& e)
    {
      xvt_dm_post_error(e.GetMessage());
      bDone = false;
    }
  }
  return bDone;
}

ULONG XVT_SQLDB_SQLite3::Execute(const char* sql, ODBC_CALLBACK cb, void* jolly)
{
  ULONG nRows = 0;
  if (!IsOk())
    return nRows;

  if (cb != NULL) // Ho una vera callback?
  {
    try
    {
      wxSQLite3ResultSet rs = m_pDB->ExecuteQuery(sql);
      short numcols = rs.GetColumnCount();

      if (numcols > 0)
      {
        wxArrayString aNames, aValues;

        const short nMaxCols = 256;
        char* values[nMaxCols];  // Lista dei valori del record corrente 
        memset(values, 0, sizeof(values));
    
        char* names[2*nMaxCols]; // Lista dei nomi dei campi e dei tipi
        memset(names, 0, sizeof(names));

        if (numcols > nMaxCols)
          numcols = nMaxCols;

        short c;
        for (c = 0; c < numcols; c++)
        {
          aNames.Add(rs.GetColumnName(c));
          names[c] = (char*)(const char*)aNames[c];
          switch (rs.GetColumnType(c))
          {
          case WXSQLITE_INTEGER: 
          case WXSQLITE_FLOAT: 
            names[c+numcols] = "NUMERIC"; break;
          default: 
            names[c+numcols] = "VARCHAR"; break;
          }
        }

        while (rs.NextRow())
        {
          aValues.Empty();
          for (c = 0; c < numcols; c++)
          {
            aValues.Add(rs.GetAsString(c));
            values[c] = (char*)(const char*)aValues[c];
          }
          if (cb(jolly, numcols, values, names) != 0)
            break;
          nRows++;
        }
      }
    }
    catch(wxSQLite3Exception& e)
    {
      xvt_dm_post_error(e.GetMessage() + "\n" + sql);
    }
  }
  else
    nRows = m_pDB->ExecuteUpdate(sql);

  return nRows;
}

SLIST XVT_SQLDB_SQLite3::ListTables() const
{
  wxASSERT(m_pDB != NULL);
  SLIST list = xvt_slist_create();
  const wxString strQuery = wxT("SELECT name FROM sqlite_master WHERE type = 'table'");
  try
  {
    wxSQLite3ResultSet rs = m_pDB->ExecuteQuery(strQuery);
    while (rs.NextRow())
      xvt_slist_add_at_elt(list, NULL, rs.GetAsString(0), 0L);
  }
  catch(wxSQLite3Exception& e)
  {
    xvt_dm_post_error(e.GetMessage() + "\n" + strQuery);
  }
  return list;
}

SLIST XVT_SQLDB_SQLite3::ListFields(const char* strTable) const
{
  SLIST list = NULL;
  if (TableExists(strTable))
  {
    list = xvt_slist_create();
    wxString strQuery; strQuery << "PRAGMA table_info(" << (const char*)strTable << ");";
    try
    {
      wxSQLite3ResultSet rs = m_pDB->ExecuteQuery(strQuery);
      while (rs.NextRow())
      {
        const wxString strField = rs.GetAsString(1);
        const wxString strType  = rs.GetAsString(2);
        if (strType == "INTEGER")
          xvt_slist_add_at_elt(list, NULL, strField, 2L); // intfld
        if (strType == "NUMERIC")
          xvt_slist_add_at_elt(list, NULL, strField, 5L); // realfld 
        else
          xvt_slist_add_at_elt(list, NULL, strField, 1L); // alfafld
      }
    }
    catch(wxSQLite3Exception& e)
    {
      xvt_dm_post_error(e.GetMessage() + "\n" + strQuery);
    }
  }
  return list;
}

bool XVT_SQLDB_SQLite3::TableExists(const char* name) const
{
  return m_pDB != NULL && name && *name && m_pDB->TableExists(name);
}

///////////////////////////////////////////////////////////
// xvt_sql_...
///////////////////////////////////////////////////////////

XVT_SQLDB xvt_sql_open(const char* dsn, const char* usr, const char* pwd, const char* dir)
{
  XVT_SQLDataBase* db = new XVT_SQLDB_SQLite3;
  db->Open(dsn, usr, pwd, dir);
  return (XVT_SQLDB)db;
}

BOOLEAN xvt_sql_close(XVT_SQLDB handle)
{
  BOOLEAN ok = handle != NULL;
  if (ok)
  {
    XVT_SQLDataBase* db = (XVT_SQLDataBase*)handle;
    if (db != NULL)
      delete db;
  }
  return ok;
}

BOOLEAN xvt_sql_begin(XVT_SQLDB handle)
{
  BOOLEAN ok = handle != NULL;
  if (ok)
  {
    XVT_SQLDataBase* db = (XVT_SQLDataBase*)handle;
    if (db != NULL)
      ok = db->Begin();
  }
  return ok;
}

BOOLEAN xvt_sql_commit(XVT_SQLDB handle)
{
  BOOLEAN ok = handle != NULL;
  if (ok)
  {
    XVT_SQLDataBase* db = (XVT_SQLDataBase*)handle;
    if (db != NULL)
      ok = db->Commit();
  }
  return ok;
}

BOOLEAN xvt_sql_rollback(XVT_SQLDB handle)
{
  BOOLEAN ok = handle != NULL;
  if (ok)
  {
    XVT_SQLDataBase* db = (XVT_SQLDataBase*)handle;
    if (db != NULL)
      ok = db->Rollback();
  }
  return ok;
}

ULONG xvt_sql_execute(XVT_SQLDB handle, const char* sql, ODBC_CALLBACK cb, void* jolly)
{
  ULONG n = 0;
  if (handle && sql && *sql)
  {
    XVT_SQLDataBase* db = (XVT_SQLDataBase*)handle;
    if (db->IsOk())
    {
      try
      {
        n = db->Execute(sql, cb, jolly);
      }
      catch(wxSQLite3Exception& e)
      {
        xvt_dm_post_error(e.GetMessage() + "\n" + sql);
        n = ~0;
      }
    }
  }  
  return n;
}

SLIST xvt_sql_list_fields(XVT_SQLDB handle, const char* table)
{
  SLIST list = NULL;
  XVT_SQLDataBase* db = (XVT_SQLDataBase*)handle;
  if (table && *table && db != NULL && db->IsOk())
    list = db->ListFields(table);
  return list;
}

SLIST xvt_sql_list_tables(XVT_SQLDB handle)
{
  SLIST list = NULL;
  XVT_SQLDataBase* db = (XVT_SQLDataBase*)handle;
  if (db != NULL && db->IsOk())
    list = db->ListTables();
  return list;
}

BOOLEAN xvt_sql_table_exists(XVT_SQLDB handle, const char* name)
{
  BOOLEAN yes = FALSE;
  if (handle && name && *name)
  {
    const XVT_SQLDataBase* db = (XVT_SQLDataBase*)handle;
    if (db->IsOk())
      yes = db->TableExists(name);
  }
  return yes;
}


BOOLEAN xvt_sql_driver(XVT_SQLDB handle, char* str, int max_size) 
{ 
  if (str != NULL && max_size > 8)
  {
    if (handle != NULL)
      wxStrncpy(str, "SQLite 3", max_size);
    else
      wxStrncpy(str, "ODBC 2.0", max_size);
  }
  return handle != NULL; 
}