#include <wx/wx.h>

#include "baseserv.h"

#include <wx/dbtable.h>
#include <wx/dynarray.h>
#include <wx/mstream.h>

wxString GetSoapParam(const TXmlItem &xmlMethod, const char* strParam)
{
	wxString val;
  const TXmlItem* xml = xmlMethod.FindFirst(strParam);
  if (xml != NULL)
		val = xml->GetEnclosedText();
	return val;
}

///////////////////////////////////////////////////////////
// TRecord
// Tabella di database con 1 o piu' viste in base alla chiave
///////////////////////////////////////////////////////////

class TRecord : public wxObject
{
private:
  wxArrayString m_Fields;
  wxString m_strLocker;  // User locking the record
  bool m_bDeleted;

public:
  bool Locked() const { return !m_strLocker.IsEmpty(); }
  const wxString& LockedBy() const { return m_strLocker; }
  
  bool Deleted() const { return m_bDeleted; }
  void MarkAsDeleted() { m_bDeleted = true; }
  void MarkAsUndeleted() { m_bDeleted = false; }

  void Add(const wxString& strValue) { m_Fields.Add(strValue); }

  const wxString& operator[] (size_t i) const { return m_Fields[i]; }
  wxString& operator[] (size_t i) { return m_Fields[i]; }
};

///////////////////////////////////////////////////////////
// TArrayObject
///////////////////////////////////////////////////////////

WX_DEFINE_ARRAY(wxObject*, TArrayObject);

///////////////////////////////////////////////////////////
// TIndexEntry
///////////////////////////////////////////////////////////

struct TIndexEntry : public wxObject
{
  wxString m_strkey;
  long m_nRecno;
};

///////////////////////////////////////////////////////////
// TIndex
///////////////////////////////////////////////////////////

class TIndex : public wxObject
{
  enum { MAX_KEY_ENTRIES = 8 };
  int m_nField[MAX_KEY_ENTRIES];
  int m_nSize[MAX_KEY_ENTRIES];
  bool m_bUpper[MAX_KEY_ENTRIES];
  int m_nKeyFields;

  TArrayObject m_ndx;

protected:  
  void AddRecord(const TRecord& rec, long recno);
  long FindPosition(long recno) const;

public:
  void AddKeyField(int nPos, int nSize, bool upper);
  long FindPosition(const wxString& key) const;
  long SkipTo(long recno, long offset) const;
  void Update(const TArrayObject& records);
  bool Ok() const { return m_nKeyFields > 0; }
  long GetCount() const { return m_ndx.GetCount(); }

  TIndex();
};

void TIndex::AddKeyField(int nPos, int nSize, bool upper)
{
  if (m_nKeyFields < MAX_KEY_ENTRIES)
  {
    m_nField[m_nKeyFields] = nPos;
    m_nSize[m_nKeyFields] = nPos;
    m_bUpper[m_nKeyFields] = upper;
    m_nKeyFields++;
  }
}

long TIndex::FindPosition(const wxString& key) const
{
  int nFirst = 0;
  int nLast = m_ndx.GetCount()-1;
  while (nFirst <= nLast)
  {
    const int nCurr = (nFirst + nLast) / 2;
    const int nCmp = ((TIndexEntry*)m_ndx[nCurr])->m_strkey.Cmp(key);
    if (nCmp == 0)
      return nCurr;
    if (nCmp > 0)
      nLast = nCurr-1;
    if (nCmp < 0)
      nFirst = nCurr+1;
  }
  return nFirst;
}

long TIndex::FindPosition(long recno) const
{
  long i;
  for (i = m_ndx.GetCount()-1; i >= 0; i--)
    if (((TIndexEntry*)m_ndx[i])->m_nRecno == recno)
      break;
  return i;
}

long TIndex::SkipTo(long recno, long offset) const
{
  const long tot = m_ndx.GetCount();
  long i = 0;
  if (tot > 0)
  {
    if (recno == 0) // Goto first
      i = ((TIndexEntry*)m_ndx[0])->m_nRecno; else
    if (recno < 0)  // Goto last
      i = ((TIndexEntry*)m_ndx[tot-1])->m_nRecno; else
    {
      const long p = FindPosition(recno) + offset;
      if (p >= 0 && p < tot)
        i = ((TIndexEntry*)m_ndx[p])->m_nRecno;
    }
  }
  return i;
}

void TIndex::AddRecord(const TRecord& rec, long recno)
{
  TIndexEntry* e = new TIndexEntry;
  e->m_nRecno = recno;
  for (int i = 0; i < m_nKeyFields; i++)
  {
    const char* val = rec[m_nField[i]];
    if (m_bUpper[i])
    {
      e->m_strkey.Printf("%*s", -m_nSize[i], val);
      e->m_strkey.Upper();
    }
    else
      e->m_strkey.Printf("%*s", m_nSize[i], val);
  }
  
  const int nPos = FindPosition(e->m_strkey);
  m_ndx.Insert(e, nPos);
}

void TIndex::Update(const TArrayObject& records)
{
  WX_CLEAR_ARRAY(m_ndx);
  for (size_t i = 0; i < records.GetCount(); i++)
    AddRecord(*((TRecord*)records[i]), i+1);
}

TIndex::TIndex() : m_nKeyFields(0) 
{ }

///////////////////////////////////////////////////////////
// TTable
// Tabella di database con 1 o piu' viste in base alla chiave
///////////////////////////////////////////////////////////

class TTable : public wxObject
{
  wxDbColInf* m_ci;
  wxDbTable* m_table;
  UWORD m_nColumns;
  char* m_field[128];

  TArrayObject m_Records, m_Indexes;
  long m_nCurrent;

protected:
  void KillRecordSet();
  bool FillRecordSet();
  bool GoTo(long& recno);

  int FindColumn(const char* name) const;

public:
  long Rows(int index) const;
  void GetRecord(long recno, wxOutputStream& out);
  void GetRecordLength(wxOutputStream& out);
  bool CreateIndex(int index, const TXmlItem& fields);
  TIndex& Index(int n);

  bool ok() const { return m_nColumns > 0; }

  TTable(wxDb* db, const wxString& strName);
  virtual ~TTable();
};

long TTable::Rows(int index) const
{
  const TIndex& idx = ((TTable*)this)->Index(index);
  return idx.Ok() ? idx.GetCount() : m_Records.GetCount();
}

bool TTable::GoTo(long& rec)
{
  const long tot = m_Records.GetCount();
  bool ok = false;
  if (rec < 0)  // -1 = last record
    rec = tot + rec + 1;
  if (rec > 0 && rec <= tot)
  {
    m_nCurrent = rec;
    ok = true;
  }
  return ok;
}

void TTable::GetRecord(long recno, wxOutputStream& out)
{
  if (GoTo(recno))
  {
    const TRecord& record = *((TRecord*)m_Records[m_nCurrent-1]);

    TXmlItem rec; rec.SetTag("Record");
    rec.SetAttr("RecNo", recno);
    for (UWORD c = 0; c < m_nColumns; c++)
    {
      const wxString& str = record[c];
      if (!str.IsEmpty() && str != "1899-12-30")
        rec.AddSoapString("Field", str).SetAttr("Name", m_ci[c].colName);
    }
    rec.Write(out, 2);
  }
}

void TTable::GetRecordLength(wxOutputStream& out)
{
  int len = 0;
  for (UWORD i = 0; i < m_nColumns; i++)
  {
    const wxDbColInf& ci = m_ci[i];
    switch (ci.sqlDataType)
    {
    case SQL_C_DATE: 
      len += 8; 
      break;
    default: 
      if (ci.bufferLength > 128) // It's a MEMO!
        len += 10;
      else
        len += ci.bufferLength; 
      break;
    }
  }
  out << wxString::Format("%d", len);
}

int TTable::FindColumn(const char* name) const
{
  int i = m_nColumns-1;
  for ( ; i >= 0; i--)
  {
    if (wxStricmp(m_ci[i].colName, name) == 0)
      break;
  }
  return i;
}

TIndex& TTable::Index(int index)
{
  if (index < 0 || index > 8)
    index = 0;
  for (int i = m_Indexes.GetCount(); i <= index; i++)
    m_Indexes.Add(new TIndex);
  return *((TIndex*)m_Indexes[index]);
}

bool TTable::CreateIndex(int index, const TXmlItem& fields)
{
  TIndex& ndx = Index(index);
  if (!ndx.Ok())
  {
    for (int c = 0; c < fields.GetChildren(); c++)
    {
      const TXmlItem& field = *fields.GetChild(c);
      if (field.GetTag() == "Field")
      {
        const int i = FindColumn(field.GetAttr("Name"));
        if (i >= 0)
        {
          const bool upper = !field.GetAttr("Upper").IsEmpty();
          ndx.AddKeyField(i, m_ci[i].bufferLength, upper);
        }
      }
    }
    ndx.Update(m_Records);
  }

  return ndx.Ok();
}

void TTable::KillRecordSet()
{
  WX_CLEAR_ARRAY(m_Records);
  m_Records.Clear();
}

bool TTable::FillRecordSet()
{
  KillRecordSet();
  const bool ok = m_table->Query();
  if (ok)
  {  
    while (m_table->GetNext())
    {
      TRecord* rec = new TRecord;
      for (UWORD i = 0; i < m_nColumns; i++)
      {
        char* spc = NULL;
        for (char* c = m_field[i]; *c; c++)
        {
          if (*c == ' ')
          {
            if (spc == NULL) 
              spc = c;
          }
          else
            spc = NULL;
        }
        if (spc != NULL)
          *spc = '\0';     
        rec->Add(m_field[i]); 
      }
      m_Records.Add(rec);
    }
  }
  for (size_t i = 1; i < m_Indexes.GetCount(); i++)
  {
    TIndex& ndx = Index(i);
    if (ndx.Ok())
      ndx.Update(m_Records);
  }
  return ok;
}


TTable::TTable(wxDb* db, const wxString& strName)
{
  m_ci = db->GetColumns(strName, &m_nColumns);

  m_table = new wxDbTable(db, strName, m_nColumns, strName, false);

  // Bind columns
  for (UWORD i = 0; i < m_nColumns; i++)
  {
    const wxDbColInf& ci = m_ci[i];
    const int length = ci.bufferLength+1;
    m_field[i] = new char[length];
    memset(m_field[i], 0, length);
    int nSqlType = ci.sqlDataType;
    if (nSqlType <= 0 || nSqlType == 2)
      nSqlType = SQL_C_CHAR;
    m_table->SetColDefs(i, ci.colName, DB_DATA_TYPE_VARCHAR, 
                        m_field[i], nSqlType, length);
  }

  if (m_table->Open(false, false))
  {
    if (!FillRecordSet())
      GetServerApp().WriteLog(wxString::Format("Can't query table %s", strName.c_str()));
  }
  else
    GetServerApp().WriteLog(wxString::Format("Can't open table %s", strName.c_str()));

  m_nCurrent = 0;  // Before the first
}


TTable::~TTable()
{
  KillRecordSet();
  
  delete m_table;

  for (UWORD i = 0; m_field[i] != NULL; i++)
    delete m_field[i];

  delete m_ci;
}

///////////////////////////////////////////////////////////
// TDataBase
// Database di dati comuni (COM) o di DITTA (00001A, ecc...)
///////////////////////////////////////////////////////////

class TDataBase : public wxHashTable
{
  wxDbConnectInf m_ci;
  wxDb* m_db;

public:
	bool Open(const wxString& dsn, const wxString& user, const wxString& password);
	bool IsOpen() const;
	void Close();
	wxDb& DataBase() { return *m_db; } 

  TTable* Table(const wxString& strName);

	TDataBase();
	virtual ~TDataBase();
};

bool TDataBase::Open(const wxString& dsn, const wxString& user, const wxString& password)
{
  Close();

  m_ci.SetDsn(dsn);
  m_ci.SetUserID(user);
  m_ci.SetPassword(password);
	
  if (m_ci.AllocHenv())
	{
		m_db = ::wxDbGetConnection(&m_ci, false); // Forward only?
		if (m_db == NULL)
      m_ci.FreeHenv();
		else
		{
			const wxString strLog = GetServerApp().GetLogFileName();
			if (!strLog.IsEmpty())
			  m_db->SetSqlLogging(sqlLogON, strLog, TRUE);
		}
	}

	return IsOpen();
}

void TDataBase::Close()
{
  if (m_db)
	{
		m_db->SetSqlLogging(sqlLogOFF);
		::wxDbFreeConnection(m_db);
		m_db = NULL;
		m_ci.FreeHenv();
	}
}

bool TDataBase::IsOpen() const
{
	return m_db != NULL;
}

TTable* TDataBase::Table(const wxString& strName)
{
	TTable* s = (TTable*)Get(strName);
  if (s == NULL)
  {
    s = new TTable(m_db, strName);
    if (s->ok())
      Put(strName, s);
  }
  return s;
}

TDataBase::TDataBase() : wxHashTable(wxKEY_STRING), m_db(NULL)
{
	m_ci.Henv = 0;
	DeleteContents(true);
}

TDataBase::~TDataBase()
{
	Close();
}

///////////////////////////////////////////////////////////
// TStudy
// Elenco di Databases: COM, 00001A, 00002A, ecc...
///////////////////////////////////////////////////////////

class TStudy : public wxHashTable
{
  wxString m_strUser, m_strPass;
	wxString m_strName;

public:
	TDataBase& DB(long firm); // 0 = COM
	TStudy(const char* strName, const char* strUser, const char* strPass);
};

TDataBase& TStudy::DB(long firm)
{
	TDataBase* db = (TDataBase*)Get(firm);
	if (db == NULL)
	{
  	wxString strDsn, strPath;

		if (firm > 0)
			strDsn.sprintf("%s_%05lda", m_strName.c_str(), firm);
		else
			strDsn.sprintf("%s_COM", m_strName.c_str());

		db = new TDataBase;

		db->Open(strDsn, m_strUser, m_strPass);
		Put(firm, db);
	}
	return *db;
}

TStudy::TStudy(const char* strName, const char* strUser, const char* strPass) 
      : wxHashTable(wxKEY_INTEGER), m_strUser(strUser), m_strPass(strPass), m_strName(strName)
{
	DeleteContents(true);
}

///////////////////////////////////////////////////////////
// TCommercialist
///////////////////////////////////////////////////////////

class TCommercialist : public wxHashTable
{
  wxString m_strUser, m_strPass;  // User impersonated by the TCommenrcialist

public:
  const wxString& User() const { return m_strUser; }
  const wxString& Password() const { return m_strPass; }

	TStudy& Study(const wxString& strStudy);
  TTable* Table(const TXmlItem& item);

  TCommercialist();
};

TStudy& TCommercialist::Study(const wxString& strStudy)
{
	TStudy* s = (TStudy*)Get(strStudy);
  if (s == NULL)
  {
    if (m_strUser.IsEmpty())
    {
      TBaseServerApp& a = GetServerApp();
      m_strUser = a.GetConfigString("User", "ADMIN");
      m_strPass = a.GetConfigString("Password", "AD.MIN");
    }
    s = new TStudy(strStudy, m_strUser, m_strPass);
    Put(strStudy, s);
  }
  return *s;
}

TTable* TCommercialist::Table(const TXmlItem& xmlMethod)
{
  TTable* tab = NULL;
  const wxString strStudy = GetSoapParam(xmlMethod, "Study");
  const long nFirm = atol(GetSoapParam(xmlMethod, "Firm"));
  TStudy& study = Study(strStudy);
  TDataBase& firm = study.DB(nFirm);
  if (firm.IsOpen())
  {
    const wxString strTable = GetSoapParam(xmlMethod, "Table");
    tab = firm.Table(strTable);
  }
  return tab;
}

TCommercialist::TCommercialist()
{
	DeleteContents(true);
}

///////////////////////////////////////////////////////////
// TDataBaseServer
///////////////////////////////////////////////////////////

class TDataBaseServer : public TBaseServerApp
{
  TCommercialist m_caminetti;

protected:  
	virtual const wxChar* GetAppName() const;
  virtual void ProcessCommand(wxString cmd, wxSocketBase& outs);
  virtual bool Initialization();
  virtual bool Deinitialization();

protected:  
  void AddAsterisk(const wxString& strField, const wxString& strQuery, 
									 TDataBase& db, wxArrayString& arr);
  wxString ParseQuery(const wxString& strOriginalQuery, TDataBase& db, wxArrayString& arr);
  void WriteTable(wxString strDsn, wxString strUser,
									wxString strPass, wxString strOriginalQuery,
									wxOutputStream& out);

  void SoapProcessMethod(const TXmlItem& xmlMethod, wxOutputStream& out);
  void SoapProcessQuery(const TXmlItem &xmlMethod, wxOutputStream& out);
  
  void SoapProcessTableRows(const TXmlItem &xmlMethod, wxOutputStream& out);
	void SoapProcessGetRecord(const TXmlItem &xmlMethod, wxOutputStream& out);
	void SoapProcessGetRecordLength(const TXmlItem &xmlMethod, wxOutputStream& out);
	void SoapProcessCreateIndex(const TXmlItem &xmlMethod, wxOutputStream& out);
  void SoapProcessSkipRecord(const TXmlItem &xmlMethod, wxOutputStream& out);

public:
	bool IsMagicName(wxString& strFilename) const;

  void ProcessFormQuery(const wxString& strFileName, THashTable& hashArgs);
  void ProcessFormCommand(wxString cmd, wxSocketBase& outs);
  void ProcessSoapCommand(wxString cmd, wxSocketBase& outs);

  void GenerateIndex(wxString& strFilename);
  void GenerateSql(wxString& strFilename);
  void GenerateFile(wxString& strFilename);
};

// Implementare almeno queste due funzioni pure virtuali

const wxChar* TDataBaseServer::GetAppName() const
{
	return "DataBase";
}

bool TDataBaseServer::IsMagicName(wxString& strFilename) const
{
  wxString strName;
	wxSplitPath(strFilename, NULL, &strName, NULL);
  strName.MakeLower();
  const int q = strName.Find('?');
	if (q > 0)
	  strName.Truncate(q);

	if (strName == "index" || strName == "sql")
	{
		strFilename = strName;
		return true;
	}
	if (strName == "log")
	{
		strFilename = GetLogFileName();
	}

  return false;
}

void TDataBaseServer::GenerateIndex(wxString& strFilename)
{
	TXmlItem html; 
	TXmlItem& body = CreatePageBody(html).AddChild("center");

	TXmlItem& table = body.AddChild("table");
  table.SetAttr("border", "1");
	table.SetAttr("width", "70%");

	TXmlItem& tr0 = body.AddChild("tr");
  TXmlItem& aq = tr0.AddChild("td").AddChild("a");
	aq.SetAttr("href", "sql.htm"); aq << "SQL query";
	tr0.AddChild("td") << "Perform any SQL query on the selected Data Source";

	TXmlItem& tr1 = body.AddChild("tr");
  TXmlItem& al = tr1.AddChild("td").AddChild("a");
	al.SetAttr("href", "Log"); al << "Log File";
	tr1.AddChild("td") << "Display current server log";

	TXmlItem& tr2 = body.AddChild("tr");
  TXmlItem& as = tr2.AddChild("td").AddChild("a");
	as.SetAttr("href", "stop.cgi"); as << "Stop";
	tr2.AddChild("td") << "Stop this database server";
	
	strFilename = GetTempFilename();
	html.Save(strFilename);
}

void TDataBaseServer::GenerateSql(wxString& strFilename)
{
	TXmlItem html; 
	TXmlItem& body = CreatePageBody(html).AddChild("center");

	TXmlItem& form = body.AddChild("form");
	form.SetAttr("method", "post");
	form.SetAttr("action", "Query.cgi");

  TXmlItem& table = form.AddChild("table").SetAttr("width", "70%");
  
	TXmlItem& tr0 = table.AddChild("tr");
	tr0.AddChild("td") << "Data Source Name";
	TXmlItem& dsn = tr0.AddChild("td").AddChild("input");
	dsn.SetAttr("type", "string"); dsn.SetAttr("name", "Dsn");
	dsn.SetAttr("value", GetConfigString("Dsn"));

	TXmlItem& tr1 = table.AddChild("tr");
	tr1.AddChild("td") << "User";
	TXmlItem& user = tr1.AddChild("td").AddChild("input");
	user.SetAttr("type", "string"); user.SetAttr("name", "User");
	user.SetAttr("value", GetConfigString("User"));

	TXmlItem& tr2 = table.AddChild("tr");
	tr2.AddChild("td") << "Password";
	TXmlItem& pass = tr2.AddChild("td").AddChild("input");
	pass.SetAttr("type", "password"); pass.SetAttr("name", "User");

	form.AddChild("br");
	form.AddChild("h3") << "SQL query:";
	TXmlItem& q = form.AddChild("textarea");
	q.SetAttr("name", "Sql");
	q.SetAttr("cols", "80"); q.SetAttr("rows", "10");

	wxString query = GetConfigString("Sql");
	q << query;
      
	form.AddChild("br");
	form.AddChild("br");

	TXmlItem& sub = form.AddChild("input");
	sub.SetAttr("type", "submit"); 
	sub.SetAttr("value", "Execute");

	AddLinkButton(body, "Return to main page", "/");

	strFilename = GetTempFilename();
	html.Save(strFilename);
}

void TDataBaseServer::GenerateFile(wxString& strFilename)
{
  wxString strName, strArgs;
	wxSplitPath(strFilename, NULL, &strName, NULL);
  strName.MakeLower();
  const int q = strFilename.Find('?');
	if (q > 0)
		strArgs = strFilename.Mid(q+1);

	if (strName == "index")
    GenerateIndex(strFilename); else
	if (strName == "sql")
    GenerateSql(strFilename); else
	if (strName == "log")
		strFilename = GetLogFileName();
}

void TDataBaseServer::AddAsterisk(const wxString& strField, const wxString& strQuery, 
																 TDataBase& db, wxArrayString& arr)
{
	wxString strTable;
	const int nDot = strField.Find(".*");
	if (nDot < 0)
	{
		const int nFrom = strQuery.Find("FROM ");
		if (nFrom > 0)
			strTable = strQuery.Mid(nFrom+5);
		const int nComma = strTable.Find(',');
		const int nSpace = strTable.Find(' ');
		int nTrunc = strTable.Length();
		if (nComma > 0)
			nTrunc = nComma;
		if (nSpace > 0 && nSpace < nTrunc)
			nTrunc = nSpace;
		strTable.Truncate(nTrunc);
	}
	else
		strTable = strField.Left(nDot);
	strTable.Trim(false); strTable.Trim(true);

	UWORD numCols;
	wxDbColInf* dci = db.DataBase().GetColumns(strTable, &numCols);

	wxString strFieldName;
	for (UWORD i = 0; i < numCols; i++)
	{
		if (strField == "*")
			strFieldName = dci[i].colName;
		else
		{
		  strFieldName = strTable;
		  strFieldName += ".";
		  strFieldName += dci[i].colName;
		}
    arr.Add(strFieldName);
	}

  delete dci;
}

wxString TDataBaseServer::ParseQuery(const wxString& strOriginalQuery, TDataBase& db, wxArrayString& arr)
{
	wxString strQuery;

  const int nSelect = strOriginalQuery.Find("SELECT ");
	const int nFrom = strOriginalQuery.Find("FROM ");

	if (nSelect >= 0 && nFrom > nSelect)
	{
		wxString strFields = strOriginalQuery.Mid(nSelect+7, nFrom-nSelect-7);
		strFields.Trim(false); strFields.Trim(true);
		while(!strFields.IsEmpty())
		{
			const int nComma = strFields.Find(',');
			wxString strField = nComma < 0 ? strFields : strFields.Mid(0, nComma);
			strField.Trim(); strField.Trim(false);
			if (strField.Find("*") >= 0)
        AddAsterisk(strField, strOriginalQuery, db, arr);
			else
				arr.Add(strField);
			if (nComma > 0)
				strFields = strFields.Mid(nComma+1);
			else
				break;
		}
		strQuery = "SELECT ";
		for (size_t i = 0; i < arr.GetCount(); i++)
		{
			if (i > 0)
				strQuery << ",";
			strQuery << arr[i];
		}
		strQuery << strOriginalQuery.Mid(nFrom-1);
	}
	else
    strQuery = strOriginalQuery; 

	return strQuery;
}

void TDataBaseServer::WriteTable(wxString strDsn, wxString strUser,
																 wxString strPass, wxString strOriginalQuery,
																 wxOutputStream& out)
{
	const clock_t tTotalStart = clock();

	clock_t tQueryTime = 0;
	clock_t tRetrieveTime = 0;
  clock_t tTotalTime = 0;
	size_t nRecords = 0;
	
	out << "<table border=1>\n";

  if (strDsn.IsEmpty())
    strDsn = GetConfigString("DataPath");
  
  if (strUser.IsEmpty())
    strUser = GetConfigString("User");
  
  if (strPass.IsEmpty())
    strPass = GetConfigString("Password");

  TStudy s(strDsn, strUser, strPass);
	TDataBase& d = s.DB(0); // Ditta/COM
	if (d.IsOpen())
	{
		wxArrayString arr;
		const wxString strQuery = ParseQuery(strOriginalQuery, d, arr);

		wxDb& db = d.DataBase();

		const clock_t tQueryStart = clock();
		db.ExecSql(strQuery);
		tQueryTime    = clock() - tQueryStart;
		
		const int nColumns = arr.GetCount();
		if (nColumns > 0)
		{
			out << " <thead>\n";
			for (int i = 0; i < nColumns; i++)
				out << "  <th>" << arr[i] << "</th>\n";
			out << " </thead>\n";
	
			const int bufsize = 33000;
			wxChar* buffer = new wxChar[bufsize];

			const clock_t tRetrieveStart = clock();
			for (nRecords = 0; db.GetNext(); nRecords++)
			{
				out << " <tr>\n";
				for (int c = 1; c <= nColumns; c++)
				{
					SDWORD cb;
					if (db.GetData(c, SQL_C_CHAR, buffer, bufsize, &cb))
					{
						if (strcmp(buffer, "1899-12-30") == 0)
							*buffer = '\0';
						out << "  <td>" << buffer << "</td>\n";
					}
					else
						break;
				}
				out << " </tr>\n";
			}
			delete buffer;
			tRetrieveTime = clock() - tRetrieveStart;
		}
	}
  out << "</table>\n";

	tTotalTime = clock() - tTotalStart;

	WriteLog(wxString::Format("--- %u Records. Query:%u Retrieve:%u Total:%u", 
		                        nRecords, (unsigned int) tQueryTime, (unsigned int) tRetrieveTime, (unsigned int) tTotalTime));
}

void TDataBaseServer::ProcessFormQuery(const wxString& strFileName, THashTable& hashArgs)
{
	const wxString strDsn = hashArgs.Get("DSN");
	const wxString strUser = hashArgs.Get("User");
	const wxString strPass = hashArgs.Get("Password");
	const wxString strOriginalQuery = hashArgs.Get("SQL");

	SetConfigString("DSN", strDsn);
	SetConfigString("User", strUser);

  wxString strQuery = strOriginalQuery;
  strQuery.Replace("\r\n", " ");
	SetConfigString("SQL", strQuery);

	wxFileOutputStream out(strFileName);

	out << "<html>\n";
	out << "<body bgcolor='#EFCEAD' background='back.gif'>\n";

	WriteTable(strDsn, strUser, strPass, strOriginalQuery, out);

	out << "</body>\n";
	out << "</html>\n";
}

void TDataBaseServer::ProcessFormCommand(wxString cmd, wxSocketBase& outs)
{
  const int stop = cmd.Find(" HTTP");
	wxString strFileName = cmd.Mid(5, stop-5).Trim();

	wxString strName, args;
	wxSplitPath(strFileName, NULL, &strName, NULL);

	const int pos = cmd.Find("\r\n\r\n");
	if (pos > 0)
	  args = cmd.Mid(pos+4);

	THashTable hashArgs(13);
  ParseArguments(args, hashArgs);

  strFileName = GetTempFilename();

	if (strName == "Query")
		ProcessFormQuery(strFileName, hashArgs);

	SendFile(strFileName, outs);
}

void TDataBaseServer::SoapProcessQuery(const TXmlItem &xmlMethod, wxOutputStream& out)
{
	const wxString dsn = GetSoapParam(xmlMethod, "DSN");
	const wxString usr = GetSoapParam(xmlMethod, "User");
	const wxString pwd = GetSoapParam(xmlMethod, "Password");
	const wxString sql = GetSoapParam(xmlMethod, "SQL");
	WriteTable(dsn, usr, pwd, sql, out);
}

void TDataBaseServer::SoapProcessTableRows(const TXmlItem &xmlMethod, wxOutputStream& out)
{
  TTable* tab = m_caminetti.Table(xmlMethod);
  if (tab != NULL)
  {
    const int index = atoi(GetSoapParam(xmlMethod, "Index"));
    out << wxString::Format("%ld", tab->Rows(index));
  }
}

void TDataBaseServer::SoapProcessGetRecord(const TXmlItem &xmlMethod, wxOutputStream& out)
{
  TTable* tab = m_caminetti.Table(xmlMethod);
  if (tab != NULL)
  {
    const long recno = atol(GetSoapParam(xmlMethod, "RecNo"));
    tab->GetRecord(recno, out);
  }
}

void TDataBaseServer::SoapProcessSkipRecord(const TXmlItem &xmlMethod, wxOutputStream& out)
{
  TTable* tab = m_caminetti.Table(xmlMethod);
  if (tab != NULL)
  {
    const int index = atoi(GetSoapParam(xmlMethod, "Index"));
    const long recno = atol(GetSoapParam(xmlMethod, "RecNo"));
    const long offset = atol(GetSoapParam(xmlMethod, "Offset"));
    const long newrecno = tab->Index(index).SkipTo(recno, offset);
    tab->GetRecord(newrecno, out);
  }
}


void TDataBaseServer::SoapProcessGetRecordLength(const TXmlItem &xmlMethod, wxOutputStream& out)
{
  TTable* tab = m_caminetti.Table(xmlMethod);
  if (tab != NULL)
    tab->GetRecordLength(out);
}

void TDataBaseServer::SoapProcessCreateIndex(const TXmlItem &xmlMethod, wxOutputStream& out)
{
  TTable* tab = m_caminetti.Table(xmlMethod);
  if (tab != NULL)
  {
    const int index = atoi(GetSoapParam(xmlMethod, "Index"));
    const bool ok = index > 0 && tab->CreateIndex(index, xmlMethod);
    out << (ok ? "1" : "0");
  }
}

void TDataBaseServer::SoapProcessMethod(const TXmlItem &xmlMethod, wxOutputStream& out)
{
  const wxString strTag = xmlMethod.GetTag();
	wxString str = strTag;
	str << "Result";
	out << "<" << str << ">\n";
	
	if (strTag == "m:GetRecord")
		SoapProcessGetRecord(xmlMethod, out); else
	if (strTag == "m:SkipRecord")
		SoapProcessSkipRecord(xmlMethod, out); else
	if (strTag == "m:GetRecordLength")
		SoapProcessGetRecordLength(xmlMethod, out); else
	if (strTag == "m:TableRows")
		SoapProcessTableRows(xmlMethod, out); else
	if (strTag == "m:CreateIndex")
		SoapProcessCreateIndex(xmlMethod, out); else
	if (strTag == "m:Query")
		SoapProcessQuery(xmlMethod, out);
	
	out << "</" << str << ">\n";
}

void TDataBaseServer::ProcessSoapCommand(wxString cmd, wxSocketBase& sock)
{
	wxMemoryOutputStream outf;
	outf << "<SOAP-ENV:Envelope>\n<SOAP-ENV:Body>\n";
	
	const int soapstart = cmd.Find("<SOAP-ENV:");
	if (soapstart > 0)
	{
		const size_t soaplen = cmd.length() - soapstart;
		const char* buff = (const char*)cmd;
		buff += soapstart;
		wxMemoryInputStream input(buff, soaplen);
		TXmlItem query; 
		if (query.Read(input))
		{
			const TXmlItem* pxmlBody = query.FindFirst("SOAP-ENV:Body");
			if (pxmlBody != NULL) for (int m = 0; ; m++)
			{
				const TXmlItem* pxmlMethod = pxmlBody->GetChild(m);
				if (pxmlMethod == NULL)
					break;
				if (pxmlMethod->GetTag().StartsWith("m:"))
				{
					SoapProcessMethod(*pxmlMethod, outf);
				}
			}
		}
	}
	outf << "</SOAP-ENV:Body>\n</SOAP-ENV:Envelope>\n";

  const size_t nSize = outf.GetSize();
  char* mem = new char[nSize];
  outf.CopyTo(mem, nSize);

  sock << "HTTP/1.1 200 OK\n";
	sock << "Server: " << GetAppName() << "\n";
	sock << "Host: " << wxGetFullHostName() << "\n";
  sock << "Connection: keep-alive\n";
	sock << "Content-Type: text/xml; charset=utf-8\n";
 	sock << "Content-Length: " << nSize << "\n";
	sock << "Date: " << wxDateTime::Now().Format("%#c") << "\n";
  sock << "\n" << mem;

	delete mem;
}

void TDataBaseServer::ProcessCommand(wxString cmd, wxSocketBase& outs)
{
	if (cmd.StartsWith("GET "))
	{
		const int stop = cmd.Find(" HTTP");
		wxString str = cmd.Mid(4, stop-4).Trim();
		if (str == "/")
			str += "index.htm";
		wxString strFilename = GetDocumentRoot() + str;

		if (IsMagicName(strFilename))
			GenerateFile(strFilename);
    else
		{
			const int ims = cmd.Find("If-Modified-Since: ");
			if (ims > 0)
			{
				const wxString strDate = cmd.Mid(ims+19, 24);
				wxDateTime tIms; 
				if (tIms.ParseDateTime(strDate))
				{
					const wxDateTime tFile = ::wxFileModificationTime(strFilename);
					if (tFile <= tIms)
          {
						SendNotModifiedFile(outs);
						return;
					}
				}
			}
		}
	  SendFile(strFilename, outs);
	} else
  if (cmd.StartsWith("POST "))
	{
    if (cmd.Find("SOAPAction") > 0)
			ProcessSoapCommand(cmd, outs);
		else
      ProcessFormCommand(cmd, outs);
	}
}

bool TDataBaseServer::Initialization()
{
	return true;
}

bool TDataBaseServer::Deinitialization()
{
	wxDbCloseConnections();
	return true;
}

// Istanziare l'applicazione principale

IMPLEMENT_APP(TDataBaseServer)