

#include "tfarmcontroller.h"
#include "tfarmexecutor.h"
#include "tfarmserver.h"
#include "tsystem.h"
#include "tconvert.h"
#include "tfilepath_io.h"
#include "service.h"
#include "tcli.h"
#include "tversion.h"
using namespace TVER;

#include "tthreadmessage.h"
#include "tthread.h"
#include "tstream.h"
#include "tlog.h"

#include <QObject>
#include <QCoreApplication>
#include <QEventLoop>

#include "tthread.h"

#include <sstream>
#include <string>
using namespace std;

#ifndef _WIN32
#include <sys/param.h>
#include <unistd.h>
#endif

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);
}
/*
#ifdef _WIN32
#define STRICMP stricmp
#else
#define STRICMP strcasecmp
#endif
*/

#ifndef _WIN32
#define NO_ERROR 0
#endif

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

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 globalRoot 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++;

      string pathName(s, t - 1);

      rootDir = TFilePath(pathName);
    }
  }

#endif
  return rootDir;
}

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

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

#ifdef _WIN32
  std::string regpath = "SOFTWARE\\" + tver.getAppName() + "\\" +
                        tver.getAppName() + "\\" + tver.getAppVersionString() +
                        "\\FARMROOT";
  TFilePath name(regpath);
  lroot = TFilePath(TSystem::getSystemValue(name).toStdString()) +
          TFilePath("toonzfarm");
#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
#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++;

      string pathName(s, t - 1);

      lroot = TFilePath(pathName);
    }
  }
#endif
  return lroot;
}

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

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

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

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 loadControllerData(QString &hostName, QString &addr, int &port) {
  TFilePath groot = getGlobalRoot();
  TFilePath fp    = groot + "config" + "controller.txt";

  ControllerData controllerData;
  ::loadControllerData(fp, controllerData);

  hostName = controllerData.m_hostName;
  addr     = controllerData.m_ipAddress;
  port     = controllerData.m_port;
  return true;
}

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

bool isAScript(TFarmTask *task) {
  return false;  // todo per gli script
}

}  // anonymous namespace

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

class CtrlFarmTask final : public TFarmTask {
public:
  CtrlFarmTask() : m_toBeDeleted(false), m_failureCount(0) {}

  CtrlFarmTask(const QString &id, const QString &name, const QString &cmdline,
               const QString &user, const QString &host, int stepCount,
               int priority)
      : TFarmTask(id, name, cmdline, user, host, stepCount, priority)
      , m_toBeDeleted(false)
      , m_failureCount(0) {
    m_id     = id;
    m_status = Waiting;
  }

  CtrlFarmTask(const CtrlFarmTask &rhs) : TFarmTask(rhs) {
    m_serverId    = rhs.m_serverId;
    m_subTasks    = rhs.m_subTasks;
    m_toBeDeleted = rhs.m_toBeDeleted;
  }

  // TPersist implementation
  void loadData(TIStream &is) override;
  void saveData(TOStream &os) override;
  const TPersistDeclaration *getDeclaration() const override;

  QString m_serverId;
  vector<QString> m_subTasks;

  bool m_toBeDeleted;
  int m_failureCount;

  vector<QString> m_failedOnServers;
};

namespace {

class TFarmTaskDeclaration final : public TPersistDeclaration {
public:
  TFarmTaskDeclaration(const std::string &id) : TPersistDeclaration(id) {}

  TPersist *create() const override { return new CtrlFarmTask; }
} Declaration("tfarmtask");
}  // namespace

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

void CtrlFarmTask::loadData(TIStream &is) {
  is >> m_name;

  QString cmdline;
  is >> cmdline;
  parseCommandLine(cmdline);

  is >> m_priority;

  is >> m_user;
  is >> m_hostName;

  is >> m_id;
  is >> m_parentId;

  int status;
  is >> status;
  m_status = (TaskState)status;
  is >> m_server;
  QString dateStr;
  is >> dateStr;
  m_submissionDate = QDateTime::fromString(dateStr);
  is >> dateStr;
  m_startDate = QDateTime::fromString(dateStr);
  is >> dateStr;
  m_completionDate = QDateTime::fromString(dateStr);

  is >> m_successfullSteps;
  is >> m_failedSteps;
  is >> m_stepCount;

  is >> m_serverId;

  string tagName;
  while (is.openChild(tagName)) {
    if (tagName == "platform") {
      int plat;
      is >> plat;
      switch (plat) {
      case NoPlatform:
        m_platform = NoPlatform;
        break;
      case Windows:
        m_platform = Windows;
        break;
      case Irix:
        m_platform = Irix;
        break;
      case Linux:
        m_platform = Linux;
        break;
      }
    } else if (tagName == "dependencies") {
      int depCount = 0;
      is >> depCount;
      if (depCount > 0) m_dependencies = new Dependencies();
      while (depCount > 0) {
        TFarmTask::Id id;
        is >> id;
        m_dependencies->add(id);
        depCount--;
      }
    }

    is.closeChild();
  }
}

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

void CtrlFarmTask::saveData(TOStream &os) {
  os << m_name;

  os << getCommandLine();

  os << m_priority;

  os << m_user;
  os << m_hostName;

  os << m_id;
  os << m_parentId;

  os << (int)m_status;
  os << m_server;
  os << m_submissionDate.toString();
  os << m_startDate.toString();
  os << m_completionDate.toString();

  os << m_successfullSteps;
  os << m_failedSteps;
  os << m_stepCount;

  os << m_serverId;

  os.openChild("platform");
  os << m_platform;
  os.closeChild();

  os.openChild("dependencies");
  if (m_dependencies) {
    int depCount = m_dependencies->getTaskCount();
    os << depCount;
    int i = 0;
    while (i < depCount) {
      TFarmTask::Id id = m_dependencies->getTaskId(i++);
      os << id;
    }
  } else
    os << 0;
  os.closeChild();
}

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

const TPersistDeclaration *CtrlFarmTask::getDeclaration() const {
  return &Declaration;
}

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

class FarmServerProxy {
public:
  FarmServerProxy(const QString &hostName, const QString &addr, int port,
                  int maxTaskCount = 1)
      : m_hostName(hostName)
      , m_addr(addr)
      , m_port(port)
      , m_offline(false)
      , m_attached(false)
      , m_maxTaskCount(maxTaskCount)
      , m_platform(NoPlatform) {
    TFarmServerFactory serverFactory;
    serverFactory.create(m_hostName, m_addr, m_port, &m_server);
  }

  ~FarmServerProxy() {}

  QString getId() const { return getIpAddress(); }

  QString getHostName() const { return m_hostName; }

  QString getIpAddress() const { return m_addr; }

  int getPort() const { return m_port; }

  const vector<QString> &getTasks() const { return m_tasks; }

  int addTask(const CtrlFarmTask *task);
  void terminateTask(const QString &taskId);

  // e' possibile rimuovere un task solo se non running
  void removeTask(const QString &taskId);

  bool testConnection(int timeout);

  void queryHwInfo(TFarmServer::HwInfo &hwInfo) {
    m_server->queryHwInfo(hwInfo);
  }

  void attachController(const QString &name, const QString &addr, int port) {
    m_server->attachController(name, addr, port);
  }

  void detachController(const QString &name, const QString &addr, int port) {
    m_server->detachController(name, addr, port);
  }

  QString m_hostName;
  QString m_addr;
  int m_port;
  bool m_offline;
  bool m_attached;

  int m_maxTaskCount;
  TFarmPlatform m_platform;

  // vettore dei taskId assegnato al server
  vector<QString> m_tasks;

  TFarmServer *m_server;
};

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

int FarmServerProxy::addTask(const CtrlFarmTask *task) {
  int rc = m_server->addTask(task->m_id, task->getCommandLine());
  if (rc == 0) m_tasks.push_back(task->m_id);
  return rc;
}

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

void FarmServerProxy::terminateTask(const QString &taskId) {
  m_server->terminateTask(taskId);
}

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

void FarmServerProxy::removeTask(const QString &taskId) {
  vector<QString>::iterator it = find(m_tasks.begin(), m_tasks.end(), taskId);
  if (it != m_tasks.end()) {
    m_tasks.erase(it);
  }
}

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

static bool doTestConnection(const QString &hostName, const QString &addr,
                             int port) {
  TTcpIpClient client;

  int sock;
  int ret = client.connect(hostName, addr, port, sock);
  if (ret == OK) {
#ifdef _WIN32
    closesocket(sock);
#else
    close(sock);
#endif
    return true;
  }
  return false;
}

//------------------------------------------------------------------------------
#ifdef _WIN32
class ConnectionTest final : public TThread::Runnable {
public:
  ConnectionTest(const FarmServerProxy *server, HANDLE hEvent)
      : m_server(server), m_hEvent(hEvent) {}

  void run() override;

  const FarmServerProxy *m_server;
  HANDLE m_hEvent;
};

void ConnectionTest::run() {
  bool res = doTestConnection(m_server->m_hostName, m_server->m_addr,
                              m_server->m_port);

  SetEvent(m_hEvent);
}

#endif

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

bool FarmServerProxy::testConnection(int timeout) {
#ifdef _WIN32

  HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  if (!hEvent) {
    // se fallisce la creazione dell'evento ci provo comunque
    // senza timeout
    return doTestConnection(m_hostName, m_addr, m_port);
  }

  TThread::Executor executor;
  executor.addTask(new ConnectionTest(this, hEvent));

  DWORD rr = WaitForSingleObject(hEvent, timeout);
  CloseHandle(hEvent);

  if (rr == WAIT_TIMEOUT)
    return false;
  else
    return true;

#else
  return doTestConnection(m_hostName, m_addr, m_port);
#endif

  TTcpIpClient client;

  int sock;
  int ret = client.connect(m_hostName, m_addr, m_port, sock);
  if (ret == OK) {
#ifdef _WIN32
    closesocket(sock);
#else
    close(sock);
#endif

    return true;
  }
  return false;
}

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

class TaskId {
  int m_id;
  int m_subId;

public:
  TaskId(int id, int subId = -1) : m_id(id), m_subId(subId){};
  TaskId(const QString &id) {
    int pos = id.indexOf(".");
    if (pos != -1) {
      m_id    = id.left(pos).toInt();
      m_subId = id.mid(pos + 1, id.length() - pos).toInt();
    } else {
      m_id    = id.toInt();
      m_subId = -1;
    }
  }

  inline bool operator==(const TaskId &f) const {
    return f.m_id == m_id && f.m_subId == m_subId;
  };
  inline bool operator!=(const TaskId &f) const {
    return (m_id != f.m_id || m_subId != f.m_subId);
  };
  inline bool operator<(const TaskId &f) const {
    return (m_id < f.m_id || (m_id == f.m_id && m_subId < f.m_subId));
  };
  inline bool operator>(const TaskId &f) const { return f < *this; }
  inline bool operator>=(const TaskId &f) const { return !operator<(f); }
  inline bool operator<=(const TaskId &f) const { return !operator>(f); }

  TaskId &operator=(const TaskId &f) {
    m_id    = f.m_id;
    m_subId = f.m_subId;
    return *this;
  }

  // operator string() const;
  QString toString() const {
    QString id(QString::number(m_id));
    if (m_subId >= 0) id += "." + ::QString::number(m_subId);
    return id;
  }
};

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

class FarmController final : public TFarmExecutor, public TFarmController {
public:
  FarmController(const QString &hostName, const QString &addr, int port,
                 TUserLog *log);

  void loadServersData(const TFilePath &globalRoot);

  // TFarmExecutor interface implementation

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

  // TFarmController interface methods implementation

  QString addTask(const QString &name, const QString &cmdline,
                  const QString &user, const QString &host, bool suspended,
                  int priority, TFarmPlatform platform);

  QString addTask(const TFarmTask &task, bool suspended) override;

  void removeTask(const QString &id) override;
  void suspendTask(const QString &id) override;
  void activateTask(const QString &id) override;
  void restartTask(const QString &id) override;

  void getTasks(vector<QString> &tasks) override;
  void getTasks(const QString &parentId, vector<QString> &tasks) override;
  void getTasks(const QString &parentId, vector<TaskShortInfo> &tasks) override;

  void queryTaskInfo(const QString &id, TFarmTask &task) override;

  void queryTaskShortInfo(const QString &id, QString &parentId, QString &name,
                          TaskState &status) override;

  // used (by a server) to notify a server start
  void attachServer(const QString &name, const QString &addr,
                    int port) override;

  // used (by a server) to notify a server stop
  void detachServer(const QString &name, const QString &addr,
                    int port) override;

  // used (by a server) to notify a task submission error
  void taskSubmissionError(const QString &taskId, int errCode) override;

  // used by a server to notify a task progress
  void taskProgress(const QString &taskId, int step, int stepCount,
                    int frameNumber, FrameState state) override;

  // used (by a server) to notify a task completion
  void taskCompleted(const QString &taskId, int exitCode) override;

  // fills the servers vector with the names of the servers
  void getServers(vector<ServerIdentity> &servers) override;

  // returns the state of the server whose id has been specified
  ServerState queryServerState2(const QString &id) override;

  // fills info with the infoes about the server whose id is specified
  void queryServerInfo(const QString &id, ServerInfo &info) override;

  // activates the server whose id has been specified
  void activateServer(const QString &id) override;

  // deactivates the server whose id has been specified
  // once deactivated, a server is not available for task rendering
  void deactivateServer(const QString &id, bool completeRunningTasks) override;

  // FarmController specific methods
  CtrlFarmTask *doAddTask(const QString &id, const QString &parentId,
                          const QString &name, const QString &cmdline,
                          const QString &user, const QString &host,
                          bool suspended, int stepCount, int priority,
                          TFarmPlatform platform);

  void startTask(CtrlFarmTask *task, FarmServerProxy *server);

  CtrlFarmTask *getTaskToStart(FarmServerProxy *server = 0);
  CtrlFarmTask *getNextTaskToStart(CtrlFarmTask *task, FarmServerProxy *server);

  // looks for a ready server to which to assign the task
  // returns true iff the task has been started
  bool tryToStartTask(CtrlFarmTask *task);

  ServerState getServerState(FarmServerProxy *server, QString &taskId);

  void initServer(FarmServerProxy *server);

  void load(const TFilePath &fp);
  void save(const TFilePath &fp) const;

  void doRestartTask(const QString &id, bool fromClient,
                     FarmServerProxy *server);

  void activateReadyServers();

  // controller name, address and port
  QString m_hostName;
  QString m_addr;
  int m_port;
  TUserLog *m_userLog;

  map<TaskId, CtrlFarmTask *> m_tasks;
  map<QString, FarmServerProxy *> m_servers;

  TThread::Mutex m_mutex;

  static int NextTaskId;
};

int FarmController::NextTaskId = 0;

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

FarmController::FarmController(const QString &hostName, const QString &addr,
                               int port, TUserLog *log)
    : TFarmExecutor(port)
    , m_hostName(hostName)
    , m_addr(addr)
    , m_port(port)
    , m_userLog(log) {
  TFilePath rootDir            = getGlobalRoot();
  TFilePath lastUsedIdFilePath = rootDir + "config" + "id.txt";
  Tifstream is(lastUsedIdFilePath);
  if (is.good()) is >> NextTaskId;
}

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

void FarmController::loadServersData(const TFilePath &globalRoot) {
  TFilePath fp = globalRoot + "config" + "servers.txt";

  Tifstream is(fp);
  while (!is.eof()) {
    char line[80];
    is.getline(line, 80);

    if (line[0] != '#' && QString(line) != "") {
      stringstream iss(line);

      char hostName[512];
      char ipAddr[80];
      int port;

      iss >> hostName >> ipAddr >> port;

      FarmServerProxy *server = new FarmServerProxy(hostName, ipAddr, port);
      m_servers.insert(make_pair(QString(ipAddr), server));

      if (server->testConnection(500)) {
        initServer(server);
        try {
          server->attachController(m_hostName, m_addr, m_port);
        } catch (TException &) {
        }
      }
    }
  }
}

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

namespace {

inline QString toString(const TFarmTask &task, int ver) {
  QString ss = task.m_name + ",";

  ss += task.getCommandLine() + ",";
  ss += QString::number((int)(task.m_priority)) + ",";
  ss += task.m_user + ",";
  ss += task.m_hostName + ",";
  ss += task.m_id + ",";
  ss += task.m_parentId + ",";
  ss += QString::number((int)(task.m_status)) + ",";
  ss += task.m_server + ",";
  ss += task.m_submissionDate.toString() + ",";
  ss += task.m_startDate.toString() + ",";
  ss += task.m_completionDate.toString() + ",";
  ss += QString::number(task.m_successfullSteps) + ",";
  ss += QString::number(task.m_failedSteps) + ",";
  ss += QString::number(task.m_stepCount);

  if (ver == 2) {
    ss += ",";
    ss += QString::number(task.m_platform) + ",";

    int depCount = 0;
    if (task.m_dependencies) depCount = task.m_dependencies->getTaskCount();

    ss += QString::number(depCount);

    for (int i = 0; i < depCount; ++i) {
      TFarmTask::Id id = task.m_dependencies->getTaskId(i);
      ss += "," + id;
    }
  }

  ss += '\0';

  return ss;
}

inline QString toString(const ServerInfo &info) {
  QString ss = info.m_name + ",";

  ss += info.m_ipAddress + ",";
  ss += info.m_portNumber + ",";
  ss += QString::number((int)(info.m_state)) + ",";
  ss += info.m_platform + ",";

  ss += QString::number((int)info.m_cpuCount) + ",";
  ss += QString::number((int)info.m_totPhysMem) + ",";
  ss += QString::number((int)info.m_totVirtMem) + ",";
  ss += QString::number((int)info.m_availPhysMem) + ",";
  ss += QString::number((int)info.m_availVirtMem) + ",";
  ss += info.m_currentTaskId + '\0';

  return ss;
}

}  // anonymous namespace

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

QString FarmController::execute(const vector<QString> &argv) {
  QMutexLocker sl(&m_mutex);

  if (argv.size() > 0) {
#ifdef TRACE
    for (int i = 0; i < argv.size(); i++) {
      m_userLog->info(argv[i]);
    }
    m_userLog->info('\n');
#endif

    if (argv[0] == "addTask@string@string" && argv.size() > 5) {
      // ordine degli argomenti:
      // name, cmdline, user, host, suspended, priority, platform

      bool suspended;
      fromStr((int &)suspended, argv[5]);

      int priority;
      fromStr(priority, argv[6]);

      TFarmPlatform platform;
      fromStr((int &)platform, argv[7]);
      return addTask(argv[1], argv[2], argv[3], argv[4], suspended, priority,
                     platform);

    } else if (argv[0] == "addTask@TFarmTask" && argv.size() > 5) {
      // ordine degli argomenti:
      // name, cmdline, user, host, suspended, priority,

      bool suspended;

      fromStr((int &)suspended, argv[5]);

      int priority;
      fromStr(priority, argv[6]);

      TFarmTaskGroup task("", argv[1], argv[2], argv[3], argv[4], priority, 50);

      for (int i = 7; i < (int)argv.size(); i += 5) {
        // ordine degli argomenti:
        // name, cmdline, user, host, priority,

        int subTaskPriority;

        fromStr(subTaskPriority, argv[i + 4]);

        TFarmTask *subTask =
            new TFarmTask("", argv[i], argv[i + 1], argv[i + 2], argv[i + 3],
                          subTaskPriority, 50);

        task.addTask(subTask);
      }

      return addTask(task, suspended);
    } else if (argv[0] == "addTask@TFarmTask_2" && argv.size() > 7) {
      // ordine degli argomenti:
      // name, cmdline, user, host, suspended, stepCount, priority, platform

      int suspended;
      fromStr(suspended, argv[5]);

      int stepCount;
      fromStr(stepCount, argv[6]);

      int priority;
      fromStr(priority, argv[7]);

      TFarmPlatform platform;
      fromStr((int &)platform, argv[8]);

      TFarmTaskGroup task("", argv[1], argv[2], argv[3], argv[4], stepCount,
                          priority);
      task.m_platform = platform;

      int depCount;
      fromStr(depCount, argv[9]);

      int i = 0;
      for (; i < depCount; ++i) {
        QString depTaskId = argv[10 + i];
        task.m_dependencies->add(depTaskId);
      }

      for (i = 10 + depCount; i < (int)argv.size(); i += 6) {
        // ordine degli argomenti:
        // name, cmdline, user, host, stepCount, priority,

        int subTaskStepCount;
        fromStr(subTaskStepCount, argv[i + 4]);

        int subTaskPriority;
        fromStr(subTaskPriority, argv[i + 5]);

        TFarmTask *subTask =
            new TFarmTask("", argv[i], argv[i + 1], argv[i + 2], argv[i + 3],
                          subTaskStepCount, subTaskPriority);

        subTask->m_dependencies =
            new TFarmTask::Dependencies(*task.m_dependencies);
        subTask->m_platform = platform;

        task.addTask(subTask);
      }

      return addTask(task, suspended != 0);
    } else if (argv[0] == "removeTask" && argv.size() > 0) {
      removeTask(argv[1]);
    } else if (argv[0] == "suspendTask" && argv.size() > 0) {
      suspendTask(argv[1]);
    } else if (argv[0] == "activateTask" && argv.size() > 0) {
      activateTask(argv[1]);
    } else if (argv[0] == "restartTask" && argv.size() > 0) {
      restartTask(argv[1]);
    } else if (argv[0] == "getTasks@vector") {
      vector<QString> tasks;
      getTasks(tasks);

      QString reply;
      std::vector<QString>::iterator it = tasks.begin();
      for (; it != tasks.end(); ++it) {
        reply += *it;
        reply += ",";
      }

      return reply;
    } else if (argv[0] == "getTasks@string@vector") {
      QString parentId;
      if (argv.size() > 1) parentId = argv[1];

      vector<QString> tasks;
      getTasks(parentId, tasks);

      QString reply;
      std::vector<QString>::iterator it = tasks.begin();
      for (; it != tasks.end(); ++it) {
        reply += *it;
        reply += ",";
      }

      if (reply.length() > 0) reply = reply.left(reply.length() - 1);
      return reply;
    } else if (argv[0] == "getTasks@string@vector$TaskShortInfo" &&
               argv.size() > 0) {
      vector<TaskShortInfo> tasks;
      getTasks(argv[1], tasks);

      QString reply;
      std::vector<TaskShortInfo>::iterator it = tasks.begin();
      for (; it != tasks.end(); ++it) {
        reply += (*it).m_id;
        reply += ",";
        reply += (*it).m_name;
        reply += ",";
        reply += QString::number((*it).m_status);
        reply += ",";
      }
      if (reply.length() > 0) reply = reply.left(reply.size() - 1);

      return reply;
    } else if (argv[0] == "queryTaskInfo" && argv.size() > 1) {
      TFarmTask task;
      queryTaskInfo(argv[1], task);
      return toString(task, 1);
    } else if (argv[0] == "queryTaskInfo_2" && argv.size() > 1) {
      TFarmTask task;
      queryTaskInfo(argv[1], task);
      return toString(task, 2);
    } else if (argv[0] == "queryTaskShortInfo" && argv.size() > 1) {
      QString parentId, name;
      TaskState status;
      queryTaskShortInfo(argv[1], parentId, name, status);

      QString reply;
      reply += parentId;
      reply += ",";
      reply += name;
      reply += ",";
      reply += QString::number((int)(status));
      return reply;
    } else if (argv[0] == "taskSubmissionError" && argv.size() > 2) {
      QString taskId = argv[1];
      int errCode;
      fromStr(errCode, argv[2]);
      taskSubmissionError(taskId, errCode);
      return "";
    } else if (argv[0] == "taskProgress" && argv.size() > 5) {
      int step, stepCount, frameNumber;
      step        = argv[2].toInt();
      stepCount   = argv[3].toInt();
      frameNumber = argv[4].toInt();

      FrameState state;
      state = (FrameState)argv[5].toInt();
      taskProgress(argv[1], step, stepCount, frameNumber, state);
      return "";
    } else if (argv[0] == "taskCompleted" && argv.size() > 2) {
      QString taskId = argv[1];
      int exitCode;
      exitCode = argv[2].toInt();

      taskCompleted(taskId, exitCode);
      return "";
    } else if (argv[0] == "getServers") {
      vector<ServerIdentity> servers;
      getServers(servers);

      QString reply;
      std::vector<ServerIdentity>::iterator it = servers.begin();
      for (; it != servers.end(); ++it) {
        reply += (*it).m_id;
        reply += ",";
        reply += (*it).m_name;
        reply += ",";
      }

      if (reply.length() > 0) reply = reply.left(reply.size() - 1);

      return reply;
    } else if (argv[0] == "queryServerState2" && argv.size() > 0) {
      ServerState state = queryServerState2(argv[1]);
      return QString::number(state);
    } else if (argv[0] == "queryServerInfo" && argv.size() > 0) {
      ServerInfo info;
      queryServerInfo(argv[1], info);
      return toString(info);
    } else if (argv[0] == "activateServer" && argv.size() > 0) {
      activateServer(argv[1]);
      return "";
    } else if (argv[0] == "deactivateServer" && argv.size() > 1) {
      int completeRunningTask = true;
      fromStr(completeRunningTask, argv[2]);
      deactivateServer(argv[1], !!completeRunningTask);
      return "";
    } else if (argv[0] == "attachServer" && argv.size() > 3) {
      int port;
      fromStr(port, argv[3]);
      attachServer(argv[1], argv[2], port);
      return "";
    } else if (argv[0] == "detachServer" && argv.size() > 3) {
      int port;
      fromStr(port, argv[3]);
      detachServer(argv[1], argv[2], port);
      return "";
    }
  }
#ifdef TRACE
  else
    m_userLog->info("empty command\n");
#endif
  return "";
}

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

CtrlFarmTask *FarmController::doAddTask(
    const QString &id, const QString &parentId, const QString &name,
    const QString &cmdline, const QString &user, const QString &host,
    bool suspended, int stepCount, int priority, TFarmPlatform platform) {
  CtrlFarmTask *task =
      new CtrlFarmTask(id, name, cmdline, user, host, stepCount, priority);
  task->m_submissionDate = QDateTime::currentDateTime();
  task->m_parentId       = parentId;
  task->m_platform       = platform;

  m_tasks.insert(std::make_pair(TaskId(id), task));

  m_userLog->info("Task " + task->m_id + " received at " +
                  task->m_submissionDate.toString() + "\n");
  m_userLog->info("\"" + task->getCommandLine() + "\"\n");

  if (suspended) task->m_status = Suspended;

  return task;
}

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

void FarmController::startTask(CtrlFarmTask *task, FarmServerProxy *server) {
  QMutexLocker sl(&m_mutex);

  CtrlFarmTask *taskToBeSubmittedParent = 0;
  CtrlFarmTask *taskToBeSubmitted       = 0;

  if (task->m_subTasks.empty()) {
    taskToBeSubmitted = task;
    if (task->m_parentId != "") {
      map<TaskId, CtrlFarmTask *>::iterator itTaskParent =
          m_tasks.find(TaskId(task->m_parentId));
      if (itTaskParent != m_tasks.end()) {
        taskToBeSubmittedParent = itTaskParent->second;
      }
    }
  } else {
    taskToBeSubmittedParent = task;

    // cerca il primo subtask WAITING
    std::vector<QString>::iterator itSubTaskId = task->m_subTasks.begin();
    for (; itSubTaskId != task->m_subTasks.end(); ++itSubTaskId) {
      QString subTaskId = *itSubTaskId;

      map<TaskId, CtrlFarmTask *>::iterator itSubTask =
          m_tasks.find(TaskId(subTaskId));
      if (itSubTask != m_tasks.end()) {
        CtrlFarmTask *subTask = itSubTask->second;
        if (subTask->m_status == Waiting) {
          taskToBeSubmitted = subTask;
          break;
        }
      }
    }
  }

  int rc = 0;
  try {
    server->addTask(taskToBeSubmitted);
  } catch (TException &e) {
    throw e;
  }

  if (rc == 0) {
    QDateTime startDate = QDateTime::currentDateTime();

    if (taskToBeSubmittedParent &&
        taskToBeSubmittedParent->m_status != Running) {
      taskToBeSubmittedParent->m_status    = Running;
      taskToBeSubmittedParent->m_startDate = startDate;
    }

    taskToBeSubmitted->m_status    = Running;
    taskToBeSubmitted->m_startDate = startDate;

    taskToBeSubmitted->m_serverId = server->getId();

    QString msg = "Task " + taskToBeSubmitted->m_id + " assigned to ";
    msg += server->getHostName();
    msg += "\n\n";
    m_userLog->info(msg);
  }
}

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

CtrlFarmTask *FarmController::getTaskToStart(FarmServerProxy *server) {
  QMutexLocker sl(&m_mutex);

  int maxPriority         = 0;
  CtrlFarmTask *candidate = 0;

  map<TaskId, CtrlFarmTask *>::iterator itTask = m_tasks.begin();
  for (; itTask != m_tasks.end(); ++itTask) {
    CtrlFarmTask *task = itTask->second;
    if ((!server || (task->m_platform == NoPlatform ||
                     task->m_platform == server->m_platform)) &&
        (((task->m_status == Waiting && task->m_priority > maxPriority) ||
          (task->m_status == Aborted && task->m_failureCount < 3)) &&
         task->m_parentId != "")) {
      bool dependenciesCompleted = true;

      if (task->m_dependencies) {
        int count = task->m_dependencies->getTaskCount();
        for (int i = 0; i < count; ++i) {
          TFarmTask::Id id = task->m_dependencies->getTaskId(i);
          map<TaskId, CtrlFarmTask *>::iterator itDepTask =
              m_tasks.find(TaskId(id));
          if (itDepTask != m_tasks.end()) {
            CtrlFarmTask *depTask = itDepTask->second;
            if (depTask->m_status != Completed) {
              dependenciesCompleted = false;
              break;
            }
          }
        }
      }

      if (dependenciesCompleted) {
        maxPriority = task->m_priority;
        candidate   = task;
      }
    }
  }

  return candidate;
}

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

// determina il prossimo task da avviare tra quelli Waiting, escludendo except
// dalla ricerca

CtrlFarmTask *FarmController::getNextTaskToStart(CtrlFarmTask *except,
                                                 FarmServerProxy *server) {
  QMutexLocker sl(&m_mutex);

  int maxPriority         = 0;
  CtrlFarmTask *candidate = 0;

  map<TaskId, CtrlFarmTask *>::iterator itTask = m_tasks.begin();
  for (; itTask != m_tasks.end(); ++itTask) {
    CtrlFarmTask *task = itTask->second;
    if (except == task) continue;
    if ((task->m_platform == NoPlatform ||
         task->m_platform == server->m_platform) &&
        task->m_status == Waiting && task->m_priority > maxPriority) {
      bool dependenciesCompleted = true;

      if (task->m_dependencies) {
        int count = task->m_dependencies->getTaskCount();
        for (int i = 0; i < count; ++i) {
          TFarmTask::Id id = task->m_dependencies->getTaskId(i);
          map<TaskId, CtrlFarmTask *>::iterator itDepTask =
              m_tasks.find(TaskId(id));
          if (itDepTask != m_tasks.end()) {
            CtrlFarmTask *depTask = itDepTask->second;
            if (depTask->m_status != Completed) {
              dependenciesCompleted = false;
              break;
            }
          }
        }
      }

      if (dependenciesCompleted) {
        maxPriority = task->m_priority;
        candidate   = task;
      }
    }
  }

  return candidate;
}

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

bool FarmController::tryToStartTask(CtrlFarmTask *task) {
  QMutexLocker sl(&m_mutex);

  bool dependenciesCompleted = true;

  if (task->m_dependencies) {
    int count = task->m_dependencies->getTaskCount();
    for (int i = 0; i < count; ++i) {
      TFarmTask::Id id = task->m_dependencies->getTaskId(i);
      map<TaskId, CtrlFarmTask *>::iterator itDepTask =
          m_tasks.find(TaskId(id));
      if (itDepTask != m_tasks.end()) {
        CtrlFarmTask *depTask = itDepTask->second;
        if (depTask->m_status != Completed) {
          dependenciesCompleted = false;
          break;
        }
      }
    }
  }

  if (!dependenciesCompleted) return false;

  if (task->m_subTasks.empty()) {
    vector<FarmServerProxy *> m_partiallyBusyServers;

    map<QString, FarmServerProxy *>::iterator it = m_servers.begin();
    for (; it != m_servers.end(); ++it) {
      FarmServerProxy *server = it->second;
      if (server->m_attached && !server->m_offline &&
          (int)server->getTasks().size() < server->m_maxTaskCount) {
        if (!(task->m_platform == NoPlatform ||
              task->m_platform == server->m_platform))
          continue;

        vector<QString>::iterator its =
            find(task->m_failedOnServers.begin(), task->m_failedOnServers.end(),
                 server->getId());

        if (its != task->m_failedOnServers.end()) continue;

        if (server->testConnection(500)) {
          if (server->getTasks().size() == 0) {
            try {
              startTask(task, server);
            } catch (TException & /*e*/) {
              continue;
            }

            return true;
          } else
            m_partiallyBusyServers.push_back(server);
        }
      }
    }

    vector<FarmServerProxy *>::iterator it2 = m_partiallyBusyServers.begin();
    for (; it2 != m_partiallyBusyServers.end(); ++it2) {
      FarmServerProxy *server = *it2;
      if (server->testConnection(500)) {
        try {
          startTask(task, server);
        } catch (TException & /*e*/) {
          continue;
        }
      }

      return true;
    }

    return false;
  } else {
    // un task composto e' considerato started sse e' started almeno uno
    // dei task che lo compongono

    bool started                          = false;
    vector<QString>::iterator itSubTaskId = task->m_subTasks.begin();
    for (; itSubTaskId != task->m_subTasks.end(); ++itSubTaskId) {
      map<TaskId, CtrlFarmTask *>::iterator itSubTask =
          m_tasks.find(TaskId(*itSubTaskId));
      if (itSubTask != m_tasks.end()) {
        CtrlFarmTask *subTask = itSubTask->second;
        if (tryToStartTask(subTask)) started = true;
      }
    }

    return started;
  }
}

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

ServerState FarmController::getServerState(FarmServerProxy *server,
                                           QString &taskId) {
  ServerState state;

  bool connected = server->testConnection(500);
  if (!connected) {
    taskId = "";
    state  = Down;
  } else {
    if (server->m_offline)
      return Offline;
    else if (server->getTasks().size() > 0) {
      taskId = server->getTasks()[0];
      state  = Busy;
    } else {
      taskId = "";
      state  = Ready;
    }
  }
  return state;
}

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

class ServerInitializer final : public TThread::Runnable {
public:
  ServerInitializer(FarmServerProxy *server) : m_server(server) {}

  void run() override {
    TFarmServer::HwInfo hwInfo;
    try {
      m_server->queryHwInfo(hwInfo);
    } catch (TException & /*e*/) {
      return;
    }

    m_server->m_attached = true;
    // m_server->m_maxTaskCount = hwInfo.m_cpuCount;
    m_server->m_maxTaskCount = 1;
  }

  FarmServerProxy *m_server;
};

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

void FarmController::initServer(FarmServerProxy *server) {
  TFarmServer::HwInfo hwInfo;
  try {
    server->queryHwInfo(hwInfo);
  } catch (TException & /*e*/) {
    TThread::Executor exec;
    exec.addTask(new ServerInitializer(server));
    return;
  }

  server->m_attached = true;
  // server->m_maxTaskCount = hwInfo.m_cpuCount;
  server->m_maxTaskCount = 1;

  server->m_platform = hwInfo.m_type;
}

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

QString FarmController::addTask(const QString &name, const QString &cmdline,
                                const QString &user, const QString &host,
                                bool suspended, int priority,
                                TFarmPlatform platform) {
  QString parentId = "";
  CtrlFarmTask *task =
      doAddTask(QString::number(NextTaskId++), parentId, name, cmdline, user,
                host, suspended, 1, priority, platform);

  return task->m_id;
}

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

class TaskStarter final : public TThread::Runnable {
public:
  TaskStarter(FarmController *controller, CtrlFarmTask *task,
              FarmServerProxy *server = 0)
      : m_controller(controller), m_task(task), m_server(server) {}

  void run() override;

  FarmController *m_controller;
  CtrlFarmTask *m_task;
  FarmServerProxy *m_server;
};

void TaskStarter::run() {
  if (m_task->m_status != Suspended) {
    if (m_server)
      m_controller->startTask(m_task, m_server);
    else {
      m_controller->tryToStartTask(m_task);
    }
  }
}

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

QString FarmController::addTask(const TFarmTask &task, bool suspended) {
  QString id = QString::number(NextTaskId++);

  CtrlFarmTask *myTask = 0;

  int count = task.getTaskCount();
  if (count == 1) {
    QString parentId = "";
    myTask = doAddTask(id, parentId, task.m_name, task.getCommandLine(),
                       task.m_user, task.m_hostName, suspended,
                       task.m_stepCount, task.m_priority, task.m_platform);
  } else {
    myTask =
        new CtrlFarmTask(id, task.m_name, task.getCommandLine(), task.m_user,
                         task.m_hostName, task.m_stepCount, task.m_priority);

    myTask->m_submissionDate = QDateTime::currentDateTime();
    myTask->m_parentId       = "";
    myTask->m_dependencies = new TFarmTask::Dependencies(*task.m_dependencies);

    if (suspended) myTask->m_status = Suspended;

    m_tasks.insert(std::make_pair(TaskId(id), myTask));

    for (int i = 0; i < count; ++i) {
      QString subTaskId  = id + "." + QString::number(i);
      TFarmTask &tt      = const_cast<TFarmTask &>(task);
      TFarmTask *subtask = tt.getTask(i);

      CtrlFarmTask *mySubTask = doAddTask(
          subTaskId, myTask->m_id, subtask->m_name, subtask->getCommandLine(),
          subtask->m_user, subtask->m_hostName, suspended, subtask->m_stepCount,
          subtask->m_priority, task.m_platform);

      mySubTask->m_dependencies =
          new TFarmTask::Dependencies(*task.m_dependencies);

      myTask->m_subTasks.push_back(subTaskId);
    }
  }

  TThread::Executor executor;
  executor.addTask(new TaskStarter(this, myTask));

  return id;
}

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

void FarmController::removeTask(const QString &id) {
  map<TaskId, CtrlFarmTask *>::iterator it = m_tasks.find(TaskId(id));
  if (it != m_tasks.end()) {
    CtrlFarmTask *task = it->second;

    bool aSubtaskIsRunning = false;

    vector<QString>::iterator it2 = task->m_subTasks.begin();
    for (; it2 != task->m_subTasks.end();) {
      QString subTaskId = *it2;
      map<TaskId, CtrlFarmTask *>::iterator it3 =
          m_tasks.find(TaskId(subTaskId));
      if (it3 != m_tasks.end()) {
        CtrlFarmTask *subTask = it3->second;
        if (subTask->m_status != Running) {
          it2 = task->m_subTasks.erase(it2);
          m_tasks.erase(it3);
          delete it3->second;
        } else {
          it2 = task->m_subTasks.erase(it2);

          map<QString, FarmServerProxy *>::iterator itServer =
              m_servers.find(subTask->m_serverId);

          if (itServer != m_servers.end()) {
            FarmServerProxy *server = itServer->second;
            if (server) {
              vector<QString>::const_iterator it3 =
                  find(server->getTasks().begin(), server->getTasks().end(),
                       subTask->m_id);

              if (it3 != server->getTasks().end()) {
                aSubtaskIsRunning = true;
                server->terminateTask(subTask->m_id);
              }
            }
          }

          subTask->m_toBeDeleted = true;
        }
      } else
        ++it2;
    }

    if (task->m_status != Running || !aSubtaskIsRunning) {
      m_tasks.erase(it);
      delete it->second;
    } else
      task->m_toBeDeleted = true;
  }
}

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

void FarmController::suspendTask(const QString &id) {
  map<TaskId, CtrlFarmTask *>::iterator it = m_tasks.find(TaskId(id));
  if (it != m_tasks.end()) {
    CtrlFarmTask *task = it->second;

    vector<QString>::iterator it2 = task->m_subTasks.begin();
    for (; it2 != task->m_subTasks.end(); ++it2) {
      QString subTaskId = *it2;
      map<TaskId, CtrlFarmTask *>::iterator it3 =
          m_tasks.find(TaskId(subTaskId));
      if (it3 != m_tasks.end()) {
        CtrlFarmTask *subTask = it3->second;
        if (subTask->m_status == Running) {
          map<QString, FarmServerProxy *>::iterator itServer =
              m_servers.find(subTask->m_serverId);

          if (itServer != m_servers.end()) {
            FarmServerProxy *server = itServer->second;
            if (server) {
              vector<QString>::const_iterator it3 =
                  find(server->getTasks().begin(), server->getTasks().end(),
                       subTask->m_id);

              if (it3 != server->getTasks().end())
                server->terminateTask(subTask->m_id);
            }
          }
        }
        subTask->m_status = Suspended;
      }
    }
    task->m_status = Suspended;
  }
}

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

void FarmController::activateTask(const QString &id) {}

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

void FarmController::restartTask(const QString &id) {
  // la scelta del server e' lasciata al controller
  FarmServerProxy *server = 0;
  doRestartTask(id, true, server);
}

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

void FarmController::doRestartTask(const QString &id, bool fromClient,
                                   FarmServerProxy *server) {
  map<TaskId, CtrlFarmTask *>::iterator it = m_tasks.find(TaskId(id));
  if (it != m_tasks.end()) {
    CtrlFarmTask *task = it->second;
    if (task->m_status != Running) {
      if (fromClient) {
        task->m_failedOnServers.clear();
        task->m_status = Waiting;
      }

      task->m_completionDate = QDateTime();
      task->m_server         = "";
      task->m_serverId       = "";
      task->m_failedSteps = task->m_successfullSteps = 0;
      task->m_failureCount                           = 0;

      if (!task->m_subTasks.empty()) {
        vector<QString>::iterator itSubTaskId = task->m_subTasks.begin();
        for (; itSubTaskId != task->m_subTasks.end(); ++itSubTaskId) {
          map<TaskId, CtrlFarmTask *>::iterator itSubTask =
              m_tasks.find(TaskId(*itSubTaskId));
          if (itSubTask != m_tasks.end()) {
            CtrlFarmTask *subtask = itSubTask->second;
            if (fromClient) {
              subtask->m_failedOnServers.clear();
              subtask->m_status = Waiting;
            }

            subtask->m_completionDate = QDateTime();
            subtask->m_server         = "";
            subtask->m_serverId       = "";
            subtask->m_failedSteps = subtask->m_successfullSteps = 0;
            subtask->m_failureCount                              = 0;
          }
        }
      }

      TThread::Executor executor;
      executor.addTask(new TaskStarter(this, task, server));
    }
  }
}

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

void FarmController::getTasks(vector<QString> &tasks) {
  map<TaskId, CtrlFarmTask *>::iterator it = m_tasks.begin();
  for (; it != m_tasks.end(); ++it) {
    CtrlFarmTask *task = it->second;
    tasks.push_back(task->m_id);
  }
}

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

void FarmController::getTasks(const QString &parentId, vector<QString> &tasks) {
  map<TaskId, CtrlFarmTask *>::iterator it = m_tasks.begin();
  for (; it != m_tasks.end(); ++it) {
    CtrlFarmTask *task = it->second;
    if (task->m_parentId == parentId) tasks.push_back(task->m_id);
  }
}

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

void FarmController::getTasks(const QString &parentId,
                              vector<TaskShortInfo> &tasks) {
  tasks.clear();

  map<TaskId, CtrlFarmTask *>::iterator it = m_tasks.begin();
  for (; it != m_tasks.end(); ++it) {
    CtrlFarmTask *task = it->second;
    if (task->m_parentId == parentId)
      tasks.push_back(TaskShortInfo(task->m_id, task->m_name, task->m_status));
  }
}

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

void FarmController::queryTaskInfo(const QString &id, TFarmTask &task) {
  map<TaskId, CtrlFarmTask *>::iterator it = m_tasks.find(TaskId(id));
  if (it != m_tasks.end()) {
    CtrlFarmTask *tt = it->second;
    task             = *tt;

    map<QString, FarmServerProxy *>::iterator it2 =
        m_servers.find(it->second->m_serverId);
    if (it2 != m_servers.end()) {
      FarmServerProxy *server = it2->second;
      task.m_server           = server->getHostName();
    } else
      task.m_server = "";
  } else
    task.m_status = TaskUnknown;
}

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

void FarmController::queryTaskShortInfo(const QString &id, QString &parentId,
                                        QString &name, TaskState &status) {
  map<TaskId, CtrlFarmTask *>::iterator it = m_tasks.find(TaskId(id));
  if (it != m_tasks.end()) {
    CtrlFarmTask *task = it->second;
    parentId           = task->m_parentId;
    name               = task->m_name;
    status             = task->m_status;
  } else
    status = TaskUnknown;
}

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

void FarmController::attachServer(const QString &name, const QString &addr,
                                  int port) {
  FarmServerProxy *server                      = 0;
  map<QString, FarmServerProxy *>::iterator it = m_servers.begin();
  for (; it != m_servers.end(); ++it) {
    FarmServerProxy *s = it->second;

    if (STRICMP(s->getHostName(), name) == 0 ||
        STRICMP(s->getIpAddress(), addr) == 0) {
      server = s;
      break;
    }
  }

  if (!server) {
    server = new FarmServerProxy(name, addr, port);
    m_servers.insert(make_pair(QString(addr), server));
  }

  initServer(server);
}

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

void FarmController::detachServer(const QString &name, const QString &addr,
                                  int port) {
  map<QString, FarmServerProxy *>::iterator it = m_servers.begin();
  for (; it != m_servers.end(); ++it) {
    FarmServerProxy *s = it->second;
    if (STRICMP(s->getHostName(), name) == 0 ||
        STRICMP(s->getIpAddress(), addr) == 0) {
      s->m_attached = false;
      break;
    }
  }
}

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

void FarmController::taskSubmissionError(const QString &taskId, int errCode) {
  FarmServerProxy *server = 0;

  map<TaskId, CtrlFarmTask *>::iterator itTask = m_tasks.find(TaskId(taskId));
  if (itTask != m_tasks.end()) {
    CtrlFarmTask *task = itTask->second;
    task->m_status     = Aborted;

    task->m_completionDate = QDateTime::currentDateTime();

    if (task->m_toBeDeleted) m_tasks.erase(itTask);

    CtrlFarmTask *parentTask = 0;

    if (task->m_parentId != "") {
      map<TaskId, CtrlFarmTask *>::iterator itParent =
          m_tasks.find(TaskId(task->m_parentId));
      if (itParent != m_tasks.end()) {
        parentTask = itParent->second;

        TaskState parentTaskState = Aborted;
        std::vector<QString>::iterator itSubTaskId =
            parentTask->m_subTasks.begin();
        for (; itSubTaskId != parentTask->m_subTasks.end(); ++itSubTaskId) {
          QString subTaskId = *itSubTaskId;
          map<TaskId, CtrlFarmTask *>::iterator itSubTask =
              m_tasks.find(TaskId(subTaskId));
          if (itSubTask != m_tasks.end()) {
            CtrlFarmTask *subTask = itSubTask->second;
            if (subTask->m_status == Running || subTask->m_status == Waiting) {
              parentTaskState = Running;
              break;
            }
          }
        }

        parentTask->m_status = parentTaskState;
        if (parentTask->m_status == Aborted) {
          parentTask->m_completionDate = task->m_completionDate;
          if (parentTask->m_toBeDeleted) m_tasks.erase(itParent);
        }
      }
    }

    map<QString, FarmServerProxy *>::iterator itServer =
        m_servers.find(task->m_serverId);
    if (itServer != m_servers.end()) server = itServer->second;

    if (server) {
      server->removeTask(taskId);
    }

    if (task->m_toBeDeleted) delete task;
    if (parentTask && parentTask->m_toBeDeleted) delete parentTask;
  }

  if (server && !server->m_offline) {
    // cerca un task da sottomettere al server

    itTask = m_tasks.begin();
    for (; itTask != m_tasks.end(); ++itTask) {
      CtrlFarmTask *task = itTask->second;
      if (task->m_status == Waiting) {
        try {
          startTask(task, server);
        } catch (TException & /*e*/) {
          continue;
        }

        break;
      }
    }
  }
}

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

void FarmController::taskProgress(const QString &taskId, int step,
                                  int stepCount, int frameNumber,
                                  FrameState state) {
  map<TaskId, CtrlFarmTask *>::iterator itTask = m_tasks.find(TaskId(taskId));
  if (itTask != m_tasks.end()) {
    CtrlFarmTask *task = itTask->second;
    if (state == FrameDone)
      ++task->m_successfullSteps;
    else
      ++task->m_failedSteps;

    if (task->m_parentId != "") {
      map<TaskId, CtrlFarmTask *>::iterator itParentTask =
          m_tasks.find(TaskId(task->m_parentId));
      CtrlFarmTask *parentTask = itParentTask->second;
      if (state == FrameDone)
        ++parentTask->m_successfullSteps;
      else
        ++parentTask->m_failedSteps;
    }
  }
}

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

void FarmController::taskCompleted(const QString &taskId, int exitCode) {
#ifdef TRACE
  m_userLog->info("completed chiamata\n\n");
#endif

  FarmServerProxy *server = 0;

  map<TaskId, CtrlFarmTask *>::iterator itTask = m_tasks.find(TaskId(taskId));
  if (itTask != m_tasks.end()) {
    CtrlFarmTask *task = itTask->second;

    if (task->getCommandLine().contains("runcasm")) {
      if (task->m_failedSteps == 0 && task->m_successfullSteps > 0)
        task->m_status = Completed;
      else
        task->m_status = Aborted;
    } else {
      switch (exitCode) {
      case 0:
        task->m_status = Completed;
        if (isAScript(task)) task->m_successfullSteps = task->m_stepCount;
        break;
      case RENDER_LICENSE_NOT_FOUND:
        task->m_status = Waiting;
        break;
      default:
        if (task->m_status != Suspended) task->m_status = Aborted;
        break;
      }
    }

    task->m_completionDate = QDateTime::currentDateTime();

    if (task->m_status == Aborted) {
      task->m_failedOnServers.push_back(task->m_serverId);
      ++task->m_failureCount;
    }

    if (task->m_toBeDeleted) m_tasks.erase(itTask);

    CtrlFarmTask *parentTask = 0;

    if (task->m_parentId != "") {
      map<TaskId, CtrlFarmTask *>::iterator itParent =
          m_tasks.find(TaskId(task->m_parentId));
      if (itParent != m_tasks.end()) {
        parentTask = itParent->second;

        TaskState parentTaskState = Completed;
        bool aSubTaskFailed       = false;
        bool noSubtaskRunning     = true;

        if (parentTask->m_status != Suspended &&
            parentTask->m_status != Aborted) {
          std::vector<QString>::iterator itSubTaskId =
              parentTask->m_subTasks.begin();
          for (; itSubTaskId != parentTask->m_subTasks.end(); ++itSubTaskId) {
            QString subTaskId = *itSubTaskId;
            map<TaskId, CtrlFarmTask *>::iterator itSubTask =
                m_tasks.find(TaskId(subTaskId));
            if (itSubTask != m_tasks.end()) {
              CtrlFarmTask *subTask = itSubTask->second;

              if (subTask->m_status == Running ||
                  subTask->m_status == Waiting) {
                parentTaskState  = Running;
                noSubtaskRunning = false;
                break;
              } else if (subTask->m_status == Aborted)
                aSubTaskFailed = true;
            }
          }
        } else
          aSubTaskFailed = true;
        ;  // si arriva se e solo se il task padre e' stato terminato

        if (aSubTaskFailed && noSubtaskRunning)
          parentTask->m_status = Aborted;
        else
          parentTask->m_status = parentTaskState;

        if (parentTask->m_status == Completed ||
            parentTask->m_status == Aborted) {
          parentTask->m_completionDate = task->m_completionDate;
          if (parentTask->m_toBeDeleted) m_tasks.erase(itParent);
        }
      }
    }

    map<QString, FarmServerProxy *>::iterator itServer =
        m_servers.find(task->m_serverId);
    if (itServer != m_servers.end()) server = itServer->second;

    if (server) {
      if (task->m_status == Completed) {
        QString msg = "Task " + taskId + " completed on ";
        msg += server->getHostName();
        msg += "\n\n";
        m_userLog->info(msg);
      } else {
        QString msg = "Task " + taskId + " failed on ";
        msg += server->getHostName();
        msg += " with exit code " + QString::number(exitCode);
        msg += "\n\n";
        m_userLog->info(msg);
      }

      server->removeTask(taskId);
    }

    bool allComplete = false;
    if (parentTask && parentTask->m_status == Completed) {
      m_userLog->info("Task " + parentTask->m_id + " completed\n\n");
      allComplete = true;
    }

    if (task->m_toBeDeleted) delete task;
    if (parentTask && parentTask->m_toBeDeleted) delete parentTask;
    //*
    if (allComplete) {
      activateReadyServers();
      return;
    }
    //*/
  }

  if (server && !server->m_offline && exitCode != RENDER_LICENSE_NOT_FOUND) {
    // cerca un task da sottomettere al server
    CtrlFarmTask *task = getTaskToStart(server);
    if (task) {
      try {
        if (task->m_status == Aborted) {
          vector<QString>::iterator it =
              find(task->m_failedOnServers.begin(),
                   task->m_failedOnServers.end(), server->getId());

          if (it == task->m_failedOnServers.end())
            doRestartTask(task->m_id, false, server);
          else {
            doRestartTask(task->m_id, false, 0);
            CtrlFarmTask *nextTask = getNextTaskToStart(task, server);
            if (nextTask) startTask(nextTask, server);
          }
        } else
          startTask(task, server);
      } catch (TException & /*e*/) {
      }
    }
  }
}

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

void FarmController::getServers(vector<ServerIdentity> &servers) {
  map<QString, FarmServerProxy *>::iterator it = m_servers.begin();
  for (; it != m_servers.end(); ++it) {
    FarmServerProxy *server = it->second;
    servers.push_back(ServerIdentity(server->m_addr, server->m_hostName));
  }
}

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

ServerState FarmController::queryServerState2(const QString &id) {
  ServerState state = ServerUnknown;

  map<QString, FarmServerProxy *>::iterator it = m_servers.find(id);
  if (it != m_servers.end()) {
    FarmServerProxy *server = it->second;

    QString taskId;
    state = getServerState(server, taskId);
  }

  return state;
}

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

void FarmController::queryServerInfo(const QString &id, ServerInfo &info) {
  map<QString, FarmServerProxy *>::iterator it = m_servers.find(id);
  if (it != m_servers.end()) {
    FarmServerProxy *server = it->second;

    info.m_name       = server->getHostName();
    info.m_ipAddress  = server->getIpAddress();
    info.m_portNumber = QString::number(server->getPort());

    info.m_state = getServerState(server, info.m_currentTaskId);
    if (info.m_state != Down && info.m_state != ServerUnknown) {
      TFarmServer::HwInfo hwInfo;
      server->queryHwInfo(hwInfo);

      info.m_cpuCount     = hwInfo.m_cpuCount;
      info.m_totPhysMem   = hwInfo.m_totPhysMem;
      info.m_totVirtMem   = hwInfo.m_totVirtMem;
      info.m_availPhysMem = hwInfo.m_availPhysMem;
      info.m_availVirtMem = hwInfo.m_availVirtMem;
    }
  }
}

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

void FarmController::activateServer(const QString &id) {
  map<QString, FarmServerProxy *>::iterator it = m_servers.find(id);
  if (it != m_servers.end()) {
    FarmServerProxy *server = it->second;
    server->m_offline       = false;

    for (int i = 0; i < server->m_maxTaskCount; ++i) {
      // cerca un task da sottomettere al server
      CtrlFarmTask *task = getTaskToStart(server);
      if (task) {
        try {
          if (task->m_status == Aborted) {
            vector<QString>::iterator it =
                find(task->m_failedOnServers.begin(),
                     task->m_failedOnServers.end(), server->getId());

            if (it == task->m_failedOnServers.end())
              doRestartTask(task->m_id, false, server);
            else {
              doRestartTask(task->m_id, false, 0);
              CtrlFarmTask *nextTask = getNextTaskToStart(task, server);
              if (nextTask) startTask(nextTask, server);
            }
          } else
            startTask(task, server);
        } catch (TException & /*e*/) {
        }
      }
    }
  }
}

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

void FarmController::deactivateServer(const QString &id,
                                      bool completeRunningTasks) {
  map<QString, FarmServerProxy *>::iterator it = m_servers.find(id);
  if (it != m_servers.end()) {
    FarmServerProxy *server = it->second;

    QString taskId;
    ServerState state = getServerState(server, taskId);
    if (state == Busy) {
      const vector<QString> &tasks       = server->getTasks();
      vector<QString>::const_iterator it = tasks.begin();
      for (; it != tasks.end(); ++it) {
        server->terminateTask(*it);
      }

      server->m_offline = true;
    } else
      server->m_offline = true;
  }
}

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

void FarmController::load(const TFilePath &fp) {
  TIStream is(fp);

  m_tasks.clear();

  string tagName;
  is.openChild(tagName);

  if (tagName == "tfarmdata") {
    is.openChild(tagName);
    if (tagName == "tfarmtasks") {
      while (!is.eos()) {
        TPersist *p;
        is >> p;

        CtrlFarmTask *task = dynamic_cast<CtrlFarmTask *>(p);
        if (task) m_tasks.insert(make_pair(TaskId(task->m_id), task));
      }

      is.closeChild();
    }
  }

  is.closeChild();

  map<TaskId, CtrlFarmTask *>::const_iterator it = m_tasks.begin();
  for (; it != m_tasks.end(); ++it) {
    CtrlFarmTask *task = it->second;
    if (task->m_parentId != "") {
      map<TaskId, CtrlFarmTask *>::const_iterator it2 =
          m_tasks.find(TaskId(task->m_parentId));

      if (it2 != m_tasks.end()) {
        CtrlFarmTask *parent = it2->second;
        parent->m_subTasks.push_back(task->m_id);
      }
    }
  }
}

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

void FarmController::save(const TFilePath &fp) const {
  TOStream os(fp);

  map<std::string, string> attributes;
  attributes.insert(make_pair("ver", "1.0"));
  os.openChild("tfarmdata", attributes);
  os.openChild("tfarmtasks");

  map<TaskId, CtrlFarmTask *>::const_iterator it = m_tasks.begin();
  for (; it != m_tasks.end(); ++it) {
    CtrlFarmTask *task = it->second;
    os << task;
  }

  os.closeChild();
  os.closeChild();
}

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

void FarmController::activateReadyServers() {
  QMutexLocker sl(&m_mutex);

  map<QString, FarmServerProxy *>::iterator it = m_servers.begin();
  for (; it != m_servers.end(); ++it) {
    FarmServerProxy *server = it->second;

    ServerState state = queryServerState2(server->getId());
    int tasksCount    = server->m_tasks.size();
    if (state == Ready ||
        (state == Busy && tasksCount < server->m_maxTaskCount)) {
      for (int i = 0; i < (server->m_maxTaskCount - tasksCount); ++i) {
        // cerca un task da sottomettere al server
        CtrlFarmTask *task = getTaskToStart(server);
        if (task) {
          try {
            if (task->m_status == Aborted) {
              vector<QString>::iterator it =
                  find(task->m_failedOnServers.begin(),
                       task->m_failedOnServers.end(), server->getId());

              if (it == task->m_failedOnServers.end())
                doRestartTask(task->m_id, false, server);
              else {
                doRestartTask(task->m_id, false, 0);
                CtrlFarmTask *nextTask = getNextTaskToStart(task, server);
                if (nextTask) startTask(nextTask, server);
              }
            } else
              startTask(task, server);
          } catch (TException & /*e*/) {
          }
        }
      }
    }
  }
}

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

class ControllerService final : public TService {
public:
  ControllerService()
      : TService("ToonzFarmController", "ToonzFarm Controller")
      , m_controller(0) {}

  ~ControllerService() { delete m_controller; }

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

  static TFilePath getTasksDataFile() {
    TFilePath fp = getGlobalRoot() + "data" + "tasks.txt";
    return fp;
  }

  FarmController *m_controller;
  TUserLog *m_userLog;
};

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

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

  if (isRunningAsConsoleApp()) {
    // i messaggi verranno ridiretti sullo standard output
    m_userLog = new TUserLog();
  } else {
    TFilePath lRootDir  = getLocalRoot();
    bool lRootDirExists = dirExists(lRootDir);
    if (!lRootDirExists) {
      QString errMsg("Unable to start the Controller");
      errMsg += "\n";
      errMsg += "The directory " + lRootDir.getQString() +
                " specified as Local Root does not exist";
      errMsg += "\n";

      addToMessageLog(errMsg);

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

    TFilePath logFilePath = lRootDir + "controller.log";
    m_userLog             = new TUserLog(logFilePath);
  }
  std::string appverinfo = tver.getAppVersionInfo("Farm Controller") + "\n\n";
  m_userLog->info(appverinfo.c_str());

  TFilePath globalRoot = getGlobalRoot();
  if (globalRoot.isEmpty()) {
    QString errMsg("Unable to get FARMROOT environment variable (" +
                   globalRoot.getQString() + ")\n");
    addToMessageLog(errMsg);

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

  bool globalRootExists = true;

  TFileStatus fs(globalRoot);
  if (!fs.isDirectory()) globalRootExists = false;

  if (!globalRootExists) {
    QString errMsg("The directory " + globalRoot.getQString() +
                   " specified as TFARMGLOBALROOT does not exist\n");
    addToMessageLog(errMsg);

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

  int port = 8000;
  QString hostName, addr;
  bool ret = loadControllerData(hostName, addr, port);
  if (!ret) {
    QString msg("Unable to get the port number of ");
    msg += TSystem::getHostName();
    msg += " from the Controller config file";
    msg += "\n";
    msg += "Using the default port number ";
    msg += QString::number(port);
    msg += "\n";
    m_userLog->info(msg);
  }

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

  m_controller = new FarmController(hostName, addr, port, m_userLog);

  // configurazione e inizializzazione dei server lato client
  // (il controller e' un client dei ToonzFarm server)
  m_controller->loadServersData(globalRoot);

  TFilePath fp = getTasksDataFile();
  if (myDoesExists(fp)) m_controller->load(fp);

  // si avvia uno dei task caricati da disco
  CtrlFarmTask *task = m_controller->getTaskToStart();
  if (task) {
    TThread::Executor executor;
    executor.addTask(new TaskStarter(m_controller, task));
  }

  QString msg("Starting Controller on port ");
  msg += QString::number(m_controller->m_port);
  msg += "\n\n";
  m_userLog->info(msg);

  // std::cout << msg;

  QEventLoop eventLoop;

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

  // Start the TcpIp server's listening thread
  m_controller->start();

  // Enter main event loop
  eventLoop.exec();

  //----------------------Farm controller loops here------------------------

  msg = "Controller exited with exit code ";
  msg += QString::number(m_controller->getExitCode());
  msg += "\n";
  m_userLog->info(msg);

  // std::cout << msg;

#ifdef __sgi
  { remove("/tmp/.tfarmcontroller.dat"); }
#endif
}

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

void ControllerService::onStop() {
  // TFilePath fp = getTasksDataFile();
  // m_controller->save(fp);

  TFilePath rootDir            = getGlobalRoot();
  TFilePath lastUsedIdFilePath = rootDir + "config" + "id.txt";
  Tofstream os(lastUsedIdFilePath);
  if (os.good()) os << FarmController::NextTaskId;

  TTcpIpClient client;

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

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

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

  // LO METTO A TRUE COSI' VEDO QUELLO CHE SCRIVE !!
  // RIMETTERLO A FALSE ALTRIMENTI NON PARTE COME SERVIZIO SU WIN
  bool console = false;

  if (argc > 1) {
    string serviceName(
        "ToonzFarmController");  // Must be the same of the installer's
    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;
}

ControllerService Service;
