#include "esystem_win32.h"

#include "logger.h"
#include "eiostream.h"
#include <windowsx.h>
#include <io.h>
#include <math.h>

#ifdef __MINGW32CE__
  #define WSTR(s) L ## s
#else
  #define WSTR(s) s
#endif
esystemTimer::esystemTimer() : timerID(0) {}
esystemTimer::~esystemTimer() { getSystem().timers.remove(timerID); }

esystemTimer *esystem::addTimer(const efunc& callback, const evararray& data, double secs, double repeat)
{
	esystemTimer *t = new esystemTimer;
	t->callback = callback;
	t->data = data;
	t->secs = secs;
	t->repeat = repeat;
	t->timerID = 0;

	t->timerID=SetTimer(NULL, 0, secs*1000.0, (TIMERPROC)&esystem::handleTimerCallback);
	if (t->timerID == 0) {
		lerror("creating timer");
		return(0x00);
	}
	timers.addref(t->timerID, t);
	return(t);
/*
  CFRunLoopTimerContext tmContext;
	bzero(&tmContext, sizeof(tmContext));
	tmContext.version = 0;
	tmContext.info = (void*)t;

	t->tmref = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + secs, repeat, 0, 0, esystem::handleTimerCallback, &tmContext);
	lassert(t->tmref == NULL);

	CFRunLoopAddTimer(CFRunLoopGetMain(), t->tmref, kCFRunLoopDefaultMode);
*/
}


esystem* esystem::cursystem=0x00;

esystem& getSystem()
{
  if (!esystem::cursystem)
    esystem::cursystem=new esystem;
  return(*esystem::cursystem);
}

/*
LRESULT CALLBACK WndProc(       HWND    hWnd,
                                UINT    uMsg,
                                WPARAM  wParam,
                                LPARAM  lParam)
{
//  if (uMsg == WM_SOCK){  
//  }else
  if (esystem::handleEvent)
    return(esystem::handleEvent(hWnd,uMsg,wParam,lParam));
  return(DefWindowProc(hWnd,uMsg,wParam,lParam));
}
*/


void esystem::handleTimerCallback(HWND hWnd,UINT timerMsg, UINT_PTR timerID, DWORD epomili)
{
  if (getSystem().timers.exists(timerID)) {
	esystemTimer& tm(getSystem().timers[timerID]);
	tm.callback.call(tm.data);
  }
  else
	lerror("unknown timer");

	// Deleting esystemTimer is a problem in the cases where the user keeps a pointer in order to delete the timer, but if the system triggers the timer, then this pointer will be invalid
	// Not deleting a timer is a problem because the user might assume the timer self-deletes
	// Solution: the user should always delete the timer!
	// Alternative: instead of passing the user a systemTimer pointer, pass a smartpointer, then the user can check if the systemtimer is no longer valid before trying to cancel the timer event

	//  if (t->repeat<=0.0l)  // if there are no more repeats delete esystemTimer
	//    delete t;
}


estr esystem::getExecutablePath() const
{
  estr tmpstr;
  return(tmpstr);
}

estrarray esystem::getLocalAddresses()
{
  estrarray tmpstr;
  return(tmpstr);
}


estr esystem::getHostname() const
{
  estr tmpstr;
  return(tmpstr);
}

int esystem::getPID() const
{
  return(0);
}



long int esystem::getMemLimit()
{
	return(0);
}

int esystem::getTotalRam()
{
	return(0);
}

int esystem::getFreeRam()
{
	return(0);
}

int esystem::getBufferRam()
{
	return(0);
}

int esystem::getSharedRam()
{
	return(0);
}

int esystem::getTotalSwap()
{
	return(0);
}

int esystem::getFreeSwap()
{
	return(0);
}

esystem::esystem(): waitcb(0x00)
{
  cursystem=this;

/*
  cout << " Getting Module Handle"<<endl;
  hInstance = GetModuleHandle(NULL);

  WNDCLASS wc;
#ifdef __MINGW32CE__
  wc.style       = CS_HREDRAW | CS_VREDRAW;
  wc.hIcon       = 0; //LoadIcon(NULL, IDI_WINLOGO);
  wc.hCursor     = 0; //LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
#else
  wc.style       = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
  wc.hIcon       = LoadIcon(NULL, IDI_WINLOGO);
  wc.hCursor     = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = NULL;
#endif
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.cbClsExtra  = 0;
  wc.cbWndExtra  = 0;
  wc.hInstance   = hInstance;
  wc.lpszMenuName  = NULL;
  wc.lpszClassName = WSTR("easycpp.edlib"); //szClassName;

  ecout << " Creating esystem!"<<endl;
  ldieif(!RegisterClass(&wc),"Failed to register The Window Class.\n");
  ecout << " Done creating esystem!"<<endl;
*/
}

esystem::~esystem()
{
/*
  if (!UnregisterClass(WSTR("easycpp.edlib"),hInstance))  //szClassName,hInstance))
    MessageBox(NULL,WSTR("Could Not Unregister Class."),WSTR("SHUTDOWN ERROR"),MB_OK | MB_ICONINFORMATION);
*/
}

void esystem::setFrontProcess()
{
  // make process active
}

void esystem::process()
{
    processMessages();
}

bool esystem::processMessages()
{
  MSG  msg;
  while (PeekMessage(&msg,0,0,0,PM_REMOVE) != 0){
    if (msg.message == WM_QUIT)
      return(false);
    else{
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  return(true);
}

bool esystem::processMessagesWait()
{
  MSG  msg;
  if (GetMessage(&msg,0,0,0)){
    if (msg.message == WM_QUIT)
      return (false);
    else{
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
	return(true);
  }
  return(false);
}

void esystem::run()
{
//	while (processMessagesWait());
	while (wait());
}

/*
void esystem::addSocket(SOCKET fd,const efunc& func,evar *data)
{
  return;
}

void esystem::removeSocket(int fd)
{
  return;
}

void esystem::add(HANDLE fHandle,const efunc& func,evar *data)
{
  fds.add(fHandle);
  funcs.addref(new efunc(func));
  datas.add(data);
}

void esystem::addfunc(HANDLE fHandle,efunc *func)
{
  fds.add(fHandle);
  funcs.addref(func);
  datas.add(NULL);
}


void esystem::remove(HANDLE fHandle)
{
  int i;
  i=fds.find(fHandle);
  lerrorifr(i==-1,"file handle not found",);

  fds.erase(i);
  funcs.erase(i);
  datas.erase(i);
}
*/


void printError(const estr& lpszFunction) 
{ 
    // Retrieve the system error message for the last-error code

    LPVOID lpMsgBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf,
        0, NULL );

    estr errmsg = (wchar_t*)lpMsgBuf;

    // Display the error message and exit the process

    printf("%s failed with error %d:\n",lpszFunction._str, dw);
    printf("%s\n", errmsg._str); 
//    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 

    LocalFree(lpMsgBuf);
}

bool esystem::wait(HANDLE fHandle)
{
  if (fHandle == 0x00 && !fdCallbacks.size()){
//	  cout << "No IO events to wait for, only processing windows messages" << endl;
    return(processMessagesWait());
  }
  cout << "IO events to wait for" << endl;

  int i;
  int nCount;
  int ret;
  int ti;
  HANDLE *pHandles=0x00;

  do{ 
    nCount=0;
    ti=0;
    if (fHandle!=0x00) ++nCount;

    ldinfo("waiting on fds: "+estr(nCount)+" fd: "+estr((int)fHandle));

    elonghashof<esystemCallbackArray>::iter it;
    for (it=fdCallbacks.begin(); it!=fdCallbacks.end(); ++it){
      esystemCallbackArray& cbarr(*it);
      if (cbarr.readEvent)
        ++nCount;
      if (cbarr.writeEvent)
        ++nCount;
      if (cbarr.acceptEvent)
        ++nCount;
	  if (cbarr.fileEvent)
        ++nCount;
	}

    pHandles = new HANDLE[nCount];
    for (it=fdCallbacks.begin(); it!=fdCallbacks.end(); ++it){
      esystemCallbackArray& cbarr(*it);
      if (cbarr.readEvent)
        pHandles[ti++]=cbarr.readEvent;
      if (cbarr.writeEvent)
        pHandles[ti++]=cbarr.writeEvent;
      if (cbarr.acceptEvent)
        pHandles[ti++]=cbarr.acceptEvent;
	  if (cbarr.fileEvent)
	    pHandles[ti++] = cbarr.fileEvent;
	}

    if (fHandle!=0x00)
      pHandles[ti]=fHandle;

    ret=MsgWaitForMultipleObjects(nCount,pHandles,FALSE,INFINITE,QS_ALLEVENTS);

    if (ret >= WAIT_OBJECT_0 && ret < WAIT_OBJECT_0 + nCount){
	  ti = ret - WAIT_OBJECT_0;
      if (fHandle!=0x00 && ti==nCount-1){
        break;
      }
//	  cout << "Got msg on handle: " << pHandles[ti] << endl;
	  if (!fdCallbacks.exists((unsigned long)pHandles[ti])) {
        cout << "error finding handle: " << pHandles[ti] << endl;
	    exit(0);
	  }
	  if (fdCallbacks.exists((unsigned long)pHandles[ti])){
        esystemCallbackArray& cbarr(fdCallbacks[(unsigned long)pHandles[ti]]);
		for (int j = 0; pHandles[ti] == cbarr.fileEvent && j < cbarr.callbacks.size(); ++j) {
//			cout << "calling file event callback" << endl;
			esystemCallback& cb(*cbarr.callbacks.at(j));
			if (cb.lcount < 0) continue;
			cb.retain();
			if (cb.readEnabled) // && FD_ISSET(cbarr.fd,&rd))
			  cb.readCallback.call(cb.readData);
			if (cb.writeEnabled) // && FD_ISSET(cbarr.fd,&rd))
              cb.writeCallback.call(cb.writeData);
			cb.release();
		}
		for (int j=0; pHandles[ti]==cbarr.readEvent && j<cbarr.callbacks.size(); ++j){
          esystemCallback& cb(*cbarr.callbacks.at(j));
          if (cb.lcount<0) continue;
          cb.retain();
          if (cb.readEnabled) // && FD_ISSET(cbarr.fd,&rd))
            cb.readCallback.call(cb.readData);
          cb.release();
        }
        for (int j=0; pHandles[ti]==cbarr.writeEvent && j<cbarr.callbacks.size(); ++j){
          esystemCallback& cb(*cbarr.callbacks.at(j));
          if (cb.lcount<0) continue;
          cb.retain();
          if (cb.writeEnabled) // && FD_ISSET(cbarr.fd,&rd))
            cb.writeCallback.call(cb.writeData);
          cb.release();
        }
        for (int j=0; pHandles[ti]==cbarr.acceptEvent && j<cbarr.callbacks.size(); ++j){
          esystemCallback& cb(*cbarr.callbacks.at(j));
          if (cb.lcount<0) continue;
          cb.retain();
          if (cb.acceptEnabled) // && FD_ISSET(cbarr.fd,&rd))
            cb.acceptCallback.call(cb.acceptData);
          cb.release();
        }
      }
    }else if (ret == WAIT_OBJECT_0 + nCount) {
//      cout << "Got system MSG event" << endl;
      processMessages();
    }else if (ret == WAIT_TIMEOUT)
      cout << "got timeout event" << endl;
    else if (ret == WAIT_FAILED){
      printError("MsgWaitForMultipleObjects");
      lwarn("got error event");
    }
    delete[] pHandles;
    pHandles=0x00;
  } while (fHandle!=0x00);

  if (pHandles)
    delete[] pHandles;

//  cout << "leaving wait" << endl;

  return(true);
}

void esystem::waitSocket(int fd)
{
  if (fd == -1) { wait(); return; }

  ldieif(waitcb != 0x00, "already waiting, not allowed to call wait inside a wait");
  waitcb = addSocketReadCallback(fd, efunc(), evararray());
  linfo("waiting: " + estr(fd) + ", running loop: " + estr(waitcb));

  wait();

  waitcb->remove();
  waitcb = 0x00;
}

/*
void esystem::wait(HANDLE fHandle)
{
  if (fHandle == 0x00 && !fds.size()){
    processMessagesWait();
    return;
  }

  int i;
  int nCount;
  int ret;
  HANDLE *pHandles;
  do{ 
    nCount = fds.size();
    if (fHandle!=0x00) ++nCount;
    ldinfo("waiting on fds: "+estr(nCount)+" fd: "+estr((int)fHandle));

    pHandles = new HANDLE[nCount];

    for (i=0; i<fds.size(); ++i)
      pHandles[i]=fds[i];
    if (fHandle!=0x00)
      pHandles[i]=fHandle;
    ret=MsgWaitForMultipleObjects(nCount,pHandles,FALSE,INFINITE,QS_ALLEVENTS);
    delete[] pHandles;
    if (ret >= WAIT_OBJECT_0 && ret < WAIT_OBJECT_0 + nCount){
      ldinfo("Got msg on fd");
      i=ret-WAIT_OBJECT_0;
      if (fHandle!=0x00 && i==nCount-1){
        return;
      }
      evararray arr;
      if (datas[i]){
        arr.add(datas[i]->var);
        funcs[i].call(arr);
      }else
        funcs[i].call(arr);
    }else if (ret == WAIT_OBJECT_0 + nCount) {
      ldinfo("Got system MSG event");
      processMessages();
    }else if (ret == WAIT_TIMEOUT)
      lwarn("got timeout event");
    else if (ret == WAIT_FAILED){
      printError("MsgWaitForMultipleObjects");
      lwarn("got error event");
    }
  } while (fHandle!=0x00);
}
*/

esystemCallbackArray::esystemCallbackArray(): readCount(0),writeCount(0),acceptCount(0),fileEvent(0x00),readEvent(0x00),writeEvent(0x00),acceptEvent(0x00) {}

esystemCallbackArray::~esystemCallbackArray()
{
  if (readEvent)
    WSACloseEvent(readEvent);
  if (writeEvent)
    WSACloseEvent(writeEvent);
  if (acceptEvent)
    WSACloseEvent(acceptEvent);
}

esystemCallback::esystemCallback(esystemCallbackArray *_cbarr): cbarr(_cbarr),readEnabled(false), writeEnabled(false),lcount(0) {}

esystemCallback::~esystemCallback() 
{
//  remove();
}

void esystemCallback::retain()
{
  ++lcount;
}

void esystemCallback::release()
{
  --lcount;
}

void esystemCallback::remove()
{
  disableReadWrite();
  release();
/*
  linfo("removing socket callback, cb: "+estr(this)+" fd: "+estr(cbarr->fd));
  int i=cbarr->callbacks.find(this);
  ldieif(i==-1,"removed socket callback twice?");
  cbarr->callbacks.erase(i);
  if (cbarr->callbacks.size()==0)
    getSystem().fdCallbacks.erase(cbarr->fd);
*/
}

void esystemCallback::disableAccept()
{
  if (!acceptEnabled) return;
  acceptEnabled=false;
  --cbarr->acceptCount;
}

void esystemCallback::disableRead()
{
  if (!readEnabled) return;
  readEnabled=false;
  --cbarr->readCount;
}

void esystemCallback::disableWrite()
{
  if (!writeEnabled) return;
  writeEnabled=false;
  --cbarr->writeCount;
}

void esystemCallback::disableReadWrite()
{
  if (readEnabled){
    readEnabled=false;
    --cbarr->readCount;
  }
  if (writeEnabled){
    writeEnabled=false;
    --cbarr->writeCount;
  }
}

void esystemCallback::enableAccept()
{
  if (acceptEnabled) return;
  acceptEnabled=true;
  ++cbarr->acceptCount;
}

void esystemCallback::enableRead()
{
  if (readEnabled) return;
  readEnabled=true;
  ++cbarr->readCount;
}

void esystemCallback::enableWrite()
{
  if (writeEnabled) return;
  writeEnabled=true;
  ++cbarr->writeCount;
}

void esystemCallback::enableReadWrite()
{
  if (!readEnabled){
    readEnabled=true;
    ++cbarr->readCount;
  }
  if (!writeEnabled){
    writeEnabled=true;
    ++cbarr->writeCount;
  }
}

esystemCallback *esystem::addReadWriteCallback(int fd,const efunc& readCallback,const evararray& readData,const efunc& writeCallback,const evararray& writeData)
{
  esystemCallbackArray *cbarr;

  HANDLE fhandle=(HANDLE)_get_osfhandle(fd);
  if (!fdCallbacks.exists((unsigned long)fhandle)){
    cbarr=new esystemCallbackArray;
	cbarr->fd = fd;
	fdCallbacks.addref((unsigned long)fhandle, cbarr);
  }
  else
    cbarr = &fdCallbacks.values((unsigned long)fhandle);

  fdCallbacks.values((unsigned long)fhandle).fileEvent = fhandle;

  esystemCallback *cb=new esystemCallback(cbarr);
  cb->readCallback=readCallback;
  cb->readData=readData;
  cb->writeCallback=writeCallback;
  cb->writeData=writeData;
  cb->enableReadWrite();
  cbarr->callbacks.add(cb);
  return(cb);
}

esystemCallback *esystem::addWriteCallback(int fd,const efunc& func,const evararray& data)
{
  esystemCallbackArray *cbarr;
  HANDLE fhandle = (HANDLE)_get_osfhandle(fd);
  if (!fdCallbacks.exists((unsigned long)fhandle)) {
	  cbarr = new esystemCallbackArray;
	  cbarr->fd = fd;
	  fdCallbacks.addref((unsigned long)fhandle, cbarr);
  }
  else
	  cbarr = &fdCallbacks.values((unsigned long)fhandle);

  fdCallbacks.values((unsigned long)fhandle).fileEvent = fhandle;

  esystemCallback *cb=new esystemCallback(cbarr);
  cb->writeCallback=func;
  cb->writeData=data;
  cb->enableWrite();
  cbarr->callbacks.add(cb);
  return(cb);
}

esystemCallback *esystem::addReadCallback(int fd,const efunc& func,const evararray& data)
{
  esystemCallbackArray *cbarr;
  HANDLE fhandle = (HANDLE)_get_osfhandle(fd);

  cout << "Adding read callback for: " << fd << " handle: " << fhandle << endl;
  if (!fdCallbacks.exists((unsigned long)fhandle)) {
	  cbarr = new esystemCallbackArray;
	  cbarr->fd = fd;
	  cout << "1 Adding read callback for: " << fd << " handle: " << fhandle << endl;
	  fdCallbacks.addref((unsigned long)fhandle, cbarr);
  }
  else
	  cbarr = &fdCallbacks.values((unsigned long)fhandle);

  fdCallbacks.values((unsigned long)fhandle).fileEvent = fhandle;

  cout << "2 Adding read callback for: " << fd << " handle: " << fhandle << endl;

  esystemCallback *cb=new esystemCallback(cbarr);
  cb->readCallback=func;
  cb->readData=data;
  cb->enableRead();
  cbarr->callbacks.add(cb);
  return(cb);
}

esystemCallback *esystem::addSocketAcceptCallback(int fd,const efunc& acceptCallback,const evararray& acceptData)
{
  esystemCallbackArray *cbarr;
  if (!fdCallbacks.exists(fd)){
    cbarr=new esystemCallbackArray;
    cbarr->fd=fd;
    fdCallbacks.addref(fd,cbarr);
  }else
    cbarr=&fdCallbacks.values(fd);

  if (!cbarr->acceptEvent){
    cbarr->acceptEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
    WSAEventSelect(fd,cbarr->acceptEvent,FD_ACCEPT);
	handleCallbacks.addref((int)cbarr->acceptEvent, cbarr);
  }

  esystemCallback *cb=new esystemCallback(cbarr);
  cb->acceptCallback=acceptCallback;
  cb->acceptData=acceptData;
  cb->enableAccept();
  cbarr->callbacks.add(cb);
  return(cb);

}

esystemCallback *esystem::addSocketReadWriteCallback(int fd,const efunc& readCallback,const evararray& readData,const efunc& writeCallback,const evararray& writeData)
{
  esystemCallbackArray *cbarr;
  if (!fdCallbacks.exists(fd)){
    cbarr=new esystemCallbackArray;
    cbarr->fd=fd;
    fdCallbacks.addref(fd,cbarr);
  }else
    cbarr=&fdCallbacks.values(fd);

  if (!cbarr->readEvent){
    cbarr->readEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
    WSAEventSelect(fd,cbarr->readEvent,FD_READ);
	handleCallbacks.addref((int)cbarr->readEvent, cbarr);
  }
  if (!cbarr->writeEvent){
    cbarr->writeEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
    WSAEventSelect(fd,cbarr->writeEvent,FD_WRITE);
	handleCallbacks.addref((int)cbarr->writeEvent, cbarr);
  }

  esystemCallback *cb=new esystemCallback(cbarr);
  cb->readCallback=readCallback;
  cb->readData=readData;
  cb->writeCallback=writeCallback;
  cb->writeData=writeData;
  cb->enableReadWrite();
  cbarr->callbacks.add(cb);
  return(cb);
}

esystemCallback *esystem::addSocketWriteCallback(int fd,const efunc& func,const evararray& data)
{
  esystemCallbackArray *cbarr;
  if (!fdCallbacks.exists(fd)){
    cbarr=new esystemCallbackArray;
    cbarr->fd=fd;
    fdCallbacks.addref(fd,cbarr);
  }else
    cbarr=&fdCallbacks.values(fd);

  if (!cbarr->writeEvent){
    cbarr->writeEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
    WSAEventSelect(fd,cbarr->writeEvent,FD_WRITE);
	handleCallbacks.addref((int)cbarr->writeEvent, cbarr);
  }

  esystemCallback *cb=new esystemCallback(cbarr);
  cb->writeCallback=func;
  cb->writeData=data;
  cb->enableWrite();
  cbarr->callbacks.add(cb);
  return(cb);
}

esystemCallback *esystem::addSocketReadCallback(int fd,const efunc& func,const evararray& data)
{
  esystemCallbackArray *cbarr;
  if (!fdCallbacks.exists(fd)){
    cbarr=new esystemCallbackArray;
    cbarr->fd=fd;
    fdCallbacks.addref(fd,cbarr);
  }else
    cbarr=&fdCallbacks.values(fd);

  if (!cbarr->readEvent){
    cbarr->readEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
    WSAEventSelect(fd,cbarr->readEvent,FD_READ);
	handleCallbacks.addref((int)cbarr->readEvent, cbarr);
  }

  esystemCallback *cb=new esystemCallback(cbarr);
  cb->readCallback=func;
  cb->readData=data;
  cb->enableRead();
  cbarr->callbacks.add(cb);
  return(cb);
}



