

#define _CRT_SECURE_NO_WARNINGS (1)   // Warning treated as errors
#define _CRT_SECURE_NO_DEPRECATE (1)  // use of fopen deprecated

// TFarmController includes
#include "tfarmcontroller.h"

// TnzStdfx includes
#include "stdfx/shaderfx.h"

// TnzLib includes
#include "toonz/toonzfolders.h"
#include "toonz/tlog.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/stage.h"
#include "toonz/preferences.h"
#include "toonz/tproject.h"
#include "toonz/toonzscene.h"
#include "toonz/sceneproperties.h"
#include "toonz/txshsoundlevel.h"
#include "toonz/txshsoundcolumn.h"
#include "toonz/tcamera.h"
#include "toonz/scenefx.h"
#include "toonz/movierenderer.h"
#include "toonz/multimediarenderer.h"
#include "toutputproperties.h"
#include "toonz/imagestyles.h"
#include "tproperty.h"
#include "toonz/levelset.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/levelproperties.h"
#include "toonz/filepathproperties.h"

// TnzSound includes
#include "tnzsound.h"

// TnzImage includes
#include "timage_io.h"
#include "tnzimage.h"

#ifdef _WIN32
#include "avicodecrestrictions.h"
#endif

// TnzBase includes
#include "tcli.h"
#include "tunit.h"
#include "tenv.h"
#include "tpassivecachemanager.h"
// #include "tcacheresourcepool.h"

// TnzCore includes
#include "tsystem.h"
#include "tsmartpointer.h"
#include "tthread.h"
#include "tthreadmessage.h"
#include "tmsgcore.h"
#include "tstopwatch.h"
#include "timagecache.h"
#include "tstream.h"
#include "tfilepath_io.h"
#include "tpluginmanager.h"
#include "tiio_std.h"
#include "tsimplecolorstyles.h"

#include "tvectorbrushstyle.h"
#include "tpalette.h"

// TnzQt includes
#include "toonzqt/pluginloader.h"

// Qt includes
#include <QApplication>
#include <QWaitCondition>
#include <QMessageBox>

#ifdef _WIN32
#ifndef x64
#include <float.h>
#endif
#endif

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

using namespace std;

using namespace TCli;
typedef ArgumentT<TFilePath> FilePathArgument;
typedef QualifierT<TFilePath> FilePathQualifier;

namespace {
double currentCameraSize = 12;
double getCurrentCameraSize() { return currentCameraSize; }
}  // namespace

//========================================================================
//
// Application names and versions
//
//------------------------------------------------------------------------

namespace {

//
// applicationName & Version compongono la registry root
// applicationFullName e' la title bar (e compare dentro i .tnz)
// rootVarName e' quello che dice di essere
// systemVarPrefix viene prefissa a tutte le variabili nei registry
//   (es <systemVarPrefix>PROJECTS etc.)
//

const char *rootVarName     = "TOONZROOT";
const char *systemVarPrefix = "TOONZ";

// TODO: forse anche questo andrebbe in tnzbase
// ci possono essere altri programmi offline oltre al tcomposer

//========================================================================
//
// fatalError
//
//------------------------------------------------------------------------

void fatalError(string msg) {
#ifdef _WIN32
  std::cout << msg << std::endl;
  // MessageBox(0,msg.c_str(),"Fatal error",MB_ICONERROR);
  exit(1);
#else
  // TODO: Come si fa ad aggiungere un messaggio di errore qui?
  std::cout << msg << std::endl;
  abort();
#endif
}

//
#ifdef MACOSX

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

//
// TODO: se questo sistema e' il modo Mac di emulare i registry di windows
// allora **DEVE** essere messo in libreria. Parliamone.
//
//========================================================================
// setToonzFolder
//------------------------------------------------------------------------

// Ritorna il path della variabile passata come secondo argomento
// entrambe vengono lette da un file di testo (filename).

TFilePath setToonzFolder(const TFilePath &filename, std::string toonzVar) {
  Tifstream is(filename);
  if (!is) return TFilePath();

  char buffer[1024];

  while (is.getline(buffer, sizeof(buffer))) {
    // le righe dentro toonzenv.txt sono del tipo
    // export set TOONZPROJECT="....."

    // devo trovare la linea che contiene toonzVar
    char *s = buffer;
    while (isBlank(*s)) s++;
    // Se la riga  vuota, o inizia per # o ! salto alla prossima
    if (*s == '\0' || *s == '#' || *s == '!') continue;
    if (*s == '=') continue;  // errore: nome variabile non c'
    char *t = s;
    // Mi prendo la sottoStringa fino all'=
    while (*t && *t != '=') t++;
    if (*t != '=') continue;  // errore: manca '='
    char *q = t;
    // Torno indietro fino al primo blank
    while (q > s && !isBlank(*(q - 1))) q--;
    if (q == s)
      continue;  // non dovrebbe mai succedere: prima di '=' tutti blanks

    string toonzVarString(q, t);

    // Confronto la stringa trovata con toonzVar, se   lei vado avanti.
    if (toonzVar != toonzVarString) continue;  // errore: stringhe diverse
    s = t + 1;
    // Salto gli spazi
    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;
    // ATTENZIONE : tolgo le virgolette !!
    string pathName(s + 1, t - s - 2);
    return TFilePath(pathName);
  }

  return TFilePath();
}

#endif

}  // namespace

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

namespace {
TStopWatch Sw1;
TStopWatch Sw2;

bool UseRenderFarm = false;
QString FarmControllerName;
int FarmControllerPort;

TFarmController *FarmController = 0;
TUserLogAppend *m_userLog;
QString TaskId;

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

void tcomposerRunOutOfContMemHandler(unsigned long size) {
  string msg("Run out of contiguous memory: tried to allocate " +
             std::to_string(size >> 10) + " KB");
  cout << msg << endl;
  m_userLog->error(msg);

  // The task will necessarily do something wrong. So we quit with an error
  // code.
  TImageCache::instance()->clear(true);
  exit(2);
}

// Check if the scene saved with the previous version AND the premultiply option
// is set to PNG level setting
void UnsetPremultiplyOptionsInPngLevels(ToonzScene *scene) {
  if (scene->getVersionNumber() <
      VersionNumber(71, 1)) {  // V1.4 = 71.0 , V1.5 = 71.1
    QStringList modifiedPNGLevelNames;
    std::vector<TXshLevel *> levels;
    scene->getLevelSet()->listLevels(levels);
    for (auto level : levels) {
      if (!level || !level->getSimpleLevel()) continue;
      TFilePath path = level->getPath();
      if (path.isEmpty() || path.getType() != "png") continue;
      if (level->getSimpleLevel()->getProperties()->doPremultiply()) {
        level->getSimpleLevel()->getProperties()->setDoPremultiply(false);
        modifiedPNGLevelNames.append(QString::fromStdWString(level->getName()));
      }
    }
    if (!modifiedPNGLevelNames.isEmpty()) {
      std::string msg =
          "The Premultiply options in the following levels are disabled, since "
          "PNG files are premultiplied on loading in the current version:" +
          modifiedPNGLevelNames.join(", ").toStdString();
      cout << msg << endl;
      m_userLog->info(msg);
    }
  }
}

}  // namespace

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

class MyMovieRenderListener final : public MovieRenderer::Listener {
public:
  MyMovieRenderListener(const TFilePath &fp, int frameCount,
                        QWaitCondition &renderCompleted, bool stereo)
      : m_fp(fp)
      , m_frameCount(frameCount)
      , m_frameCompletedCount(0)
      , m_frameFailedCount(0)
      , m_renderCompleted(renderCompleted)
      , m_stereo(stereo) {}

  bool onFrameCompleted(int frame) override;
  bool onFrameFailed(int frame, TException &e) override;
  void onSequenceCompleted(const TFilePath &fp) override;

  TFilePath m_fp;
  int m_frameCount;
  int m_frameCompletedCount;
  int m_frameFailedCount;
  QWaitCondition &m_renderCompleted;
  bool m_stereo;
};

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

bool MyMovieRenderListener::onFrameCompleted(int frame) {
  TFilePath fp = m_fp.withFrame(frame + 1);
  string msg;
  if (m_stereo)
    msg = ::to_string(fp.withName(fp.getName() + "_l")) + " and " +
          ::to_string(fp.withName(fp.getName() + "_r")) + " computed";
  else
    msg = ::to_string(fp) + " computed";
  cout << msg << endl;
  m_userLog->info(msg);
  DVGui::info(QString::fromStdString(msg));
  if (FarmController) {
    try {
      FarmController->taskProgress(TaskId,
                                   m_frameCompletedCount + m_frameFailedCount,
                                   m_frameCount, frame + 1, FrameDone);
    } catch (...) {
      msg = "Unable to connect to " + std::to_string(FarmControllerPort) + "@" +
            FarmControllerName.toStdString();
      cout << msg;
      m_userLog->error(msg);
    }
  }

  m_frameCompletedCount++;

  return true;
}

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

bool MyMovieRenderListener::onFrameFailed(int frame, TException &e) {
  TFilePath fp = m_fp.withFrame(frame + 1);
  string msg;
  msg = ::to_string(fp) + " failed";

  if (!e.getMessage().empty()) msg += ": " + ::to_string(e.getMessage());

  cout << msg << endl;
  m_userLog->error(msg);
  if (FarmController) {
    try {
      FarmController->taskProgress(TaskId,
                                   m_frameCompletedCount + m_frameFailedCount,
                                   m_frameCount, frame + 1, FrameFailed);
    } catch (...) {
      msg = "Unable to connect to " + std::to_string(FarmControllerPort) + "@" +
            FarmControllerName.toStdString();
      cout << msg;
      m_userLog->error(msg);
    }
  }

  m_frameFailedCount++;

  return true;
}

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

void MyMovieRenderListener::onSequenceCompleted(const TFilePath &fp) {
  cout << endl;
  QCoreApplication::instance()->quit();
}

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

class MyMultimediaRenderListener final : public MultimediaRenderer::Listener {
public:
  TFilePath m_fp;
  int m_frameCount;
  int m_frameCompletedCount;
  int m_frameFailedCount;

  MyMultimediaRenderListener(const TFilePath &fp, int frameCount)
      : m_fp(fp)
      , m_frameCount(frameCount)
      , m_frameCompletedCount(0)
      , m_frameFailedCount(0) {}

  bool onFrameCompleted(int frame, int column) override;
  bool onFrameFailed(int frame, int column, TException &e) override;
  void onSequenceCompleted(int column) override {}
  void onRenderCompleted() override {}
};

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

bool MyMultimediaRenderListener::onFrameCompleted(int frame, int column) {
  int actualFrame = frame + 1;
  string msg      = ::to_string(m_fp) + ", column " + std::to_string(column) +
               ", frame " + std::to_string(actualFrame) + " computed";
  cout << msg << endl;
  m_userLog->info(msg);
  if (FarmController) {
    try {
      FarmController->taskProgress(TaskId,
                                   m_frameCompletedCount + m_frameFailedCount,
                                   m_frameCount, actualFrame, FrameDone);
    } catch (...) {
      msg = "Unable to connect to " + std::to_string(FarmControllerPort) + "@" +
            FarmControllerName.toStdString();
      cout << msg;
      m_userLog->error(msg);
    }
  }

  m_frameCompletedCount++;
  return true;
}

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

bool MyMultimediaRenderListener::onFrameFailed(int frame, int column,
                                               TException &e) {
  int actualFrame = frame + 1;
  string msg      = ::to_string(m_fp) + ", column " + std::to_string(column) +
               ", frame " + std::to_string(actualFrame) + " failed";

  if (!e.getMessage().empty()) msg += ": " + ::to_string(e.getMessage());

  cout << msg << endl;
  m_userLog->error(msg);
  if (FarmController) {
    try {
      FarmController->taskProgress(TaskId,
                                   m_frameCompletedCount + m_frameFailedCount,
                                   m_frameCount, actualFrame, FrameFailed);
    } catch (...) {
      msg = "Unable to connect to " + std::to_string(FarmControllerPort) + "@" +
            FarmControllerName.toStdString();
      cout << msg;
      m_userLog->error(msg);
    }
  }

  m_frameFailedCount++;
  return true;
}

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

static std::pair<int, int> generateMovie(ToonzScene *scene, const TFilePath &fp,
                                         int r0, int r1, int step, int shrink,
                                         int threadCount, int maxTileSize) {
  QWaitCondition renderCompleted;

  // riporto gli indici a base zero
  r0 = r0 - 1;
  r1 = r1 - 1;

  if (r0 < 0) r0 = 0;
  if (r1 < 0 || r1 >= scene->getFrameCount()) r1 = scene->getFrameCount() - 1;
  string msg;
  assert(r1 >= r0);
  TSceneProperties *sprop          = scene->getProperties();
  TOutputProperties outputSettings = *sprop->getOutputProperties();

  currentCameraSize = scene->getCurrentCamera()->getSize().lx;

  TDimension cameraSize = scene->getCurrentCamera()->getRes();
  TSystem::touchParentDir(fp);

  string ext = fp.getType();

#ifdef _WIN32
  if (ext == "avi") {
    TPropertyGroup *props =
        scene->getProperties()->getOutputProperties()->getFileFormatProperties(
            ext);
    string codecName = props->getProperty(0)->getValueAsString();
    TDimension res   = scene->getCurrentCamera()->getRes();
    if (!AviCodecRestrictions::canWriteMovie(::to_wstring(codecName), res)) {
      string msg(
          "The resolution of the output camera does not fit with the options "
          "chosen for the output file format.");
      m_userLog->error(msg);
      exit(1);
    }
  }
#endif

  double cameraXDpi, cameraYDpi;
  TPointD camDpi = scene->getCurrentCamera()->getDpi();
  cameraXDpi     = camDpi.x;
  cameraYDpi     = camDpi.y;

  int which        = outputSettings.getWhichLevels();
  double stretchTo = (double)outputSettings.getRenderSettings().m_timeStretchTo;
  double stretchFrom =
      (double)outputSettings.getRenderSettings().m_timeStretchFrom;

  double timeStretchFactor = stretchFrom / stretchTo;
  int numFrames            = (int)((r1 - r0 + 1) / timeStretchFactor);
  double r                 = (r0)*timeStretchFactor;
  double stepd             = step * timeStretchFactor;

  int multimediaRender = outputSettings.getMultimediaRendering();

  //---------------------------------------------------------
  //    Multimedia render
  //---------------------------------------------------------

  if (multimediaRender) {
    MultimediaRenderer multimediaRenderer(scene, fp, multimediaRender,
                                          threadCount);
    TRenderSettings rs = outputSettings.getRenderSettings();
    rs.m_maxTileSize   = maxTileSize;
    rs.m_shrinkX = rs.m_shrinkY = shrink;
    multimediaRenderer.setRenderSettings(rs);
    multimediaRenderer.setDpi(cameraXDpi, cameraYDpi);
    multimediaRenderer.enablePrecomputing(true);
    for (int i = 0; i < numFrames; i += step, r += stepd)
      multimediaRenderer.addFrame(r);

    MyMultimediaRenderListener *listener = new MyMultimediaRenderListener(
        fp, multimediaRenderer.getFrameCount() *
                multimediaRenderer.getColumnsCount());
    multimediaRenderer.addListener(listener);

    multimediaRenderer.start();

    //----------------- main thread remains above until render is done
    //----------------

    // NOTE: the passed total frame count "columns * frames" is an overestimate
    // in this case:
    // columns which do not appear on certain frames are not rendered. So, just
    // realize the
    // number of failed frames if any.
    std::pair<int, int> framePair =
        std::make_pair(listener->m_frameCount, listener->m_frameCount);

    delete listener;
    return framePair;
  }

  //---------------------------------------------------------
  //    Movie render
  //---------------------------------------------------------

  else {
    MovieRenderer movieRenderer(scene, fp, threadCount, false);
    TRenderSettings rs = outputSettings.getRenderSettings();
    rs.m_maxTileSize   = maxTileSize;
    rs.m_shrinkX = rs.m_shrinkY = shrink;

    movieRenderer.setRenderSettings(rs);
    movieRenderer.setDpi(cameraXDpi, cameraYDpi);

    movieRenderer.enablePrecomputing(true);

    MyMovieRenderListener *listener =
        new MyMovieRenderListener(fp, tceil((numFrames) / (float)step),
                                  renderCompleted, rs.m_stereoscopic);

    movieRenderer.addListener(listener);

    for (int i = 0; i < numFrames; i += step, r += stepd) {
      TFxPair fx;
      if (rs.m_stereoscopic) scene->shiftCameraX(-rs.m_stereoscopicShift / 2);

      fx.m_frameA = (TRasterFxP)buildSceneFx(scene, scene->getXsheet(), r,
                                             which, shrink, false);

      if (rs.m_fieldPrevalence != TRenderSettings::NoField)
        fx.m_frameB = (TRasterFxP)buildSceneFx(scene, scene->getXsheet(),
                                               r + 0.5 * timeStretchFactor,
                                               which, shrink, false);
      else if (rs.m_stereoscopic) {
        scene->shiftCameraX(rs.m_stereoscopicShift);
        fx.m_frameB = (TRasterFxP)buildSceneFx(scene, scene->getXsheet(), r,
                                               which, shrink, false);
        scene->shiftCameraX(-rs.m_stereoscopicShift / 2);
      } else
        fx.m_frameB = TRasterFxP();

      movieRenderer.addFrame(r, fx);
    }

    movieRenderer.start();

    // Start main loop
    QCoreApplication::instance()->exec();

    //----------------- tcomposer's main thread loops here ----------------

    // int frameCompleted = listener->m_frameCompletedCount;
    std::pair<int, int> framePair =
        std::make_pair(listener->m_frameCompletedCount, listener->m_frameCount);

    delete listener;
    return framePair;
  }
}

//==================================================================================
//
// main()
//
//----------------------------------------------------------------------------------

// TODO: il main comincia a diventare troppo lungo. Forse val la pena
// separarlo in varie funzioni
// (tipo initToonzEnvironment(), parseCommandLine(), ecc)
// TODO: the main starts getting too long. Perhaps it is worth
// separated into various functions
// (type initToonzEnvironment (), ParseCommandLine (), etc.)

DV_IMPORT_API void initStdFx();
DV_IMPORT_API void initColorFx();
int main(int argc, char *argv[]) {
  TCli::UsageLine usageLine;
  //  setCurrentModule("tcomposer");
  TCli::FilePathArgument srcName("srcName", "Source file");
  FilePathQualifier dstName("-o dstName", "Target file");
  RangeQualifier range;
  IntQualifier stepOpt("-step n", "Step");
  IntQualifier shrinkOpt("-shrink n", "Shrink");
  IntQualifier multimedia("-multimedia n", "Multimedia rendering mode");
  StringQualifier farmData("-farm data", "TFarm Controller");
  StringQualifier idq("-id n", "id");
  StringQualifier nthreads("-nthreads n", "Number of rendering threads");
  StringQualifier tileSize("-maxtilesize n",
                           "Enable tile rendering of max n MB per tile");
  StringQualifier tmsg("-tmsg val", "only internal use");
  usageLine = srcName + dstName + range + stepOpt + shrinkOpt + multimedia +
              farmData + idq + nthreads + tileSize + tmsg;

  // system path qualifiers
  std::map<QString, std::unique_ptr<TCli::QualifierT<TFilePath>>>
      systemPathQualMap;
  QString qualKey  = QString("%1ROOT").arg(systemVarPrefix);
  QString qualName = QString("-%1 folderpath").arg(qualKey);
  QString qualHelp =
      QString(
          "%1 path. It will automatically set other system paths to %1 "
          "unless individually specified with other qualifiers.")
          .arg(qualKey);
  systemPathQualMap[qualKey].reset(new TCli::QualifierT<TFilePath>(
      qualName.toStdString(), qualHelp.toStdString()));
  usageLine = usageLine + *systemPathQualMap[qualKey];

  const std::map<std::string, std::string> &spm = TEnv::getSystemPathMap();
  for (auto itr = spm.begin(); itr != spm.end(); ++itr) {
    qualKey = QString("%1%2")
                  .arg(systemVarPrefix)
                  .arg(QString::fromStdString((*itr).first));
    qualName = QString("-%1 folderpath").arg(qualKey);
    qualHelp = QString("%1 path.").arg(qualKey);
    systemPathQualMap[qualKey].reset(new TCli::QualifierT<TFilePath>(
        qualName.toStdString(), qualHelp.toStdString()));
    usageLine = usageLine + *systemPathQualMap[qualKey];
  }

  Usage usage(argv[0]);
  usage.add(usageLine);
  if (!usage.parse(argc, argv)) exit(1);

  QHash<QString, QString> argumentPathValues;
  for (auto q_itr = systemPathQualMap.begin(); q_itr != systemPathQualMap.end();
       ++q_itr) {
    if (q_itr->second->isSelected())
      argumentPathValues.insert(q_itr->first,
                                q_itr->second->getValue().getQString());
  }

  QApplication app(argc, argv);

  // Create a QObject destroyed just before app - see Tnz6's main.cpp for
  // rationale
  std::unique_ptr<QObject> mainScope(new QObject(&app));
  mainScope->setObjectName("mainScope");

#ifdef _WIN32
#ifndef x64
  // Store the floating point control word. It will be re-set before Toonz
  // initialization
  // has ended.
  unsigned int fpWord = 0;
  _controlfp_s(&fpWord, 0, 0);
#endif
#endif

  // Set the app's locale for numeric stuff to standard C. This is important for
  // atof() and similar
  // calls that are locale-dependent.
  setlocale(LC_NUMERIC, "C");

  // Install run out of contiguous memory callback
  TBigMemoryManager::instance()->setRunOutOfContiguousMemoryHandler(
      &tcomposerRunOutOfContMemHandler);

#ifdef _WIN32
// Define 64-bit precision for floating-point arithmetic. Please observe that
// the
// initImageIo() call below would already impose this precision. This just wants
// to be
// explicit.
//_controlfp_s(0, 0, 0x10000);
#endif

  // Initialize thread components
  TThread::init();

  // questo definisce la registry root e inizializza TEnv
  TEnv::setRootVarName(rootVarName);
  TEnv::setSystemVarPrefix(systemVarPrefix);
  TEnv::setApplicationFileName(argv[0]);

  QCoreApplication::setOrganizationName("OpenToonz");
  QCoreApplication::setOrganizationDomain("");
  QCoreApplication::setApplicationName(
      QString::fromStdString(TEnv::getApplicationName()));

  QHash<QString, QString>::const_iterator argItr =
      argumentPathValues.constBegin();
  while (argItr != argumentPathValues.constEnd()) {
    if (!TEnv::setArgPathValue(argItr.key().toStdString(),
                               argItr.value().toStdString()))
      cerr << "The qualifier " << argItr.key().toStdString()
           << " is not a valid key name. Skipping." << endl;
    ++argItr;
  }

  TSystem::hasMainLoop(true);

  // QMessageBox::information(0, QString("eccolo"), QString("composer!"));

  int i;
  for (i = 0; i < argc; i++)  // tmsg must be set as soon as it's possible
  {
    QString str = argv[i];
    if (str == "-tmsg") {
      TMsgCore::instance()->connectTo(argv[i + 1]);
      break;
    }
  }
  if (i == argc) TMsgCore::instance()->connectTo("");

  // controllo se la xxxroot e' definita e corrisponde ad un file esistente
  TFilePath fp = TEnv::getStuffDir();
  if (fp == TFilePath())
    fatalError(string("Undefined: \"") + ::to_string(TEnv::getRootVarPath()) +
               "\"");
  if (!TFileStatus(fp).isDirectory())
    fatalError(string("Directory \"") + ::to_string(fp) +
               "\" not found or not readable");

  TFilePath lRootDir    = fp + "toonzfarm";
  TFilePath logFilePath = lRootDir + "tcomposer.log";
  m_userLog             = new TUserLogAppend(logFilePath);
  string msg;

  // Initialize measure units
  Preferences::instance();                      // Loads standard (linear) units
  TMeasureManager::instance()->                 // Loads camera-related units
      addCameraMeasures(getCurrentCameraSize);  //

  TFilePathSet fps = ToonzFolder::getProjectsFolders();
  TFilePathSet::iterator fpIt;
  for (fpIt = fps.begin(); fpIt != fps.end(); ++fpIt)
    TProjectManager::instance()->addProjectsRoot(*fpIt);

  TFilePath libraryFolder = ToonzFolder::getLibraryFolder();
  TRasterImagePatternStrokeStyle::setRootDir(libraryFolder);
  TVectorImagePatternStrokeStyle::setRootDir(libraryFolder);
  TVectorBrushStyle::setRootDir(libraryFolder);
  TPalette::setRootDir(libraryFolder);
  TImageStyle::setLibraryDir(libraryFolder);
  TFilePath cacheRoot = ToonzFolder::getCacheRootFolder();
  if (cacheRoot.isEmpty()) cacheRoot = TEnv::getStuffDir() + "cache";
  TImageCache::instance()->setRootDir(cacheRoot);
  // #endif

  TaskId       = QString::fromStdString(idq.getValue());
  string fdata = farmData.getValue();
  if (fdata.empty())
    UseRenderFarm = false;
  else {
    UseRenderFarm         = true;
    string::size_type pos = fdata.find('@');
    if (pos == string::npos)
      UseRenderFarm = false;
    else {
      FarmControllerPort = std::stoi(fdata.substr(0, pos));
      FarmControllerName = QString::fromStdString(fdata.substr(pos + 1));
    }
  }

  if (UseRenderFarm) {
    TFarmControllerFactory factory;
    factory.create(FarmControllerName, FarmControllerPort, &FarmController);
  }

  while (!PluginLoader::load_entries("")) app.processEvents();

  std::pair<int, int> framePair(1, 0);

  try {
    Tiio::defineStd();

    initImageIo();
    Tiio::defineStd();
    initSoundIo();
    initStdFx();
    initColorFx();

    loadShaderInterfaces(ToonzFolder::getLibraryFolder() +
                         TFilePath("shaders"));

    // #endif

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

    TFilePath srcFilePath = srcName.getValue();

    try {
      srcFilePath = TSystem::toLocalPath(srcFilePath);
    } catch (...) {
    }

    //---------------------------------------------------------
    msg = "Loading " + srcFilePath.getName();
    cout << endl << msg << endl;
    m_userLog->info(msg);
    // pm->enableTabMode(true);

    auto project = TProjectManager::instance()->loadSceneProject(srcFilePath);
    if (!project) {
      msg = "Couldn't find the project";  //+ project->getName().getName();
      cerr << msg << endl;
      m_userLog->error(msg);
      return -2;
    }
    msg = "project: " + project->getName().getName();
    cout << msg << endl;
    m_userLog->info(msg);
    // pm->setCurrentProject(project, false); // false => temporaneamente

    // update TFilePath condition on loading the current project
    FilePathProperties *fpProp = project->getFilePathProperties();
    TFilePath::setFilePathProperties(fpProp->useStandard(),
                                     fpProp->acceptNonAlphabetSuffix(),
                                     fpProp->letterCountForSuffix());

    Sw1.start();

    if (!TSystem::doesExistFileOrLevel(srcFilePath)) return -2;
    ToonzScene *scene = new ToonzScene();

    TImageStyle::setCurrentScene(scene);

    try {
      Sw2.start();
      scene->load(srcFilePath);
      Sw2.stop();
    } catch (TException &e) {
      cout << ::to_string(e.getMessage()) << endl;
      m_userLog->error(::to_string(e.getMessage()));
      return -2;
    } catch (...) {
      string msg;
      msg = "There were problems loading the scene " +
            ::to_string(srcFilePath) + ".\n Some files may be missing.";
      cout << msg << endl;
      m_userLog->error(msg);
      // return false;
    }

    // Check if the scene saved with the previous version AND the premultiply
    // option is set to PNG level setting
    UnsetPremultiplyOptionsInPngLevels(scene);

    msg = "scene loaded";
    cout << "scene loaded" << endl;
    m_userLog->info(msg);

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

    TFilePath dstFilePath;
    if (dstName.isSelected())
      dstFilePath = dstName.getValue();
    else {
      dstFilePath = scene->getProperties()->getOutputProperties()->getPath();
      if (dstFilePath == TFilePath())
        dstFilePath = TFilePath("+outputs") + "$scenename.tif";
      else if (dstFilePath.getName() == "")
        dstFilePath = (dstFilePath.getParentDir() + scene->getSceneName())
                          .withType(dstFilePath.getType());
    }

    dstFilePath = scene->decodeFilePath(dstFilePath);

    //---------------------------------------------------------
    msg = "Generating " + dstFilePath.getName();
    cout << endl << "Generating " << dstFilePath << endl << endl;
    m_userLog->info(msg);

    TFilePath theDstFilePath = dstFilePath;
    try {
      theDstFilePath = TSystem::toLocalPath(dstFilePath);
    } catch (...) {
    }

    int r0 = -1, r1 = -1, step = 1, shrink = 1;
    TOutputProperties *outProp = scene->getProperties()->getOutputProperties();
    int scene_from, scene_to, scene_step;

    outProp->getRange(scene_from, scene_to, scene_step);
    int scene_shrink = outProp->getRenderSettings().m_shrinkX;

    if (scene_from == 0 && scene_to == -1) {
      scene_from = 1;
      scene_to   = scene->getFrameCount();
    } else {
      scene_from++;
      scene_to++;
    }
    if (range.isSelected()) {
      r0 = range.getFrom();
      r1 = range.getTo();
    } else {
      r0 = scene_from;
      r1 = scene_to;
    }

    if (stepOpt.isSelected())
      step = stepOpt.getValue();
    else
      step = scene_step;
    if (shrinkOpt.isSelected())
      shrink = shrinkOpt.getValue();
    else
      shrink = scene_shrink;
    if (multimedia.isSelected())
      scene->getProperties()->getOutputProperties()->setMultimediaRendering(
          multimedia.getValue());

    // Retrieve Thread count
    const int procCount = TSystem::getProcessorCount();
    int threadCount;
    const int threadCounts[3] = {1, procCount / 2, procCount};
    if (nthreads.isSelected()) {
      QString threadCountStr = QString::fromStdString(nthreads.getValue());
      threadCount            = (threadCountStr == "single") ? threadCounts[0]
                               : (threadCountStr == "half") ? threadCounts[1]
                               : (threadCountStr == "all")  ? threadCounts[2]
                                                           : threadCountStr.toInt();

      if (threadCount <= 0) {
        cout << "Qualifier 'nthreads': bad input" << endl;
        exit(1);
      }
    } else {
      int threadIndex = outProp->getThreadIndex();
      threadCount     = threadCounts[threadIndex];
    }

    threadCount = tcrop(1, procCount, threadCount);

    // Retrieve max tile size (raster granularity)
    int maxTileSize;
    const int maxTileSizes[4] = {
        (std::numeric_limits<int>::max)(), TOutputProperties::LargeVal,
        TOutputProperties::MediumVal, TOutputProperties::SmallVal};
    if (tileSize.isSelected()) {
      QString tileSizeStr = QString::fromStdString(tileSize.getValue());
      maxTileSize         = (tileSizeStr == "none")     ? maxTileSizes[0]
                            : (tileSizeStr == "large")  ? maxTileSizes[1]
                            : (tileSizeStr == "medium") ? maxTileSizes[2]
                            : (tileSizeStr == "small")  ? maxTileSizes[3]
                                                        : tileSizeStr.toInt();

      if (maxTileSize <= 0) {
        cout << "Qualifier 'maxtilesize': bad input" << endl;
        exit(1);
      }
    } else {
      int maxTileSizeIndex = outProp->getMaxTileSizeIndex();
      maxTileSize          = maxTileSizes[maxTileSizeIndex];
    }

    m_userLog->info("Threads count: " + std::to_string(threadCount));
    if (maxTileSize != (std::numeric_limits<int>::max)())
      m_userLog->info("Render tile: " + std::to_string(maxTileSize));

    // Disable the Passive cache manager. It has no sense if it cannot write on
    // disk...
    // TCacheResourcePool::instance();   //Needs to be instanced before
    // TPassiveCacheManager...
    TPassiveCacheManager::instance()->setEnabled(false);

#ifdef _WIN32
#ifndef x64
    // On 32-bit architecture, there could be cases in which initialization
    // could alter the
    // FPU floating point control word. I've seen this happen when loading some
    // AVI coded (VFAPI),
    // where 80-bit internal precision was used instead of the standard 64-bit
    // (much faster and
    // sufficient - especially considering that x86 truncates to 64-bit
    // representation anyway).
    // IN ANY CASE, revert to the original control word.
    // In the x64 case these precision changes simply should not take place up
    // to _controlfp_s
    // documentation.
    _controlfp_s(0, fpWord, -1);
#endif
#endif

    framePair = generateMovie(scene, theDstFilePath, r0, r1, step, shrink,
                              threadCount, maxTileSize);

    Sw1.stop();

    m_userLog->info(
        "Raster Allocation Peak: " +
        std::to_string(TBigMemoryManager::instance()->getAllocationPeak()) +
        " KB");
    m_userLog->info(
        "Raster Allocation Mean: " +
        std::to_string(TBigMemoryManager::instance()->getAllocationMean()) +
        " KB");

    msg = "Compositing completed in " +
          ::to_string(Sw1.getTotalTime() / 1000.0, 2) + " seconds";
    string msg2 =
        "\n" + ::to_string(Sw2.getTotalTime() / 1000.0, 2) +
        " seconds spent on loading" + "\n" +
        ::to_string(TStopWatch::global(0).getTotalTime() / 1000.0, 2) +
        " seconds spent on saving" + "\n" +
        ::to_string(TStopWatch::global(8).getTotalTime() / 1000.0, 2) +
        " seconds spent on rendering" + "\n";
    cout << msg + msg2;
    m_userLog->info(msg + msg2);
    DVGui::info(QString::fromStdString(msg));
    TImageCache::instance()->clear(true);
  } catch (TException &e) {
    msg = "Untrapped exception: " + ::to_string(e.getMessage()),
    cout << msg << endl;
    m_userLog->error(msg);
    TImageCache::instance()->clear(true);
  } catch (...) {
    cout << "Untrapped exception" << endl;
    m_userLog->error("Untrapped exception");
    TImageCache::instance()->clear(true);
  }

  if (framePair.first != framePair.second) return -1;
  return 0;
}
