

// Toonz app and handles
#include "tapp.h"
#include "toonz/tpalettehandle.h"
#include "toonz/palettecontroller.h"
#include "toonz/tobjecthandle.h"
#include "toonz/tscenehandle.h"
#include "toonz/tframehandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/tfxhandle.h"

// Toonz scene
#include "toonz/toonzscene.h"

// Toonz schematic includes
#include "toonz/fxdag.h"
#include "toonz/tcolumnfxset.h"
#include "toonz/tcolumnfx.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/levelset.h"
#include "toonz/txshchildlevel.h"
#include "toonz/txshzeraryfxcolumn.h"

// Render cache includes
#include "tpassivecachemanager.h"
#include "tcacheresourcepool.h"

#include "cachefxcommand.h"

//******************************************************************************************
//    Preliminaries
//******************************************************************************************

namespace {
void buildNodeTreeDescription(std::string &desc, const TFxP &root);
void enableCacheOnAllowedChildren(TRasterFx *fx);

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

void traceMaximalAncestors(TFx *fx, std::vector<TFx *> &ancestors) {
  // Retrieve the output port
  int outputPortsCount = fx->getOutputConnectionCount();
  if (outputPortsCount == 0) ancestors.push_back(fx);

  for (int i = 0; i < outputPortsCount; ++i) {
    TFx *outFx = fx->getOutputConnection(i)->getOwnerFx();  // Owner fx??
    if (outFx) traceMaximalAncestors(outFx, ancestors);
  }
}

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

void enableCacheOnAllowedChildren(TRasterFx *fx) {
  int portsCount = fx->getInputPortCount();
  if (portsCount == 0) {
    // It's a Column Fx. In this case, zeraries may still have descendants,
    // in their inner fx.
    TZeraryColumnFx *zcolfx = dynamic_cast<TZeraryColumnFx *>(fx);
    if (zcolfx) fx          = static_cast<TRasterFx *>(zcolfx->getZeraryFx());
  }

  if (fx->isCacheEnabled()) return;  // Already done

  fx->enableCache(true);

  for (int i = 0; i < portsCount; ++i) {
    if (fx->allowUserCacheOnPort(i)) {
      TRasterFx *childFx =
          dynamic_cast<TRasterFx *>(fx->getInputPort(i)->getFx());

      if (childFx && !childFx->isCacheEnabled())
        enableCacheOnAllowedChildren(childFx);
    }
  }
}
}  // local namespace

//******************************************************************************************
//    CacheFxCommand implementation
//******************************************************************************************

CacheFxCommand::CacheFxCommand() {
  TApp *app = TApp::instance();
  bool ret  = true;

  // Connect the various handles to the associated function in the
  // CacheFxCommand singleton.
  // Please observe that these connections are performed BEFORE rendering
  // classes are instanced -
  // which is needed to ensure that the proper cache updates have already been
  // performed when
  // rendering instances receive the update signal.

  // ret = ret &&
  // connect(app->getCurrentLevel(),SIGNAL(xshLevelChanged()),this,SLOT(onLevelChanged()));
  ret = ret && connect(app->getCurrentFx(), SIGNAL(fxChanged()), this,
                       SLOT(onFxChanged()));
  ret = ret && connect(app->getCurrentXsheet(), SIGNAL(xsheetChanged()), this,
                       SLOT(onXsheetChanged()));
  // ret = ret &&
  // connect(app->getCurrentXsheet(),SIGNAL(xsheetSwitched()),this,SLOT(onXsheetChanged()));
  ret = ret && connect(app->getCurrentObject(), SIGNAL(objectChanged(bool)),
                       this, SLOT(onObjectChanged()));

  TCacheResourcePool::instance();  // The resources pool must be instanced
                                   // before the passive delegate
  TPassiveCacheManager::instance()->setTreeDescriptor(&buildTreeDescription);
}

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

CacheFxCommand::~CacheFxCommand() {}

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

CacheFxCommand *CacheFxCommand::instance() {
  static CacheFxCommand theInstance;
  return &theInstance;
}

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

void CacheFxCommand::onNewScene() {
  TPassiveCacheManager::instance()->reset();
  TCacheResourcePool::instance()->reset();
}

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

void CacheFxCommand::onSceneLoaded() {
  TPassiveCacheManager::instance()->onSceneLoaded();
}

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

void CacheFxCommand::onLevelChanged(const std::string &levelName) {
  TPassiveCacheManager::instance()->invalidateLevel(levelName);
}

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

void CacheFxCommand::onFxChanged() {
  TFx *fx = TApp::instance()->getCurrentFx()->getFx();
  if (fx) TPassiveCacheManager::instance()->onFxChanged(fx);
}

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

void CacheFxCommand::onXsheetChanged() {
  TPassiveCacheManager::instance()->onXsheetChanged();

  // Update the disabled cache fx commands - retrieve the current dag
  // and analyze each fx.
  /*TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
FxDag* dag = xsh->getFxDag();

{
//Disable all internal fxs
std::set<TFx*> internalFxs;
dag->getInternalFxs()->getFxs(internalFxs);

std::set<TFx*>::iterator it;
for(it = internalFxs.begin(); it != internalFxs.end(); ++it)
{
TRasterFx* rfx = dynamic_cast<TRasterFx*>(*it);
rfx->enableCache(false);
}
}

//Disable each column
int i, columnsCount = xsh->getColumnCount();
for(i=0; i<columnsCount; ++i)
{
TXshColumn* col = xsh->getColumn(i);
assert(col);
TFx* fx = col->getFx();

TZeraryColumnFx* zcolfx = dynamic_cast<TZeraryColumnFx*>(fx);
if(zcolfx) fx = zcolfx->getZeraryFx();

TRasterFx* rfx = dynamic_cast<TRasterFx*>(fx);
rfx->enableCache(false);
}

//Now, we have to enable the user cache for all fxs which have allowed output
connections
//to another user-cachable fx (every root node is considered cachable).

//First off, deal with the XSheet fx
TXsheetFx* xshFx = static_cast<TXsheetFx*>(dag->getXsheetFx());

//Trace all maximal ancestors of the fx
std::vector<TFx*> maximalAncestors;
::traceMaximalAncestors(xshFx, maximalAncestors);

//Then, for each, enable the cache for all allowed children
unsigned int j, size = maximalAncestors.size();
for(j=0; j<size; ++j)
::enableCacheOnAllowedChildren(dynamic_cast<TRasterFx*>(maximalAncestors[j]));

//Now, if the XSheetFx is user-cache enabled, propagate to terminal fxs (which
were not
//automatic, since connections to them do not exist).
if(xshFx->isCacheEnabled())
{
std::set<TFx*> terminalFxs;
dag->getTerminalFxs()->getFxs(terminalFxs);

std::set<TFx*>::iterator it;
for(it = terminalFxs.begin(); it != terminalFxs.end(); ++it)
::enableCacheOnAllowedChildren(dynamic_cast<TRasterFx*>(*it));
}


//Now, deal with leaves the same way
for(i=0; i<columnsCount; ++i)
{
TXshColumn* col = xsh->getColumn(i);
assert(col);
TFx* fx = col->getFx();
if(fx)
{
std::vector<TFx*> maximalAncestors;
::traceMaximalAncestors(fx, maximalAncestors);

unsigned int j, size = maximalAncestors.size();
for(j=0; j<size; ++j)
  ::enableCacheOnAllowedChildren(dynamic_cast<TRasterFx*>(maximalAncestors[j]));
}
}*/
}

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

void CacheFxCommand::onObjectChanged() {}

//*****************************************************************************************
//    Fx tree description generator
//*****************************************************************************************

namespace {
void buildXsheetTreeDescription(std::string &desc, const FxDag *dag,
                                bool xshFx) {
  if (xshFx) desc += "xsh,";

  std::set<TFx *> terminalFxs;
  dag->getTerminalFxs()->getFxs(terminalFxs);

  std::set<TFx *>::iterator it;
  for (it = terminalFxs.begin(); it != terminalFxs.end(); ++it)
    buildNodeTreeDescription(desc, *it);
}

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

void buildNodeTreeDescription(std::string &desc, const TFxP &root) {
  if (!root) {
    desc += ";";
    return;
  }

  // Look if this node is a sub-xsheet one
  TLevelColumnFx *lcfx = dynamic_cast<TLevelColumnFx *>(root.getPointer());
  if (lcfx) {
    TXshLevelColumn *levelColumn = lcfx->getColumn();
    if (levelColumn) {
      int startRow = lcfx->getTimeRegion().getFirstFrame();
      TXshChildLevel *xshChildLevel =
          lcfx->getColumn()->getCell(startRow).getChildLevel();

      if (xshChildLevel) {
        buildXsheetTreeDescription(desc, xshChildLevel->getXsheet()->getFxDag(),
                                   true);
        return;
      }
    }
  }

  desc += std::to_string(root->getIdentifier()) + ";";

  unsigned int count = root->getInputPortCount();
  for (unsigned int i = 0; i < count; ++i)
    buildNodeTreeDescription(desc, root->getInputPort(i)->getFx());
}

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

void searchXsheetContainingInternalFx(TXsheet *&xsh, TXsheet *currXsh,
                                      const TFxP &fx) {
  // Test if root belongs to xsh's dag
  /*if(currXsh->getFxDag()->getInternalFxs()->containsFx(fx.getPointer()))
{
xsh = currXsh;
return;
}

//Otherwise, search among all sub-xsheets.
int colsCount = currXsh->getColumnCount();
for(int i=0; i<colsCount; ++i)
{
TXsheet* childXsh = currXsh->getColumn(i)->getXsheet();
if(childXsh)
  searchXsheetContainingInternalFx(xsh, childXsh, fx);
}*/

  // NOTE: If we search the first column, and perform the
  // getColumn()->getXsheet(),
  // we DO find the xsheet containing the column? That could be good...

  // Dummy proc, for now...
  return;
}
}  // namespace

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

//! Builds a description of a \b schematic fx tree which is unique in a Toonz
//! scene session.
//! The returned description is made of fx ids in the form of numbers separated
//! by semicolons -
//! so it will not be familiar at glance - however, it has the property that
//! nephews
//! of passed root actually build descriptions contained in that of the root.
void buildTreeDescription(std::string &desc, const TFxP &root) {
  TXsheetFx *xsheetFx = dynamic_cast<TXsheetFx *>(root.getPointer());
  if (xsheetFx) {
    buildXsheetTreeDescription(desc, xsheetFx->getFxDag(), true);
    return;
  }

  // Start from passed root. Find if the root is children of an xsheet node. In
  // this case,
  // and the input at the leftXSheet port is empty, return the description of
  // the xsheet node.

  TFxPort *xsheetPort = root->getXsheetPort();
  if (xsheetPort && !xsheetPort->getFx()) {
    // Retrieve the xsheet containing the fx
    ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
    TXsheet *xsh = 0, *topXsh = scene->getTopXsheet();
    searchXsheetContainingInternalFx(xsh, topXsh, root);

    if (!xsh) {
      desc = "ERROR";
      return;
    }

    // If the node is a terminal fx, return the secription of the xsheet.
    FxDag *dag = xsh->getFxDag();
    if (dag->getTerminalFxs()->containsFx(root.getPointer())) {
      buildXsheetTreeDescription(desc, xsh->getFxDag(), false);
      return;
    }
  }

  buildNodeTreeDescription(desc, root);
}
