
// ToonzCore includes
#include "tmsgcore.h"

// ToonzLib includes
#include "toonz/txshlevelhandle.h"
#include "toonz/tscenehandle.h"
#include "toonz/toonzscene.h"
#include "toonz/toonzfolders.h"
#include "toonz/cleanupcolorstyles.h"

// ToonzQt includes
#include "toonzqt/gutil.h"
#include "toonzqt/doublefield.h"

// Toonz includes
#include "tapp.h"
#include "cleanupsettingsmodel.h"
#include "cleanuppaletteviewer.h"
#include "pane.h"
#include "menubarcommandids.h"
#include "floatingpanelcommand.h"

// Qt includes
#include <QComboBox>
#include <QCheckBox>
#include <QLabel>
#include <QPushButton>
#include <QHBoxLayout>
#include <QGroupBox>

#include "cleanupsettingspane.h"

using namespace DVGui;

/*
"Save In"
フィールドのためのFileField。browseDirectoryを再実装して、フィールドが空欄のときは、
カレントレベル（Scan画像。TIF等）の入っているフォルダの１つ上をデフォルトフォルダにして開くようにしたい。
*/
void CleanupSaveInField::browseDirectory() {
  if (!m_fileBrowseButton->hasFocus()) return;
  QString directory = QString();

  if (!m_browserPopupController) return;

  /*
  ここで、m_lastSelectedPathが空のとき、カレントレベルがScan画像の場合、
  そのファイルの入っているフォルダの１つ上のフォルダを初期フォルダにする
  */
  QString initialFolder = m_lastSelectedPath;
  if (initialFolder.isEmpty()) {
    /*--- 親Widgetを取得する ---*/
    CleanupSettingsPane *parentCSP =
        dynamic_cast<CleanupSettingsPane *>(parentWidget());
    if (parentCSP) {
      TFilePath lastSelectedPath = parentCSP->getLastSelectedPath();
      if (!lastSelectedPath.isEmpty()) {
        /*----
         * 親Widgetのm_lastSelectedPathが、CLNファイルの見込み所在地なので、その１つ上のフォルダを初期フォルダにする。---*/
        initialFolder = QString::fromStdWString(
            lastSelectedPath.getParentDir().getParentDir().getWideString());
      }
    }
  }

  m_browserPopupController->openPopup(QStringList(), true, initialFolder);
  if (m_browserPopupController->isExecute())
    directory = m_browserPopupController->getPath();

  if (!directory.isEmpty()) {
    setPath(directory);
    m_lastSelectedPath = directory;
    emit pathChanged();
    return;
  }
}

//=============================================================================
// CleanupSettingsPane
//-----------------------------------------------------------------------------

CleanupSettingsPane::CleanupSettingsPane(QWidget *parent)
    : QFrame(parent), m_attached(false) {
  // Autocenter
  m_autocenterBox = new QGroupBox(tr("Autocenter"), this);
  m_pegHolesOm    = new QComboBox(this);
  m_fieldGuideOm  = new QComboBox(this);
  // Rotate&Flip
  QFrame *rotFlipFrame = new QFrame(this);
  m_rotateOm           = new QComboBox(this);
  m_flipX              = new QCheckBox(tr("Horizontal"), this);
  m_flipY              = new QCheckBox(tr("Vertical"), this);
  // Camera
  QFrame *cameraFrame = new QFrame(this);
  m_cameraWidget      = new CleanupCameraSettingsWidget();
  // LineProcessing
  QFrame *lineProcFrame = new QFrame(this);
  m_antialias           = new QComboBox(this);
  m_sharpness           = new DoubleField(this);
  m_despeckling         = new IntField(this);
  m_aaValueLabel        = new QLabel(tr("MLAA Intensity:"));
  m_aaValue             = new IntField(this);
  m_lineProcessing      = new QComboBox(this);
  m_lpNoneFormatLabel   = new QLabel(tr("Format:"));
  m_lpNoneFormat        = new QComboBox(this);
  m_paletteViewer       = new CleanupPaletteViewer(this);
  m_pathField           = new CleanupSaveInField(this, QString(""));

  QPushButton *saveBtn  = new QPushButton(tr("Save"));
  QPushButton *loadBtn  = new QPushButton(tr("Load"));
  QPushButton *resetBtn = new QPushButton(tr("Reset"));

  QLabel *antialiasLabel   = new QLabel(tr("Antialias:"));
  QLabel *sharpnessLabel   = new QLabel(tr("Sharpness:"));
  QLabel *despecklingLabel = new QLabel(tr("Despeckling:"));

  // Autocenter
  m_autocenterBox->setCheckable(true);
  QStringList pegbarHoles;
  pegbarHoles << tr("Bottom") << tr("Top") << tr("Left") << tr("Right");
  m_pegHolesOm->addItems(pegbarHoles);
  std::vector<std::string> fdgNames;
  CleanupParameters::getFdgNames(fdgNames);
  for (int i = 0; i < (int)fdgNames.size(); i++)
    m_fieldGuideOm->addItem(QString(fdgNames[i].c_str()));

  // Rotate&Flip
  rotFlipFrame->setObjectName("CleanupSettingsFrame");
  QStringList rotate;
  rotate << "0"
         << "90"
         << "180"
         << "270";
  m_rotateOm->addItems(rotate);
  // Camera
  cameraFrame->setObjectName("CleanupSettingsFrame");
  m_cameraWidget->setCameraPresetListFile(ToonzFolder::getReslistPath(true));
  // LineProcessing
  lineProcFrame->setObjectName("CleanupSettingsFrame");
  QStringList items;
  items << tr("Standard") << tr("None") << tr("Morphological");
  m_antialias->addItems(items);

  items.clear();
  items << tr("None") << tr("Greyscale") << tr("Color");
  m_lineProcessing->addItems(items);

  items.clear();
  items << "tif"
        << "png"
        << "jpg"
        << "tga";
  m_lpNoneFormat->addItems(items);

  m_sharpness->setValues(90, 0, 100);
  m_despeckling->setValues(2, 0, 20);
  m_aaValue->setValues(70, 0, 100);

  //  Model-related stuff
  CleanupSettingsModel *model = CleanupSettingsModel::instance();
  m_backupParams.assign(model->getCurrentParameters(), false);

  m_lpWidgets << antialiasLabel << m_antialias << sharpnessLabel << m_sharpness
              << despecklingLabel << m_despeckling << m_paletteViewer;

  //----layout
  QVBoxLayout *mainLay = new QVBoxLayout();
  mainLay->setSpacing(2);
  mainLay->setContentsMargins(5, 5, 5, 5);
  {
    // Autocenter
    QGridLayout *autocenterLay = new QGridLayout();
    autocenterLay->setContentsMargins(5, 5, 5, 5);
    autocenterLay->setSpacing(3);
    {
      autocenterLay->addWidget(new QLabel(tr("Pegbar Holes")), 0, 0,
                               Qt::AlignRight | Qt::AlignVCenter);
      autocenterLay->addWidget(m_pegHolesOm, 0, 1);
      autocenterLay->addWidget(new QLabel(tr("Field Guide")), 1, 0,
                               Qt::AlignRight | Qt::AlignVCenter);
      autocenterLay->addWidget(m_fieldGuideOm, 1, 1);
    }
    autocenterLay->setColumnStretch(0, 0);
    autocenterLay->setColumnStretch(1, 1);
    m_autocenterBox->setLayout(autocenterLay);
    mainLay->addWidget(m_autocenterBox, 0);

    // Rotate&Flip
    QGridLayout *rotFlipLay = new QGridLayout();
    rotFlipLay->setContentsMargins(5, 5, 5, 5);
    rotFlipLay->setSpacing(3);
    {
      rotFlipLay->addWidget(new QLabel(tr("Rotate")), 0, 0,
                            Qt::AlignRight | Qt::AlignVCenter);
      rotFlipLay->addWidget(m_rotateOm, 0, 1, 1, 2);
      rotFlipLay->addWidget(new QLabel(tr("Flip")), 1, 0,
                            Qt::AlignRight | Qt::AlignVCenter);
      rotFlipLay->addWidget(m_flipX, 1, 1);
      rotFlipLay->addWidget(m_flipY, 1, 2);
    }
    rotFlipLay->setColumnStretch(0, 0);
    rotFlipLay->setColumnStretch(1, 0);
    rotFlipLay->setColumnStretch(2, 1);
    rotFlipFrame->setLayout(rotFlipLay);
    mainLay->addWidget(rotFlipFrame, 0);

    // Camera
    QVBoxLayout *cleanupCameraFrameLay = new QVBoxLayout();
    cleanupCameraFrameLay->setContentsMargins(0, 0, 0, 0);
    cleanupCameraFrameLay->setSpacing(0);
    {
      cleanupCameraFrameLay->addWidget(m_cameraWidget);
    }
    cameraFrame->setLayout(cleanupCameraFrameLay);
    mainLay->addWidget(cameraFrame, 0);

    // Cleanup Palette
    QGridLayout *lineProcLay = new QGridLayout();
    lineProcLay->setContentsMargins(5, 5, 5, 5);
    lineProcLay->setSpacing(3);
    {
      lineProcLay->addWidget(new QLabel(tr("Line Processing:")), 0, 0,
                             Qt::AlignRight | Qt::AlignVCenter);
      lineProcLay->addWidget(m_lineProcessing, 0, 1);
      lineProcLay->addWidget(antialiasLabel, 1, 0,
                             Qt::AlignRight | Qt::AlignVCenter);
      lineProcLay->addWidget(m_antialias, 1, 1);
      lineProcLay->addWidget(sharpnessLabel, 2, 0,
                             Qt::AlignRight | Qt::AlignVCenter);
      lineProcLay->addWidget(m_sharpness, 2, 1);
      lineProcLay->addWidget(despecklingLabel, 3, 0,
                             Qt::AlignRight | Qt::AlignVCenter);
      lineProcLay->addWidget(m_despeckling, 3, 1);
      lineProcLay->addWidget(m_aaValueLabel, 4, 0,
                             Qt::AlignRight | Qt::AlignVCenter);
      lineProcLay->addWidget(m_aaValue, 4, 1);
      lineProcLay->addWidget(m_lpNoneFormatLabel, 5, 0,
                             Qt::AlignRight | Qt::AlignVCenter);
      lineProcLay->addWidget(m_lpNoneFormat, 5, 1,
                             Qt::AlignLeft | Qt::AlignVCenter);

      lineProcLay->addWidget(m_paletteViewer, 6, 0, 1, 2);
    }
    for (int r = 0; r <= 5; r++) lineProcLay->setRowStretch(r, 0);
    lineProcLay->setRowStretch(6, 1);
    lineProcLay->setColumnStretch(0, 0);
    lineProcLay->setColumnStretch(1, 1);

    lineProcFrame->setLayout(lineProcLay);

    mainLay->addWidget(lineProcFrame, 100);

    // Bottom Parts
    QHBoxLayout *pathLay = new QHBoxLayout();
    pathLay->setContentsMargins(0, 0, 0, 0);
    pathLay->setSpacing(3);
    {
      pathLay->addWidget(new QLabel(tr("Save In")), 0);
      pathLay->addWidget(m_pathField);
    }
    mainLay->addLayout(pathLay, 0);

    mainLay->addSpacing(5);

    QHBoxLayout *saveLoadLay = new QHBoxLayout();
    saveLoadLay->setContentsMargins(0, 0, 0, 0);
    saveLoadLay->setSpacing(1);
    {
      saveLoadLay->addWidget(saveBtn);
      saveLoadLay->addWidget(loadBtn);
      saveLoadLay->addWidget(resetBtn);
    }
    mainLay->addLayout(saveLoadLay, 0);
  }
  setLayout(mainLay);

  //-----signal-slot connections
  bool ret = true;
  ret      = ret && connect(m_autocenterBox, SIGNAL(clicked(bool)),
                            SLOT(onGenericSettingsChange()));
  ret      = ret && connect(m_pegHolesOm, SIGNAL(activated(int)),
                            SLOT(onGenericSettingsChange()));
  ret      = ret && connect(m_fieldGuideOm, SIGNAL(activated(int)),
                            SLOT(onGenericSettingsChange()));

  ret = ret && connect(m_rotateOm, SIGNAL(activated(int)),
                       SLOT(onGenericSettingsChange()));
  ret = ret && connect(m_flipX, SIGNAL(clicked(bool)),
                       SLOT(onGenericSettingsChange()));
  ret = ret && connect(m_flipY, SIGNAL(clicked(bool)),
                       SLOT(onGenericSettingsChange()));
  ret =
      ret && connect(m_pathField, SIGNAL(pathChanged()), SLOT(onPathChange()));
  ret = ret && connect(m_sharpness, SIGNAL(valueChanged(bool)),
                       SLOT(onSharpnessChange(bool)));
  ret = ret && connect(m_antialias, SIGNAL(activated(int)),
                       SLOT(onGenericSettingsChange()));
  ret = ret && connect(m_lineProcessing, SIGNAL(activated(int)),
                       SLOT(onGenericSettingsChange()));
  ret = ret && connect(m_lpNoneFormat, SIGNAL(activated(int)),
                       SLOT(onGenericSettingsChange()));
  ret = ret && connect(m_despeckling, SIGNAL(valueChanged(bool)),
                       SLOT(onGenericSettingsChange()));
  ret = ret && connect(m_aaValue, SIGNAL(valueChanged(bool)),
                       SLOT(onGenericSettingsChange()));
  ret = ret &&
        connect(TApp::instance()->getCurrentLevel(),
                SIGNAL(xshLevelSwitched(TXshLevel *)), SLOT(onLevelSwitched()));
  ret = ret && connect(m_cameraWidget, SIGNAL(cleanupSettingsChanged()),
                       SLOT(onGenericSettingsChange()));

  ret =
      ret && connect(saveBtn, SIGNAL(pressed()), this, SLOT(onSaveSettings()));

  ret = ret && connect(loadBtn, SIGNAL(pressed()), model, SLOT(promptLoad()));
  ret = ret &&
        connect(resetBtn, SIGNAL(pressed()), this, SLOT(onRestoreSettings()));

  assert(ret);
}

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

void CleanupSettingsPane::showEvent(QShowEvent *se) {
  QFrame::showEvent(se);

  if (!m_attached) {
    m_attached = true;
    // Should ensure that swatch is off...
    CleanupSettingsModel *model = CleanupSettingsModel::instance();

    model->attach(CleanupSettingsModel::LISTENER);

    bool ret = true;
    ret      = ret && connect(model, SIGNAL(imageSwitched()), this,
                              SLOT(onImageSwitched()));
    ret      = ret && connect(model, SIGNAL(modelChanged(bool)), this,
                              SLOT(updateGui(bool)));
    ret = ret && connect(model, SIGNAL(clnLoaded()), this, SLOT(onClnLoaded()));
    assert(ret);

    m_cameraWidget->setCurrentLevel(
        TApp::instance()->getCurrentLevel()->getLevel());

    updateGui(false);
    onImageSwitched();
    onClnLoaded();
  }
}

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

void CleanupSettingsPane::hideEvent(QHideEvent *he) {
  // Surprisingly enough, it seems that Qt may trigger hideEvents for widgets
  // that
  // have never been shown - this is why we need to check a user-made bool to
  // know whether
  // model attachment has happened... and no, detaching without attaching is
  // *BAD*

  if (m_attached) {
    m_attached = false;
    // Should put swatch to off...
    CleanupSettingsModel *model = CleanupSettingsModel::instance();

    model->detach(CleanupSettingsModel::LISTENER);

    bool ret = true;
    ret      = ret && disconnect(model, SIGNAL(imageSwitched()), this,
                                 SLOT(onImageSwitched()));
    ret      = ret && disconnect(model, SIGNAL(modelChanged(bool)), this,
                                 SLOT(updateGui(bool)));
    ret      = ret &&
          disconnect(model, SIGNAL(clnLoaded()), this, SLOT(onClnLoaded()));
    assert(ret);
  }

  QFrame::hideEvent(he);
}

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

void CleanupSettingsPane::updateGui(bool needsPostProcess) {
  CleanupParameters *params =
      CleanupSettingsModel::instance()->getCurrentParameters();

  updateGui(params, &m_backupParams);

  m_backupParams.assign(params, false);

  if (needsPostProcess) postProcess();
}

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

void CleanupSettingsPane::updateGui(CleanupParameters *params,
                                    CleanupParameters *oldParams) {
  m_autocenterBox->setChecked(params->m_autocenterType ==
                              CleanupTypes::AUTOCENTER_FDG);
  m_pegHolesOm->setCurrentIndex(params->m_pegSide - 1);

  QString fieldName = QString::fromStdString(params->getFdgName());
  int index = (fieldName.isEmpty()) ? 0 : m_fieldGuideOm->findText(fieldName);
  assert(index != -1);
  m_fieldGuideOm->setCurrentIndex(index);

  m_rotateOm->setCurrentIndex(params->m_rotate / 90);
  m_flipX->setChecked(params->m_flipx);
  m_flipY->setChecked(params->m_flipy);

  m_path = params->m_path;
  m_pathField->setPath(toQString(m_path));

  m_lineProcessing->setCurrentIndex(params->m_lineProcessingMode);
  m_antialias->setCurrentIndex(params->m_postAntialias ? 2
                               : params->m_noAntialias ? 1
                                                       : 0);
  m_sharpness->setValue(params->m_sharpness);
  m_despeckling->setValue(params->m_despeckling);
  m_aaValue->setValue(params->m_aaValue);
  m_lpNoneFormat->setCurrentText(
      QString::fromStdString(params->m_lpNoneFormat));

  updateVisibility();

  blockSignals(true);
  m_cameraWidget->setFields(params);
  blockSignals(false);

  if (params->m_lineProcessingMode != oldParams->m_lineProcessingMode ||
      params->m_camera.getRes() != oldParams->m_camera.getRes() ||
      params->m_camera.getSize() != oldParams->m_camera.getSize() ||
      params->m_closestField != oldParams->m_closestField) {
    updateImageInfo();
  }
}

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

void CleanupSettingsPane::updateImageInfo() {
  CleanupSettingsModel *model = CleanupSettingsModel::instance();
  CleanupParameters *params   = model->getCurrentParameters();

  TDimension outRes(0, 0);
  TPointD outDpi;

  params->getOutputImageInfo(outRes, outDpi.x, outDpi.y);
  m_cameraWidget->setImageInfo(outRes.lx, outRes.ly, outDpi.x, outDpi.y);

  TXshSimpleLevel *sl;
  TFrameId fid;
  model->getCleanupFrame(sl, fid);

  ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
  TFilePath outputPath(
      sl ? scene->decodeFilePath(model->getOutputPath(sl, params))
         : TFilePath());

  m_cameraWidget->setImageInfo(outputPath);

  if (!parentWidget()) return;

  if (model->clnPath().isEmpty())
    parentWidget()->setWindowTitle(tr("Cleanup Settings (Global)"));
  else
    parentWidget()->setWindowTitle(
        tr("Cleanup Settings: ") +
        toQString(model->clnPath().withoutParentDir()));
}

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

void CleanupSettingsPane::updateVisibility() {
  bool lp     = (m_lineProcessing->currentIndex() != 0);
  bool lpGrey = (m_lineProcessing->currentIndex() == 1);
  bool MLAA   = (m_antialias->currentIndex() == 2);

  for (QWidget *w : m_lpWidgets) w->setVisible(lp);
  m_aaValueLabel->setVisible(MLAA);
  m_aaValue->setVisible(MLAA);
  m_lpNoneFormatLabel->setVisible(!lp);
  m_lpNoneFormat->setVisible(!lp);

  m_paletteViewer->setMode(lpGrey);
  m_paletteViewer->setContrastEnabled(m_antialias->currentIndex() == 0);
}

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

void CleanupSettingsPane::onImageSwitched() { updateImageInfo(); }

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

void CleanupSettingsPane::onPreviewDataChanged() {
  TRasterImageP original, transformed;
  TAffine transform;

  CleanupSettingsModel::instance()->getPreviewData(original, transformed,
                                                   transform);
}

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

void CleanupSettingsPane::postProcess() {
  // m_swatch->updateCleanupped();
}

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

void CleanupSettingsPane::onClnLoaded() {
  const TFilePath &fp = CleanupSettingsModel::instance()->clnPath();
  if (fp.isEmpty())
    setWindowTitle(tr("Cleanup Settings"));
  else
    setWindowTitle(
        tr("Cleanup Settings: %1").arg(toQString(fp.withoutParentDir())));
}

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

void CleanupSettingsPane::onRestoreSettings() {
  CleanupSettingsModel *model = CleanupSettingsModel::instance();

  if (model->clnPath().isEmpty()) {
    model->saveSettingsIfNeeded();
    model->restoreGlobalSettings();
  } else {
    model->loadSettings(model->clnPath());
  }
}

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

void CleanupSettingsPane::onGenericSettingsChange() {
  CleanupSettingsModel *model = CleanupSettingsModel::instance();
  CleanupParameters *params   = model->getCurrentParameters();

  params->m_autocenterType = m_autocenterBox->isChecked()
                                 ? CleanupTypes::AUTOCENTER_FDG
                                 : CleanupTypes::AUTOCENTER_NONE;
  params->m_pegSide =
      (CleanupTypes::PEGS_SIDE)(m_pegHolesOm->currentIndex() + 1);
  params->setFdgByName(m_fieldGuideOm->currentText().toStdString());

  params->m_rotate = m_rotateOm->currentIndex() * 90;
  params->m_flipx  = m_flipX->isChecked();
  params->m_flipy  = m_flipY->isChecked();

  //------
  if (params->m_lineProcessingMode != m_lineProcessing->currentIndex()) {
    int oldMode                  = params->m_lineProcessingMode;
    params->m_lineProcessingMode = m_lineProcessing->currentIndex();
    if (params->m_lineProcessingMode == lpNone) {
      params->m_transparencyCheckEnabled = false;
    }
    // When switching from/to Greyscale processing, replace the brightness and
    // contrast values by the registered ones.
    if (oldMode == lpGrey || params->m_lineProcessingMode == lpGrey) {
      TCleanupStyle *blackStyle =
          dynamic_cast<TCleanupStyle *>(params->m_cleanupPalette->getStyle(1));
      double b                = params->m_altBrightness;
      double c                = params->m_altContrast;
      params->m_altBrightness = blackStyle->getBrightness();
      params->m_altContrast   = blackStyle->getContrast();
      blackStyle->setBrightness(b);
      blackStyle->setContrast(c);
    }
  }
  params->m_noAntialias   = (m_antialias->currentIndex() > 0);
  params->m_postAntialias = (m_antialias->currentIndex() == 2);
  params->m_despeckling   = m_despeckling->getValue();
  params->m_aaValue       = m_aaValue->getValue();
  params->m_lpNoneFormat  = m_lpNoneFormat->currentText().toStdString();
  //------

  m_cameraWidget->getFields(model->getCurrentParameters());

  model->commitChanges();
}

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

void CleanupSettingsPane::onPathChange() {
  CleanupSettingsModel *model = CleanupSettingsModel::instance();
  CleanupParameters *params   = model->getCurrentParameters();

  m_path = params->m_path = TFilePath(m_pathField->getPath().toStdWString());

  model->commitChanges();
}

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

void CleanupSettingsPane::onSharpnessChange(bool dragging = false) {
  if (dragging) return;

  CleanupSettingsModel *model = CleanupSettingsModel::instance();

  model->getCurrentParameters()->m_sharpness = m_sharpness->getValue();
  model->commitChanges();
}

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

void CleanupSettingsPane::onLevelSwitched() {
  m_cameraWidget->setCurrentLevel(
      TApp::instance()->getCurrentLevel()->getLevel());
}

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

void CleanupSettingsPane::onSaveSettings() {
  /*--- Clueaup保存先を指定していないとエラーを返す ---*/
  if (m_pathField->getPath().isEmpty()) {
    DVGui::warning(tr("Please fill the Save In field."));
    return;
  }
  CleanupSettingsModel::instance()->promptSave();
}
