

#include "celldata.h"
#include <assert.h>

#include "toonz/txsheet.h"
#include "toonz/txshcolumn.h"
#include "toonz/txshleveltypes.h"
#include "toonz/txshzeraryfxcolumn.h"
#include "toonz/txshzeraryfxlevel.h"
#include "toonz/tcolumnfx.h"
#include "toonz/fxdag.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/preferences.h"
#include "toonz/tstageobject.h"
#include "tapp.h"
#include "toonz/tscenehandle.h"
#include "toonz/toonzscene.h"
#include "toonz/levelset.h"

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

TCellData::TCellData() : m_rowCount(0), m_colCount(0) {}

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

TCellData::TCellData(const TCellData *src)
    : m_cells(src->m_cells)
    , m_rowCount(src->m_rowCount)
    , m_colCount(src->m_colCount) {}

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

TCellData::~TCellData() {}

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

const TXshCell TCellData::getCell(int row, int col) const {
  assert(0 <= row && row < m_rowCount);
  assert(0 <= col && col < m_colCount);
  return m_cells[row + col * m_rowCount];
}

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

// data <- xsh
void TCellData::setCells(TXsheet *xsh, int r0, int c0, int r1, int c1) {
  assert(r0 <= r1 && c0 <= c1);
  m_rowCount = r1 - r0 + 1;
  m_colCount = c1 - c0 + 1;
  assert(m_rowCount > 0);
  assert(m_colCount > 0);
  m_cells.clear();

  m_cells.resize(m_rowCount * m_colCount);

  int c;
  for (c = c0; c <= c1; c++) {
    int index          = c - c0;
    TXshColumn *column = xsh->getColumn(c);
    if (!column || column->isEmpty()) continue;
    xsh->getCells(r0, c, m_rowCount, &m_cells[index * m_rowCount]);
  }
  setText("TCellData");
}

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

// data -> xsh
bool TCellData::getCells(TXsheet *xsh, int r0, int c0, int &r1, int &c1,
                         bool insert, bool doZeraryClone,
                         bool skipEmptyCells) const {
  int c;
  r1           = r0 + m_rowCount - 1;
  c1           = c0 + m_colCount - 1;
  bool cellSet = false;
  for (c = c0; c < c0 + m_colCount; c++) {
    TXshColumn *column = xsh->getColumn(c);
    /*- index:選択範囲左から何番目のカラムか(一番左は0) -*/
    int index                   = c - c0;
    std::vector<TXshCell> cells = m_cells;
    bool isColumnEmpty          = true;
    if (column) {
      isColumnEmpty = column->isEmpty();
      /*- 各セルに左上→右下で順に割り振られるIndex -*/
      int cellIndex = index * m_rowCount;
      // increment the cellIndex and skip empty cells
      if (skipEmptyCells) {
        while (cellIndex < (index + 1) * m_rowCount &&
               m_cells[cellIndex].isEmpty())
          ++cellIndex;
      }
      // if the cellIndex reaches the end of the selection
      if ((int)m_cells.size() <= cellIndex)  // Celle vuote.
        return cellSet;
      /*- カラムが変更不可なら次のカラムへ -*/
      if (!canChange(column, index)) continue;
    }
    if (doZeraryClone && isColumnEmpty) cloneZeraryFx(index, cells);

    // inserisco le celle
    if (insert) xsh->insertCells(r0, c, m_rowCount);
    cellSet = xsh->setCells(r0, c, m_rowCount, &cells[index * m_rowCount]);
  }
  return cellSet;
}

//-----------------------------------------------------------------------------
// Paste only cell numbers.
// As a special behavior, enable to copy one column and paste into
// multiple columns.

bool TCellData::getNumbers(TXsheet *xsh, int r0, int c0, int &r1,
                           int &c1) const {
  r1              = r0 + m_rowCount - 1;
  bool oneToMulti = m_colCount == 1 && c0 < c1;
  if (!oneToMulti) c1 = c0 + m_colCount - 1;

  bool cellSet = false;

  for (int c = c0; c <= c1; c++) {
    TXshColumn *column       = xsh->getColumn(c);
    TXshLevel *reservedLevel = nullptr;
    if (!column) continue;
    if (column->isEmpty()) {
      if (Preferences::instance()->isLinkColumnNameWithLevelEnabled() &&
          xsh->getStageObject(TStageObjectId::ColumnId(c))
              ->hasSpecifiedName()) {
        std::string columnName =
            xsh->getStageObject(TStageObjectId::ColumnId(c))->getName();
        ToonzScene *scene   = TApp::instance()->getCurrentScene()->getScene();
        TLevelSet *levelSet = scene->getLevelSet();
        reservedLevel       = levelSet->getLevel(to_wstring(columnName));
        if (!reservedLevel) continue;
        // connect to xsheet
        if (!column->getPaletteColumn()) {
          TFx *fx = column->getFx();
          if (fx) xsh->getFxDag()->addToXsheet(fx);
        }

      } else
        continue;
    }
    TXshLevelColumn *levelColumn = column->getLevelColumn();
    if (!levelColumn) continue;

    std::vector<TXshCell> cells = m_cells;

    int sourceColIndex  = (oneToMulti) ? 0 : c - c0;
    int sourceCellIndex = sourceColIndex * m_rowCount;

    if (!canChange(column, sourceColIndex)) continue;

    bool isSet = levelColumn->setNumbers(
        r0, m_rowCount, &cells[sourceColIndex * m_rowCount], reservedLevel);

    cellSet = cellSet || isSet;
  }
  xsh->updateFrameCount();

  return cellSet;
}

//-----------------------------------------------------------------------------
/*-- c0 を起点に、TCellDataの選択範囲のカラム幅分、
        全てのカラムがcanChangeかどうか調べる。
--*/
bool TCellData::canChange(TXsheet *xsh, int c0) const {
  int c;
  for (c = c0; c < c0 + m_colCount; c++) {
    int index          = c - c0;
    TXshColumn *column = xsh->getColumn(c);
    if (!canChange(column, index)) return false;
  }
  return true;
}

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

bool TCellData::canChange(TXshColumn *column, int index) const {
  /*- カラムが無い、又は空のときTrue（編集可） -*/
  if (!column || column->isEmpty()) return true;
  /*- カラムがロックされていたらFalse（編集不可） -*/
  if (column->isLocked()) return false;
  /*-- 全てのセルがcanSetCellかどうか調べる 今からペーストするセルが、
          ペースト先のカラムと同種ならばtrueとなる。
  --*/
  TXshCellColumn *cellColumn = column->getCellColumn();
  assert(cellColumn);
  int i;
  for (i = index * m_rowCount; i < (index * m_rowCount) + m_rowCount; i++)
    // for(i = 0; i < m_cells.size(); i++)
    if (!cellColumn->canSetCell(m_cells[i])) return false;
  return true;
}

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

void TCellData::cloneZeraryFx(int index, std::vector<TXshCell> &cells) const {
  int firstNotEmptyIndex = index * m_rowCount;
  while (firstNotEmptyIndex < (index + 1) * m_rowCount &&
         m_cells[firstNotEmptyIndex].isEmpty())
    firstNotEmptyIndex++;
  if (firstNotEmptyIndex >= (index + 1) * m_rowCount) return;
  TXshZeraryFxLevel *fxLevel =
      m_cells[firstNotEmptyIndex].m_level->getZeraryFxLevel();
  if (!fxLevel) return;
  TXshZeraryFxColumn *fxColumn = fxLevel->getColumn();
  assert(fxColumn);
  TFx *zeraryFx = fxColumn->getZeraryColumnFx()->getZeraryFx();
  if (zeraryFx) {
    std::wstring fxName = zeraryFx->getName();
    TFx *newZeraryFx    = zeraryFx->clone(false);
    fxColumn->getXsheet()->getFxDag()->assignUniqueId(newZeraryFx);
    newZeraryFx->setName(fxName);
    newZeraryFx->linkParams(zeraryFx);
    TXshZeraryFxLevel *newFxLevel   = new TXshZeraryFxLevel();
    TXshZeraryFxColumn *newFxColumn = new TXshZeraryFxColumn(0);
    newFxColumn->getZeraryColumnFx()->setZeraryFx(newZeraryFx);
    newFxLevel->setColumn(newFxColumn);
    // replace the zerary fx cells by the new fx
    int r;
    for (r = firstNotEmptyIndex; r < (index + 1) * m_rowCount; r++)
      cells[r] = TXshCell(newFxLevel, m_cells[r].getFrameId());
  }
}
