#include "baseserv.h"
#include <wx/hashmap.h>
#include <wx/textfile.h>

//////////////////////////////////////////////////////////
// Conversion utilities
///////////////////////////////////////////////////////////

int FindChar(const wxString& str, char c, int from)
{
  if (from <= 0)
    return str.Find(c);
  for (int i = from; str[i]; i++)
    if (str[i] == c)
      return i;
  return -1;
}

wxString txt2xml(const wxString& str)
{
  wxString tmp;
  for (int i = 0; str[i]; i++)
  {
    if (str[i] < ' ' || str[i] > 'z' || strchr("&<>/", str[i]) != NULL)
      tmp << wxString::Format("&#%X;", (int)(unsigned char)(str[i]));
    else
      tmp << str[i];
  }
  return tmp;
}

wxString xml2txt(const wxString& str)
{
  int i = str.Find('&');
  if (i >= 0)
  {
    wxString tmp = str.Left(i);
    for (; str[i]; i++)
    {
      bool bProcessed = false;
      if (str[i] == '&')
      {
        const int semicolon = FindChar(str, ';', i);
        if (semicolon > i)
        {
          const wxString sub = str.Mid(i+1, semicolon-i-1);
          int n;
          if (sub[0] == '#')
            bProcessed = sscanf(sub, "#%X", &n) == 1;
          else
            bProcessed = sscanf(sub, "%d", &n) == 1;
          if (bProcessed)
          {
            tmp << char(n & 0xFF);
            i = semicolon;
          }
        }
      }
      if (!bProcessed)
        tmp << str[i];
    }
    return tmp;
  }
  return str;
}

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

struct TEntry : public wxObject
{
  wxString m_eng, m_src;
  int m_max;

  TEntry& operator=(const TEntry& e)
  { m_eng = e.m_eng; m_src = e.m_src; m_max = e.m_max; return *this; }
};

WX_DECLARE_STRING_HASH_MAP( TEntry, TStringHashTable );

class TDictionary : public TStringHashTable
{
  TBaseServerApp* m_app;
	wxSortedArrayString m_sorted;
  bool m_bDirty;

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

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

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

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

  void SetApp(TBaseServerApp* app) { m_app = app; }

  TDictionary();
  ~TDictionary();
};

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

void TDictionary::Save()
{
  const char* eol = "\r\n";
	wxFileOutputStream outf(GetFileName());
  outf << "<?xml version=\"1.0\"?>" << eol;
	outf << "<dictionary>" << eol;
	for (size_t i = 0; i < m_sorted.GetCount(); i++)
	{
		const wxString& ita = m_sorted[i];
		outf << " <entry>" << eol;
		outf << "  <ita>";
		WriteXmlString(outf, ita);
		outf << "</ita>" << eol;
		outf << "  <eng>";
    const TEntry& e = operator[](ita);
		WriteXmlString(outf, e.m_eng);
		outf << "</eng>" << eol;
    if (!e.m_src.IsEmpty())
    {
  		outf << "  <src>";
		  WriteXmlString(outf, e.m_src);
		  outf << "</src>" << eol;
    }
    if (e.m_max > 0)
    {
      wxString str; str.Printf("%d", e.m_max);
		  outf << "  <max>" << str << "</max>" << eol;
    }
		outf << " </entry>" << eol;
	}
	outf << "</dictionary>" << eol;
  m_bDirty = false;
}

void TDictionary::SaveIfNeeded()
{
	if (m_bDirty)
		Save();
}

void TDictionary::AddEntry(const wxString& ita, const wxString& eng, const wxString& src, int max)
{
  TEntry e;
  e.m_eng = eng;
  e.m_src = src;
  e.m_max = max;

  operator[](ita) = e;
  m_sorted.Add(ita);
	m_bDirty = true;
}

void TDictionary::UpdateEntry(size_t nEntry, const wxString& strValue)
{
	if (nEntry >= 0 && nEntry < m_sorted.GetCount())
	{
	  const wxString& ita = m_sorted[nEntry];
    TEntry& e = operator[](ita);
	  e.m_eng = strValue;
		Save();
	}
}

wxString TDictionary::OriginalEntry(size_t i)
{
	return m_sorted[i];
}

const TEntry& TDictionary::GetEntry(size_t i)
{
  const wxString& ita = m_sorted[i];
  const TEntry& e = operator[](ita);
  return e;
}


wxString TDictionary::TranslatedEntry(size_t i)
{
  const TEntry& e = GetEntry(i);
  return e.m_eng;
}

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

bool TDictionary::Load()
{
  clear();
  m_sorted.Clear();
	wxTextFile scan;
  if (scan.Open(GetFileName()))
  {
    wxString ita, eng, src, line;
    int max;
    for (line = scan.GetFirstLine(); !scan.Eof(); line = scan.GetNextLine())
    {
      line.Trim(false);
      if (line.StartsWith("<entry>"))
      {
        ita = eng = src = "";
        max = 0;
      } else
      if (line.StartsWith("<ita>"))
      {
        const int eoi = line.Find("</ita>");
        ita = xml2txt(line.Mid(5, eoi-5));
      } else
      if (line.StartsWith("<eng>") && !ita.IsEmpty())
      {
        const int eoe = line.Find("</eng>");
        eng = xml2txt(line.Mid(5, eoe-5));
      } else
      if (line.StartsWith("<max>"))
      {
        const int eom = line.Find("</max>");
        max = atoi(line.Mid(5, eom-5));
      } else
      if (line.StartsWith("<src>"))
      {
        const int eos = line.Find("</src>");
        src = xml2txt(line.Mid(5, eos-5));
      } else
      if (line.StartsWith("</entry>"))
      {
        AddEntry(ita, eng, src, max);
      }
    }
  }
  m_bDirty = false;
	return size() > 0;
}

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

inline bool IsGoodChar(wxChar c)
{
	return isalnum(c) || strchr("@%'", c);
}

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

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

	wxString prefix, body, postfix;
	ParseSpaces(ita, prefix, body, postfix);

  const TStringHashTable::iterator i = find(body);
	if (i != end())
	{
    const TEntry& e = i->second;
	  if (e.m_eng != "???")
		{
			body = prefix;
			body += e.m_eng;
			body += postfix;
		  return body;
		}
	}
  else
  {
	  AddEntry(ita, "???", wxEmptyString, 0);
    if (m_app != NULL)
      m_app->WriteLog("*** Unknown sentence");
  }
	
	return ita;
}

TDictionary::TDictionary() 
           : TStringHashTable(10000), m_app(NULL), m_bDirty(false)
{ 
}

TDictionary::~TDictionary()
{
	SaveIfNeeded();
}

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

class TDictionaryServer : public TBaseServerApp
{
  TDictionary m_DevotoOli;

protected:  
	virtual const wxChar* GetAppName() const;
  virtual void SoapProcessMethod(const TXmlItem& xmlMethod, TXmlItem& xmlAnswer);

  size_t FindIndex(const wxString& strKey);

public:
  bool DoTranslate(const TXmlItem& xmlMethod, TXmlItem& xmlAnswer);
  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 wxChar* txt3, const wxChar* txt4) const;
  void GenerateIndex(TXmlItem& body);
	void GenerateFile(wxString& strFilename);

	virtual bool Initialization();
  virtual bool Deinitialization();

  TDictionaryServer();
};

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

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)
{
  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.size(); i++)
	{
		const wxChar cCurr = toupper(m_DevotoOli.OriginalEntry(i)[0u]);
		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 wxChar* txt3, const wxChar* txt4) 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; 
	tr.AddChild("td") << txt3; 
	tr.AddChild("td") << txt4; 
}

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[ 0u]);
		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"); th0.SetAttr("width", "6%");
    TXmlItem& th1 = table_th.AddChild("th"); th1.SetAttr("width", "47%"); 
		th1 << "Original text";
    TXmlItem& th2 = table_th.AddChild("th").SetAttr("width", "47%"); 
		th2 << "Translated text";
    TXmlItem& th3 = table_th.AddChild("th");
		th3 << "Sources";
    TXmlItem& th4 = table_th.AddChild("th");
		th4 << "Max. Size";

		for (size_t i = 0; i < m_DevotoOli.size(); i++)
		{
			const wxString& orig = m_DevotoOli.OriginalEntry(i);
			if (toupper(orig[0]) == cFilter)
      {
        const TEntry& e = m_DevotoOli.GetEntry(i);
        wxString str; 
        if (e.m_max > 0)
          str.Printf("%d", e.m_max);
			  AddEditableRow(table, orig, e.m_eng, e.m_src, str);
      }
		}
		
		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;
	size_t i;
	
	for (i = 0; i < m_DevotoOli.size(); i++)
	{
		const wxChar c = toupper(m_DevotoOli.OriginalEntry(i)[ 0u]);
		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);
      const TEntry& e = m_DevotoOli.GetEntry(i);

      TXmlItem& ee = body.AddChild("h2");
			ee << "Edit Entry " << strArgs;

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

			TXmlItem& ot = form.AddChild("h3");
      ot << "Original text";
      if (!e.m_src.IsEmpty())
        ot << " (" << e.m_src << ")";

			TXmlItem& ita = form.AddChild("textarea");
			ita.SetAttr("cols", "80"); ita.SetAttr("rows", "4");
			ita << m_DevotoOli.OriginalEntry(i);
      
			form.AddChild("br");

			TXmlItem& tt = form.AddChild("h3");
      tt << "Translated text";
      if (e.m_max > 0)
        tt << wxString::Format(" (Max. %d chars)", e.m_max);
				
			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", "4");
			eng << e.m_eng;

			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", "4");
			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::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);
}

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

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

TDictionaryServer::TDictionaryServer()
{ 
  m_DevotoOli.SetApp(this);
}

// Istanziare l'applicazione principale

IMPLEMENT_APP(TDictionaryServer)