#include "BaseServ.h"
#include <wx/mstream.h>

#include <ctype.h>

//////////////////////////////////////////////////////////
// Sorted array of THashString
///////////////////////////////////////////////////////////

static int CompareNodes(wxNode** n1, wxNode** n2)
{
	return strcmp((*n1)->GetKeyString(), (*n2)->GetKeyString());
}

WX_DEFINE_ARRAY(wxNode*, TArrayOfNodes);

//////////////////////////////////////////////////////////
// TDictionary
///////////////////////////////////////////////////////////

class TDictionary : public THashTable
{
  unsigned int m_nNewEntries;
	TArrayOfNodes m_sorted;

protected:
  void ParseSpaces(const wxString& str, wxString& prefix, 
			 						 wxString& body, wxString& postfix) const;
  wxString Accentuate(const wxString& str) const;
  void AddEntry(const wxString& ita, const wxString& eng); 

	static bool FillCallback(TXmlItem& item, long jolly);
	bool Load();
  void Save();

public:
	wxString Translate(const wxString& ita);
	
	wxString GetFileName() const;
  bool LoadIfEmpty();
	bool SortIfNeeded();
	void SaveIfNeeded();
	
	wxString OriginalEntry(size_t i);
	wxString TranslatedEntry(size_t i);

  void UpdateEntry(size_t i, const wxString& strValue);

  TDictionary();
  ~TDictionary();
};

wxString TDictionary::GetFileName() const
{
	TBaseServerApp& app = (TBaseServerApp&)*wxTheApp;
  return app.GetConfigString("Dictionary", "campodic.xml");
}

wxString TDictionary::Accentuate(const wxString& str) const
{
	const int pos = str.Find('\'');
  if (pos <= 0)
		return str;

	wxString bello = str.Left(pos);
	for (size_t a = pos; str[a]; a++)
	{
		if (str[a] == '\'')
		{
			if ((isspace(str[a+1]) || str[a+1] == '\0') &&
					strchr("aeiou", str[a-1]))
			{
				switch(str[a-1])
				{
				case 'a':
					bello[a-1] = '�'; break;
				case 'e':
					if (a >= 2 && (isspace(str[a-2]) || str[a-2] == '\''))
						bello[a-1] = '�'; 
					else
						bello[a-1] = '�'; 
					break;
				case 'i':
					bello[a-1] = '�'; break;
				case 'o':
					bello[a-1] = '�'; break;
				case 'u':
					bello[a-1] = '�'; break;
				default:
					break;
				}
			}
  		else
	  		bello << str[a];
		}
		else
			bello << str[a];
	}
	return bello;
}

void TDictionary::Save()
{
	const bool full = SortIfNeeded();
	if (full)
	{
		wxFileOutputStream outf(GetFileName());
		outf << "<xml><dictionary>\n";
		for (size_t i = 0; i < m_sorted.GetCount(); i++)
		{
			const wxNode* pNode = m_sorted[i];
			outf << " <entry>\n";
			outf << "  <ita>";
			WriteXmlString(outf, pNode->GetKeyString());
			outf << "</ita>\n";
			outf << "  <eng>";
			WriteXmlString(outf, ((THashString*)pNode->GetData())->m_str);
			outf << "</eng>\n";
			outf << " </entry>\n";
		}
		outf << "</dictionary></xml>\n";
		m_nNewEntries = 0;
	}
}

void TDictionary::SaveIfNeeded()
{
	if (m_nNewEntries > 0)
		Save();
}

void TDictionary::AddEntry(const wxString& ita, const wxString& eng)
{
	const wxString key = Accentuate(ita);
  Put(key, eng);
	m_nNewEntries++;
}

void TDictionary::UpdateEntry(size_t nEntry, const wxString& strValue)
{
	if (nEntry >= 0 && nEntry < m_sorted.GetCount())
	{
	  const wxNode* pNode = m_sorted[nEntry];
	  ((THashString*)pNode->GetData())->m_str = strValue;
		Save();
	}
}

wxString TDictionary::OriginalEntry(size_t i)
{
	SortIfNeeded();
	const wxNode* pNode = m_sorted[i];
	return pNode->GetKeyString();
}

wxString TDictionary::TranslatedEntry(size_t i)
{
	const wxNode* pNode = m_sorted[i];
	return ((THashString*)pNode->GetData())->m_str;
}

wxSocketClient& operator<<(wxSocketClient& sock, const wxChar* str)
{
	if (str && *str)
		sock.Write(str, wxStrlen(str));
	return sock;
}

bool TDictionary::FillCallback(TXmlItem& item, long jolly)
{
	if (item.GetTag() == "entry")
	{
    const TXmlItem* ita = item.GetChild(0);
		const TXmlItem* eng = item.GetChild(1);
		if (ita != NULL && eng != NULL)
		{
			ita = ita->GetChild(0);
			eng = eng->GetChild(0);
  		if (ita != NULL && eng != NULL)
			{
				TDictionary* d = (TDictionary*)jolly;
				d->AddEntry(ita->GetText(), eng->GetText());
			}
		}
	}
	return false;
}

bool TDictionary::Load()
{
	wxFileInputStream inf(GetFileName());
	if (inf.Ok())
	{
		TXmlItem item;
		item.Read(inf);
		item.ForEach(FillCallback, (long)this);
	}
	m_nNewEntries = 0; // No last minute additions :-)

	return GetCount() > 0;
}

bool TDictionary::LoadIfEmpty()
{
	bool full = GetCount() > 0;
	if (!full)
		full = Load();
	return full;
}

bool TDictionary::SortIfNeeded()
{
	const bool full = LoadIfEmpty();
	if (m_sorted.GetCount() != GetCount())
	{
  	// Fill an array of nodes and sort them out
		m_sorted.Empty();
		BeginFind();
		for (wxNode* pNode = Next(); pNode != NULL; pNode = Next())
			m_sorted.Add(pNode);
		m_sorted.Sort(CompareNodes);
	}
	return full;
}


inline bool IsTrimmable(wxChar c)
{
	return strchr(" :.,;", c) != NULL;
}

void TDictionary::ParseSpaces(const wxString& str, wxString& prefix, 
															wxString& body, wxString& postfix) const
{
	int i, j;
  for (i = 0; IsTrimmable(str[i]); i++);
  for (j = str.Length()-1; j >= i && IsTrimmable(str[j]); j--);
	if (i > 0)
		prefix = str.Left(i);
	if (j >= i)
		postfix = str.Mid(j+1);
	body = Accentuate(str.Mid(i, j-i+1));
}

wxString TDictionary::Translate(const wxString& ita)
{
  LoadIfEmpty();

	wxString prefix, body, postfix;
	ParseSpaces(ita, prefix, body, postfix);
	wxString eng = Get(body);
	if (!eng.IsEmpty())
	{
	  if (eng != "???")
		{
			body = prefix;
			body += eng;
			body += postfix;
		  return body;
		}
	}
  else
	  AddEntry(ita, "???");
	
	return ita;
}

TDictionary::TDictionary() : THashTable(3883), m_nNewEntries(0)
{ 
}

TDictionary::~TDictionary()
{
	if (m_nNewEntries > 0)
		Save();
}

///////////////////////////////////////////////////////////
// TDictionaryServer
///////////////////////////////////////////////////////////

class TDictionaryServer : public TBaseServerApp
{
  TDictionary m_DevotoOli;

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

  size_t FindIndex(const wxString& strKey);

public:
  bool DoTranslate(const TXmlItem& xmlMethod, TXmlItem& xmlAnswer);
  bool SoapProcessMethod(const TXmlItem& xmlMethod, TXmlItem& xmlAnswer);
  void ProcessSoapCommand(wxString cmd, wxSocketBase& outs);
  void ProcessHttpGet(wxString cmd, wxSocketBase& outs);
  void ProcessFormCommand(wxString cmd, wxSocketBase& outs);

	void ProcessFormUpdateEntry(wxString& strFileName, THashTable& hashArgs);
  void ProcessFormTranslate(wxString& strFileName, THashTable& hashArgs);

  void CallCgi(wxString& strFilename);
	bool IsMagicName(wxString& strFilename) const;
  bool IsCgiName(wxString strFilename) const;

	void Add2Columns(TXmlItem& table, const wxChar* href0, const wxChar* td0, const wxChar* td1) const;
  void AddEditableRow(TXmlItem& table, const wxChar* txt1, const wxChar* txt2) const;
  void GenerateIndex(TXmlItem& body);
	void GenerateFile(wxString& strFilename);

	virtual bool Initialize();
  virtual bool Deinitialize();
};

bool TDictionaryServer::SoapProcessMethod(const TXmlItem& xmlMethod, TXmlItem& xmlAnswer)
{
	const wxString& strMethod = xmlMethod.GetTag();
	if (strMethod == "m:Translate")
		return DoTranslate(xmlMethod, xmlAnswer);

	return false;
}

bool TDictionaryServer::DoTranslate(const TXmlItem& xmlMethod, TXmlItem& xmlAnswer)
{
  const TXmlItem* xmlSentence = xmlMethod.FindFirst("sentence");
  if (xmlSentence != NULL)
	{
		const wxString ita = xmlSentence->GetEnclosedText();
		wxString result = m_DevotoOli.Translate(ita);
    xmlAnswer.AddSoapString("sentence", result);
		return true;
	}
	return false;
}

bool TDictionaryServer::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 == "dictionary")
	{
		return true;
	}
	if (strName == "log")
	{
		strFilename = GetLogFileName();
	}

  return false;
}

bool TDictionaryServer::IsCgiName(wxString strFilename) const
{
	const int q = strFilename.Find('?');
	if (q > 0)
		strFilename = strFilename.Left(q);

	wxString strExt;
	wxSplitPath(strFilename, NULL, NULL, &strExt);
  strExt.MakeLower();
	return strExt == "cgi" || strExt == "exe";
}

void TDictionaryServer::Add2Columns(TXmlItem& table, const wxChar* href0, const wxChar* txt0, const wxChar* txt1) const
{
	TXmlItem& tr = table.AddChild("tr");
	TXmlItem& a = tr.AddChild("td").AddChild("a");
	a.SetAttr("href", href0);
	a << txt0;
  tr.AddChild("td") << txt1;
}

void TDictionaryServer::GenerateIndex(TXmlItem& body)
{
	m_DevotoOli.SortIfNeeded();

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

	wxChar cLast = '\0';
	for (size_t i = 0; i < m_DevotoOli.GetCount(); i++)
	{
		const wxChar cCurr = toupper(m_DevotoOli.OriginalEntry(i)[0]);
		if (cCurr > cLast)
		{
      TXmlItem& td = tr.AddChild("td").SetAttr("align", "center");
			td.AddChild("a").SetAttr("href", wxString::Format("Dictionary?%c", cCurr));
      td << wxString::Format("%c", cCurr);
			cLast = cCurr;
		}
	}
  body.AddChild("br");
}

void TDictionaryServer::AddEditableRow(TXmlItem& table, const wxChar* txt1, const wxChar* txt2) const
{
	TXmlItem& tr = table.AddChild("tr");
	TXmlItem& td0 = tr.AddChild("td");
	const wxString cgi = wxString::Format("EditEntry.cgi?%c-%u", 
		                                    toupper(*txt1), table.GetChildren()-3);
	AddLinkButton(td0, "Edit", cgi).SetAttr("width", "100%");
	tr.AddChild("td") << txt1; 
	tr.AddChild("td") << txt2; 
}

void TDictionaryServer::GenerateFile(wxString& strFilename)
{
  const int q = strFilename.Find('?');
	wxString strArgs;
	if (q > 0)
	{
		strArgs = strFilename.Mid(q+1);
	  strFilename.Truncate(q);
	}

  wxString strName;
	wxSplitPath(strFilename, NULL, &strName, NULL);
  strName.MakeLower();

	TXmlItem html; 
	TXmlItem& body = CreatePageBody(html);

	if (strName == "index")
	{
		TXmlItem& table = body.AddChild("center").AddChild("table");
		table.SetAttr("border", "1"); table.SetAttr("width", "70%");
		Add2Columns(table, "Dictionary", "Dictionary", "Sorted listing of entries");
		Add2Columns(table, "TranslateSentence.cgi", "Translate", "Translate a sentence");
		Add2Columns(table, "Log", "Log", "Server activity log");
		Add2Columns(table, "stop.cgi", "Stop", "Stop the server");
		
		strFilename = GetTempFilename();
  }
	if (strName == "dictionary")
	{
		const wxChar cFilter = strArgs.IsEmpty() ? 'A' : toupper(strArgs[0]);
		GenerateIndex(body);

		TXmlItem& table = body.AddChild("table");
		table.SetAttr("border", "1"); table.SetAttr("width", "100%");
		table.AddChild("caption").AddChild("h1") << wxString::Format("%c", cFilter);

		TXmlItem& table_th = table.AddChild("thead");
    TXmlItem& th0 = table_th.AddChild("th");
    TXmlItem& th1 = table_th.AddChild("th"); 
		th1 << "Original text";
    TXmlItem& th2 = table_th.AddChild("th"); 
		th2 << "Translated text";

		for (size_t i = 0; i < m_DevotoOli.GetCount(); i++)
		{
			const wxString& orig = m_DevotoOli.OriginalEntry(i);
			if (toupper(orig[0]) == cFilter)
			  AddEditableRow(table, orig, m_DevotoOli.TranslatedEntry(i));
		}
		
		strFilename = GetTempFilename();
	}

  html.Save(strFilename);
}

// Convert code (B-23) to position (107)
size_t TDictionaryServer::FindIndex(const wxString& strKey)
{
	const wxChar cFirst = toupper(strKey[0]);
	const size_t nPos = atoi(strKey.Mid(2));
	size_t nFound = 0;
	for (size_t i = 0; i < m_DevotoOli.GetCount(); i++)
	{
		const wxChar c = toupper(m_DevotoOli.OriginalEntry(i)[0]);
		if (c == cFirst)
		{
			if (nFound == nPos)
				break;
			nFound++;
		}
	}
	return i;
}

void TDictionaryServer::CallCgi(wxString& strFileName)
{
	wxString strName, strExt, strArgs;
	const int q = strFileName.Find('?');
	if (q > 0)
	{
		strArgs = strFileName.Mid(q+1);
		strFileName = strFileName.Left(q);
	}
	wxSplitPath(strFileName, NULL, &strName, &strExt);
	if (strExt == "cgi")
	{
		TXmlItem html; 
		TXmlItem& body = CreatePageBody(html).AddChild("center");
    if (strName == "EditEntry")
		{
			const size_t i = FindIndex(strArgs);

			body.AddChild("h2") << "Edit Entry " << strArgs;

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

			form.AddChild("h3") << "Original text:";
			TXmlItem& ita = form.AddChild("textarea");
			ita.SetAttr("cols", "80"); ita.SetAttr("rows", "3");
			ita << m_DevotoOli.OriginalEntry(i);
      
			form.AddChild("br");
			form.AddChild("h3") << "Translated text:";
				
			TXmlItem& ent = form.AddChild("input");
			ent.SetAttr("type", "hidden"); ent.SetAttr("name", "Entry");
			ent.SetAttr("value", strArgs);
			
			TXmlItem& eng = form.AddChild("textarea");
			eng.SetAttr("name", "Trans"); 
			eng.SetAttr("cols", "80"); eng.SetAttr("rows", "3");
			eng << m_DevotoOli.TranslatedEntry(i);

			form.AddChild("br");
      form.AddChild("br");

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

			AddLinkButton(body, "Return to main page", "/");
		}
    if (strName == "TranslateSentence")
		{
			body.AddChild("h3") << "Input the text to be translated:";

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

			TXmlItem& ita = form.AddChild("textarea");
			ita.SetAttr("name", "Sentence"); 
			ita.SetAttr("cols", "80"); ita.SetAttr("rows", "3");
			ita << "Menu Principale";

			form.AddChild("br");
      form.AddChild("br");

			TXmlItem& sub = form.AddChild("input");
			sub.SetAttr("type", "submit"); 
			sub.SetAttr("value", "Translate");
    }
		strFileName = GetTempFilename();
		html.Save(strFileName);
	} else
	if (strExt == "exe")
	{
	}
}

// Implementazione delle due funzioni pure virtuali

const wxChar* TDictionaryServer::GetAppName() const
{
	return "Dictionary";
}

void TDictionaryServer::ProcessSoapCommand(wxString cmd, wxSocketBase& sock)
{
	TXmlItem xmlEnvelope;
	xmlEnvelope.SetTag("SOAP-ENV:Envelope");
	TXmlItem& xmlBody = xmlEnvelope.AddChild("SOAP-ENV:Body");

	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:"))
				{
					wxString str = pxmlMethod->GetTag(); str += "Result";
					TXmlItem& xmlAnswer = xmlBody.AddChild(str);
					SoapProcessMethod(*pxmlMethod, xmlAnswer);
				}
			}
		}
	}
	const wxString strResult = xmlEnvelope.AsString();
  
	sock << "HTTP/1.1 200 OK" << endl;
	sock << "Connection: keep-alive" << endl;
	sock << "Content-Length: " << strResult.Length() << endl;
	sock << "Content-Type: text/xml; charset=utf-8" << endl;
	sock << "Date: " << wxDateTime::Now().Format("%#c") << endl;
	sock << "Server: " << GetAppName() << endl;
  sock << "Host: " << wxGetFullHostName() << endl;
	sock << endl;
	sock << strResult;
}

void TDictionaryServer::ProcessHttpGet(wxString cmd, wxSocketBase& outs)
{
  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
 	if (IsCgiName(strFilename))
	  CallCgi(strFilename);

	SendFile(strFilename, outs);
}

void TDictionaryServer::ProcessFormUpdateEntry(wxString& strFileName, 
																							 THashTable& hashArgs)
{
	const wxString key = hashArgs.Get("Entry");
	size_t nEntry = FindIndex(key);
	const wxString strValue = hashArgs.Get("Trans");
	m_DevotoOli.UpdateEntry(nEntry, strValue);

	TXmlItem html; 
	TXmlItem& body = CreatePageBody(html).AddChild("center");
	body.AddChild("h2") << "Entry updated!";
	body.AddChild("br");

	strFileName = "dictionary?";
	strFileName << key[0];
  AddLinkButton(body, "Return to Dictionary", strFileName);

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

void TDictionaryServer::ProcessFormTranslate(wxString& strFileName, 
																						 THashTable& hashArgs)
{
	const wxString strValue = hashArgs.Get("Sentence");

	TXmlItem html; 
	TXmlItem& body = CreatePageBody(html).AddChild("center");
	body.AddChild("h3") << "Input the text to be translated:";

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

	TXmlItem& ita = form.AddChild("textarea");
	ita.SetAttr("name", "Sentence"); 
	ita.SetAttr("cols", "80"); ita.SetAttr("rows", "3");
	ita << strValue;

	form.AddChild("h3") << "Translated text:";

	const wxString strTrans = m_DevotoOli.Translate(strValue);
	if (strTrans == strValue)
	{
	  form.AddChild("p") << "Couldn't find a good translation for your text!";
	}
  else
	{
  	TXmlItem& eng = form.AddChild("textarea");
	  eng.SetAttr("cols", "80"); eng.SetAttr("rows", "3");
	  eng << strTrans;
	}

	form.AddChild("br");
	form.AddChild("br");

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

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

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

void TDictionaryServer::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 == "UpdateEntry")
		ProcessFormUpdateEntry(strFileName, hashArgs); else
	if (strName == "Translate")
		ProcessFormTranslate(strFileName, hashArgs);

	SendFile(strFileName, outs);
}

void TDictionaryServer::ProcessCommand(wxString cmd, wxSocketBase& outs)
{
  if (cmd.StartsWith("POST "))
	{
    if (cmd.Find("SOAPAction") > 0)
			ProcessSoapCommand(cmd, outs);
		else
      ProcessFormCommand(cmd, outs);
	} else
  if (cmd.StartsWith("GET "))
		ProcessHttpGet(cmd, outs);
}

bool TDictionaryServer::Initialize()
{
  return m_DevotoOli.LoadIfEmpty();
}

bool TDictionaryServer::Deinitialize()
{
  m_DevotoOli.SaveIfNeeded();
	return true;
}

// Istanziare l'applicazione principale

IMPLEMENT_APP(TDictionaryServer)