
#include "tfarmserver.h"
#include "tfarmexecutor.h"
#include "tfarmcontroller.h"
#include "tthreadmessage.h"
#include "tthread.h"
#include "tsystem.h"
#include "tsmartpointer.h"
#include "service.h"
#include "tlog.h"
#include "tfilepath_io.h"
#include "tcli.h"
#include "tversion.h"
using namespace TVER;

#include <string>
#include <map>
#include <sstream>

#include <QString>
#include <QProcess>
#include <QCoreApplication>
#include <QEventLoop>

#include "tthread.h"

#ifdef _WIN32
#include <iostream>
#else
#include <sys/param.h>
#include <unistd.h>
#endif

// #define REDIRECT_OUTPUT

#ifdef _WIN32
#define QUOTE_STR "\""
#define CASMPMETER "casmpmeter.exe"
#else
#define QUOTE_STR "'"
#define CASMPMETER "casmpmeter"
#endif

#ifndef _WIN32
#define NO_ERROR 0
#endif

// forward declaration
class FarmServer;

//-------------------------------------------------------------------

namespace {

//--------------------------------------------------------------------
TFilePath getGlobalRoot() {
  TVER::ToonzVersion tver;
  TFilePath rootDir;

#ifdef _WIN32
  std::string regpath = "SOFTWARE\\" + tver.getAppName() + "\\" +
                        tver.getAppName() + "\\" + tver.getAppVersionString() +
                        "\\FARMROOT";
  TFilePath name(regpath);
  rootDir = TFilePath(TSystem::getSystemValue(name).toStdString());
#else
// Leggo la localRoot da File txt
#ifdef MACOSX
  // If MACOSX, change to MACOSX path
  std::string unixpath =
      "./" + tver.getAppName() + ".app/Contents/Resources/configfarmroot.txt";
#else
  // set path to something suitable for most linux (Unix?) systems
  std::string unixpath = "/etc/" + tver.getAppName() + "/opentoonz.conf";
#endif
  TFilePath name(unixpath);
  Tifstream is(name);
  if (is) {
    char line[1024];
    is.getline(line, 80);

    char *s = line;
    while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\"') s++;
    if (*s != '\0') {
      char *t = s;
      while (*t) t++;

      std::string pathName(s, t - 1);

      rootDir = TFilePath(pathName);
    }
  }

#endif
  return rootDir;
}

//--------------------------------------------------------------------

TFilePath getLocalRoot() {
  TVER::ToonzVersion tver;
  TFilePath lroot;

#ifdef _WIN32
  QString regpath = QString::fromStdString(
      "SOFTWARE\\" + tver.getAppName() + "\\" + tver.getAppName() + "\\" +
      tver.getAppVersionString() + "\\FARMROOT");
  TFilePath name(regpath);
  lroot = TFilePath(TSystem::getSystemValue(name).toStdString()) +
          TFilePath("toonzfarm");
#else
#ifdef MACOSX
  // If MACOSX, change to MACOSX path
  std::string unixpath =
      "./" + tver.getAppName() + ".app/Contents/Resources/configfarmroot.txt";
#else
  // set path to something suitable for most linux (Unix?) systems
#ifdef FREEBSD
  std::string unixpath =
      "/usr/local/etc/" + tver.getAppName() + "/opentoonz.conf";
#else
  std::string unixpath = "/etc/" + tver.getAppName() + "/opentoonz.conf";
#endif
#endif
  TFilePath name(unixpath);
  Tifstream is(name);
  if (is) {
    char line[1024];
    is.getline(line, 80);

    char *s = line;
    while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\"') s++;
    if (*s != '\0') {
      char *t = s;
      while (*t) t++;

      std::string pathName(s, t - 1);

      lroot = TFilePath(pathName);
    }
  }

#endif
  return lroot;
}

//--------------------------------------------------------------------

TFilePath getBinRoot() {
#ifdef _WIN32
  return TSystem::getBinDir();
#else
  return getLocalRoot() + "bin";
#endif
}

//--------------------------------------------------------------------

bool dirExists(const TFilePath &dirFp) {
  bool exists = false;
#ifdef _WIN32
  TFileStatus fs(dirFp);
  exists = fs.isDirectory();
#else
  int acc = access(::to_string(dirFp).c_str(), 00);  // 00 == solo esistenza
  exists  = acc != -1;
#endif
  return exists;
}

//--------------------------------------------------------------------

bool myDoesExists(const TFilePath &fp) {
  bool exists = false;
#ifdef _WIN32
  TFileStatus fs(fp);
  exists = fs.doesExist();
#else
  int acc = access(::to_string(fp).c_str(), 00);  // 00 == solo esistenza
  exists  = acc != -1;
#endif
  return exists;
}

//-----------------------------------------------------------------------------

inline bool isBlank(char c) { return c == ' ' || c == '\t' || c == '\n'; }

}  // anonymous namespace

//==============================================================================
//==============================================================================

class FarmServerService final : public TService {
public:
  FarmServerService(std::ostream &os)
      : TService("ToonzFarm Server", "ToonzFarm Server")
      , m_os(os)
      , m_userLog(0) {}

  ~FarmServerService() { delete m_userLog; }

  void onStart(int argc, char *argv[]) override;
  void onStop() override;

  void loadControllerData(QString &hostName, std::string &ipAddr, int &port);
#ifdef _WIN32
  void loadDiskMountingPoints(const TFilePath &fp);

  void mountDisks();
  void unmountDisks();

  std::map<std::string, std::string> m_disks;
  std::vector<std::string> m_disksMounted;
#endif

  int m_port;
  QString m_addr;

  FarmServer *m_farmServer;
  std::ostream &m_os;

  TUserLog *m_userLog;
};

FarmServerService service(std::cout);

//==============================================================================
//==============================================================================

class FarmControllerProxy final : public TSmartObject {
  TFarmController *m_controller;

public:
  FarmControllerProxy(TFarmController *controller) : m_controller(controller) {}

  ~FarmControllerProxy() { delete m_controller; }
  TFarmController *getController() const { return m_controller; }

private:
  // not implemented
  FarmControllerProxy(const FarmControllerProxy &);
  FarmControllerProxy &operator=(const FarmControllerProxy &);
};

// non posso usare lo smartpointer di tnzcore perche' non linka su .NET

class FarmControllerProxyP {
  FarmControllerProxy *m_proxy;

public:
  FarmControllerProxyP() : m_proxy(0) {}
  ~FarmControllerProxyP() {
    if (m_proxy) m_proxy->release();
  }
  FarmControllerProxyP(const FarmControllerProxyP &src) : m_proxy(src.m_proxy) {
    if (m_proxy) m_proxy->addRef();
  }
  FarmControllerProxyP &operator=(const FarmControllerProxyP &src) {
    FarmControllerProxyP tmp(*this);
    std::swap(tmp.m_proxy, m_proxy);
    return *this;
  }

  FarmControllerProxyP &operator=(TFarmController *controller) {
    if (m_proxy && m_proxy->getController() == controller) return *this;
    if (m_proxy) m_proxy->release();
    m_proxy = new FarmControllerProxy(controller);
    m_proxy->addRef();
    return *this;
  }

  TFarmController *operator->() { return getPointer(); }
  TFarmController *getPointer() const {
    return m_proxy ? m_proxy->getController() : 0;
  }
};

//==============================================================================
//==============================================================================

class FarmServer final : public TFarmExecutor, public TFarmServer {
public:
  FarmServer(int port, TUserLog *log);
  ~FarmServer();

  void setController(const ControllerData &data) {
    m_controllerData            = data;
    TFarmController *controller = 0;
    TFarmControllerFactory factory;
    factory.create(data, &controller);
    m_controller = controller;
  }
  TFarmController *getController() const { return m_controller.getPointer(); }
  void setAppPaths(const std::vector<TFilePath> &);

  QString execute(const std::vector<QString> &argv) override;

  // TFarmServer overrides
  int addTask(const QString &taskid, const QString &cmdline) override;
  int terminateTask(const QString &taskid) override;
  int getTasks(std::vector<QString> &tasks) override;

  void queryHwInfo(HwInfo &hwInfo) override;

  void attachController(const ControllerData &data);
  void attachController(const QString &name, const QString &addr,
                        int port) override {
    attachController(ControllerData(name, addr, port));
  }

  void detachController(const ControllerData &data);
  void detachController(const QString &name, const QString &addr,
                        int port) override {
    detachController(ControllerData(name, addr, port));
  }

  // class specific methods
  void removeTask(const QString &id);

private:
  TThread::Executor *m_executor;

  ControllerData m_controllerData;
  FarmControllerProxyP m_controller;

  TThread::Mutex m_mux;
  std::vector<QString> m_tasks;

  TUserLog *m_userLog;

public:
  // vector<TFilePath> m_appPaths;

private:
  // not implemented
  FarmServer(const FarmServer &);
  FarmServer &operator=(const FarmServer &);
};

//===================================================================
//===================================================================
//
// class Task
//
//===================================================================
//===================================================================

class Task final : public TThread::Runnable {
public:
  Task(const QString &id, const QString &cmdline, TUserLog *log,
       FarmServer *server, const FarmControllerProxyP &controller)
      : m_id(id)
      , m_cmdline(cmdline)
      , m_log(log)
      , m_server(server)
      , m_controller(controller) {}

  void run() override;

private:
  QString m_id;
  QString m_cmdline;
  TUserLog *m_log;
  FarmServer *m_server;
  FarmControllerProxyP m_controller;

private:
  // not implemented
  Task(const Task &);
  Task &operator=(const Task &);
};

//-------------------------------------------------------------------
static QString getExeName(bool isComposer) {
  QString name = isComposer ? "tcomposer" : "tcleanup";

#ifdef _WIN32
  return name + ".exe ";
#elif defined(MACOSX)
  TVER::ToonzVersion tver;
  return "\"./" + QString::fromStdString(tver.getAppName()) +
         ".app/Contents/MacOS/" + name + "\" ";
#else
  return name;
#endif
}

//------------------------------------------------------------------------------

void Task::run() {
  QString cmdline;

  // ===========
  // remap commandLine to local executable

  QStringList l   = m_cmdline.split(" ");
  QString appName = l.at(0);
  // m_log->info(appName);
  if (appName.contains("tcomposer") || appName.contains("tcleanup")) {
    bool m_isComposerTask = appName.contains("tcomposer");
    // m_log->info(QString::number(m_isComposerTask));
    appName = getExeName(m_isComposerTask);
    // m_log->info(appName);

    int i   = 0;
    cmdline = appName;
    // m_log->info(cmdline);
    for (i = 1; i < l.size(); i++) {
      cmdline += " ";
      cmdline += l.at(i);
      // m_log->info(cmdline);
    }
    // m_log->info("remap commandLine to local executable");
    // m_log->info(appName);
  } else {
    cmdline = m_cmdline;
  }

  // ===========
  QString logMsg("Starting task at ");
  logMsg += QDateTime::currentDateTime().toString();
  logMsg += "\n";
  logMsg += "\"" + cmdline + "\"";
  logMsg += "\n\n";

  m_log->info(logMsg);

  // ===========

#ifdef _WIN32
  if (m_cmdline.contains("runcasm")) service.mountDisks();
#endif

  if (m_cmdline.contains(".bat"))
    cmdline = "cmd /C " + cmdline;
  else
    cmdline = cmdline;
#ifdef LEVO
  else {
    // metto da parte il primo token della command line che e' il nome
    // dell'eseguibile
    // Attenzione: case sensitive!
    QStringList l = m_cmdline.split(" ");
    // assert(!"CONTROLLARE QUI");
    QString appName = l.at(1);
    int i;
    for (i = 2; i < l.size(); i++) cmdline += l.at(i);

    // cerco se il nome dell'applicazione e' tra quelle del file di
    // configurazione
    for (auto const &appPath : m_server->m_appPaths) {
      if (appPath.getName() == appName.toStdString()) {
        exename = QString::fromStdWString(appPath.getWideString());
        break;
      }
    }
  }
#endif  // LEVO

  // cout << exename << endl;
  // cout << cmdline << endl;

  // parse command line
  QString prgName;
  QString argsStr = "";
  int sepPos      = cmdline.indexOf(" ");

  if (sepPos == -1) {
    prgName = cmdline;
  } else {
    prgName = cmdline.left(sepPos);
    argsStr = cmdline.right(cmdline.size() - sepPos - 1);
  }

  QProcess process;
  process.setProgram(prgName);
#if defined(_WIN32)
  process.setNativeArguments(argsStr);
#else
  process.setArguments(argsStr.split(" ", Qt::SkipEmptyParts));
#endif
  process.start();
  process.waitForFinished(-1);

  int exitCode  = process.exitCode();
  int errorCode = process.error();
  bool ret      = (errorCode != QProcess::UnknownError) || exitCode;

  // int ret=QProcess::execute(/*"C:\\depot\\vincenzo\\toonz\\main\\x86_debug\\"
  // +*/cmdline);

  if (ret != 0) {
    QString logMsg("Task aborted ");
    logMsg += "\n\n";
    m_log->warning(logMsg);
    m_controller->taskSubmissionError(m_id, exitCode);
  } else {
    logMsg = "Task completed at ";
    logMsg += QDateTime::currentDateTime().toString();
    logMsg += "\n\n";

    m_log->info(logMsg);
    m_controller->taskCompleted(m_id, exitCode);

    // CloseHandle(hJob);
  }

  m_server->removeTask(m_id);

  //************* COMMENTATO A CAUSA DI UN PROBLEMA SU XP
  // ora i dischi vengono montati al primo task di tipo "runcasm"
  // e smontati allo stop del servizio
  // service.unmountDisks();
}

//==============================================================================
//==============================================================================

FarmServer::FarmServer(int port, TUserLog *log)
    : TFarmExecutor(port), m_controller(), m_userLog(log) {
  TFarmServer::HwInfo hwInfo;
  queryHwInfo(hwInfo);
  m_executor = new TThread::Executor;
  m_executor->setMaxActiveTasks(1);
}

//------------------------------------------------------------------------------

FarmServer::~FarmServer() { delete m_executor; }

//------------------------------------------------------------------------------
QString FarmServer::execute(const std::vector<QString> &argv) {
  if (argv.size() > 0) {
    if (argv[0] == "addTask" && argv.size() == 3) {
      // assert(!"Da fare");
      int ret = addTask(argv[1], argv[2]);
      return QString::number(ret);
    } else if (argv[0] == "terminateTask" && argv.size() > 1) {
      int ret = terminateTask(argv[1]);
      return QString::number(ret);
    } else if (argv[0] == "getTasks") {
      std::vector<QString> tasks;
      int ret = getTasks(tasks);

      QString reply(QString::number(ret));
      reply += ",";

      for (auto const &e : tasks) {
        reply += e;
        reply += ",";
      }

      if (!reply.isEmpty()) reply = reply.left(reply.size() - 1);

      return reply;
    } else if (argv[0] == "queryHwInfo") {
      TFarmServer::HwInfo hwInfo;
      queryHwInfo(hwInfo);

      QString ret;
      ret += QString::number((unsigned long)hwInfo.m_cpuCount);
      ret += ",";
      ret += QString::number((unsigned long)(hwInfo.m_totPhysMem / 1024));
      ret += ",";
      ret += QString::number((unsigned long)(hwInfo.m_availPhysMem / 1024));
      ret += ",";
      ret += QString::number((unsigned long)(hwInfo.m_totVirtMem / 1024));
      ret += ",";
      ret += QString::number((unsigned long)(hwInfo.m_availVirtMem / 1024));
      ret += ",";
      ret += QString::number(hwInfo.m_type);
      return ret;
    } else if (argv[0] == "attachController" && argv.size() > 3) {
      int port;
      fromStr(port, argv[3]);
      attachController(ControllerData(argv[1], argv[2], port));
      return "";
    } else if (argv[0] == "detachController" && argv.size() > 3) {
      int port;
      fromStr(port, argv[3]);
      detachController(ControllerData(argv[1], argv[2], port));
      return "";
    }
  }

  return QString::number(-1);
  ;
}

//------------------------------------------------------------------------------

int FarmServer::addTask(const QString &id, const QString &cmdline) {
  // std::cout << "Server: addTask" << id << cmdline << std::endl;

  QString lcmdline = cmdline;

  if (lcmdline.contains("runcasm")) {
    // il task e' runcasm
    TFilePath rootDir = getGlobalRoot();
    TFilePath logfilePath =
        (rootDir + "logs" + id.toStdString()).withType(".log");

    lcmdline +=
        " -logfile " + QString::fromStdWString(logfilePath.getWideString());

    TFilePath casmpmeterFp = getBinRoot() + CASMPMETER;

    lcmdline += " -ac " + QString::fromStdWString(casmpmeterFp.getWideString());
    lcmdline += " -ac_args " + QString(QUOTE_STR);
    lcmdline += "$count $total $frame $filename " + id + QUOTE_STR;
  }

  if (lcmdline.contains("zrender")) lcmdline += " -taskid " + id;

  if (lcmdline.contains("tcomposer")) {
    lcmdline += " -farm " + QString::number(m_controllerData.m_port) + "@" +
                m_controllerData.m_hostName;
    lcmdline += " -id " + id;
  }

  m_executor->addTask(new Task(id, lcmdline, m_userLog, this, m_controller));

  QMutexLocker sl(&m_mux);
  m_tasks.push_back(id);
  return 0;
}

//------------------------------------------------------------------------------

int FarmServer::terminateTask(const QString &taskid) {
#ifdef _WIN32
  HANDLE hJob = OpenJobObject(MAXIMUM_ALLOWED,   // access right
                              TRUE,              // inheritance state
                              taskid.toUtf8());  // job name

  if (hJob != NULL) {
    BOOL res = TerminateJobObject(hJob,  // handle to job
                                  2);    // exit code
  }
#else
#endif
  return 0;
}

//------------------------------------------------------------------------------

int FarmServer::getTasks(std::vector<QString> &tasks) {
  QMutexLocker sl(&m_mux);
  tasks = m_tasks;
  return m_tasks.size();
}

//------------------------------------------------------------------------------

void FarmServer::queryHwInfo(HwInfo &hwInfo) {
#ifdef _WIN32
  MEMORYSTATUS buff;
  GlobalMemoryStatus(&buff);

  hwInfo.m_totPhysMem   = buff.dwTotalPhys;
  hwInfo.m_availPhysMem = buff.dwAvailPhys;
  hwInfo.m_totVirtMem   = buff.dwTotalVirtual;
  hwInfo.m_availVirtMem = buff.dwAvailVirtual;
  hwInfo.m_cpuCount     = TSystem::getProcessorCount();
  hwInfo.m_type         = Windows;
#else
  // We can just retrieve the overall physical memory - the rest is defaulted to
  // 500 MB
  hwInfo.m_totPhysMem   = TSystem::getMemorySize(true);
  hwInfo.m_availPhysMem = TSystem::getFreeMemorySize(true);
  hwInfo.m_totVirtMem   = 500000000;
  hwInfo.m_availVirtMem = 500000000;
  hwInfo.m_cpuCount     = TSystem::getProcessorCount();
#ifdef __sgi
  hwInfo.m_type         = Irix;
#else
  hwInfo.m_type = Linux;
#endif
#endif
}

//------------------------------------------------------------------------------

void FarmServer::attachController(const ControllerData &data) {
  setController(data);
}

//------------------------------------------------------------------------------

void FarmServer::detachController(const ControllerData &data) {
  if (m_controllerData == data) {
    // delete m_controller;
    m_controller = 0;
  }
}

//------------------------------------------------------------------------------

void FarmServer::removeTask(const QString &id) {
  QMutexLocker sl(&m_mux);
  std::vector<QString>::iterator it = find(m_tasks.begin(), m_tasks.end(), id);
  if (it != m_tasks.end()) m_tasks.erase(it);
}

//==============================================================================

namespace {

std::string getLine(std::istream &is) {
  std::string out;
  char c;

  while (!is.eof()) {
    is.get(c);
    if (c != '\r') {
      if (c != '\n') {
        if (!is.fail()) {
          out.append(1, c);
        } else {
          break;
        }
      } else {
        break;
      }
    }
  }
  return out;
}

}  // anonymous namespace

int inline STRICMP(const QString &a, const QString &b) {
  return a.compare(b, Qt::CaseSensitive);
}
int inline STRICMP(const char *a, const char *b) {
  QString str(a);
  return str.compare(QString(b), Qt::CaseSensitive);
}

static bool loadServerData(const QString &hostname, QString &addr, int &port) {
  TFilePath rootDir = getGlobalRoot();

  TFilePath fp = rootDir + "config" + "servers.txt";

#ifndef _WIN32
  int acc = access(::to_string(fp).c_str(), 00);  // 00 == solo esistenza
  bool fileExists = acc != -1;
  if (!fileExists) return false;
#endif

  Tifstream is(fp);
  if (!is.good()) return false;
  while (!is.eof()) {
    std::string line = getLine(is);
    std::istringstream iss(line);

    std::string name;
    std::string ipAddress;

    iss >> name >> ipAddress >> port;
    if (name[0] == '#') continue;
    if (STRICMP(hostname.toUtf8(), name.c_str()) == 0) {
      addr = QString(ipAddress.c_str());
      return true;
    }
  }
  return false;
}

//==============================================================================

void FarmServerService::onStart(int argc, char *argv[]) {
  // Initialize thread components
  TThread::init();
  TVER::ToonzVersion tver;

#ifdef _WIN32
//  DebugBreak();
#endif

  TFilePath lRootDir = getLocalRoot();
  TFileStatus fs(lRootDir);
  bool lRootDirExists = fs.isDirectory();

  if (!lRootDirExists) {
    std::string errMsg("Unable to start the Server");
    errMsg += "\n";
    errMsg += "The directory " + ::to_string(lRootDir) +
              " specified as Local Root does not exist";
    errMsg += "\n";

    addToMessageLog(errMsg);

// DEBUG MAC SERVIZIO (DA TOGLIERE)
#ifdef MACOSX
    system("echo 'local root does not exist' >> err.log");
#endif

    // exit the program
    setStatus(TService::Stopped, NO_ERROR, 0);
  }

  TFilePath gRootDir = getGlobalRoot();
  if (::to_string(gRootDir) == "") {
    std::string errMsg("Unable to get TFARMGLOBALROOT environment variable (" +
                       ::to_string(gRootDir) + ")");
    addToMessageLog(errMsg);

// DEBUG MAC SERVIZIO (DA TOGLIERE)
#ifdef MACOSX
    system("echo 'Unable to set the global root' >> err.log");
#endif

    // exit the program
    setStatus(TService::Stopped, NO_ERROR, 0);
  }

  bool gRootDirExists = dirExists(gRootDir);
  ;
  if (!gRootDirExists) {
    std::string errMsg("Unable to start the Server");
    errMsg += "\n";
    errMsg += "The directory " + ::to_string(gRootDir) +
              " specified as Global Root does not exist";
    ;

    addToMessageLog(errMsg);

// DEBUG MAC SERVIZIO (DA TOGLIERE)
#ifdef MACOSX
    system("echo 'Global root does not exist' >> err.log");
#endif

    // exit the program
    setStatus(TService::Stopped, NO_ERROR, 0);
  }

  // legge dal file di configurazione le informazioni sul controller

  TFilePath fp = gRootDir + "config" + "controller.txt";

  ControllerData controllerData;

  try {
    ::loadControllerData(fp, controllerData);
  } catch (TException &e) {
    std::string errMsg("Unable to start the Server");
    errMsg += "\n";
    errMsg += ::to_string(e.getMessage());
    addToMessageLog(errMsg);
    setStatus(TService::Stopped, NO_ERROR, 0);  // exit the program
  }

  if (isRunningAsConsoleApp()) {
    // i messaggi verranno ridiretti sullo standard output
    m_userLog = new TUserLog();
  } else {
    TFilePath logFilePath = lRootDir + "server.log";
    m_userLog             = new TUserLog(logFilePath);
  }

  std::string appverinfo = tver.getAppVersionInfo("Farm Server") + "\n\n";
  m_userLog->info(appverinfo.c_str());

  // legge dal file di configurazione dei server il numero di porta da
  // utilizzare

  bool ret = loadServerData(TSystem::getHostName(), m_addr, m_port);

  if (!ret) {
    QString msg("Unable to get the port number of ");
    msg += TSystem::getHostName();
    msg += " from the servers config file";
    msg += "\n";
    msg += "Using the default port number ";
    msg += QString::number(m_port);
    msg += "\n";
    msg += "\n";

    m_userLog->info(msg);
    m_port = 8002;
  }

#ifdef __sgi
  {
    std::ofstream os("/tmp/.tfarmserverd.dat");
    os << m_port;
  }
#endif

  m_farmServer = new FarmServer(m_port, m_userLog);
  m_farmServer->setController(controllerData);

  try {
    m_farmServer->getController()->attachServer(TSystem::getHostName(), m_addr,
                                                m_port);
  } catch (TException const &) {
  }

#ifdef _WIN32
  TFilePath diskMountingsFilePath = lRootDir + "config" + "diskmap.cfg";
  if (myDoesExists(diskMountingsFilePath)) {
    loadDiskMountingPoints(diskMountingsFilePath);

    // i dischi vengono montati al primo task di tipo "runcasm"
    // e smontati allo stop del servizio

    // mountDisks();
  }
#endif

  // Carica da un file di configurazione i path dei programmi da lanciare.
  // Per tutti i programmi il cui path non e' contenuto nel file di
  // configurazione
  // si assume che il path del folder del programma sia specificato
  // nella variabile di sistema PATH

  QEventLoop eventLoop;

  // Connect the server's listening finished signal to main loop quit.
  QObject::connect(m_farmServer, SIGNAL(finished()), &eventLoop, SLOT(quit()));

  // Run the TcpIp server's listening state
  m_farmServer->start();

  // Main loop starts here
  eventLoop.exec();

  //----------------------Farm server loops here------------------------

  int rc = m_farmServer->getExitCode();

#ifdef __sgi
  remove("/tmp/.tfarmserver.dat");
#endif

  if (rc != 0) {
    std::string msg("An error occurred starting the ToonzFarm Server");
    msg += "\n";

#ifdef _WIN32
    LPVOID lpMsgBuf;
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
                      FORMAT_MESSAGE_IGNORE_INSERTS,
                  NULL, rc,
                  0,  // Default language
                  (LPTSTR)&lpMsgBuf, 0, NULL);

    msg += std::string((char *)lpMsgBuf);

    // Free the buffer.
    LocalFree(lpMsgBuf);
#else
#endif

    addToMessageLog(msg);
    m_userLog->error(QString::fromStdString(msg));
    setStatus(TService::Stopped, NO_ERROR, 0);
  }

  std::string msg("Exiting with code ");
  msg += std::to_string(ret);
  msg += "\n";
  m_userLog->info(QString::fromStdString(msg));
}

//------------------------------------------------------------------------------

void FarmServerService::onStop() {
  try {
    m_farmServer->getController()->detachServer(TSystem::getHostName(), m_addr,
                                                m_port);
  } catch (TException & /*e*/) {
  }

  // i dischi vengono montati al primo task di tipo "runcasm"
  // e smontati allo stop del servizio

#ifdef _WIN32
  unmountDisks();
#endif

  TTcpIpClient client;

  int socketId;
  int ret = client.connect(TSystem::getHostName(), "", m_farmServer->getPort(),
                           socketId);
  if (ret == OK) {
    client.send(socketId, "shutdown");
  }
}

#ifdef _WIN32

//------------------------------------------------------------------------------

void FarmServerService::loadDiskMountingPoints(const TFilePath &fp) {
  Tifstream is(fp);
  if (!is) throw std::string("File " + ::to_string(fp) + " not found");
  char buffer[1024];
  while (is.getline(buffer, sizeof(buffer))) {
    char *s = buffer;
    while (isBlank(*s)) s++;
    if (*s == '\0' || *s == '#' || *s == '!') continue;
    if (*s == '=') continue;  // errore: from vuoto
    char *t = s;
    while (*t && *t != '=') t++;
    if (*t != '=') continue;  // errore: manca '='
    char *q = t;
    while (q > s && isBlank(*(q - 1))) q--;
    if (q == s)
      continue;  // non dovrebbe succedere mai: prima di '=' tutti blanks
    std::string from(s, q - s);
    s = t + 1;
    while (isBlank(*s)) s++;
    if (*s == '\0') continue;  // errore: dst vuoto
    t = s;
    while (*t) t++;
    while (t > s && isBlank(*(t - 1))) t--;
    if (t == s) continue;  // non dovrebbe succedere mai: dst vuoto
    std::string dst(s, t - s);
    m_disks[from] = dst;
  }
}

//------------------------------------------------------------------------------

void FarmServerService::mountDisks() {
  std::map<std::string, std::string>::iterator it = m_disks.begin();
  for (; it != m_disks.end(); ++it) {
    std::string drive      = it->first;
    std::string remoteName = it->second;

    NETRESOURCE NetResource;
    NetResource.dwType      = RESOURCETYPE_DISK;
    NetResource.lpLocalName = (LPSTR)drive.c_str();  // "O:";
    NetResource.lpRemoteName =
        (LPSTR)remoteName.c_str();  // "\\\\vega\\PERSONALI";
    NetResource.lpProvider = NULL;

    DWORD res =
        WNetAddConnection2(&NetResource,  // connection details
                           0,             // password
                           TSystem::getUserName().toUtf8(),  // user name
                           0);  // connection options

    if (res == NO_ERROR) m_disksMounted.push_back(drive);

    if (res != NO_ERROR && res != ERROR_ALREADY_ASSIGNED) {
      // ERROR_BAD_NET_NAME

      DWORD dwLastError;
      char errorBuf[1024];
      char nameBuf[1024];

      DWORD rett =
          WNetGetLastError(&dwLastError,      // error code
                           errorBuf,          // error description buffer
                           sizeof(errorBuf),  // size of description buffer
                           nameBuf,           // buffer for provider name
                           sizeof(nameBuf));  // size of provider name buffer

      std::string errorMessage("Unable to map ");
      errorMessage += NetResource.lpRemoteName;
      errorMessage += " to logic volume ";
      errorMessage += NetResource.lpLocalName;

      addToMessageLog(errorMessage);
    }
  }
}

//------------------------------------------------------------------------------

void FarmServerService::unmountDisks() {
  for (auto const &drive : m_disksMounted) {
    DWORD res =
        WNetCancelConnection2(drive.c_str(),           // resource name
                              CONNECT_UPDATE_PROFILE,  // connection type
                              TRUE);  // unconditional disconnect option

    if (res != NO_ERROR && res != ERROR_NOT_CONNECTED) {
      std::string errorMessage("Unable to unmap ");
      errorMessage += drive.c_str();
      addToMessageLog(errorMessage);
    }
  }

  m_disksMounted.clear();
}

#endif

//==============================================================================
//==============================================================================
//==============================================================================

int main(int argc, char **argv) {
  QCoreApplication a(argc, argv);

  bool console = false;

  if (argc > 1) {
    std::string serviceName(
        "ToonzFarmServer");  // Must be the same of the installer's
    std::string serviceDisplayName = serviceName;

    TCli::SimpleQualifier consoleQualifier("-console", "Run as console app");
    TCli::StringQualifier installQualifier("-install name",
                                           "Install service as 'name'");
    TCli::SimpleQualifier removeQualifier("-remove", "Remove service");

    TCli::Usage usage(argv[0]);
    usage.add(consoleQualifier + installQualifier + removeQualifier);
    if (!usage.parse(argc, argv)) exit(1);

#ifdef _WIN32
    if (installQualifier.isSelected()) {
      char szPath[512];

      if (installQualifier.getValue() != "")
        serviceDisplayName = installQualifier.getValue();

      if (GetModuleFileName(NULL, szPath, 512) == 0) {
        std::cout << "Unable to install";
        std::cout << serviceName << " - ";
        std::cout << getLastErrorText().c_str() << std::endl << std::endl;

        return 0;
      }

      TService::install(serviceName, serviceDisplayName, TFilePath(szPath));

      return 0;
    }

    if (removeQualifier.isSelected()) {
      TService::remove(serviceName);
      return 0;
    }
#endif

    if (consoleQualifier.isSelected()) console = true;
  }

  TSystem::hasMainLoop(false);
  TService::instance()->run(argc, argv, console);

  return 0;
}
