#include "wxinc.h"
#include "incstr.h"

#include "agasys.h"
#include "xvt.h"

///////////////////////////////////////////////////////////
// Unzip support
///////////////////////////////////////////////////////////

#include <wx/dir.h>
#include <wx/progdlg.h>
#include <wx/wfstream.h>
#include <wx/zipstrm.h>

static unsigned int aga_getziplist(const char* zipfile, wxArrayString& aFiles)
{
	wxFFileInputStream fin(zipfile);
  wxZipInputStream zip(fin);
  for (wxZipEntry* z = zip.GetNextEntry(); z; z = zip.GetNextEntry())
  {
    const wxString str = z->GetInternalName();
    aFiles.Add(str);
  }

	return aFiles.GetCount();
}

static int aga_find_slash(const wxString& path, int from)
{
  for (int i = from; path[i]; i++)
    if (wxIsPathSeparator(path[i]))
      return i;

    return -1;
}

static void aga_create_dir(wxString strPath)
{
  if (!wxEndsWithPathSeparator(strPath))
		strPath += wxFILE_SEP_PATH;

  for (int i = aga_find_slash(strPath, 0); i > 0; i = aga_find_slash(strPath, i+1))
  {
    const wxString strDir = strPath.Mid(0,i);
	  if (!::wxDirExists(strDir))
    {
      if (!::wxMkdir(strDir))
      {
        wxString strMsg = "Impossibile creare la cartella ";
        strMsg << strDir << " !!";
        wxMessageBox(strMsg, "Attenzione!", wxICON_ERROR);
      }
    }			
  }
}

bool aga_unzip(const char* zipfile, const char* destdir)
{
	wxArrayString aFiles;
	const unsigned int files = aga_getziplist(zipfile, aFiles);

  wxProgressDialog pi("Unzip", "", files, NULL, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
  pi.SetSize(480, 120);
  pi.Center();

	for (unsigned int f = 0; f < files; f++)
	{
    const wxString& strFileName = aFiles[f];
    if (!pi.Update(f, strFileName))
      break;

    if (wxEndsWithPathSeparator(strFileName) || strFileName.Find('.') < 0)  // Is dir name
    {
		  wxString strOutDir = destdir;
      if (!wxEndsWithPathSeparator(strOutDir))
		    strOutDir += wxFILE_SEP_PATH;
		  strOutDir += strFileName;
      aga_create_dir(strOutDir);
    }
    else
    {
		  wxZipInputStream fin(zipfile, strFileName);

		  wxString strOutFile = destdir;
      if (!wxEndsWithPathSeparator(strOutFile) && !wxIsPathSeparator(strFileName[0]))
		    strOutFile += wxFILE_SEP_PATH;
		  strOutFile += strFileName;

		  wxString strPath;
		  ::wxSplitPath(strOutFile, &strPath, NULL, NULL);
      aga_create_dir(strPath);
	
      wxFileOutputStream fout(strOutFile);
		  fout.Write(fin);
    }
	}
	return files > 0;
}

///////////////////////////////////////////////////////////
// Zip support
///////////////////////////////////////////////////////////

static void AddFileToZip(const wxString& strPrefix, const wxString& strFile,
												 wxZipOutputStream& zip)
{
	if (!wxFileExists(strFile))
		return;

	wxString strPath, strName, strExt;
	wxSplitPath(strFile, &strPath, &strName, &strExt);	

	wxString strRelName;
	strRelName = strPath.Mid(strPrefix.Length());
	if (!strRelName.IsEmpty())
	  strRelName += '/';
	strRelName += strName;
	strRelName += '.';
	strRelName += strExt;

  zip.PutNextEntry(strRelName);
 	wxFileInputStream fin(strFile);
	zip.Write(fin);                                  // Scrivo file compresso
}

static bool AddFilesToZip(const wxString& strBase, wxArrayString& aFiles, const char* zipfile)
{
	wxFFileOutputStream out(zipfile);
	wxZipOutputStream zip(out);

  const size_t nFiles = aFiles.GetCount();
  wxProgressDialog pi("Zip", "", nFiles, NULL, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
  pi.SetSize(480, 120);
  pi.Center();

  for (size_t i = 0; i < nFiles; i++) 
  {
    const wxString& str = aFiles[i];
    if (!pi.Update(i, str))
      break;
		AddFileToZip(strBase, str, zip);
  }
	return true;
}

bool aga_zip(const char* srcfiles, const char* zipfile)
{
	wxString strBase, strMask, strExt;
	wxSplitPath(srcfiles, &strBase, &strMask, &strExt);
	strMask += '.';
	strMask += strExt;

	wxArrayString aFiles;
  wxDir::GetAllFiles(strBase, &aFiles, strMask);

	return AddFilesToZip(strBase, aFiles, zipfile);
}

bool aga_zip_filelist(const char* filelist, const char* zipfile)
{
  wxArrayString aFiles;
	ifstream fin(filelist);
	while (!fin.eof())
	{
		char name[_MAX_PATH];
		fin.getline(name, sizeof(name));
		if (*name)
		  aFiles.Add(name);
		else
			break;
	}
	return AddFilesToZip("", aFiles, zipfile);
}

///////////////////////////////////////////////////////////
// DDE
///////////////////////////////////////////////////////////

#ifdef __WXMSW__
  #include <wx/dde.h>
  #define wxAgaClient wxDDEClient
  #define wxAgaConnection wxDDEConnection
#else
  // Forse inutile: forse gia' fatto da wxWidgets
  #include <wx/sckipc.h>
  #define wxAgaClient wxTCPClient
  #define wxAgaConnection wxTCPConnection
#endif

static wxAgaClient* _net_client = NULL;
static unsigned long _net_conns = 0;

unsigned long aga_dde_connect(const char* host, const char* service, const char* topic)
{
  if (_net_client == NULL)
  {
    _net_client = new wxAgaClient;
    _net_conns = 0;
  }

  wxConnectionBase* conn = _net_client->MakeConnection(host, service, topic);
  if (conn != NULL)
    _net_conns++;

  return (unsigned long)conn;
}

bool aga_dde_execute(unsigned long connection, const char* msg)
{
  bool ok = false;
  if (connection != 0)
  {
    wxAgaConnection* conn = (wxAgaConnection*)connection;
    ok = conn->Execute((char*)msg, -1);
  }
  return ok;
}

bool aga_dde_terminate(unsigned long connection)
{
  bool ok = false;
  if (connection != 0 && _net_client != NULL)
  {
    wxAgaConnection* conn = (wxAgaConnection*)connection;
    ok = conn->Disconnect();
    if (ok && _net_conns > 0)
    {
      _net_conns--;
      if (_net_conns == 0)
      {
        delete _net_client;
        _net_client = NULL;
      }
    }
  }
  return ok;
}

///////////////////////////////////////////////////////////
// TProgressIndicator
///////////////////////////////////////////////////////////

class TProgressIndicator : public wxDialog
{
  enum { MAX_GAUGES = 16 };
  wxGauge* m_pGauge[MAX_GAUGES];
  wxStaticText* m_pPassed;
  wxStaticText* m_pResidual;
  wxStaticText* m_pEstimated;
  wxStopWatch m_chrono;
  long m_nNextUpdate;

protected:
  DECLARE_EVENT_TABLE();

  void Init(wxString msg, int nGauges, bool bCancellable);
  wxString msec2str(long ms) const;
  long Elapsed() const { return m_chrono.Time(); }

public:
  void SetRange(int nRange, int nGauge = 0);
  bool SetProgress(int nPos, int nGauge = 0);
  void SetMessage(wxString msg);

  TProgressIndicator(wxString msg, bool bCancellable, int nGauges);
  TProgressIndicator(size_t nRange, wxString msg, bool bCancellable);
  virtual ~TProgressIndicator();
};

BEGIN_EVENT_TABLE(TProgressIndicator, wxDialog)
//  EVT_MENU(wxID_CANCEL, TProgressIndicator::OnCancel)
END_EVENT_TABLE()

wxString TProgressIndicator::msec2str(long ms) const
{
  int s = ms/1000;
  const int h = s / 3600;
  s -= h*3600;
  const int m = s / 60;
  s -= m*60;
  return wxString::Format(wxT("%02d:%02d:%02d"), h, m, s);
}

void TProgressIndicator::SetRange(int nRange, int nGauge)
{
  wxASSERT(nRange >= 0 && nGauge >= 0 && nGauge < MAX_GAUGES);
  m_pGauge[nGauge]->SetRange(nRange);
}

bool TProgressIndicator::SetProgress(int nPos, int nGauge)
{
  if (!IsShown())
    return false;

  wxASSERT(nPos >= 0 && nGauge >= 0 && nGauge < MAX_GAUGES);
  m_pGauge[nGauge]->SetValue(nPos);

  const long elap = Elapsed();
  if (elap > m_nNextUpdate)
  {
    m_nNextUpdate = elap+CLOCKS_PER_SEC;
    m_pPassed->SetLabel(msec2str(elap));

    wxLongLong_t nTotValue = 0, nTotRange = 0;
    for (int i = 0; i < MAX_GAUGES && m_pGauge[i]; i++)
    {
      const int nValue = m_pGauge[i]->GetValue();
      const int nRange = m_pGauge[i]->GetRange();
      if (nValue < nRange) // Escludi dal calcolo i processi gi� terminati
      {
        nTotValue += nValue;
        nTotRange += nRange;
      }
    }
    if (nTotValue > 0)
    {
      const long est = long(elap * nTotRange / nTotValue);
      m_pEstimated->SetLabel(msec2str(est));
      m_pResidual->SetLabel(msec2str(est-elap));
    }

    wxYield();
  }

  return true;
}

void TProgressIndicator::SetMessage(wxString msg)
{ SetLabel(msg); }

void TProgressIndicator::Init(wxString msg, int nGauges, bool bCancellable)
{
  const int nGap = 4;
  wxBoxSizer* pTopSizer = new wxBoxSizer(wxVERTICAL);
  SetSizer(pTopSizer);

  memset(m_pGauge, 0, sizeof(m_pGauge));
  for (int i = 0; i < nGauges; i++)
  {
    m_pGauge[i] = new wxGauge(this, 1001+i, 100, wxDefaultPosition, wxSize(400, -1));
    pTopSizer->Add(m_pGauge[i], 0, wxALL, nGap);
  }

  wxGridSizer* pTimersSizer = new wxGridSizer(1, 3, nGap, nGap);
  pTopSizer->Add(pTimersSizer, 0, wxEXPAND);

  wxStaticBoxSizer* pPassed = new wxStaticBoxSizer(wxVERTICAL, this, _("Elapsed Time"));
  pTimersSizer->Add(pPassed, 0, wxEXPAND);
  m_pPassed = new wxStaticText(this, 1101, wxT("00:00:00"));
  pPassed->Add(m_pPassed, 0, wxALL|wxALIGN_CENTER, 0);

  wxStaticBoxSizer* pResidual = new wxStaticBoxSizer(wxVERTICAL, this, _("Residual Time"));
  pTimersSizer->Add(pResidual, 0, wxEXPAND);
  m_pResidual = new wxStaticText(this, 1102, wxT("00:00:00"));
  pResidual->Add(m_pResidual, 0, wxALL|wxALIGN_CENTER, 0);

  wxStaticBoxSizer* pEstimated = new wxStaticBoxSizer(wxVERTICAL, this, _("Estimated Time"));
  pTimersSizer->Add(pEstimated, 0, wxEXPAND);
  m_pEstimated = new wxStaticText(this, 1103, wxT("00:00:00"));
  pEstimated->Add(m_pEstimated, 0, wxALL|wxALIGN_CENTER, 0);

  wxButton* pCancel = NULL;
  if (bCancellable)
  {
    pCancel = new wxButton(this, wxID_CANCEL, _("Cancel"));
    pTopSizer->Add(pCancel, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, nGap);
  }

  pTopSizer->SetSizeHints(this);

  SetMessage(msg);

  Show();
  Enable();
  Update();

  m_nNextUpdate = 0;
  m_chrono.Start();
}

TProgressIndicator::TProgressIndicator(size_t nRange, wxString msg, bool bCancellable)
                   : wxDialog(NULL, wxID_ANY, msg)  
{
  Init(msg, 1, bCancellable);
  SetRange(nRange, 0);
}

TProgressIndicator::TProgressIndicator(wxString msg, bool bCancellable, int nGauges)
                   : wxDialog(NULL, wxID_ANY, msg)
{
  Init(msg, nGauges, bCancellable);
}

TProgressIndicator::~TProgressIndicator()
{ }

///////////////////////////////////////////////////////////
// Multiprocess
///////////////////////////////////////////////////////////

class TWorker : public wxThread
{
  XVT_MULTITHREAD_CALLBACK m_pFunc;
  void* m_pCaller;
  void* m_pData;
  int m_nFirst, m_nLast, m_nTotal;
  TProgressIndicator* m_pi;
  int m_nGauge;

protected:
  virtual ExitCode Entry();

public:
  TWorker(XVT_MULTITHREAD_CALLBACK func, void* pCaller, void* pData, 
          int nFirst, int nLast, int nTotal, TProgressIndicator* pi, int nGauge);
};

wxThread::ExitCode TWorker::Entry()
{
  ExitCode ec = 0;
  if (m_pi != NULL)
  {
    m_pi->SetRange(m_nLast-m_nFirst, m_nGauge);
    for (int i = m_nFirst; i < m_nLast && ec == 0; i++)
    {
      ec = (ExitCode)m_pFunc(m_pCaller, m_pData, i, m_nTotal);
      if (!m_pi->SetProgress(i-m_nFirst+1, m_nGauge))
        ec = ExitCode(-1);
    }
  }
  else
  {
    for (int i = m_nFirst; i < m_nLast && ec == 0; i++)
      ec = (ExitCode)m_pFunc(m_pCaller, m_pData, i, m_nTotal);
  }
  return ec;
}

TWorker::TWorker(XVT_MULTITHREAD_CALLBACK func, void* pCaller, void* pData, 
                   int nFirst, int nLast, int nTotal, TProgressIndicator* pi, int nGauge)
        : wxThread(wxTHREAD_JOINABLE), m_pFunc(func), m_pCaller(pCaller), m_pData(pData), 
          m_nFirst(nFirst), m_nLast(nLast), m_nTotal(nTotal), m_pi(pi), m_nGauge(nGauge)

{ 
//  SetPriority(WXTHREAD_MIN_PRIORITY);
  Create(); 
}

BOOLEAN xvt_sys_multithread(XVT_MULTITHREAD_CALLBACK pFunc, void* pCaller, void* pData,
                            int tot, const char* msg)
{
  const int MAX_WORKERS = 16;

  // Bilanciamento automatico
  int nWorkers = wxThread::GetCPUCount();
  if (nWorkers <= 0) 
    nWorkers = 1;
  if (nWorkers > tot)
    nWorkers = tot;
  if (nWorkers > MAX_WORKERS)
    nWorkers = MAX_WORKERS;

  int ret = 0;
  if (nWorkers > 1)
  {
    TProgressIndicator* pi = NULL;
    if (msg && *msg)
      pi = new TProgressIndicator(msg, tot > nWorkers, nWorkers);
    
    TWorker* worker[MAX_WORKERS]; memset(worker, 0, sizeof(worker));

    int nFirst, nLast = 0, w;
    for (w = 0; w < nWorkers; w++)
    {
      nFirst = nLast;
      nLast = tot*(w+1)/nWorkers;
      worker[w] = new TWorker(pFunc, pCaller, pData, nFirst, nLast, tot, pi, w);
    }
  
    for (w = 0; w < nWorkers; w++)
      worker[w]->Run();

    if (pi != NULL)
      pi->Refresh();
    
    for (w = 0; w < nWorkers; w++)
    {
      const wxThread::ExitCode r = worker[w]->Wait();
      if (ret == 0 && r != 0) 
        ret = int(r);
      delete worker[w];
      worker[w] = NULL;
    }

    if (pi != NULL)
      delete pi;
  }
  else
  {
    if (msg && *msg)
    {
      TProgressIndicator pi(size_t(tot), msg, tot > nWorkers);
      for (int i = 0; i < tot && ret == 0; i++)
      {
        ret = pFunc(pCaller, pData, i, tot);
        if (!pi.SetProgress(i+1))
          ret = -1;
      }
    }
    else
    {
      for (int i = 0; i < tot && ret == 0; i++)
        ret = pFunc(pCaller, pData, i, tot);
    }
  }

  return ret;
}