#ifndef _ONELABDATABASE_H_
#define _ONELABDATABASE_H_
#endif
#include <signal.h>
#include <stdio.h>

#include "GmshMessage.h"
#include "Options.h"
#include "Context.h"
#include "onelabUtils.h"
#include "OnelabServer.h"
#include "VirtualClient.h"
#include "GmshNetworkClient.h"
#include "GmshLocalClient.h"

#include "OnelabException.h"

void solver_batch_cb(void *data);
#ifndef WIN32
void *OnelabDatabase_listen(void *arg);
#else
DWORD WINAPI OnelabDatabase_listen(LPVOID arg);
#endif

static void initializeLoops();
static bool incrementLoops();

// OnelabDatabase is a singleton that get/set/... parameters from server/clients
class OnelabDatabase {
private:
  static OnelabDatabase *_instance;
#ifndef WIN32
  pthread_t _listenThread, _serverThread;
#else
  HANDLER _listenThread, _serverThread;
#endif
  GmshNetworkClient *_client;
  GmshLocalClient *_localGUI, *_localGmsh;
  void _clear() {
#ifndef WIN32
    if(_client) pthread_cancel(_listenThread);
    else {
      OnelabServer::instance()->stopTcp();
      OnelabServer::instance()->stopUnix();
#ifdef HAVE_UDT
      OnelabServer::instance()->stopUdt();
#endif
    }
#else
    // TODO
#endif
    if(_client) {
      _client->disconnect();
      delete _client;
    }
    if(_localGUI) delete _localGUI;
    if(_localGmsh) delete _localGmsh;
    _client = NULL; _localGUI = _localGmsh = NULL;
  }

public:
  OnelabDatabase() {
    _localGUI = NULL; _client = NULL; _localGmsh = NULL;
  }
  static OnelabDatabase *instance() {
    if(!_instance) _instance = new OnelabDatabase;
    return _instance;
  }
  GmshLocalClient *useAsClient(){
    // the server is in the same memory space
    _clear();

    CTX::instance()->onelab.unixConnected = CTX::instance()->onelab.tcpConnected = CTX::instance()->onelab.udtConnected = false;

    try {
      if(CTX::instance()->onelab.unixSock.size() > 0) { // UNIX
        std::ostringstream tmp;
        tmp << CTX::instance()->homeDir << CTX::instance()->onelab.unixSock;
        OnelabServer::instance()->listenOnUnix(tmp.str().c_str());
        CTX::instance()->onelab.unixConnected = true;
      }
      if(CTX::instance()->onelab.tcpSock.size() > 0) {
        std::size_t colon = CTX::instance()->onelab.tcpSock.find(":");
        OnelabServer::instance()->listenOnTcp(
          ip4_inet_pton(CTX::instance()->onelab.tcpSock.substr(0, colon).c_str()),
          atoi(CTX::instance()->onelab.tcpSock.substr(colon+1, CTX::instance()->onelab.tcpSock.size()-colon-1).c_str()));
        CTX::instance()->onelab.tcpConnected = true;
      }
#ifdef HAVE_UDT
      if(CTX::instance()->onelab.udtSock.size() > 0) {
        std::size_t colon = CTX::instance()->onelab.tcpSock.find(":");
        OnelabServer::instance()->listenOnUdt(
          ip4_inet_pton(CTX::instance()->onelab.tcpSock.substr(0, colon).c_str()),
          atoi(CTX::instance()->onelab.tcpSock.substr(colon+1, CTX::instance()->onelab.tcpSock.size()-colon-1).c_str()));
        CTX::instance()->onelab.udtConnected = true;
      }
#endif
      if(CTX::instance()->onelab.unixSock.size() == 0 && CTX::instance()->onelab.tcpSock.size() == 0 && CTX::instance()->onelab.udtSock.size() == 0) {
        OnelabServer::instance()->listenOnTcp(0x7F000001, 0);
        CTX::instance()->onelab.tcpConnected = true;
      }
    } catch(NetworkException e) {
      std::cout << e.what() << std::endl;
      OnelabServer::instance()->listenOnTcp(0x7F000001, 0);
      CTX::instance()->onelab.tcpSock = "127.0.0.1:0";
      CTX::instance()->onelab.tcpConnected = true;
    }
    _localGUI = new GmshLocalClient("localGUI", OnelabServer::instance()->getParameterSpace());
    OnelabServer::instance()->addClient(_localGUI);
    _localGmsh = new GmshLocalClient("Gmsh", OnelabServer::instance()->getParameterSpace());
    OnelabServer::instance()->addClient(_localGmsh);

    return _localGUI;
  }
  GmshNetworkClient *useAsNetworkClient(UInt32 address, UInt16 port, std::string cli="GUI"){
    // use this as a network client (take server address/port)
    if(!_client) {
      _clear();

      _client = new GmshNetworkClient(cli, address, port);
      if(_client->connect()) {
#ifndef WIN32
        pthread_create(&_listenThread, NULL, OnelabDatabase_listen, &(((OnelabLocalClient *)_client)->getName()));
#else
        _listenThread = CreateThread(NULL, 0, NULL, OnelabDatabase_listen, 0, NULL);
#endif
        return _client;
      }
      else {
        delete _client;
        _client = NULL;
        return NULL;
      }
    }
    else {
#ifndef WIN32
      pthread_create(&_listenThread, NULL, OnelabDatabase_listen, &(((OnelabLocalClient *)_client)->getName()));
#else
      _listenThread = CreateThread(NULL, 0, NULL, OnelabDatabase_listen, 0, NULL);
#endif
      return _client;
    }
    return NULL;
  }
  bool isNetworkClient() {return _client != NULL;}
  GmshNetworkClient *getNetworkClient(){return _client;}
  void finalize();
  int listen(OnelabProtocol &msg) {
    if(_client) return _client->recvfrom(msg);
    return 0;
  }
  int listen(UInt8 *buff, unsigned int maxlen){
    if(_client) return _client->recvfrom(buff, maxlen);
    return 0;
  }
  void sendbytes(UInt8 *buff, unsigned int len){
    if(_client) _client->sendto(buff, len);
  }
  GmshLocalClient *useAsServer(UInt32 address, UInt16 port){
    // use this as a network server (take interface/port to listen to)
    _clear();

    // FIXME TCP / UDT ?
    OnelabServer::instance()->listenOnTcp(address, port);
    _localGUI = new GmshLocalClient("localGUI", OnelabServer::instance()->getParameterSpace());
    return _localGUI;
  }
  template <class T> bool set(const T &p, const std::string &client) {
    if(_client) return _client->set(p, true);
    return OnelabServer::instance()->set(p, client);
  }
  template <class T> bool set(const T &p, bool update=true) {
    if(_client) return _client->set(p, update);
    //if(_localGUI) return _localGUI->set(p);
    return OnelabServer::instance()->set(p);
  }
  template <class T> bool get(std::vector<T> &ps, const std::string &name="", const std::string &client="") {
    if(_client) return _client->get(ps, name);
    //if(_localGUI) _localGUI->get(ps, name);
    return OnelabServer::instance()->get(ps, name, client);
  }
  void clear(const std::string &name="", const std::string &client="") {
     if(_client) return _client->clear(name, client);
    return OnelabServer::instance()->clear(name, client);
  }
  bool fromFile(FILE *fp, const std::string &client="")
  {
    if(_client) return _client->fromFile(fp, client);
    return OnelabServer::instance()->fromFile(fp, client);

  }
  bool run(const std::string action, const std::string client="") {
    UInt8 buff[1024];
    OnelabProtocol msg(OnelabProtocol::OnelabAction);
    if(client.size()) {
      std::cout << "try to " << action << ' ' << client <<  std::endl;
      if(_client && ((OnelabLocalClient *)_client)->getName() == client) {
        if(client == "Gmsh") onelabUtils::runGmshClient(action, true);
      }
      else if(_client) {
        std::cout << "client is remote" << std::endl;
        msg.attrs.push_back(new OnelabAttrAction(action, client));
        int size = msg.encodeMsg(buff, 1024);
        sendbytes(buff, size);
        return true;
      }
      else {
        std::cout << "client is local" << std::endl;
        OnelabServer::instance()->performAction(action, client);
        return true;
      }
    }
    else { // run all client
      if(action == "compute") initializeLoops();

      do{ // enter loop
        run(action, "Gmsh"); // run Gmsh client

        if(CTX::instance()->solverToRun >= 0) { // launch the solver
          std::string solver = opt_solver_name(CTX::instance()->solverToRun, GMSH_GET, "");
          std::string exe = opt_solver_executable(CTX::instance()->solverToRun, GMSH_GET, "");
          if(_client && exe.size()) {
            onelab::string o(solver + "/CommandLine", exe);
            o.setVisible(false);
            o.setNeverChanged(true);
            set(o, solver);
          }
          run(action, solver);
        }
        else { // send action to all connected client except Gmsh
          if(_client) {
            std::cout << "server is remote" << std::endl;
            msg.attrs.push_back(new OnelabAttrAction(action, client));
            int size = msg.encodeMsg(buff, 1024);
            sendbytes(buff, size);
          }
          else {
            std::cout << "server is local" << std::endl;
            OnelabServer::instance()->performAction(action, client, true);
          }
          return true;
        }
      } while(action == "compute" && /*!FlGui::instance()->onelab->stop() &&*/
          incrementLoops());
    }
    return false;
  }
  bool getChanged(const std::string &client="") const {
    if(!_client)
      return OnelabServer::instance()->getChanged(client);
    else
      return true; // TODO
  }
  void setChanged(bool changed, const std::string &client="") {
    if(!_client)
      OnelabServer::instance()->setChanged(changed, client);
    else
      return; // TODO
  }
};

static void initializeLoops()
{
  onelabUtils::initializeLoop("1");
  onelabUtils::initializeLoop("2");
  onelabUtils::initializeLoop("3");
}

static bool incrementLoops()
{
  bool ret = false;
  if(onelabUtils::incrementLoop("3"))      ret = true;
  else if(onelabUtils::incrementLoop("2")) ret = true;
  else if(onelabUtils::incrementLoop("1")) ret = true;

  //Define ONELAB parameter indicating whether or not in a loop
  onelab::number n("0Metamodel/Loop",ret?1:0);
  n.setVisible(false);
  OnelabDatabase::instance()->set(n);

  return ret;
}

