#include "esystem_linux.h"

#include <stdio.h>
#include <signal.h>
#include <unistd.h>


#include <sys/sysinfo.h>
#include <sys/time.h>
#include <sys/resource.h>


#include <stdio.h>      
#include <sys/types.h>
#include <ifaddrs.h>
#include <netinet/in.h> 
#include <string.h> 
#include <arpa/inet.h>

estr esystem::getExecutablePath() const
{
  estr tmpstr;
  tmpstr.reserve(4096);
  size_t len=readlink("/proc/self/exe", tmpstr._str, 4096);
  if (len>=0){
    tmpstr[len]=0x00;
    tmpstr._strlen=len;
  }
  return(tmpstr);
}

estrarray esystem::getLocalAddresses()
{
  estrarray tmpstr;
  struct ifaddrs * ifAddrStruct=NULL;
  struct ifaddrs * ifa=NULL;
  void * tmpAddrPtr=NULL;

  getifaddrs(&ifAddrStruct);

  for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
    if (ifa ->ifa_addr->sa_family==AF_INET) { // check it is IP4
      // is a valid IP4 Address
      tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
      char addressBuffer[INET_ADDRSTRLEN];
      inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
      printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); 
      tmpstr.add(ifa->ifa_name,addressBuffer);
    }
/*
    else if (ifa->ifa_addr->sa_family==AF_INET6) { // check it is IP6
      // is a valid IP6 Address
      tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
      char addressBuffer[INET6_ADDRSTRLEN];
      inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
      printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); 
    }
*/
  }
  if (ifAddrStruct!=NULL) freeifaddrs(ifAddrStruct);
  return(tmpstr);
}


estr esystem::getHostname() const
{
  estr tmpstr;
  tmpstr.reserve(1024);
  ldieif(::gethostname(tmpstr._str,1024)==-1,"unable to get hostname");
  tmpstr._strlen=strlen(tmpstr._str);
  return(tmpstr);
}

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


// needed in OSX (maybe windows) to bring focus to application, not used in linux, or should be part of windows/UI system in edlibs
void esystem::setFrontProcess()
{
}

long int esystem::getMemLimit()
{
  struct rlimit rlim;
  getrlimit(RLIMIT_AS,&rlim);
  return(rlim.rlim_cur/1024);
}

int esystem::getTotalRam()
{
  struct sysinfo info;
  lerrorif(sysinfo(&info)!=0,"getting sysinfo");
  return(info.totalram*info.mem_unit/1024);
}

int esystem::getFreeRam()
{
  struct sysinfo info;
  lerrorif(sysinfo(&info)!=0,"getting sysinfo");
  return(info.freeram*info.mem_unit/1024);
}

int esystem::getBufferRam()
{
  struct sysinfo info;
  lerrorif(sysinfo(&info)!=0,"getting sysinfo");
  return(info.bufferram*info.mem_unit/1024);
}

int esystem::getSharedRam()
{
  struct sysinfo info;
  lerrorif(sysinfo(&info)!=0,"getting sysinfo");
  return(info.sharedram*info.mem_unit/1024);
}

int esystem::getTotalSwap()
{
  struct sysinfo info;
  lerrorif(sysinfo(&info)!=0,"getting sysinfo");
  return(info.totalswap*info.mem_unit/1024);
}

int esystem::getFreeSwap()
{
  struct sysinfo info;
  lerrorif(sysinfo(&info)!=0,"getting sysinfo");
  return(info.freeswap*info.mem_unit/1024);
}

esystemCallbackArray::esystemCallbackArray(): readCount(0),writeCount(0) {}

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::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::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;
  }
}



esystem *esystem::cursystem=0x00;

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

void esystem::handleTimerSignal(int signal,siginfo_t *si,void *data)
{
  esystemTimer *stimer=(esystemTimer*)si->_sifields._rt.si_sigval.sival_ptr;
  stimer->callback.call(stimer->data);
}

esystem::esystem()
{
  struct sigaction sa;
  sigemptyset(&sa.sa_mask);
  sigaddset(&sa.sa_mask,SIGALRM);
  sa.sa_flags=SA_SIGINFO;
  sa.sa_sigaction=&esystem::handleTimerSignal;
  sigaction(SIGALRM,&sa,0x00);
}

void esystem::handleInterruptSignal(int signal,siginfo_t *si,void *data)
{
//  for (int i=0; i<interruptHandlers.size(); ++i)
//    interruptHandlers[i].call();
}

/*
void esystem::addInterruptHandler(const efunc& cbfunc,const evararray& data)
{
  if (!interruptHandlerInstalled){
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask,SIGINT);
    sa.sa_flags=SA_SIGINFO;
    sa.sa_sigaction=&esystem::handleInterruptSignal;
    sigaction(SIGINT,&sa,0x00);
    interruptHandlerInstalled=true;
  }
  interruptHandlers.add(ecallback(cbfunc,data));
}
*/

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

  struct sigevent se;
  se.sigev_notify=SIGEV_SIGNAL;
  se.sigev_signo=SIGALRM;
  se.sigev_value.sival_ptr=(void*)t;

  int retval;
  retval=timer_create(CLOCK_REALTIME,&se,&t->timer);
  lassert(retval==-1);
  t->callback=callback;
  t->data=data;
  timers.addref(0,t);
}

esystemCallback *esystem::addReadWriteCallback(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);

  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;
  if (!fdCallbacks.exists(fd)){
    cbarr=new esystemCallbackArray;
    cbarr->fd=fd;
    fdCallbacks.addref(fd,cbarr);
  }else
    cbarr=&fdCallbacks.values(fd);

  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;
  if (!fdCallbacks.exists(fd)){
    cbarr=new esystemCallbackArray;
    cbarr->fd=fd;
    fdCallbacks.addref(fd,cbarr);
  }else
    cbarr=&fdCallbacks.values(fd);

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

/*
void esystem::add(int fd,const efunc& func,const evar& data)
{
  addReadCallback(fd,func,data);
//  fds.add(fd);
//  funcs.addref(new efunc(func));
//  datas.add(data);
}

void esystem::addfunc(int fd,efunc *func)
{
  addReadCallback(fd,*func,evararray());
//  fds.add(fd);
//  funcs.addref(func);
//  datas.add(evar());
}

void esystem::remove(int fd)
{
  int i;
  i=callbacks.findkey(fd);
  lerrorifr(i==-1,"file descriptor not found",);
  callbacks.erase(i);
  
}
*/

void esystem::run()
{
  while (1) wait();
}

void esystem::process()
{
  fd_set rd,wd;
  int i;
  int fdmax;

  timeval tv;

  tv.tv_sec=0;
  tv.tv_usec=0;

  {
//    ldinfo("waiting for activity on fds. callbacks.size: "+estr(callbacks.size())+" fd: "+estr(fd));
    FD_ZERO(&rd);
    FD_ZERO(&wd);
    fdmax=-1;
//    if (callbacks.size())
//      fdmax=callbacks.values(0).fd;
    einthashof<esystemCallbackArray>::iter it;
    for (it=fdCallbacks.begin(); it!=fdCallbacks.end(); ++it){
      esystemCallbackArray& cbarr(*it);
      if (cbarr.readCount){
        if (cbarr.fd>fdmax) fdmax=cbarr.fd;
        FD_SET(cbarr.fd,&rd);
      }
      if (cbarr.writeCount){
        if (cbarr.fd>fdmax) fdmax=cbarr.fd;
        FD_SET(cbarr.fd,&wd);
      }
    }

    int res;
    res=select(fdmax+1,&rd,&wd,NULL,&tv);

    lerrorif(res==-1,"error in select");

    for (it=fdCallbacks.begin(); it!=fdCallbacks.end(); ++it){
      esystemCallbackArray& cbarr(*it);
      for (int j=0; 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);
        if (cb.writeEnabled && FD_ISSET(cbarr.fd,&wd))
          cb.writeCallback.call(cb.writeData);
        cb.release();
      }
    }
  }
}

void esystem::wait(int fd)
{
//  struct timeval tv;
  fd_set rd,wd;
  int i;
  int fdmax;

  do{
//    ldinfo("waiting for activity on fds. callbacks.size: "+estr(callbacks.size())+" fd: "+estr(fd));
    FD_ZERO(&rd);
    FD_ZERO(&wd);
    fdmax=-1;
//    if (callbacks.size())
//      fdmax=callbacks.values(0).fd;
    einthashof<esystemCallbackArray>::iter it;
    for (it=fdCallbacks.begin(); it!=fdCallbacks.end(); ++it){
      esystemCallbackArray& cbarr(*it);
      if (cbarr.readCount){
        if (cbarr.fd>fdmax) fdmax=cbarr.fd;
        FD_SET(cbarr.fd,&rd);
      }
      if (cbarr.writeCount){
        if (cbarr.fd>fdmax) fdmax=cbarr.fd;
        FD_SET(cbarr.fd,&wd);
      }
    }

    if (fd!=-1){
      FD_SET(fd,&rd);
      if (!fdCallbacks.size() || fd > fdmax) fdmax=fd;
    }

    int res;
    res=select(fdmax+1,&rd,&wd,NULL,NULL);

    lerrorif(res==-1,"error in select");

    for (it=fdCallbacks.begin(); it!=fdCallbacks.end(); ++it){
      esystemCallbackArray& cbarr(*it);
      for (int j=0; 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);
        if (cb.writeEnabled && FD_ISSET(cbarr.fd,&wd))
          cb.writeCallback.call(cb.writeData);
        cb.release();
      }
    }
  } while (fdCallbacks.size() && fd!=-1 && !FD_ISSET(fd,&rd));
}



