#include "evar.h"
#include "eparser.h"
#include "estr.h"
#include "estrarray.h"
#include "efile.h"
#include "edir.h"
//#include "emain.h"
#include "eparserinterpreter.h"
#include "edistcomp.h"
#include "estrhashof.h"
#include "evarcommon.h"
#include "edaemon.h"

#ifndef _WIN32
#include <unistd.h>
#include <pwd.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>

//#include <uuid/uuid.h>

estr sfile;
estr saddr;
estr optInterpret;

efunc onDaemonInit;
efunc onConnectInit;

void doStartDBridge()
{
  eparseArgs();
  startDaemonBridge(sfile);
  exit(0);
}

void doStartDConnect()
{
  eparseArgs();
//  if (saddr.len())
//    startTCPDaemon(saddr)
//  else
  startDaemonConnect(sfile,saddr);
  exit(0);
}

void doStartDaemon()
{
  eparseArgs();
//  if (saddr.len())
//    startTCPDaemon(saddr);
//  else
  startDaemon(sfile,saddr);
  getSystem().run();

  exit(0); 
}

void daemonArgs(const efunc& f,const efunc& fconn)
{
  onDaemonInit=f;
  onConnectInit=fconn;
  epregister2(optInterpret,"i");
  epregisterI(sfile,"named socket file to connect with daemon");
  epregisterI(saddr,"socket address to listen/connect with daemon");
  epregisterAction("daemon",doStartDaemon); //"start daemon");
  epregisterAction("dconnect",doStartDConnect); // "connect to local daemon");
  epregisterAction("dbridge",doStartDBridge); //"bridge connection to local daemon");
}

#ifdef _WIN32
void initDaemon2(const estr& sfile)
{
}
#else
void initDaemon2(const estr& sfile)
{
  struct passwd *sp=getpwuid(getuid());
  pid_t pid;

  /* Fork off the parent process */
  pid = fork();
  /* An error occurred */
  if (pid < 0)
    exit(EXIT_FAILURE);
  /* Success: Let the parent continue */
  if (pid > 0) {
    cerr << "# parent returning" << endl;
    return;
  }
//    exit(EXIT_SUCCESS);

  cerr << "# child after fork" << endl;

  /* On success: The child process becomes session leader */
  if (setsid() < 0)
    exit(EXIT_FAILURE);

  /* Catch, ignore and handle signals */
  //TODO: Implement a working signal handler */
  signal(SIGCHLD, SIG_IGN);
  signal(SIGHUP, SIG_IGN);

  cerr << "# child before 2nd fork" << endl;
  /* Fork off for the second time*/
  pid = fork();

  /* An error occurred */
  if (pid < 0)
    exit(EXIT_FAILURE);

  /* Success: Let the parent terminate */
  if (pid > 0)
    exit(EXIT_SUCCESS);

  cerr << "# child after 2nd fork" << endl;
  /* Set new file permissions */
  umask(0);

  /* Change the working directory to the root directory */
  /* or another appropriated directory */
//  chdir("/");

//  cerr << "# changing dir" << endl;
//  if (sp!=0x00){
//    chdir(sp->pw_dir);
//  }

/*
  cerr << "# closing fd" << endl;
  // Close all open file descriptors 
  int x;
  for (x = sysconf(_SC_OPEN_MAX); x>0; x--)
  {
    close(x);
  }
*/
//  cerr << "# starting daemon" << endl;
//  estr path=pwd()+"/"+getParser().args[0];
//  execlp(getParser().args[0]._str,"--daemon","--sfile",sfile._str,(char*)0x00);
//  ldie("error in execl");

  system(getParser().args[0]+" --daemon --sfile "+sfile);
  exit(0);

  /* Open the log file */
//  openlog("firstdaemon", LOG_PID, LOG_DAEMON);
}
#endif

#ifdef _WIN32
void initDaemon()
{

}
#else
void initDaemon()
{
  pid_t pid;

  /* Fork off the parent process */
  pid = fork();

  /* An error occurred */
  if (pid < 0)
    exit(EXIT_FAILURE);

  /* Success: Let the parent terminate */
  if (pid > 0)
    exit(EXIT_SUCCESS);

  /* On success: The child process becomes session leader */
  if (setsid() < 0)
    exit(EXIT_FAILURE);

  /* Catch, ignore and handle signals */
  //TODO: Implement a working signal handler */
  signal(SIGCHLD, SIG_IGN);
  signal(SIGHUP, SIG_IGN);

  /* Fork off for the second time*/
  pid = fork();

  /* An error occurred */
  if (pid < 0)
    exit(EXIT_FAILURE);

  /* Success: Let the parent terminate */
  if (pid > 0)
    exit(EXIT_SUCCESS);

  /* Set new file permissions */
  umask(0);

  /* Change the working directory to the root directory */
  /* or another appropriated directory */
//  chdir("/");

  struct passwd *sp=getpwuid(getuid());
  if (sp!=0x00){
    chdir(sp->pw_dir);
  }

  /* Close all open file descriptors */
  int x;
  for (x = sysconf(_SC_OPEN_MAX); x>0; x--)
  {
    close(x);
  }

  /* Open the log file */
//  openlog("firstdaemon", LOG_PID, LOG_DAEMON);
}
#endif

void nmsAccept(enamedsocket& server)
{
  enamedsocket s;
  while (server.accept(s)){
    cerr << "got connection" << endl;
    getDistComp().startUnixClient(s);
  }
} 

void tcpsAccept(esocket& server)
{
  esocket s;
  while (server.accept(s))
    getDistComp().startTCPClient(s);
} 


#ifdef _WIN32
void startDaemon(const estr& _sfile,const estr& _saddr)
{
 ldie("not implemented");
}
#else
void startDaemon(const estr& _sfile,const estr& _saddr)
{

/*
  initDaemon();
*/

/*
  mkdir(".esh",0700);

  getLogger().level=0;

  efile f;
  f.open(".esh/daemon.log","a");
  dup2(f.fileno(),1);
  f.open(".esh/daemon.err","a");
  dup2(f.fileno(),2);
*/

//  cout << "daemon started" << endl;

  estr sfile(_sfile);
  if (sfile.len()==0) {
    struct passwd *sp=getpwuid(getuid());
    estr homedir;
    if (sp!=0x00)
      homedir=sp->pw_dir;
    sfile=homedir+"/.esh/socket";
  }

  signal(SIGPIPE, SIG_IGN);
  enamedsocket &nmserver(getDistComp().nmserver);

  nmserver.listen(sfile);
  chmod(sfile._str,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
  nmserver.onIncoming=nmsAccept;

  esocket &tcpserver(getDistComp().tcpserver);

  if (_saddr.len()){
    int port=19888;
    estr addr(_saddr);
    if (addr.find(":")){
      estrarray arr(addr.explode(":"));
      ldieif(arr.size()>2,"incorrectly formatted address: "+addr);
      ldieif(!arr[1].is_int(),"incorrectly formatted address: "+addr);
      addr=arr[0];
      port=arr[1].i();
    }
  
    tcpserver.onIncoming=tcpsAccept;
    tcpserver.listen(port,addr);
  }

  epregisterFunctions();
  onDaemonInit();
}

void startTCPDaemon(const estr& _addr)
{
  int port=19888;
  estr addr(_addr);
  if (addr.len()==0)
    addr="localhost";
  else{
    if (addr.find(":")){
      estrarray arr(addr.explode(":"));
      ldieif(arr.size()>2,"incorrectly formatted address: "+addr);
      ldieif(!arr[1].is_int(),"incorrectly formatted address: "+addr);
      addr=arr[0];
      port=arr[1].i();
    }
  }

  signal(SIGPIPE, SIG_IGN);
  esocket tcpserver;

  tcpserver.onIncoming=tcpsAccept;
  tcpserver.listen(port,addr);

  epregisterFunctions();
  onDaemonInit();
//  getSystem().run();
}

#endif

void daemonConnectInitDefault()
{
  if (optInterpret.len()){
    epregisterFunctions();
    epinterpret(optInterpret);
  }

  if (getParser().args.size()>1){
    getParser().args.erase(0); // remove esh argument, keep only script name
    epinterpretfile(getParser().args[0]);
  } else
    setupInterpret();
}

void daemonConnectReady()
{
//  cerr << "DaemonConnectReady" << endl;
//  setupInterpret();
  onConnectInit();
}

void startDaemonConnect(const estr& _sfile,const estr& _saddr)
{
  if (_saddr.len()){
    int port=19888;
    estr saddr(_saddr);
    if (saddr.len()==0)
      saddr="localhost";
    else{
      if (saddr.find(":")){
        estrarray arr(saddr.explode(":"));
        ldieif(arr.size()>2,"incorrectly formatted address: "+saddr);
        ldieif(!arr[1].is_int(),"incorrectly formatted address: "+saddr);
        saddr=arr[0];
        port=arr[1].i();
      }
    }

    esocket client;
    bool connf=false;
    for (int i=0; i<3 && !(connf=client.connect(saddr,port)); ++i);
    ldieif(!connf,"failed to connect 3 times");

    getDistComp().startTCPClient(client,daemonConnectReady);
    getSystem().run();
    return;
  }
  estr sfile(_sfile);
  if (sfile.len()==0) {
    struct passwd *sp=getpwuid(getuid());
    estr homedir;
    if (sp!=0x00)
      homedir=sp->pw_dir;
    sfile=homedir+"/.esh/socket";
  }
  enamedsocket client;
//  client.onReady=daemonConnectReady;
//  if (!efile(homedir+"/.esh/socket").exists() || !client.connect(homedir+"/.esh/socket")){
  if (!efile(sfile).exists() || !client.connect(sfile)){
    lwarn("daemon not started");
    exit(-1);

    initDaemon2(sfile);
//    system("esh --daemon 1 --sfile "+sfile);
    sleep(1);
    ldieif(!client.connect(sfile),"daemon not started");
  }
  getDistComp().startUnixClient(client,daemonConnectReady);
  getSystem().run();
//  epruninterpret();
}

class esocketBridge : public enamedsocket
{
 public:
  int oldoutfd;
  efile fin;
  efile fout;

  estr sendbuffer;
  estr recvbuffer;

  esystemCallback *outCallback;
  esystemCallback *inCallback;

  esocketBridge(); 
  esocketBridge(enamedsocket& nms); 
  void doInput();
  void doOutput();
  virtual void doRecv();
  virtual void doSend();
  virtual void doConnect();
};

esocketBridge::esocketBridge() {}
esocketBridge::esocketBridge(enamedsocket& nms): enamedsocket(nms) {}

void esocketBridge::doConnect()
{
  oldoutfd=dup(1);

  efile f;
  f.open("bridgeout.log","w");
  dup2(f.fileno(),1);
  f.open("bridgeerr.log","w");
  dup2(f.fileno(),2);

 
  fout.open(oldoutfd,"w");
  fout.disableBuffer();
  fout.setNonBlocking();
  fin.open(stdin);
  fin.disableBuffer();
  fin.setNonBlocking();

  cout << "# started bridge" << endl;

//  getSystem().addReadCallback(outpipe,efunc(*this,&edcnode_proc::sendOutput),evararray());
  getSystem().addReadCallback(fin.fileno(),efunc(*this,&esocketBridge::doInput),evararray());
  outCallback=getSystem().addWriteCallback(fout.fileno(),efunc(*this,&esocketBridge::doOutput),evararray());
  outCallback->disableWrite();
}

void esocketBridge::doInput()
{
  int len;
  estr tmpdata;
  while (!fin.eof() && fin.read(tmpdata)>0){
    recvbuffer+=tmpdata;
    tmpdata.clear();
  }
  doSend();
  if (fin.eof()){
    cout << "client disconnected" << endl;
    lwarn("client disconnected");
    exit(0);
    return;
  }
}


void esocketBridge::doOutput()
{
  if (!sendbuffer.len()) return;

  int len;
  len=fout.write(sendbuffer);
  fout.flush();
  if (len<sendbuffer.len()) {
    if (len>0)
      sendbuffer.del(0,len);
    outCallback->enableWrite();
    return;
  }
  sendbuffer.clear();
  outCallback->disableWrite();
}

void esocketBridge::doRecv()
{
  int len;
  estr tmpdata;
  while (0<(len=recv(tmpdata))){
    sendbuffer+=tmpdata;
    tmpdata.clear();
  }
  doOutput();
  if (len==0) {
    lwarn("client disconnected");
    exit(0);
  }
}

void esocketBridge::doSend()
{
  if (!recvbuffer.len()) return;

  int len;
  len=send(recvbuffer);
  if (len<recvbuffer.len()) {
    if (len>0)
      recvbuffer.del(0,len);
    enableWriteCallback();
    return;
  }

  recvbuffer.clear();
  disableWriteCallback();
}


void startDaemonBridge(const estr& _sfile)
{
  estr sfile(_sfile);
  if (sfile.len()==0) {
    struct passwd *sp=getpwuid(getuid());
    estr homedir;
    if (sp!=0x00)
      homedir=sp->pw_dir;
    sfile=homedir+"/.esh/socket";
  }
  esocketBridge bridge;
//  if (!efile(homedir+"/.esh/socket").exists() || !client.connect(homedir+"/.esh/socket")){
  if (!bridge.connect(sfile)){
    lwarn("daemon not started, starting it now");
    initDaemon2(sfile);
//    system("esh --daemon 1 --sfile "+sfile);
    sleep(1);
    ldieif(!bridge.connect(sfile),"daemon not started");
  }
  getSystem().run();
}




