

#include "toonz/scriptbinding_level.h"
#include "toonz/scriptbinding_files.h"
#include <QScriptEngine>
#include "timage_io.h"
#include "tlevel_io.h"
#include "tlevel.h"
#include "toonz/tcenterlinevectorizer.h"
#include "toonz/tcamera.h"
#include "trop.h"
#include "trasterimage.h"
#include "ttoonzimage.h"
#include "tvectorimage.h"
#include "tpalette.h"
#include "tofflinegl.h"
#include "tvectorrenderdata.h"
#include "tgeometry.h"
#include "toonz/stage.h"
#include "toonz/txshleveltypes.h"
#include "toonz/levelproperties.h"
#include "toonz/toonzscene.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/levelset.h"
#include "tfiletype.h"
#include "tsystem.h"
#include <QRegExp>
#include <QColor>

namespace TScriptBinding {

Level::Level()
    : m_sl(0)
    , m_type(NO_XSHLEVEL)
    , m_scene(new ToonzScene())
    , m_sceneOwner(true) {}

Level::Level(TXshSimpleLevel *sl)
    : m_sl(sl)
    , m_type(sl->getType())
    , m_scene(sl->getScene())
    , m_sceneOwner(false) {
  sl->addRef();
}

Level::~Level() {
  if (m_sceneOwner) delete m_scene;
  if (m_sl) m_sl->release();
}

QScriptValue Level::ctor(QScriptContext *context, QScriptEngine *engine) {
  Level *level     = new Level();
  QScriptValue obj = engine->newQObject(level, QScriptEngine::AutoOwnership);
  if (context->argumentCount() == 1) {
    return obj.property("load").call(obj, context->argumentsObject());
  }
  return obj;
}

QScriptValue Level::toString() {
  QString info  = "(";
  QString comma = "";
  if (getName() != "") {
    info.append(comma).append(getName());
    comma = ", ";
  }
  info.append(comma).append(tr("%1 frames").arg(getFrameCount()));
  info.append(")");
  if (m_type == PLI_XSHLEVEL)
    return QString("Vector level %1").arg(info);
  else if (m_type == TZP_XSHLEVEL)
    return QString("Toonz level %1").arg(info);
  else if (m_type == NO_XSHLEVEL)
    return QString("Empty level");
  else if (m_type == OVL_XSHLEVEL)
    return QString("Raster level %1").arg(info);
  else
    return QString("Level %1").arg(info);
}

QString Level::getType() const {
  if (m_type == NO_XSHLEVEL)
    return "Empty";
  else if (m_type == PLI_XSHLEVEL)
    return "Vector";
  else if (m_type == TZP_XSHLEVEL)
    return "ToonzRaster";
  else if (m_type == OVL_XSHLEVEL)
    return "Raster";
  else
    return "Unknown";
}

int Level::getFrameCount() const { return m_sl ? m_sl->getFrameCount() : 0; }

QString Level::getName() const {
  return m_sl ? QString::fromStdWString(m_sl->getName()) : "";
}

void Level::setName(const QString &name) {
  if (m_sl) m_sl->setName(name.toStdWString());
}

QScriptValue Level::getPath() const {
  if (m_sl) {
    FilePath *result = new FilePath(m_sl->getPath());
    return result->create<FilePath>(engine());
  } else
    return QScriptValue();
  return m_sl ? QString::fromStdWString(m_sl->getName()) : "";
}

void Level::setPath(const QScriptValue &pathArg) {
  TFilePath fp;
  FilePath *filePath = qscriptvalue_cast<FilePath *>(pathArg);
  if (filePath)
    fp = filePath->getToonzFilePath();
  else if (pathArg.isString())
    fp = TFilePath(pathArg.toString().toStdString());
  else
    context()->throwError(
        tr("Bad argument (%1). It should be FilePath or string")
            .arg(pathArg.toString()));
  if (m_sl) {
    m_sl->setPath(fp);
    try {
      m_sl->load();
    } catch (...) {
      context()->throwError(
          tr("Exception loading level (%1)")
              .arg(QString::fromStdWString(fp.getWideString())));
    }
  }
}

QScriptValue Level::load(const QScriptValue &fpArg) {
  if (m_sl) {
    m_scene->getLevelSet()->removeLevel(m_sl, true);
    m_sl->release();
    m_sl = 0;
  }

  // get the path
  TFilePath fp;
  QScriptValue err = checkFilePath(context(), fpArg, fp);
  if (err.isError()) return err;
  QString fpStr = fpArg.toString();

  try {
    if (!TSystem::doesExistFileOrLevel(fp)) {
      return context()->throwError(tr("File %1 doesn't exist").arg(fpStr));
    }
    TFileType::Type fileType = TFileType::getInfo(fp);
    if (TFileType::isVector(fileType))
      m_type = PLI_XSHLEVEL;
    else if (0 != (fileType & TFileType::CMAPPED_IMAGE))
      m_type = TZP_XSHLEVEL;
    else if (0 != (fileType & TFileType::RASTER_IMAGE))
      m_type = OVL_XSHLEVEL;
    else {
      return context()->throwError(tr("File %1 is unsupported").arg(fpStr));
    }
    TXshLevel *xl = m_scene->loadLevel(fp);
    if (xl) {
      m_sl = xl->getSimpleLevel();
      m_sl->addRef();
    }
    return context()->thisObject();
  } catch (...) {
    return context()->throwError(tr("Exception reading %1").arg(fpStr));
  }
}

QScriptValue Level::save(const QScriptValue &fpArg) {
  if (getFrameCount() == 0) {
    return context()->throwError(tr("Can't save an empty level"));
  }

  // get the path
  TFilePath fp;
  QScriptValue err = checkFilePath(context(), fpArg, fp);
  if (err.isError()) return err;
  QString fpStr = fpArg.toString();

  // handle conversion (if it is needed and possible)
  TFileType::Type fileType = TFileType::getInfo(fp);

  bool isCompatible = false;
  if (TFileType::isFullColor(fileType)) {
    if (m_sl->getType() == OVL_XSHLEVEL) isCompatible = true;
  } else if (TFileType::isVector(fileType)) {
    if (m_sl->getType() == PLI_XSHLEVEL) isCompatible = true;
  } else if (fileType & TFileType::CMAPPED_IMAGE) {
    if (m_sl->getType() == TZP_XSHLEVEL) isCompatible = true;
  } else {
    return context()->throwError(tr("Unrecognized file type :").arg(fpStr));
  }
  if (!isCompatible) {
    return context()->throwError(
        tr("Can't save a %1 level to this file type : %2")
            .arg(getType())
            .arg(fpStr));
  }

  try {
    m_sl->save(fp);
  } catch (TSystemException se) {
    return context()->throwError(
        tr("Exception writing %1")
            .arg(QString::fromStdWString(se.getMessage())));
  }
  return context()->thisObject();
}

TFrameId Level::getFid(const QScriptValue &arg, QString &err) {
  if (arg.isNumber() || arg.isString()) {
    QString s = arg.toString();
    QRegExp re("(-?\\d+)(\\w?)");
    if (re.exactMatch(s)) {
      int d     = re.cap(1).toInt();
      QString c = re.cap(2);
      TFrameId fid;
      if (c.length() == 1)
        fid = TFrameId(d, c[0].unicode());
      else
        fid = TFrameId(d);
      err = "";
      return fid;
    }
  }
  err = QObject::tr("Argument '%1' does not look like a FrameId")
            .arg(arg.toString());
  return TFrameId();
}

TImageP Level::getImg(const TFrameId &fid) {
  if (m_sl)
    return m_sl->getFrame(fid, false);
  else
    return TImageP();
}

QScriptValue Level::getFrame(const QScriptValue &fidArg) {
  if (getFrameCount() == 0)
    return context()->throwError("An empty level has no frames");
  QString err;
  TFrameId fid = getFid(fidArg, err);
  if (err != "") return context()->throwError(err);

  TImageP content = m_sl->getFrame(fid, false);
  if (content) {
    Image *img = new Image(content.getPointer());
    return create(img);
  } else {
    return QScriptValue();
  }
}

QScriptValue Level::getFrameByIndex(const QScriptValue &indexArg) {
  if (getFrameCount() == 0)
    return context()->throwError("An empty level has no frames");
  if (!indexArg.isNumber()) {
    return context()->throwError(
        tr("frame index (%1) must be a number").arg(indexArg.toString()));
  }
  int index = indexArg.toInteger();
  if (index < 0 || index >= getFrameCount()) {
    return context()->throwError(tr("frame index (%1) is out of range (0-%2)")
                                     .arg(index)
                                     .arg(getFrameCount() - 1));
  }
  TFrameId fid    = m_sl->index2fid(index);
  TImageP content = m_sl->getFrame(fid, false);
  if (content) {
    Image *img = new Image(content.getPointer());
    return create(img);
  } else {
    return QScriptValue();
  }
}

// TODO: chiamare setFrame(const TFrameId &fid, const TImageP &img)
QScriptValue Level::setFrame(const QScriptValue &fidArg,
                             const QScriptValue &imageArg) {
  QString err;
  TFrameId fid = getFid(fidArg, err);
  if (err != "") return context()->throwError(err);
  Image *img = qscriptvalue_cast<Image *>(imageArg);
  if (!img) {
    return context()->throwError(
        tr("second argument (%1) is not an image").arg(imageArg.toString()));
  }

  QString imgType = img->getType();
  int levelType   = NO_XSHLEVEL;
  if (imgType == "ToonzRaster")
    levelType = TZP_XSHLEVEL;
  else if (imgType == "Raster")
    levelType = OVL_XSHLEVEL;
  else if (imgType == "Vector")
    levelType = PLI_XSHLEVEL;
  else {
    return context()->throwError(
        tr("can not insert a %1 image into a level").arg(imgType));
  }

  if (m_type == NO_XSHLEVEL) {
    m_type        = levelType;
    TXshLevel *xl = m_scene->createNewLevel(levelType);
    m_sl          = xl->getSimpleLevel();
    m_sl->addRef();
    m_sl->setPalette(img->getImg()->getPalette());
    if (levelType != PLI_XSHLEVEL) {
      LevelProperties *lprop = m_sl->getProperties();
      lprop->setDpiPolicy(LevelProperties::DP_ImageDpi);
      int xres   = img->getWidth();
      int yres   = img->getHeight();
      double dpi = img->getDpi();
      lprop->setDpi(dpi);
      lprop->setImageDpi(TPointD(dpi, dpi));
      lprop->setImageRes(TDimension(xres, yres));
      // lprop->setHasAlpha(true);
    }
  } else if (m_type != levelType) {
    return context()->throwError(tr("can not insert a %1 image to a %2 level")
                                     .arg(imgType)
                                     .arg(getType()));
  }
  if (m_sl->getFrameCount() == 0) m_sl->setPalette(img->getImg()->getPalette());

  m_sl->setFrame(fid, img->getImg());
  m_sl->setDirtyFlag(true);
  return context()->thisObject();
}

QScriptValue Level::getFrameIds() {
  QList<TFrameId> fids;
  getFrameIds(fids);
  QScriptValue result = engine()->newArray();
  quint32 index       = 0;
  for (const TFrameId &fid : fids) {
    QString fidStr = QString::fromStdString(fid.expand());
    result.setProperty(index++, fidStr);
  }
  return result;
}

void Level::getFrameIds(QList<TFrameId> &result) {
  if (getFrameCount() > 0) {
    std::vector<TFrameId> fids;
    m_sl->getFids(fids);
    for (std::vector<TFrameId>::iterator it = fids.begin(); it != fids.end();
         ++it) {
      result.append(*it);
    }
  }
}

int Level::setFrame(const TFrameId &fid, const TImageP &img) {
  TImage::Type imgType = img->getType();
  int levelType        = NO_XSHLEVEL;
  if (imgType == TImage::TOONZ_RASTER)
    levelType = TZP_XSHLEVEL;
  else if (imgType == TImage::RASTER)
    levelType = OVL_XSHLEVEL;
  else if (imgType == TImage::VECTOR)
    levelType = PLI_XSHLEVEL;
  else {
    return -1;
  }

  if (m_type == NO_XSHLEVEL) {
    m_type        = levelType;
    TXshLevel *xl = m_scene->createNewLevel(levelType);
    m_sl          = xl->getSimpleLevel();
    m_sl->addRef();
    m_sl->setPalette(img->getPalette());
    if (levelType != PLI_XSHLEVEL) {
      LevelProperties *lprop = m_sl->getProperties();
      lprop->setDpiPolicy(LevelProperties::DP_ImageDpi);
      int xres = 0, yres = 0;
      double dpix = 0, dpiy = 0;
      if (TRasterImageP ri = img) {
        if (ri->getRaster()) {
          TDimension size = ri->getRaster()->getSize();
          xres            = size.lx;
          yres            = size.ly;
          ri->getDpi(dpix, dpiy);
        }
      } else if (TToonzImageP ti = img) {
        if (ti->getRaster()) {
          TDimension size = ri->getRaster()->getSize();
          xres            = size.lx;
          yres            = size.ly;
          ri->getDpi(dpix, dpiy);
        }
      }
      lprop->setDpi(dpix);
      lprop->setImageDpi(TPointD(dpix, dpiy));
      lprop->setImageRes(TDimension(xres, yres));
      // lprop->setHasAlpha(true);
    }
  } else if (m_type != levelType) {
    return -2;
  }
  if (m_sl->getFrameCount() == 0) m_sl->setPalette(img->getPalette());
  m_sl->setFrame(fid, img);
  m_sl->setDirtyFlag(true);
  return 1;
}

}  // namespace TScriptBinding
