/** -*- mode: c++ ; c-basic-offset: 2 -*-
 *
 *  @file FiltersView.cpp
 *
 *  Copyright 2017 Sebastien Fourey
 *
 *  This file is part of G'MIC-Qt, a generic plug-in for raster graphics
 *  editors, offering hundreds of filters thanks to the underlying G'MIC
 *  image processing framework.
 *
 *  gmic_qt is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  gmic_qt is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with gmic_qt.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#include "FilterSelector/FiltersView/FiltersView.h"
#include <QDebug>
#include <QEvent>
#include <QKeyEvent>
#include <QLineEdit>
#include <QMessageBox>
#include <QSettings>
#include <QStandardItem>
#include <QStringList>
#include "Common.h"
#include "FilterSelector/FilterTagMap.h"
#include "FilterSelector/FiltersView/FilterTreeFolder.h"
#include "FilterSelector/FiltersView/FilterTreeItem.h"
#include "FilterSelector/FiltersView/FilterTreeItemDelegate.h"
#include "FilterSelector/FiltersVisibilityMap.h"
#include "FilterTextTranslator.h"
#include "Globals.h"
#include "ui_filtersview.h"

namespace GmicQt
{

const QString FiltersView::FilterTreePathSeparator("\t");

FiltersView::FiltersView(QWidget * parent) : QWidget(parent), ui(new Ui::FiltersView), _isInSelectionMode(false)
{
  ui->setupUi(this);
  ui->treeView->setModel(&_emptyModel);
  _faveFolder = nullptr;
  _cachedFolder = _model.invisibleRootItem();
  auto delegate = new FilterTreeItemDelegate(ui->treeView);
  ui->treeView->setItemDelegate(delegate);
  ui->treeView->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
  ui->treeView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

  connect(delegate, &FilterTreeItemDelegate::commitData, this, &FiltersView::onRenameFaveFinished);
  connect(ui->treeView, &TreeView::returnKeyPressed, this, &FiltersView::onReturnKeyPressedInFiltersTree);
  connect(ui->treeView, &TreeView::clicked, this, &FiltersView::onItemClicked);
  connect(&_model, &QStandardItemModel::itemChanged, this, &FiltersView::onItemChanged);

  ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
  connect(ui->treeView, &TreeView::customContextMenuRequested, this, &FiltersView::onCustomContextMenu);
  _faveContextMenu = nullptr;
  _filterContextMenu = nullptr;
  ui->treeView->installEventFilter(this);
}

FiltersView::~FiltersView()
{
  delete ui;
}

void FiltersView::enableModel()
{
  if (_isInSelectionMode) {
    uncheckFullyUncheckedFolders();
    _model.setHorizontalHeaderItem(1, new QStandardItem(QObject::tr("Visible")));
    _model.setColumnCount(2);
  }
  ui->treeView->setModel(&_model);
  if (_isInSelectionMode) {
    QStandardItem * headerItem = _model.horizontalHeaderItem(1);
    QString title = QString("_%1_").arg(headerItem->text());
    QFont font;
    QFontMetrics fm(font);
#if QT_VERSION_GTE(5, 11, 0)
    int w = fm.horizontalAdvance(title);
#else
    int w = fm.width(title);
#endif
    ui->treeView->setColumnWidth(0, ui->treeView->width() - 2 * w);
    ui->treeView->setColumnWidth(1, w);
  }
}

void FiltersView::disableModel()
{
  ui->treeView->setModel(&_emptyModel);
}

void FiltersView::createFolder(const QList<QString> & path)
{
  createFolder(_model.invisibleRootItem(), path);
}

void FiltersView::addFilter(const QString & text, const QString & hash, const QList<QString> & path, bool warning)
{
  const bool filterIsVisible = FiltersVisibilityMap::filterIsVisible(hash);
  TagColorSet tagColors = FiltersTagMap::filterTags(hash);
  if (!_isInSelectionMode && !filterIsVisible) {
    return;
  }
  if (!_visibleTagColors.isEmpty() && (tagColors & _visibleTagColors).isEmpty()) {
    return;
  }
  QStandardItem * folder = getFolderFromPath(path);
  if (!folder) {
    folder = createFolder(_model.invisibleRootItem(), path);
  }
  auto item = new FilterTreeItem(text);
  item->setHash(hash);
  item->setWarningFlag(warning);
  item->setTags(tagColors);
  if (_isInSelectionMode) {
    addStandardItemWithCheckbox(folder, item);
    item->setVisibility(filterIsVisible);
  } else {
    folder->appendRow(item);
  }
}

void FiltersView::addFave(const QString & text, const QString & hash)
{
  const bool faveIsVisible = FiltersVisibilityMap::filterIsVisible(hash);
  TagColorSet tagColors = FiltersTagMap::filterTags(hash);
  if (!_isInSelectionMode && !faveIsVisible) {
    return;
  }
  if (!_visibleTagColors.isEmpty() && (tagColors & _visibleTagColors).isEmpty()) {
    return;
  }
  if (!_faveFolder) {
    createFaveFolder();
  }
  auto item = new FilterTreeItem(text);
  item->setHash(hash);
  item->setWarningFlag(false);
  item->setFaveFlag(true);
  item->setTags(tagColors);
  if (_isInSelectionMode) {
    addStandardItemWithCheckbox(_faveFolder, item);
    item->setVisibility(faveIsVisible);
  } else {
    _faveFolder->appendRow(item);
  }
}

void FiltersView::selectFave(const QString & hash)
{
  // Select the fave if the model is enabled
  if (ui->treeView->model() == &_model) {
    FilterTreeItem * fave = findFave(hash);
    if (fave) {
      ui->treeView->setCurrentIndex(fave->index());
      ui->treeView->scrollTo(fave->index(), QAbstractItemView::PositionAtCenter);
      updateIndexBeforeClick();
    }
  }
}

void FiltersView::selectActualFilter(const QString & hash, const QList<QString> & path)
{
  QStandardItem * folder = getFolderFromPath(path);
  if (folder) {
    for (int row = 0; row < folder->rowCount(); ++row) {
      auto filter = dynamic_cast<FilterTreeItem *>(folder->child(row));
      if (filter && (filter->hash() == hash)) {
        ui->treeView->setCurrentIndex(filter->index());
        ui->treeView->scrollTo(filter->index(), QAbstractItemView::PositionAtCenter);
        updateIndexBeforeClick();
        return;
      }
    }
  }
}

void FiltersView::removeFave(const QString & hash)
{
  FilterTreeItem * fave = findFave(hash);
  if (fave) {
    _model.removeRow(fave->row(), fave->index().parent());
    if (_faveFolder->rowCount() == 0) {
      removeFaveFolder();
    }
  }
}

void FiltersView::clear()
{
  removeFaveFolder();
  _model.invisibleRootItem()->removeRows(0, _model.invisibleRootItem()->rowCount());
  _model.setColumnCount(1);
  _cachedFolder = _model.invisibleRootItem();
  _cachedFolderPath.clear();
}

void FiltersView::sort()
{
  _model.invisibleRootItem()->sortChildren(0);
}

void FiltersView::sortFaves()
{
  if (_faveFolder) {
    _faveFolder->sortChildren(0);
  }
}

void FiltersView::updateFaveItem(const QString & currentHash, const QString & newHash, const QString & newName)
{
  FilterTreeItem * item = findFave(currentHash);
  if (!item) {
    return;
  }
  item->setText(newName);
  item->setHash(newHash);
}

void FiltersView::setHeader(const QString & header)
{
  _model.setHorizontalHeaderItem(0, new QStandardItem(header));
}

FilterTreeItem * FiltersView::selectedItem() const
{
  QModelIndex index = ui->treeView->currentIndex();
  return filterTreeItemFromIndex(index);
}

FilterTreeItem * FiltersView::filterTreeItemFromIndex(QModelIndex index) const
{
  // Get filter item even if it is the checkbox which is actually selected
  if (!index.isValid()) {
    return nullptr;
  }
  QStandardItem * item = _model.itemFromIndex(index);
  if (item) {
    int row = index.row();
    QStandardItem * parentFolder = item->parent();
    // parent is 0 for top level items
    if (!parentFolder) {
      parentFolder = _model.invisibleRootItem();
    }
    QStandardItem * leftItem = parentFolder->child(row, 0);
    if (leftItem) {
      auto item = dynamic_cast<FilterTreeItem *>(leftItem);
      if (item) {
        return item;
      }
    }
  }
  return nullptr;
}

QString FiltersView::selectedFilterHash() const
{
  FilterTreeItem * item = selectedItem();
  return item ? item->hash() : QString();
}

bool FiltersView::aFaveIsSelected() const
{
  FilterTreeItem * item = selectedItem();
  return item && item->isFave();
}

void FiltersView::preserveExpandedFolders()
{
  if (ui->treeView->model() == &_emptyModel) {
    return;
  }
  _expandedFolderPaths.clear();
  preserveExpandedFolders(_model.invisibleRootItem(), _expandedFolderPaths);
}

void FiltersView::restoreExpandedFolders()
{
  expandFolders(_expandedFolderPaths);
}

void FiltersView::loadSettings(const QSettings &)
{
  FiltersVisibilityMap::load();
  FiltersTagMap::load();
}

void FiltersView::saveSettings(QSettings & settings)
{
  if (_isInSelectionMode) {
    saveFiltersVisibility(_model.invisibleRootItem());
  }
  saveFiltersTags(_model.invisibleRootItem());
  preserveExpandedFolders();
  settings.setValue("Config/ExpandedFolders", QStringList(_expandedFolderPaths));
  FiltersVisibilityMap::save();
  FiltersTagMap::save();
}

void FiltersView::enableSelectionMode()
{
  _isInSelectionMode = true;
}

void FiltersView::disableSelectionMode()
{
  _model.setHorizontalHeaderItem(1, nullptr);
  _isInSelectionMode = false;
  saveFiltersVisibility(_model.invisibleRootItem());
}

void FiltersView::uncheckFullyUncheckedFolders()
{
  uncheckFullyUncheckedFolders(_model.invisibleRootItem());
}

void FiltersView::adjustTreeSize()
{
  ui->treeView->adjustSize();
}

void FiltersView::expandFolders(QList<QString> & folderPaths)
{
  expandFolders(folderPaths, _model.invisibleRootItem());
}

bool FiltersView::eventFilter(QObject * watched, QEvent * event)
{
  if (watched != ui->treeView) {
    return QObject::eventFilter(watched, event);
  }
  if (event->type() == QEvent::KeyPress) {
    auto keyEvent = dynamic_cast<QKeyEvent *>(event);
    if (keyEvent && (keyEvent->key() == Qt::Key_Delete)) {
      FilterTreeItem * item = selectedItem();
      if (item && item->isFave()) {
        QMessageBox::StandardButton button;
        button = QMessageBox::question(this,                                                                                      //
                                       tr("Remove fave"),                                                                         //
                                       QString(tr("Do you really want to remove the following fave?\n\n%1\n")).arg(item->text()), //
                                       QMessageBox::Yes | QMessageBox::No,                                                        //
                                       QMessageBox::Yes);
        if (button == QMessageBox::Yes) {
          emit faveRemovalRequested(item->hash());
          return true;
        }
      }
    }
  }
  return QObject::eventFilter(watched, event);
}

void FiltersView::setVisibleTagColors(const TagColorSet & colors)
{
  _visibleTagColors = colors;
}

TagColorSet FiltersView::visibleTagColors() const
{
  return _visibleTagColors;
}

void FiltersView::expandFolders(const QList<QString> & folderPaths, QStandardItem * folder)
{
  int rows = folder->rowCount();
  for (int row = 0; row < rows; ++row) {
    auto * subFolder = dynamic_cast<FilterTreeFolder *>(folder->child(row));
    if (subFolder) {
      if (folderPaths.contains(subFolder->path().join(FilterTreePathSeparator))) {
        ui->treeView->expand(subFolder->index());
      } else {
        ui->treeView->collapse(subFolder->index());
      }
      expandFolders(folderPaths, subFolder);
    }
  }
}

void FiltersView::editSelectedFaveName()
{
  FilterTreeItem * item = selectedItem();
  if (item && item->isFave()) {
    ui->treeView->edit(item->index());
  }
}

void FiltersView::expandAll()
{
  ui->treeView->expandAll();
}

void FiltersView::collapseAll()
{
  ui->treeView->collapseAll();
}

void FiltersView::expandFaveFolder()
{
  if (_faveFolder) {
    ui->treeView->expand(_faveFolder->index());
  }
}

void FiltersView::onCustomContextMenu(const QPoint & point)
{
  QModelIndex index = ui->treeView->indexAt(point);
  if (!index.isValid()) {
    return;
  }
  FilterTreeItem * item = filterTreeItemFromIndex(index);
  if (!item) {
    return;
  }
  onItemClicked(index);
  if (item->isFave()) {
    _faveContextMenu->deleteLater();
    _faveContextMenu = itemContextMenu(MenuType::Fave, item);
    _faveContextMenu->exec(ui->treeView->mapToGlobal(point));
  } else {
    _filterContextMenu->deleteLater();
    _filterContextMenu = itemContextMenu(MenuType::Filter, item);
    _filterContextMenu->exec(ui->treeView->mapToGlobal(point));
  }
}

void FiltersView::onRenameFaveFinished(QWidget * editor)
{
  auto lineEdit = dynamic_cast<QLineEdit *>(editor);
  Q_ASSERT_X(lineEdit, "Rename Fave", "Editor is not a QLineEdit!");
  FilterTreeItem * item = selectedItem();
  if (!item) {
    return;
  }
  emit faveRenamed(item->hash(), lineEdit->text());
}

void FiltersView::onReturnKeyPressedInFiltersTree()
{
  FilterTreeItem * item = selectedItem();
  if (item) {
    emit filterSelected(item->hash());
  } else {
    QModelIndex index = ui->treeView->currentIndex();
    QStandardItem * item = _model.itemFromIndex(index);
    FilterTreeFolder * folder = item ? dynamic_cast<FilterTreeFolder *>(item) : nullptr;
    if (folder) {
      if (ui->treeView->isExpanded(index)) {
        ui->treeView->collapse(index);
      } else {
        ui->treeView->expand(index);
      }
    }
    emit filterSelected(QString());
  }
}

void FiltersView::onItemClicked(QModelIndex index)
{
  if (index != _indexBeforeClick) {
    FilterTreeItem * item = filterTreeItemFromIndex(index);
    if (item) {
      emit filterSelected(item->hash());
    } else {
      emit filterSelected(QString());
    }
  }
  updateIndexBeforeClick();
}

void FiltersView::onItemChanged(QStandardItem * item)
{
  if (!item->isCheckable()) {
    return;
  }
  int row = item->index().row();
  QStandardItem * parentFolder = item->parent();
  if (!parentFolder) {
    // parent is 0 for top level items
    parentFolder = _model.invisibleRootItem();
  }
  QStandardItem * leftItem = parentFolder->child(row);
  if (!leftItem) {
    return;
  }
  auto folder = dynamic_cast<FilterTreeFolder *>(leftItem);
  if (folder) {
    folder->applyVisibilityStatusToFolderContents();
  }
  // Force an update of the view by triggering a call of
  // QStandardItem::emitDataChanged()
  leftItem->setData(leftItem->data());
}

void FiltersView::onContextMenuRemoveFave()
{
  emit faveRemovalRequested(selectedFilterHash());
}

void FiltersView::onContextMenuRenameFave()
{
  editSelectedFaveName();
}

void FiltersView::onContextMenuAddFave()
{
  emit faveAdditionRequested(selectedFilterHash());
}

void FiltersView::uncheckFullyUncheckedFolders(QStandardItem * folder)
{
  int rows = folder->rowCount();
  for (int row = 0; row < rows; ++row) {
    auto subFolder = dynamic_cast<FilterTreeFolder *>(folder->child(row));
    if (subFolder) {
      uncheckFullyUncheckedFolders(subFolder);
      if (subFolder->isFullyUnchecked()) {
        subFolder->setVisibility(false);
      }
    }
  }
}

void FiltersView::preserveExpandedFolders(QStandardItem * folder, QList<QString> & list)
{
  int rows = folder->rowCount();
  for (int row = 0; row < rows; ++row) {
    auto subFolder = dynamic_cast<FilterTreeFolder *>(folder->child(row));
    if (subFolder) {
      if (ui->treeView->isExpanded(subFolder->index())) {
        list.push_back(subFolder->path().join(FilterTreePathSeparator));
      }
      preserveExpandedFolders(subFolder, list);
    }
  }
}

void FiltersView::createFaveFolder()
{
  if (_faveFolder) {
    return;
  }
  _faveFolder = new FilterTreeFolder(tr(FAVE_FOLDER_TEXT));
  _faveFolder->setFaveFolderFlag(true);
  _model.invisibleRootItem()->appendRow(_faveFolder);
  _model.invisibleRootItem()->sortChildren(0);
}

void FiltersView::removeFaveFolder()
{
  if (!_faveFolder) {
    return;
  }
  _model.invisibleRootItem()->removeRow(_faveFolder->row());
  _faveFolder = nullptr;
}

void FiltersView::addStandardItemWithCheckbox(QStandardItem * folder, FilterTreeAbstractItem * item)
{
  QList<QStandardItem *> items;
  items.push_back(item);
  auto checkBox = new QStandardItem;
  checkBox->setCheckable(true);
  checkBox->setEditable(false);
  item->setVisibilityItem(checkBox);
  items.push_back(checkBox);
  folder->appendRow(items);
}

QStandardItem * FiltersView::getFolderFromPath(const QList<QString> & path)
{
  if (path == _cachedFolderPath) {
    return _cachedFolder;
  }
  _cachedFolder = getFolderFromPath(_model.invisibleRootItem(), path);
  _cachedFolderPath = path;
  return _cachedFolder;
}

QStandardItem * FiltersView::createFolder(QStandardItem * parent, QList<QString> path)
{
  Q_ASSERT_X(parent, "FiltersView", "Create folder path in null parent");
  if (path.isEmpty()) {
    return parent;
  }

  // Look for already existing base folder in parent
  QString translatedFirstFolderText = FilterTreeAbstractItem::removeWarningPrefix(FilterTextTranslator::translate(path.front()));
  for (int row = 0; row < parent->rowCount(); ++row) {
    auto folder = dynamic_cast<FilterTreeFolder *>(parent->child(row));
    if (folder && (folder->text() == translatedFirstFolderText)) {
      path.pop_front();
      return createFolder(folder, path);
    }
  }
  // Folder does not exist, we create it
  auto folder = new FilterTreeFolder(path.front());
  path.pop_front();
  if (_isInSelectionMode) {
    addStandardItemWithCheckbox(parent, folder);
    folder->setVisibility(true);
  } else {
    parent->appendRow(folder);
  }
  return createFolder(folder, path);
}

QStandardItem * FiltersView::getFolderFromPath(QStandardItem * parent, QList<QString> path)
{
  Q_ASSERT_X(parent, "FiltersView", "Get folder path from null parent");
  if (path.isEmpty()) {
    return parent;
  }
  QString translatedFirstFolderText = FilterTreeAbstractItem::removeWarningPrefix(FilterTextTranslator::translate(path.front()));
  for (int row = 0; row < parent->rowCount(); ++row) {
    auto folder = dynamic_cast<FilterTreeFolder *>(parent->child(row));
    if (folder && (folder->text() == translatedFirstFolderText)) {
      path.pop_front();
      return getFolderFromPath(folder, path);
    }
  }
  return nullptr;
}

void FiltersView::saveFiltersVisibility(QStandardItem * item)
{
  if (!item) {
    return;
  }
  auto filterItem = dynamic_cast<FilterTreeItem *>(item);
  if (filterItem) {
    FiltersVisibilityMap::setVisibility(filterItem->hash(), filterItem->isVisible());
    return;
  }
  int rows = item->rowCount();
  for (int row = 0; row < rows; ++row) {
    saveFiltersVisibility(item->child(row));
  }
}

void FiltersView::saveFiltersTags(QStandardItem * item)
{
  if (!item) {
    return;
  }
  auto filterItem = dynamic_cast<FilterTreeItem *>(item);
  if (filterItem) {
    FiltersTagMap::setFilterTags(filterItem->hash(), filterItem->tags());
    return;
  }
  int rows = item->rowCount();
  for (int row = 0; row < rows; ++row) {
    saveFiltersTags(item->child(row));
  }
}

QMenu * FiltersView::itemContextMenu(MenuType type, FilterTreeItem * item)
{
  QMenu * menu = new QMenu(this);
  QAction * action;
  switch (type) {
  case MenuType::Fave:
    action = menu->addAction(tr("Rename Fave"));
    connect(action, &QAction::triggered, this, &FiltersView::onContextMenuRenameFave);
    action = menu->addAction(tr("Remove Fave"));
    connect(action, &QAction::triggered, this, &FiltersView::onContextMenuRemoveFave);
    action = menu->addAction(tr("Clone Fave"));
    connect(action, &QAction::triggered, this, &FiltersView::onContextMenuAddFave);
    break;
  case MenuType::Filter:
    action = menu->addAction(tr("Add Fave"));
    connect(action, &QAction::triggered, this, &FiltersView::onContextMenuAddFave);
    break;
  }
  TagColorSet tags = item->tags();
  menu->addSeparator();
  for (TagColor color : TagColorSet::ActualColors) {
    QAction * action = TagAssets::action(menu,  //
                                         color, //
                                         tags.contains(color) ? TagAssets::IconMark::Check : TagAssets::IconMark::None);
    connect(action, &QAction::triggered, [this, item, color]() { //
      toggleItemTag(item, color);
      emit tagToggled(int(color));
    });
    menu->addAction(action);
  }
  menu->addSeparator();
  int tagCount[int(TagColor::Count)];
  TagColorSet existingColors = FiltersTagMap::usedColors(tagCount);
  QMenu * removeMenu = menu->addMenu(tr("Remove All"));
  if (existingColors.isEmpty()) {
    removeMenu->setEnabled(false);
  } else {
    for (TagColor color : existingColors) {
      int iColor = int(color);
      removeMenu->addAction(action = TagAssets::action(removeMenu, color, TagAssets::IconMark::None));
      action->setText(QString(tr("%1 (%2 %3)")).arg(TagAssets::colorName(color)).arg(tagCount[iColor]).arg((tagCount[iColor] != 1) ? tr("Filters") : tr("Filter")));
      connect(action, &QAction::triggered, [this, color, iColor]() {
        FiltersTagMap::removeAllTags(color);
        emit tagToggled(iColor);
      });
    }
  }
  return menu;
}

void FiltersView::toggleItemTag(FilterTreeItem * item, TagColor color)
{
  item->toggleTag(color);
  if (!_visibleTagColors.contains(color)) {
    return;
  }
  QStandardItem * folder = item->parent();
  folder->removeRow(item->row());
  while (folder && (folder != _model.invisibleRootItem()) && (folder->rowCount() == 0)) {
    QStandardItem * folderParent = folder->parent();
    if (!folderParent) {
      folderParent = _model.invisibleRootItem();
    }
    const int row = folder->row();
    folderParent->removeRow(row);
    folder = folderParent;
  }
}

void FiltersView::updateIndexBeforeClick()
{
  _indexBeforeClick = ui->treeView->currentIndex();
}

FilterTreeItem * FiltersView::findFave(const QString & hash)
{
  const int count = _faveFolder ? _faveFolder->rowCount() : 0;
  for (int faveIndex = 0; faveIndex < count; ++faveIndex) {
    auto item = dynamic_cast<FilterTreeItem *>(_faveFolder->child(faveIndex));
    if (item && (item->hash() == hash)) {
      return item;
    }
  }
  return nullptr;
}

} // namespace GmicQt
