#include "baseserv.h"

#include <wx/config.h>
#include <wx/filename.h>
#include <wx/process.h>
#ifdef WIN32
#include <wx/fileconf.h>
#endif

#include <ctype.h>

///////////////////////////////////////////////////////////
// TProcess declaration
///////////////////////////////////////////////////////////

class TLurchServer; //segnaposto della TLurchServer che serve alla TProcess

class TProcess : public wxProcess
{
  TLurchServer* m_pLurch;
  wxString m_strApp;

protected:
  virtual void OnTerminate(int pid, int status);

public:
  void ForcePid(int pid) { SetPid(pid); }
  TProcess(TLurchServer* pLurch, const wxString& strApp) : m_pLurch(pLurch), m_strApp(strApp) {}
};

///////////////////////////////////////////////////////////
// TLurchServer declaration
///////////////////////////////////////////////////////////

//classe TProcessHashMap derivata da wxHashMap (รจ una specie di assocarray!)
WX_DECLARE_HASH_MAP( wxString, wxProcess*, wxStringHash, wxStringEqual, TProcessHashMap );


class TLurchServer : public TBaseServerApp
{
  TProcessHashMap m_ProcMap;
  wxTimer m_Timer;
  int m_nFreq;

  DECLARE_EVENT_TABLE();
protected:  
	virtual const wxChar* GetAppName() const;
  virtual bool Initialization();
  void OnTimer(wxTimerEvent& evt);
  bool RestartTimer();

	void AddMiniForm(TXmlItem& tr, const wxChar* action, const wxChar* app, const wxChar* prompt) const;
  void EnumerateVariables(const wxString& strApp, wxArrayString& arr) const;

  void CreateServersList(wxArrayString& arr) const;
  bool PingProcess(const wxString& strApp);
  void StopProcess(const wxString& strApp);
  wxString StartProcess(const wxString& strApp);

public:
	void GenerateFile(wxString& strFilename);
  bool IsMagicName(wxString& strFilename) const;
  bool IsCgiName(wxString strFilename) const;

  void ProcessHttpGet(wxString cmd, wxSocketBase& outs);
  void ProcessHttpPost(wxString cmd, wxSocketBase& outs);

  bool ForgetProcess(const wxString& strApp);

  //metodi riguardanti l'interfaccia html
	void ProcessFormStart(const THashTable& args, wxSocketBase& sock);
  void ProcessFormStop(const THashTable& args, wxSocketBase& sock);
  void ProcessFormConfig(const THashTable& args, wxSocketBase& sock);
  void ProcessFormUpdate(THashTable& args, wxSocketBase& sock);
  void CallCgi(wxString& strFileName, wxSocketBase& sock);
};

///////////////////////////////////////////////////////////
// TProcess implementation
///////////////////////////////////////////////////////////

void TProcess::OnTerminate(int pid, int WXUNUSED(status))
{
  SetPid(pid);
  m_pLurch->ForgetProcess(m_strApp);
}

///////////////////////////////////////////////////////////
// TLurchServer implementation
///////////////////////////////////////////////////////////

BEGIN_EVENT_TABLE(TLurchServer, TBaseServerApp)
EVT_TIMER(wxID_ANY, TLurchServer::OnTimer)
END_EVENT_TABLE()

bool TLurchServer::PingProcess(const wxString& strApp)
{
  bool bPinged = false;
  TProcess* pProcess = (TProcess*)m_ProcMap[strApp];
  if (pProcess != NULL)
  {
    const wxString strPort = GetConfigString("Port", "", strApp);
    if (!strPort.IsEmpty())
    {
      const int nTimeOut = m_nFreq > 3000 ? m_nFreq / 3000 : 1;
      wxIPV4address ipAddress;
      ipAddress.LocalHost();
      ipAddress.Service(strPort);
      wxSocketClient sSock(wxSOCKET_NOWAIT);
      sSock.Connect(ipAddress, false);
      if (sSock.WaitOnConnect(nTimeOut))
      {
        sSock.Write("PING\n", 5);
        if (sSock.WaitForWrite(nTimeOut))
        {
          char buffer[8]; memset(buffer, 0, sizeof(buffer));
          sSock.Read(buffer, 4);
          if (sSock.WaitForRead(nTimeOut))
            bPinged = wxStrcmp(buffer, "PONG") == 0;
        }
      }
    }
  }
  return bPinged;
}

void TLurchServer::OnTimer(wxTimerEvent& WXUNUSED(evt))
{
  m_Timer.Stop();
  const wxString strApp = "Authorization";
  if (!PingProcess(strApp))
  {
    TProcess* pProcess = (TProcess*)m_ProcMap[strApp];
    wxKillError ke = pProcess->Kill(pProcess->GetPid());
    if (ke == wxKILL_OK)
      WriteLog(strApp + " killed!");
    else
    {
      wxString strMsg;
      strMsg << "Error " << ke << " killing " << strApp;
      WriteLog(strMsg);
    }
    wxSleep(2);
    StartProcess(strApp);
  }
  RestartTimer();
}

const wxChar* TLurchServer::GetAppName() const
{
	return "Lurch";
}

void TLurchServer::CreateServersList(wxArrayString& arr) const
{
	wxFileInputStream ini(GetConfigName());
  const size_t size = ini.GetSize();
	wxChar* buff = new wxChar[size];
	ini.Read(buff, size);

	const char* aperta = strchr(buff, '[');
	while (aperta != NULL)
	{
    char* chiusa = (char*)strchr(aperta+1, ']');
		if (chiusa != NULL)
		{
			*chiusa = '\0';
			wxString str = aperta+1;
		  arr.Add(str);
			aperta = strchr(chiusa+1, '[');
		}
		else
			break;
	}

	delete buff;
}

void TLurchServer::AddMiniForm(TXmlItem& tr, const wxChar* action, const wxChar* app, const wxChar* prompt) const
{
	TXmlItem& td = tr.AddChild("td").SetAttr("width", "15%");
  TXmlItem& form = td.AddChild("center").AddChild("form");
	form.SetAttr("action", action);
	
	TXmlItem& name = form.AddChild("input");
	name.SetAttr("type", "hidden"); name.SetAttr("name", "App");
	name.SetAttr("value", app);

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

void TLurchServer::GenerateFile(wxString& strFilename)
{
	TXmlItem html; 
	TXmlItem& body = CreatePageBody(html);

	if (strFilename == "index")
	{
		TXmlItem& table = body.AddChild("table");
		table.SetAttr("border", "1"); table.SetAttr("width", "100%");
		strFilename = GetTempFilename();
		const wxString strLurchName = GetAppName();

    wxArrayString arr; CreateServersList(arr);
		for (size_t i = 0; i < arr.GetCount(); i++)
		{
			const bool bLurch = arr[i] == strLurchName;
			wxFileConfig ini("", "", GetConfigName(), "", wxCONFIG_USE_LOCAL_FILE | wxCONFIG_USE_RELATIVE_PATH);
			wxString str;
			str << '/' << arr[i];
			ini.SetPath(str);

      wxString strHost; 
			ini.Read("Host", &strHost, "127.0.0.1");

			int nPort;
			ini.Read("Port", &nPort, 3883);

      wxString strIcon; 
			ini.Read("Icon", &strIcon, "euro.gif");

			const wxSingleInstanceChecker sic(arr[i]);
			const bool bRunning = sic.IsAnotherRunning();

			TXmlItem& tr = table.AddChild("tr");
			TXmlItem& td0 = tr.AddChild("td");
			td0.SetAttr("width", "15%"); td0.SetAttr("align", "center");
			TXmlItem& a = td0.AddChild("a");
			if (!bLurch && bRunning)
			{
				a.SetAttr("href", wxString::Format("http://%s:%d/index.htm", strHost.c_str(), nPort));
				a.SetAttr("target", "_blank");
			} 
 			TXmlItem& img = a.AddChild("img");
			img.SetAttr("src", strIcon); img.SetAttr("border", 0L); img.SetAttr("alt", arr[i]);

			AddMiniForm(tr, (bRunning || bLurch) ? "kill.cgi" : "start.cgi", arr[i], (bRunning || bLurch) ? "Stop" : "Start");
			AddMiniForm(tr, "config.cgi", arr[i], "Configure");

			TXmlItem& a3 = tr.AddChild("td").AddChild("a");
			if (!bLurch && bRunning)
			{
				a3.SetAttr("href", wxString::Format("http://%s:%d/index.htm", strHost.c_str(), nPort)); 
				a3.SetAttr("target", "_blank"); 
      }
     	a3 << arr[i] << " Server";
		}

    body.AddChild("hr");
    TXmlItem& panel = body.AddChild("table");
    panel.SetAttr("border", "1"); panel.SetAttr("width", "100%");
    panel.AddChild("caption").AddEnclosedText("Options");
    
    TXmlItem& tr0 = panel.AddChild("tr");
    tr0.AddChild("td").AddEnclosedText("Working directory");
    tr0.AddChild("td").AddEnclosedText(wxGetCwd());

    TXmlItem& tr1 = panel.AddChild("tr");
    tr1.AddChild("td").AddEnclosedText("Host Name");
    wxString strHost; strHost << wxGetHostName() << wxT(":") << GetDefaultPort();
    tr1.AddChild("td").AddEnclosedText(strHost);
    
    TXmlItem& tr2 = panel.AddChild("tr");
    tr2.AddChild("td").AddEnclosedText("Ping Frequency");
    wxString strFreq; strFreq << m_nFreq/1000;
    tr2.AddChild("td").AddEnclosedText(strFreq);

		html.Save(strFilename);
  }
}

bool TLurchServer::IsMagicName(wxString& strFilename) const
{
  wxString strName;
	wxSplitPath(strFilename, NULL, &strName, NULL);
  strName.MakeLower();
	if (strName == "index")
	{
		strFilename = strName;
		return true;
	}
	if (strName == "log")
	{
		strFilename = GetLogFileName();
	}

  return false;
}

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

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

bool TLurchServer::RestartTimer()
{
  bool ok = true;
  if (m_nFreq > 0)
  {
    ok = m_Timer.Start(m_nFreq) && m_Timer.IsRunning();
    if (!ok)
    {
      wxString strMsg;
      strMsg << "Error starting timer with " << m_nFreq << " ms frequency";
      WriteLog(strMsg);
    }
  }
  return ok;
}

wxString TLurchServer::StartProcess(const wxString& strApp)
{
  bool ok = false;

  wxString strMessage;
	if (!strApp.IsEmpty())
	{
  	const wxSingleInstanceChecker sic(strApp);
		ok = !sic.IsAnotherRunning();
	}
	
	if (ok)
	{
		const wxString strRun = GetConfigString("Run", "", strApp);
		if (wxFileExists(strRun))
		{
      wxFileName fnPath; 
      fnPath.SetPath(GetServerPath());
      fnPath.SetFullName(strRun);

      TProcess* pProcess = new TProcess(this, strApp);
			const long nProc = wxExecute(fnPath.GetFullPath(), wxEXEC_ASYNC, pProcess);
			if (nProc == 0 || nProc == -1)
      {
				strMessage.Printf("Can't run %s executable (%s)", strApp.c_str(), strRun.c_str());
        delete pProcess;
      }
			else
      {
        pProcess->ForcePid(nProc);
        m_ProcMap[strApp] = pProcess;   //memorizza il numero del processo che ha lanciato per poterlo usare in fase di controllo
			  strMessage = wxEmptyString;
      }
		}
		else
			strMessage.Printf("Can't find %s executable (%s)", strApp.c_str(), strRun.c_str());
	}
	else
		strMessage.Printf("%s il already running", strApp.c_str());

  if (strMessage.IsEmpty())
    WriteLog(strApp + " started.");
  else
    WriteLog(strMessage);

  return strMessage;
}

void TLurchServer::ProcessFormStart(const THashTable& args, wxSocketBase& sock)
{
  const wxString strApp = args.Get("App");
	if (!strApp.IsEmpty()) // Dummy test
	{
    const wxString strMessage = StartProcess(strApp);
    if (strMessage.IsEmpty())
      MessageBox("Server Started", strApp, sock);
    else
      MessageBox("ERROR", strMessage, sock);
	}
}

bool TLurchServer::ForgetProcess(const wxString& strApp)
{
  WriteLog(strApp + " terminated.");
  return m_ProcMap.erase(strApp) != 0;
}

void TLurchServer::StopProcess(const wxString& strApp)
{
	const wxString strHost = GetConfigString("Host", "localhost", strApp);
	const int nPort = GetConfigInt("Port", 0, strApp);
	if (nPort > 0)
	{
		wxIPV4address addr;
		addr.Hostname(strHost);
		addr.Service(nPort);

		wxSocketClient sock;
		if (sock.Connect(addr))
		{
  		const wxString str = "GET /stop.cgi HTTP/1.1\r\n\r\n";
			sock.Write(str, str.Length());
			if (strHost == "localhost")
			{
				const wxSingleInstanceChecker sic(strApp);
				for (int i = 0; i < 5 && sic.IsAnotherRunning(); i++)
					wxSleep(1);
        ForgetProcess(strApp);
			}
		}
	}
}

void TLurchServer::ProcessFormStop(const THashTable& args, wxSocketBase& sock)
{
  const wxString strApp = args.Get("App");
	if (strApp == GetAppName()) // Stop myself!
	{
	  wxArrayString app; CreateServersList(app);
    for (size_t i = 0; i < app.GetCount(); i++)
		{
			if (app[i] != GetAppName())  // Stop Children only!
			{
  			const wxSingleInstanceChecker sic(app[i]);
				if (sic.IsAnotherRunning())
	        StopProcess(app[i]);		
			}
		}
	}
	StopProcess(strApp);
  MessageBox("Server stopped", strApp, sock);
}

void TLurchServer::EnumerateVariables(const wxString& strApp, wxArrayString& arr) const
{
	wxFileInputStream inf(GetConfigName());
	wxString strParagraph = wxString::Format("[%s]", strApp.c_str());
	wxString str;

	bool bFound = false;
	while (inf.Ok())
	{
    inf >> str;
    if (str == strParagraph)
		{
			bFound = true;
			break;
		}
	}

	if (bFound)
	{
		while (inf.Ok())
		{
			inf >> str;
			if (str.IsEmpty() || str[0u] == '[')
				break;
			const int nEqual = str.Find('=');
      if (nEqual > 0)
			{
				str.Truncate(nEqual);
				str.Trim(false);
				str.Trim(true);
				arr.Add(str);
			}
		}
		arr.Sort();
	}
}

void TLurchServer::ProcessFormConfig(const THashTable& args, wxSocketBase& sock)
{
  const wxString strApp = args.Get("App");
  wxArrayString arr;
	EnumerateVariables(strApp, arr);

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

  TXmlItem& form = body.AddChild("form");
  form.SetAttr("action", "update.cgi").SetAttr("method", "post");
	
	TXmlItem& table = form.AddChild("table").SetAttr("width", "100%").SetAttr("border", "1");
	table.AddChild("caption").AddChild("h2") << strApp;
	TXmlItem& thead = table.AddChild("thead");
	thead.AddChild("th").SetAttr("width", "15%") << "Property";
	thead.AddChild("th").SetAttr("width", "85%") << "Value";

	for (size_t v = 0; v < arr.GetCount(); v++)
	{
		TXmlItem& tr = table.AddChild("tr");
		tr.AddChild("td") << arr[v];
		TXmlItem& input = tr.AddChild("td").AddChild("input");
		input.SetAttr("type", "text"); input.SetAttr("name", arr[v]);
		input.SetAttr("size", "80"); input.SetAttr("maxlength", "256");
		input.SetAttr("value", GetConfigString(arr[v], "", strApp));
	}

	TXmlItem& app = form.AddChild("input").SetAttr("type", "hidden");
	app.SetAttr("name", "App"); app.SetAttr("value", strApp);

	TXmlItem& submit = form.AddChild("br").AddChild("center").AddChild("input");
	submit.SetAttr("type", "submit"); submit.SetAttr("value", "Update Parameters");

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

	const wxString strFilename = GetTempFilename();
  html.Save(strFilename);
  SendFile(strFilename, sock);
}

void TLurchServer::ProcessFormUpdate(THashTable& args, wxSocketBase& sock)
{
  const wxString strApp = args.Get("App");
	args.BeginFind();
  for (wxHashTable::Node* pNode = args.Next(); pNode; pNode = args.Next())
	{
		const wxString strKey = pNode->GetKeyString();
		if (strKey != "App")
		{
		  const wxString strVal = args.Get(strKey);
		  SetConfigString(strKey, strVal, strApp);
		}
	}
	const wxString msg = wxString::Format("%s parameters updated", strApp.c_str());
  MessageBox("Success!", msg, sock);
}

void TLurchServer::CallCgi(wxString& strFileName, wxSocketBase& sock)
{
	wxString strName, strExt, strArgs;
	const int q = strFileName.Find('?');
	if (q > 0)
	{
		strArgs = strFileName.Mid(q+1);
		strFileName.Truncate(q);
	}
	wxSplitPath(strFileName, NULL, &strName, &strExt);

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

	if (strExt == "cgi")
	{
		if (strName == "start")
			ProcessFormStart(hashArgs, sock); else
		if (strName == "kill")
			ProcessFormStop(hashArgs, sock); else
		if (strName == "config")
			ProcessFormConfig(hashArgs, sock); else
		if (strName == "update")
			ProcessFormUpdate(hashArgs, sock);
	}
}

void TLurchServer::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 (IsCgiName(strFilename))
	  CallCgi(strFilename, outs);
	else
	{
	  if (IsMagicName(strFilename))
		  GenerateFile(strFilename);
	  SendFile(strFilename, outs);
	}
}

void TLurchServer::ProcessHttpPost(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(17);
  ParseArguments(args, hashArgs);

	if (strName == "update")
		ProcessFormUpdate(hashArgs, outs);
}

bool TLurchServer::Initialization()
{
  m_nFreq = GetConfigInt("PingFreq") * 1000;  // sec to msec

  wxArrayString arr; CreateServersList(arr);
	for (size_t i = 0; i < arr.GetCount(); i++)
	{
		const wxString& strApp = arr[i];
		const bool bAutorun = GetConfigBool("Autorun", false, strApp);
		if (bAutorun)
      StartProcess(strApp);
	}
  
  RestartTimer();


	return true;
}

// Istanziare l'applicazione principale

IMPLEMENT_APP(TLurchServer)