

// Tnz6 includes
#include "colormodelviewer.h"
#include "menubarcommandids.h"
#include "floatingpanelcommand.h"
#include "tapp.h"
#include "pane.h"
#include "colormodelbehaviorpopup.h"

// TnzTools includes
#include "tools/cursormanager.h"
#include "tools/cursors.h"
#include "tools/stylepicker.h"
#include "tools/toolcommandids.h"
#include "tools/tool.h"
#include "tools/toolhandle.h"
#include "../tnztools/stylepickertool.h"

// TnzQt includes
#include "toonzqt/menubarcommand.h"
#include "toonzqt/viewcommandids.h"
#include "toonzqt/dvdialog.h"
#include "toonzqt/icongenerator.h"
#include "toonzqt/gutil.h"
#include "toonzqt/tselectionhandle.h"
#include "toonzqt/styleselection.h"

// TnzLib includes
#include "toonz/palettecmd.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tpalettehandle.h"
#include "toonz/tframehandle.h"
#include "toonz/tscenehandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/toonzscene.h"
#include "toonz/txsheet.h"
#include "toonz/palettecontroller.h"
#include "toonz/txshlevel.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/txshcell.h"

// TnzCore includes
#include "tsystem.h"
#include "ttoonzimage.h"

// Qt includes
#include <QMimeData>
#include <QMouseEvent>
#include <QUrl>
#include <QMenu>

#define LINES "Lines"
#define AREAS "Areas"
#define ALL "Lines & Areas"

using namespace DVGui;

namespace {
TPaletteHandle *getPaletteHandle() {
  return TApp::instance()->getPaletteController()->getCurrentLevelPalette();
}

}  // namespace

//=============================================================================
/*! \class ColorModelViewer
                \brief The ColorModelViewer class is a flip book used to manage
   color model.

                Inherits \b FlipBook.

                This object show the reference image linked to current level
   palette.
*/
/*!	\fn void ColorModelViewer::resetImageViewer()
                Set current level to TLevelP() and image to "0".
*/
ColorModelViewer::ColorModelViewer(QWidget *parent)
    : FlipBook(parent, QString(tr("Color Model")),
               std::vector<int>(
                   {FlipConsole::eRed,          FlipConsole::eGreen,
                    FlipConsole::eBlue,         FlipConsole::eMatte,
                    FlipConsole::eGRed,         FlipConsole::eGGreen,
                    FlipConsole::eGBlue,        FlipConsole::eRate,
                    FlipConsole::eSound,        FlipConsole::eSaveImg,
                    FlipConsole::eHisto,        FlipConsole::eCompare,
                    FlipConsole::eCustomize,    FlipConsole::eSave,
                    FlipConsole::eFilledRaster, FlipConsole::eDefineLoadBox,
                    FlipConsole::eUseLoadBox,   FlipConsole::eDefineSubCamera,
                    FlipConsole::eLocator,      FlipConsole::eZoomIn,
                    FlipConsole::eZoomOut,      FlipConsole::eFlipHorizontal,
                    FlipConsole::eFlipVertical, FlipConsole::eResetView}),
               eDontKeepFilesOpened, true)
    , m_mode(0)
    , m_currentRefImgPath(TFilePath()) {
  setObjectName("colormodel");

  setToolCursor(m_imageViewer, ToolCursor::PickerCursor);

  // Do not call the special procedure for flipbook closures...
  disconnect(parentWidget(), SIGNAL(closeButtonPressed()), this,
             SLOT(onCloseButtonPressed()));

  bool ret = connect(this, SIGNAL(refImageNotFound()), this,
                     SLOT(onRefImageNotFound()), Qt::QueuedConnection);
  assert(ret);

  m_imageViewer->setMouseTracking(true);
}
//-----------------------------------------------------------------------------

ColorModelViewer::~ColorModelViewer() {}

//-----------------------------------------------------------------------------
/*! Accept event if current palette is not empty, event data has urls and each
                urls type are different from "src" and "tpl" .
*/
void ColorModelViewer::dragEnterEvent(QDragEnterEvent *event) {
  TPalette *palette = getPaletteHandle()->getPalette();
  if (!palette) return;
  const QMimeData *mimeData = event->mimeData();
  if (!acceptResourceDrop(mimeData->urls())) return;

  for (const QUrl &url : mimeData->urls()) {
    TFilePath fp(url.toLocalFile().toStdWString());
    std::string type = fp.getType();
    if (type == "scr" || type == "tpl") return;
  }
  // Force CopyAction
  event->setDropAction(Qt::CopyAction);
  // For files, don't accept original proposed action in case it's a move
  event->accept();
}

//-----------------------------------------------------------------------------
/*! If event data has urls, convert each urls in path and set view and current
    palette reference image (recall loadImage() and setLevel()).
*/
void ColorModelViewer::dropEvent(QDropEvent *event) {
  const QMimeData *mimeData = event->mimeData();
  if (mimeData->hasUrls()) {
    for (const QUrl &url : mimeData->urls()) {
      TFilePath fp(url.toLocalFile().toStdWString());
      loadImage(fp);
      setLevel(fp);
    }
    // Force CopyAction
    event->setDropAction(Qt::CopyAction);
    // For files, don't accept original proposed action in case it's a move
    event->accept();
  }
}

//-----------------------------------------------------------------------------
/*! Set current palette reference image to \b fp recall:
                \b PaletteCmd::loadReferenceImage().
*/
// This function will be called when drag & drop the file into the color model
// viewer

void ColorModelViewer::loadImage(const TFilePath &fp) {
  if (fp.isEmpty()) return;

  TPaletteHandle *paletteHandle = getPaletteHandle();
  TPalette *palette             = paletteHandle->getPalette();

  if (!palette || palette->isCleanupPalette()) {
    DVGui::error(QObject::tr("Cannot load Color Model in current palette."));
    return;
  }

  PaletteCmd::ColorModelLoadingConfiguration config;

  // if the palette is locked, replace the color model's palette with the
  // destination
  if (palette->isLocked()) {
    // do nothing as config will use behavior = ReplaceColorModelPlt by default
    // config.behavior = PaletteCmd::ReplaceColorModelPlt;
  } else {
    std::set<TFilePath> fpSet;
    fpSet.insert(fp);
    ColorModelBehaviorPopup popup(fpSet, 0);
    int ret = popup.exec();
    if (ret == QDialog::Rejected) return;
    popup.getLoadingConfiguration(config);
  }

  ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();

  int isLoaded =
      PaletteCmd::loadReferenceImage(paletteHandle, config, fp, scene);

  if (0 != isLoaded) {
    std::cout << "loadReferenceImage Failed for some reason." << std::endl;
    return;
  }

  // no changes in the icon with replace (Keep the destination palette) option
  if (config.behavior != PaletteCmd::ReplaceColorModelPlt) {
    TXshLevel *level = TApp::instance()->getCurrentLevel()->getLevel();
    if (!level) return;
    std::vector<TFrameId> fids;
    level->getFids(fids);
    invalidateIcons(level, fids);
  }
}

//-----------------------------------------------------------------------------
/*! Create and open the Right-click menu color model viewer.
 */
void ColorModelViewer::contextMenuEvent(QContextMenuEvent *event) {
  /*-- Levelが取得できない場合はMenuを出さない --*/
  TApp *app     = TApp::instance();
  TXshLevel *xl = app->getCurrentLevel()->getLevel();
  if (!xl) return;
  TXshSimpleLevel *sl = xl->getSimpleLevel();
  if (!sl) return;
  TPalette *currentPalette =
      app->getPaletteController()->getCurrentLevelPalette()->getPalette();
  if (!currentPalette) return;

  QMenu menu(this);

  menu.addAction(CommandManager::instance()->getAction(MI_LoadColorModel));

  QAction *loadCurrentFrame =
      new QAction(QString(tr("Use Current Frame")), this);
  connect(loadCurrentFrame, SIGNAL(triggered()), SLOT(loadCurrentFrame()));
  menu.addAction(loadCurrentFrame);

  if (!m_imageViewer->getImage()) {
    menu.exec(event->globalPos());
    return;
  }

  QAction *removeColorModel =
      new QAction(QString(tr("Remove Color Model")), this);
  connect(removeColorModel, SIGNAL(triggered()), SLOT(removeColorModel()));
  menu.addAction(removeColorModel);

  /* If there is at least one style with "picked pos" parameter, then enable
   * repick command */
  TRasterImageP ri = m_imageViewer->getImage();
  if (ri && currentPalette->hasPickedPosStyle()) {
    menu.addSeparator();
    QAction *repickFromColorModelAct = new QAction(
        QString(tr("Update Colors by Using Picked Positions")), this);
    connect(repickFromColorModelAct, SIGNAL(triggered()),
            SLOT(repickFromColorModel()));
    menu.addAction(repickFromColorModelAct);
  }

  menu.addSeparator();

  QString shortcut = QString::fromStdString(
      CommandManager::instance()->getShortcutFromId(V_ViewReset));
  QAction *reset = menu.addAction(tr("Reset View") + "\t " + shortcut);
  connect(reset, SIGNAL(triggered()), m_imageViewer, SLOT(resetView()));

  shortcut = QString::fromStdString(
      CommandManager::instance()->getShortcutFromId(V_ZoomFit));
  QAction *fit = menu.addAction(tr("Fit to Window") + "\t" + shortcut);
  connect(fit, SIGNAL(triggered()), m_imageViewer, SLOT(fitView()));

  menu.exec(event->globalPos());
}

//-----------------------------------------------------------------------------
/*! If left button is pressed recall \b pick() in event pos.
 */
void ColorModelViewer::mousePressEvent(QMouseEvent *event) {
  if (event->button() == Qt::LeftButton)
    pick(event->pos() * getDevicePixelRatio(this));
}

//-----------------------------------------------------------------------------
/*! If left button is moved recall \b pick() in event pos.
 */
void ColorModelViewer::mouseMoveEvent(QMouseEvent *event) {
  if (event->buttons() & Qt::LeftButton)
    pick(event->pos() * getDevicePixelRatio(this));
}

//-----------------------------------------------------------------------------
/*! Pick color from image and set it as current style.
 */
void ColorModelViewer::pick(const QPoint &p) {
  TImageP img = m_imageViewer->getImage();
  if (!img) return;

  TPaletteHandle *ph =
      TApp::instance()->getPaletteController()->getCurrentLevelPalette();
  TPalette *currentPalette = ph->getPalette();
  if (!currentPalette) return;

  /*- 画面外ではPickできない -*/
  if (!m_imageViewer->rect().contains(p)) return;

  StylePicker picker(this, img, currentPalette);

  QPoint viewP = m_imageViewer->mapFrom(this, p);
  TPointD pos  = m_imageViewer->getViewAff().inv() *
                TPointD(viewP.x() - m_imageViewer->width() / 2,
                        -viewP.y() + m_imageViewer->height() / 2);

  double scale2 = m_imageViewer->getViewAff().det();
  /*---
          カレントToolに合わせてPickモードを変更
          0=Area, 1=Line, 2=Line&Areas(default)
  ---*/
  int styleIndex =
      picker.pickStyleId(pos + TPointD(-0.5, -0.5), 1, scale2, m_mode);

  if (styleIndex < 0) return;

  /*-- pickLineモードのとき、取得Styleが0の場合 /
   * PurePaintの部分をクリックした場合 はカレントStyleを変えない --*/
  if (m_mode == 1) {
    if (styleIndex == 0) return;
    TToonzImageP ti = img;
    if (ti && picker.pickTone(pos) == 255) return;
  }
  /*- Paletteに存在しない色が取れた場合はreturn -*/
  TPalette::Page *page = currentPalette->getStylePage(styleIndex);
  if (!page) return;

  /*-- Styleを選択している場合は選択を解除する --*/
  TSelection *selection =
      TApp::instance()->getCurrentSelection()->getSelection();
  if (selection) {
    TStyleSelection *styleSelection =
        dynamic_cast<TStyleSelection *>(selection);
    if (styleSelection) styleSelection->selectNone();
  }

  /*
    if the Style Picker tool is current and "organize palette" is activated,
    then move the picked style to the first page of the palette.
  */
  TTool *tool = TApp::instance()->getCurrentTool()->getTool();
  if (tool->getName() == "T_StylePicker") {
    StylePickerTool *spTool = dynamic_cast<StylePickerTool *>(tool);
    if (spTool && spTool->isOrganizePaletteActive()) {
      TPoint point = picker.getRasterPoint(pos);
      int frame    = m_flipConsole->getCurrentFrame() - 1;
      PaletteCmd::organizePaletteStyle(
          ph, styleIndex, TColorStyle::PickedPosition(point, frame));
    }
  }

  ph->setStyleIndex(styleIndex);
}

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

void ColorModelViewer::hideEvent(QHideEvent *e) {
  TPaletteHandle *paletteHandle = getPaletteHandle();
  TXshLevelHandle *levelHandle  = TApp::instance()->getCurrentLevel();
  ToolHandle *toolHandle        = TApp::instance()->getCurrentTool();
  disconnect(paletteHandle, SIGNAL(paletteSwitched()), this,
             SLOT(showCurrentImage()));
  disconnect(paletteHandle, SIGNAL(paletteChanged()), this,
             SLOT(showCurrentImage()));
  disconnect(paletteHandle, SIGNAL(colorStyleChanged(bool)), this,
             SLOT(showCurrentImage()));

  disconnect(toolHandle, SIGNAL(toolSwitched()), this, SLOT(changePickType()));
  disconnect(toolHandle, SIGNAL(toolChanged()), this, SLOT(changePickType()));

  disconnect(levelHandle, SIGNAL(xshLevelViewChanged()), this,
             SLOT(updateViewer()));
}

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

void ColorModelViewer::showEvent(QShowEvent *e) {
  TPaletteHandle *paletteHandle = getPaletteHandle();
  TXshLevelHandle *levelHandle  = TApp::instance()->getCurrentLevel();
  ToolHandle *toolHandle        = TApp::instance()->getCurrentTool();
  bool ret = connect(paletteHandle, SIGNAL(paletteSwitched()), this,
                     SLOT(showCurrentImage()));
  ret      = ret && connect(paletteHandle, SIGNAL(paletteChanged()), this,
                       SLOT(showCurrentImage()));
  ret = ret && connect(paletteHandle, SIGNAL(colorStyleChanged(bool)), this,
                       SLOT(showCurrentImage()));
  /*- ツールのTypeに合わせてPickのタイプも変え、カーソルも切り替える -*/
  ret = ret && connect(toolHandle, SIGNAL(toolSwitched()), this,
                       SLOT(changePickType()));
  ret = ret && connect(toolHandle, SIGNAL(toolChanged()), this,
                       SLOT(changePickType()));
  ret = ret && connect(levelHandle, SIGNAL(xshLevelViewChanged()), this,
                       SLOT(updateViewer()));
  assert(ret);
  changePickType();
  showCurrentImage();
}

//-----------------------------------------------------------------------------
/*- ツールのTypeに合わせてPickのタイプも変え、カーソルも切り替える -*/
void ColorModelViewer::changePickType() {
  TTool *tool = TApp::instance()->getCurrentTool()->getTool();
  if (tool->getName() == T_StylePicker) {
    StylePickerTool *stylePickerTool = dynamic_cast<StylePickerTool *>(tool);
    if (stylePickerTool->isOrganizePaletteActive()) {
      setToolCursor(m_imageViewer, ToolCursor::PickerCursorOrganize);
      return;
    }
  }

  TPropertyGroup *propGroup = tool->getProperties(0);
  /*- Propertyの無いツールの場合 -*/
  if (!propGroup) {
    m_mode = 2;
    setToolCursor(m_imageViewer, ToolCursor::PickerCursor);
    return;
  }

  /*- Mode: の無いツールの場合は0が返る -*/
  TProperty *modeProp = propGroup->getProperty("Mode:");
  if (!modeProp) {
    m_mode = 2;
    setToolCursor(m_imageViewer, ToolCursor::PickerCursor);
    return;
  }

  else {
    std::string var = modeProp->getValueAsString();
    if (var == LINES) {
      m_mode = 1;
      setToolCursor(m_imageViewer, ToolCursor::PickerCursorLine);
    } else if (var == AREAS) {
      m_mode = 0;
      setToolCursor(m_imageViewer, ToolCursor::PickerCursorArea);
    } else  // Line & Areas
    {
      m_mode = 2;
      setToolCursor(m_imageViewer, ToolCursor::PickerCursor);
    }
  }
}

//-----------------------------------------------------------------------------
/*! If current palette level exists reset image viewer and set current viewer
    to reference image path level.
*/

void ColorModelViewer::updateViewer() { getImageViewer()->repaint(); }

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

void ColorModelViewer::showCurrentImage() {
  TPalette *palette = getPaletteHandle()->getPalette();
  if (!palette) {
    resetImageViewer();
    return;
  }
  TApp *app     = TApp::instance();
  TXshLevel *xl = app->getCurrentLevel()->getLevel();
  if (!xl) {
    resetImageViewer();
    return;
  }
  /*-- UseCurrentFrameの場合、paletteを差し替える --*/
  if (palette->getRefImgPath() == xl->getPath()) {
    TXshSimpleLevel *sl = xl->getSimpleLevel();
    if (!sl) {
      resetImageViewer();
      return;
    }
    /*- 同じTFrameIdだった場合、パレットを差し替える -*/
    if (m_currentRefImgPath == xl->getPath()) {
      TImageP refImg = m_imageViewer->getImage();
      if (!refImg) {
        resetImageViewer();
        return;
      }

      TImageP refImg_clone = refImg->cloneImage();
      refImg_clone->setPalette(palette);
      m_imageViewer->setImage(refImg_clone);
      return;
    }
    /*- UseCurrentFrameのLevelに移動してきた場合、Levelを入れ直す -*/
    else {
      reloadCurrentFrame();
      return;
    }
  }

  /*- 以下、通常のColorModelが読み込まれている場合 -*/
  resetImageViewer();

  ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
  TFilePath fp      = scene->decodeFilePath(palette->getRefImgPath());
  if (TSystem::doesExistFileOrLevel(fp)) {
    setLevel(fp, palette);
  } else if (!fp.isEmpty())
    emit refImageNotFound();
}

//-----------------------------------------------------------------------------
/*! Clone current image and set it in viewer.
 */
void ColorModelViewer::loadCurrentFrame() {
  TApp *app     = TApp::instance();
  TXshLevel *xl = app->getCurrentLevel()->getLevel();
  if (!xl) return;
  TXshSimpleLevel *sl = xl->getSimpleLevel();
  if (!sl) return;
  /*- カレントフレームのFIdの取得 -*/
  TFrameId fid;
  if (app->getCurrentFrame()->isEditingLevel())
    fid = app->getCurrentFrame()->getFid();
  else if (app->getCurrentFrame()->isEditingScene()) {
    int columnIndex = app->getCurrentColumn()->getColumnIndex();
    TXsheet *xsh    = app->getCurrentXsheet()->getXsheet();
    if (!xsh) return;
    TXshColumn *column = xsh->getColumn(columnIndex);
    if (!column) return;
    int frame = app->getCurrentFrame()->getFrame();
    fid       = column->getCellColumn()->getCell(frame).getFrameId();
  }
  TImageP img = sl->getFrame(fid, true);

  TPalette *currentPalette =
      app->getPaletteController()->getCurrentLevelPalette()->getPalette();
  if (!img || !currentPalette) return;
  TImageP refImg = img->cloneImage();
  currentPalette->setRefImg(TImageP());
  /*--onPaletteSwitchedでRefImagePathを見て、自分自身のパスを参照していたらUpdateのルーチンを切り替える--*/
  currentPalette->setRefImgPath(xl->getPath());

  std::vector<TFrameId> fids;
  fids.push_back(fid);
  currentPalette->setRefLevelFids(fids, false);

  m_currentRefImgPath = xl->getPath();

  m_levels.clear();

  m_levelNames.clear();
  m_framesCount = 1;
  m_palette     = 0;
  m_lr          = TLevelReaderP();
  m_dim         = TDimension(0, 0);
  m_loadbox     = TRect();
  m_loadboxes.clear();
  m_flipConsole->enableProgressBar(false);
  m_flipConsole->setProgressBarStatus(0);
  m_flipConsole->setFrameRange(1, 1, 1);
  m_title1 = m_viewerTitle +
             " :: " + m_currentRefImgPath.withoutParentDir().withFrame(fid);
  m_title = "  ::  <Use Current Frame>";

  m_imageViewer->setImage(refImg);
}

//-----------------------------------------------------------------------------
/*--
 * UseCurrentFrameのLevelに移動してきたときに、改めてCurrentFrameを格納しなおす
 * --*/
void ColorModelViewer::reloadCurrentFrame() {
  TApp *app     = TApp::instance();
  TXshLevel *xl = app->getCurrentLevel()->getLevel();
  if (!xl) return;
  TXshSimpleLevel *sl = xl->getSimpleLevel();
  if (!sl) return;

  TPalette *currentPalette =
      app->getPaletteController()->getCurrentLevelPalette()->getPalette();
  if (!currentPalette) return;

  std::vector<TFrameId> fids = currentPalette->getRefLevelFids();
  /*- CurrentFrameなので、1Frameのみのはず -*/
  if (fids.size() != 1) return;

  TImageP img = sl->getFrame(fids[0], true);
  if (!img) return;

  TImageP refImg = img->cloneImage();

  currentPalette->setRefImg(TImageP());

  currentPalette->setRefImgPath(xl->getPath());

  /*- currentRefImgPathの更新 -*/
  m_currentRefImgPath = xl->getPath();

  m_levels.clear();

  m_levelNames.clear();
  m_framesCount = 1;
  m_palette     = 0;
  m_lr          = TLevelReaderP();
  m_dim         = TDimension(0, 0);
  m_loadbox     = TRect();
  m_loadboxes.clear();
  m_flipConsole->enableProgressBar(false);
  m_flipConsole->setProgressBarStatus(0);
  m_flipConsole->setFrameRange(1, 1, 1);
  m_title1 = m_viewerTitle +
             " :: " + m_currentRefImgPath.withoutParentDir().withFrame(fids[0]);
  m_title = "  ::  <Use Current Frame>";

  m_imageViewer->setImage(refImg);
}

//-----------------------------------------------------------------------------
/*! Remove reference image from current palette using
                \b PaletteCmd::removeReferenceImage() and reset image viewer.
*/
void ColorModelViewer::removeColorModel() {
  PaletteCmd::removeReferenceImage(getPaletteHandle());
  resetImageViewer();
  m_currentRefImgPath = TFilePath();
}

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

void ColorModelViewer::onRefImageNotFound() {
  DVGui::info(
      tr("It is not possible to retrieve the color model set for the current "
         "level."));
}

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

void ColorModelViewer::repickFromColorModel() {
  TImageP img = m_imageViewer->getImage();
  if (!img) return;
  TPaletteHandle *ph =
      TApp::instance()->getPaletteController()->getCurrentLevelPalette();

  PaletteCmd::pickColorByUsingPickedPosition(
      ph, img, m_flipConsole->getCurrentFrame() - 1);
}

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

OpenFloatingPanel openColorModelCommand(MI_OpenColorModel, "ColorModel",
                                        QObject::tr("Color Model"));
