

#include "toonz/studiopalette.h"
#include "tfiletype.h"
#include "tstream.h"
#include "tconvert.h"
#include "tlevel_io.h"
#include "trasterimage.h"
#include "traster.h"
#include "tsystem.h"
#include "tcolorstyles.h"
#include "toonz/toonzfolders.h"
#include "toonz/tproject.h"
#include "toonz/toonzscene.h"
#include "tpalette.h"

#include <time.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>

#include <QSettings>

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

//-------------------------------------------------------------------
namespace {
//-------------------------------------------------------------------

TFilePath makeUniqueName(TFilePath fp) {
  if (TFileStatus(fp).doesExist() == false) return fp;
  std::wstring name = fp.getWideName();
  int index         = 2;
  int j             = name.find_last_not_of(L"0123456789");
  if (j != (int)std::wstring::npos && j + 1 < (int)name.length()) {
    index = std::stoi(name.substr(j + 1)) + 1;
    name  = name.substr(0, j + 1);
  }
  for (;;) {
    fp = fp.withName(name + std::to_wstring(index));
    if (TFileStatus(fp).doesExist() == false) return fp;
    index++;
  }
}

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

TPalette *loadPliPalette(const TFilePath &fp) {
  TLevelReaderP lr(fp);
  TLevelP level  = lr->loadInfo();
  int frameCount = level->getFrameCount();
  if (frameCount < 1) return 0;
  TPalette *palette = level->getPalette();
  if (!palette) return 0;
  return palette->clone();
}

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

TPalette *loadTplPalette(const TFilePath &fp) {
  TPersist *p = 0;
  TIStream is(fp);
  is >> p;
  TPalette *palette = dynamic_cast<TPalette *>(p);
  return palette;
}

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

TPalette *loadToonz46Palette(const TFilePath &fp) {
  TImageP pltImg;
  TImageReader::load(fp, pltImg);
  if (!pltImg) return 0;
  TRasterImageP pltRasImg(pltImg);
  if (!pltRasImg) return 0;
  TRaster32P rasPlt = pltRasImg->getRaster();
  if (!rasPlt) return 0;
  TPalette *palette = new TPalette();
  const int offset  = 0;  // FirstUserStyle-1;
  assert(rasPlt->getLy() == 2);
  rasPlt->lock();
  TPixel32 *pixelRow = rasPlt->pixels(0);
  int x;
  for (x = 1; x < rasPlt->getLx(); ++x) {
    TPixel32 color = pixelRow[x];
    int styleId    = offset + x;
    if (styleId < palette->getStyleCount())
      palette->setStyle(styleId, color);
    else {
      int j = palette->addStyle(color);
      assert(j == styleId);
    }
  }

  // aggiungo solo i colori usati (salvo il BG)

  pixelRow             = rasPlt->pixels(1);
  TPalette::Page *page = palette->getPage(0);
  for (x = 1; x < rasPlt->getLx(); ++x) {
    if (pixelRow[x].r == 255)
      page->addStyle(offset +
                     x);  // palette->addStyleToPage(offset+x, L"colors");
  }
  rasPlt->unlock();
  return palette;
}

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

std::wstring readPaletteGlobalName(TFilePath path) {
  try {
    TIStream is(path);
    if (!is) return L"";
    std::string tagName;
    if (!is.matchTag(tagName) || tagName != "palette") return L"";
    std::string name;
    if (is.getTagParam("name", name)) return ::to_wstring(name);
  } catch (...) {
  }
  return L"";
}
//-------------------------------------------------------------------

TFilePath searchPalette(TFilePath path, std::wstring paletteId) {
  TFilePathSet q;
  try {
    TSystem::readDirectory(q, path);
  } catch (...) {
  }

  for (TFilePathSet::iterator i = q.begin(); i != q.end(); ++i) {
    TFilePath fp = *i;
    if (fp.getType() == "tpl") {
      std::wstring gname = readPaletteGlobalName(fp);
      if (gname == paletteId) return fp;
    } else if (TFileStatus(fp).isDirectory()) {
      TFilePath palettePath = searchPalette(fp, paletteId);
      if (palettePath != TFilePath()) return palettePath;
    }
  }
  return TFilePath();
}

bool studioPaletteHasBeenReferred = false;

static std::map<std::wstring, TFilePath> table;
// table loaded from the cache. verify once before storing in the table
static std::map<std::wstring, TFilePath> table_cached;

const std::string pathTableFileName = "palette_paths.ini";

//-------------------------------------------------------------------
}  // namespace
//-------------------------------------------------------------------

//===================================================================
//
// StudioPalette
//
//-------------------------------------------------------------------

StudioPalette::StudioPalette() {
  try {
    m_root = ToonzFolder::getStudioPaletteFolder();
  } catch (...) {
    return;
  }
  if (!TFileStatus(m_root).doesExist()) {
    try {
      TSystem::mkDir(m_root);
      FolderListenerManager::instance()->notifyFolderChanged(
          m_root.getParentDir());
    } catch (...) {
    }
    try {
      TSystem::mkDir(getLevelPalettesRoot());
      FolderListenerManager::instance()->notifyFolderChanged(
          getLevelPalettesRoot().getParentDir());
    } catch (...) {
    }
  }

  TProjectManager *pm = TProjectManager::instance();
  pm->addListener(this);
  onProjectSwitched();
}

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

StudioPalette::~StudioPalette() {}

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

bool StudioPalette::m_enabled = true;

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

void StudioPalette::enable(bool enabled) {
  assert(studioPaletteHasBeenReferred == false);
  m_enabled = enabled;
}

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

StudioPalette *StudioPalette::instance() {
  static StudioPalette _instance;
  studioPaletteHasBeenReferred = true;
  assert(m_enabled);
  return &_instance;
}

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

TFilePath StudioPalette::getLevelPalettesRoot() {
  return m_root + "Global Palettes";
}

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

TFilePath StudioPalette::getProjectPalettesRoot() {
  auto project = TProjectManager::instance()->getCurrentProject();
  TFilePath folderName = project->getFolder(TProject::Palettes);
  if (folderName.isEmpty()) return TFilePath();
  if (folderName.isAbsolute()) return folderName;
  return project->getProjectFolder() + folderName;
}

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

static bool loadRefImg(TPalette *palette, TFilePath dir) {
  assert(palette);
  TFilePath fp = palette->getRefImgPath();
  if (fp == TFilePath() || !TSystem::doesExistFileOrLevel(fp)) return false;
  if (!fp.isAbsolute()) fp = dir + fp;
  TLevelReaderP lr(fp);
  if (!lr) return false;
  TLevelP level = lr->loadInfo();
  if (!level || level->getFrameCount() == 0) return false;
  TLevel::Iterator it = level->begin();
  TImageP img         = lr->getFrameReader(it->first)->load();
  if (!img) return false;
  img->setPalette(0);
  palette->setRefImg(img);
  return true;
}

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

TPalette *StudioPalette::getPalette(const TFilePath &path,
                                    bool loadRefImgFlag) {
  try {
    if (path.getType() != "tpl") return 0;
    TPalette *palette = load(path);
    if (!palette) return 0;
    if (loadRefImgFlag) loadRefImg(palette, path.getParentDir());
    // palette->addRef(); // ci va??
    return palette;
  } catch (...) {
    return 0;
  }
}

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

void StudioPalette::movePalette(const TFilePath &dstPath,
                                const TFilePath &srcPath) {
  try {
    // do not allow overwrite palette
    TSystem::renameFile(dstPath, srcPath, false);
  } catch (...) {
    throw;
  }
  std::wstring id = readPaletteGlobalName(dstPath);
  table.erase(id);
  removeEntry(id);
  FolderListenerManager::instance()->notifyFolderChanged(
      dstPath.getParentDir());
  notifyMove(dstPath, srcPath);
}

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

int StudioPalette::getChildren(std::vector<TFilePath> &fps,
                               const TFilePath &folderPath) {
  TFilePathSet q;
  if (TFileStatus(folderPath).isDirectory()) {
    try {
      TSystem::readDirectory(q, folderPath, false, false);
    } catch (...) {
    }
  }

  // put the folders above the palette items
  std::vector<TFilePath> palettes;
  for (TFilePathSet::iterator i = q.begin(); i != q.end(); ++i) {
    if (isFolder(*i))
      fps.push_back(*i);
    else if (isPalette(*i))
      palettes.push_back(*i);
  }
  if (!palettes.empty()) {
    fps.reserve(fps.size() + palettes.size());
    std::copy(palettes.begin(), palettes.end(), std::back_inserter(fps));
  }
  //  fps.push_back(m_root+"butta.tpl");
  return fps.size();
}

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

int StudioPalette::getChildCount(const TFilePath &folderPath) {
  TFilePathSet q;
  try {
    TSystem::readDirectory(q, folderPath);
  } catch (...) {
  }
  return q.size();
}

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

bool StudioPalette::isFolder(const TFilePath &path) {
  return TFileStatus(path).isDirectory();
}

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

bool StudioPalette::isReadOnly(const TFilePath &path) {
  return !TFileStatus(path).isWritable();
}

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

bool StudioPalette::isPalette(const TFilePath &path) {
  return path.getType() == "tpl";
}

//-------------------------------------------------------------------
/*! check if the palette is studio palette or level palette in order to separate
 * icons in the StudioPaletteTree.
 */
bool StudioPalette::hasGlobalName(const TFilePath &path) {
  return (readPaletteGlobalName(path) != L"");
}

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

bool StudioPalette::isLevelPalette(const TFilePath &path) {
  TPalette *palette = getPalette(path);
  if (!palette) return false;
  bool ret = !palette->isCleanupPalette();
  delete palette;
  return ret;
}

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

TFilePath StudioPalette::createFolder(const TFilePath &parentFolderPath) {
  TFilePath path = makeUniqueName(parentFolderPath + "new folder");
  try {
    TSystem::mkDir(path);
  } catch (...) {
    return TFilePath();
  }
  FolderListenerManager::instance()->notifyFolderChanged(parentFolderPath);
  notifyTreeChange();
  return path;
}

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

void StudioPalette::createFolder(const TFilePath &parentFolderPath,
                                 std::wstring name) {
  TFilePath fp = parentFolderPath + name;
  if (TFileStatus(fp).doesExist()) return;
  try {
    TSystem::mkDir(fp);
  } catch (...) {
    return;
  }
  FolderListenerManager::instance()->notifyFolderChanged(parentFolderPath);
  notifyTreeChange();
}

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

TFilePath StudioPalette::createPalette(const TFilePath &folderPath,
                                       std::string name) {
  TPalette *palette = 0;
  if (name == "") name = "new palette";
  palette      = new TPalette();
  TFilePath fp = makeUniqueName(folderPath + (name + ".tpl"));
  time_t ltime;
  time(&ltime);
  std::wstring gname = std::to_wstring(ltime) + L"_" + std::to_wstring(rand());
  palette->setGlobalName(gname);
  setStylesGlobalNames(palette);
  try {
    save(fp, palette);
  } catch (...) {
    delete palette;
    throw;
  }
  delete palette;
  notifyTreeChange();
  return fp;
}

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

void StudioPalette::setPalette(const TFilePath &palettePath,
                               const TPalette *plt, bool notifyPaletteChanged) {
  assert(palettePath.getType() == "tpl");
  TPalette *palette = plt->clone();
  palette->setIsLocked(plt->isLocked());
  palette->addRef();
  std::wstring pgn = palette->getGlobalName();
  if (TFileStatus(palettePath).doesExist())
    pgn = readPaletteGlobalName(palettePath);
  palette->setGlobalName(pgn);
  setStylesGlobalNames(palette);
  try {
    save(palettePath, palette);
  } catch (...) {
    palette->release();
    throw;
  }
  palette->release();
  if (notifyPaletteChanged) notifyPaletteChange(palettePath);
}

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

void StudioPalette::deletePalette(const TFilePath &palettePath) {
  assert(palettePath.getType() == "tpl");
  try {
    TSystem::deleteFile(palettePath);
  } catch (...) {
    return;
  }
  notifyTreeChange();
}

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

void StudioPalette::deleteFolder(const TFilePath &path) {
  try {
    TSystem::rmDirTree(path);
  } catch (...) {
  }
  notifyTreeChange();
}

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

TFilePath StudioPalette::importPalette(const TFilePath &dstFolder,
                                       const TFilePath &srcPath) {
  TPaletteP palette;
  std::string ext = srcPath.getType();
  try {
    if (ext == "plt")
      palette = loadToonz46Palette(srcPath);
    else if (ext == "pli")
      palette = loadPliPalette(srcPath);
    else if (ext == "tpl")
      palette = loadTplPalette(srcPath);
  } catch (...) {
  }

  if (!palette) return TFilePath();
  std::wstring name = srcPath.getWideName();

  assert(!palette->isCleanupPalette());
  //    convertToLevelPalette(palette.getPointer());

  TFilePath fp = makeUniqueName(dstFolder + (name + L".tpl"));
  time_t ltime;
  time(&ltime);
  std::wstring gname = std::to_wstring(ltime) + L"_" + std::to_wstring(rand());
  palette->setGlobalName(gname);
  setStylesGlobalNames(palette.getPointer());
  TSystem::touchParentDir(fp);
  save(fp, palette.getPointer());
  notifyTreeChange();
  return fp;
}

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

// TFilePath StudioPalette::getRefImage(const TFilePath palette)
//{
//  return palette.withType("pli");
//}

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

static void foobar(std::wstring paletteId) { table.erase(paletteId); }

TFilePath StudioPalette::getPalettePath(std::wstring paletteId) {
  std::map<std::wstring, TFilePath>::iterator it = table.find(paletteId);
  if (it != table.end()) return it->second;
  TFilePath fp;
  // not found in the verified table, then check for the cached table
  it = table_cached.find(paletteId);
  // found in the cached table
  if (it != table_cached.end()) {
    fp = it->second;
    // verify if cached path is correct
    if (fp.getType() != "tpl" ||
        readPaletteGlobalName(it->second) != paletteId) {
      fp = TFilePath();
      // erase the entry
      it = table_cached.erase(it);
      removeEntry(paletteId);
    }
  }
  if (fp.isEmpty()) {
    fp = searchPalette(m_root, paletteId);
    if (fp.isEmpty()) fp = searchPalette(getProjectPalettesRoot(), paletteId);

    addEntry(paletteId, fp);
  }
  table[paletteId] = fp;
  return fp;
}

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

TPalette *StudioPalette::getPalette(std::wstring paletteId) {
  TFilePath palettePath = getPalettePath(paletteId);
  if (palettePath != TFilePath())
    return getPalette(palettePath);
  else
    return 0;
}

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

TColorStyle *StudioPalette::getStyle(std::wstring styleId) { return 0; }

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

std::pair<TFilePath, int> StudioPalette::getSourceStyle(TColorStyle *cs) {
  std::pair<TFilePath, int> ret(TFilePath(), -1);
  if (!cs) return ret;
  std::wstring gname = cs->getGlobalName();
  if (gname == L"") return ret;
  int k = gname.find_first_of(L'-', 1);
  if (k == (int)std::wstring::npos) return ret;
  std::wstring paletteId = gname.substr(1, k - 1);
  ret.first              = getPalettePath(paletteId) - m_root;
  ret.second             = std::stoi(gname.substr(k + 1));
  return ret;
}

//-------------------------------------------------------------------
/*! return if any style in the palette is changed
 */
bool StudioPalette::updateLinkedColors(TPalette *palette) {
  bool paletteIsChanged = false;
  std::map<std::wstring, TPaletteP> table;

  for (int i = 0; i < palette->getStyleCount(); i++) {
    TColorStyle *cs    = palette->getStyle(i);
    std::wstring gname = cs->getGlobalName();
    if (gname == L"" || gname[0] != L'+') continue;
    int k = gname.find_first_of(L'-', 1);
    if (k == (int)std::wstring::npos) continue;
    std::wstring paletteId = gname.substr(1, k - 1);
    std::map<std::wstring, TPaletteP>::iterator it;
    it                  = table.find(paletteId);
    TPalette *spPalette = 0;
    if (it == table.end()) {
      spPalette = getPalette(paletteId);
      if (!spPalette) continue;
      table[paletteId] = spPalette;
      // spPalette->release();
      assert(spPalette->getRefCount() == 1);
    } else
      spPalette = it->second.getPointer();

    int j = std::stoi(gname.substr(k + 1));
    if (spPalette && 0 <= j && j < spPalette->getStyleCount()) {
      TColorStyle *spStyle = spPalette->getStyle(j);
      assert(spStyle);
      spStyle = spStyle->clone();
      spStyle->setGlobalName(gname);

      // put the style name in the studio palette into the original name
      spStyle->setOriginalName(spStyle->getName());
      //.. and keep the style name unchanged
      spStyle->setName(cs->getName());

      palette->setStyle(i, spStyle);

      paletteIsChanged = true;
    }
  }
  return paletteIsChanged;
}

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

void StudioPalette::setStylesGlobalNames(TPalette *palette) {
  for (int i = 0; i < palette->getStyleCount(); i++) {
    TColorStyle *cs = palette->getStyle(i);
    // set global name only to the styles of which the global name is empty
    if (cs->getGlobalName() == L"") {
      std::wstring gname =
          L"-" + palette->getGlobalName() + L"-" + std::to_wstring(i);
      cs->setGlobalName(gname);
    }
  }
}

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

void StudioPalette::save(const TFilePath &path, TPalette *palette) {
  TFileStatus fs(path);
  if (fs.doesExist() && !fs.isWritable()) {
    throw TSystemException(path,
                           "The studio palette cannot be saved: it is a read "
                           "only studio palette.");
  }

  TOStream os(path);
  if (!os) {
    throw TSystemException(path,
                           "The studio palette cannot be saved: the output "
                           "stream status is invalid.");
  }
  std::map<std::string, std::string> attr;
  attr["name"] = ::to_string(palette->getGlobalName());
  os.openChild("palette", attr);
  palette->saveData(os);
  os.closeChild();
}

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

TPalette *StudioPalette::load(const TFilePath &path) {
  try {
    TIStream is(path);
    if (!is) return 0;
    std::string tagName;
    if (!is.matchTag(tagName) || tagName != "palette") return 0;
    std::string gname;
    is.getTagParam("name", gname);
    TPalette *palette = new TPalette();
    palette->loadData(is);
    palette->setGlobalName(::to_wstring(gname));
    is.matchEndTag();
    palette->setPaletteName(path.getWideName());
    return palette;
  } catch (...) {
    return 0;
  }
}

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

void StudioPalette::addListener(Listener *listener) {
  if (std::find(m_listeners.begin(), m_listeners.end(), listener) ==
      m_listeners.end())
    m_listeners.push_back(listener);
}

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

void StudioPalette::removeListener(Listener *listener) {
  m_listeners.erase(
      std::remove(m_listeners.begin(), m_listeners.end(), listener),
      m_listeners.end());
}

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

void StudioPalette::notifyTreeChange() {
  for (std::vector<Listener *>::iterator it = m_listeners.begin();
       it != m_listeners.end(); ++it)
    (*it)->onStudioPaletteTreeChange();
}

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

void StudioPalette::notifyMove(const TFilePath &dstPath,
                               const TFilePath &srcPath) {
  for (std::vector<Listener *>::iterator it = m_listeners.begin();
       it != m_listeners.end(); ++it)
    (*it)->onStudioPaletteMove(dstPath, srcPath);
}

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

void StudioPalette::notifyPaletteChange(const TFilePath &palette) {
  for (std::vector<Listener *>::iterator it = m_listeners.begin();
       it != m_listeners.end(); ++it)
    (*it)->onStudioPaletteChange(palette);
}

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

void StudioPalette::removeEntry(const std::wstring paletteId) {
  TFilePath rootFps[2] = {m_root, getProjectPalettesRoot()};
  for (auto rootFp : rootFps) {
    if (rootFp.isEmpty()) continue;
    TFilePath tablePath = rootFp + pathTableFileName;
    if (!TFileStatus(tablePath).doesExist()) continue;
    QSettings tableSettings(QString::fromStdWString(tablePath.getWideString()),
                            QSettings::IniFormat);
    if (tableSettings.contains(QString::fromStdWString(paletteId))) {
      tableSettings.remove(QString::fromStdWString(paletteId));
      break;
    }
  }
}

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

void StudioPalette::addEntry(const std::wstring paletteId,
                             const TFilePath &path) {
  TFilePath rootFps[2] = {m_root, getProjectPalettesRoot()};
  for (auto rootFp : rootFps) {
    if (rootFp.isEmpty()) continue;
    if (!rootFp.isAncestorOf(path)) continue;

    TFilePath tablePath = rootFp + pathTableFileName;
    QSettings tableSettings(QString::fromStdWString(tablePath.getWideString()),
                            QSettings::IniFormat);
    QString pathValue = (path - rootFp).getQString();
    tableSettings.setValue(QString::fromStdWString(paletteId), pathValue);
  }
}

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

void StudioPalette::onProjectSwitched() {
  table_cached.clear();
  // load [global id] - [path] table file
  TFilePath rootFps[2] = {m_root, getProjectPalettesRoot()};
  for (auto rootFp : rootFps) {
    if (rootFp.isEmpty()) continue;
    TFilePath tablePath = rootFp + pathTableFileName;
    if (!TFileStatus(tablePath).doesExist()) continue;
    QSettings tableSettings(QString::fromStdWString(tablePath.getWideString()),
                            QSettings::IniFormat);
    for (auto key : tableSettings.allKeys())
      table_cached[key.toStdWString()] =
          rootFp + TFilePath(tableSettings.value(key, "").toString());
  }
}

void StudioPalette::onProjectChanged() { onProjectSwitched(); }