//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#include "TransferFunctionDlg.hpp"
#include "TransferFunction.hpp"
#include <QPalette>
#include <QFileDialog>
#include <QDialogButtonBox>
#include <QInputDialog>
#include <QColorDialog>
#include <QMessageBox>
#include <cassert>
#include "Dir.hpp"

#include <QTextStream>
#include <stdio.h>
#include "Information.hpp"

#define CURRENT_FUNCTION "<Current Function>"

namespace mgx 
{
  using Information::err;
  
  TransferFunctionDlg::TransferFunctionDlg(QWidget* parent, Qt::WindowFlags f)
    : QDialog(parent, f)
  {
    ui.setupUi(this);
    QPushButton* btn = ui.buttonBox->addButton("Auto Adjust", QDialogButtonBox::ActionRole);
    connect(ui.tickWidth, SIGNAL(valueChanged(int)), ui.fctViewer, SLOT(setMarkerSize(int)));
    connect(ui.checkSize, SIGNAL(valueChanged(int)), ui.fctViewer, SLOT(setCheckSize(int)));
    connect(ui.fctViewer, SIGNAL(changedTransferFunction(const TransferFunction &)), this,
            SIGNAL(changedTransferFunction(const TransferFunction &)));
    connect(btn, SIGNAL(clicked()), ui.fctViewer, SLOT(autoAdjust()));
    // For testing, set a random histogram
    std::vector<double> h(256);
    for(int i = 0; i < 256; ++i) {
      h[i] = 128 * 128 - (i - 128) * (i - 128);
    }
    ui.fctViewer->setHistogram(h);
    loadSettings();
  }
  
  void TransferFunctionDlg::on_useChecks_toggled(bool on)
  {
    if(on)
      ui.fctViewer->setBackgroundType(QTransferFunctionViewer::BG_CHECKS);
  }
  
  void TransferFunctionDlg::on_useWhite_toggled(bool on)
  {
    if(on)
      ui.fctViewer->setBackgroundType(QTransferFunctionViewer::BG_WHITE);
  }
  
  void TransferFunctionDlg::on_useBlack_toggled(bool on)
  {
    if(on)
      ui.fctViewer->setBackgroundType(QTransferFunctionViewer::BG_BLACK);
  }
  
  void TransferFunctionDlg::on_useRGB_toggled(bool on)
  {
    if(on)
      ui.fctViewer->setInterpolation(TransferFunction::RGB);
  }
  
  void TransferFunctionDlg::on_useHSV_toggled(bool on)
  {
    if(on)
      ui.fctViewer->setInterpolation(TransferFunction::HSV);
  }
  
  void TransferFunctionDlg::on_useCyclicHSV_toggled(bool on)
  {
    if(on)
      ui.fctViewer->setInterpolation(TransferFunction::CYCLIC_HSV);
  }
  
  #define NB_DEFAULT_FCT 24
  
  const char* default_fct_names[NB_DEFAULT_FCT]
    = { "Hue Scale",     "Jet",             "Scale Gray",    "Scale Red",       "Scale Green",  "Scale Blue",
        "Scale Yellow",  "Scale Cyan",      "Scale Purple",  "Constant Gray",   "Constant Red", "Constant Green",
        "Constant Blue", "Constant Yellow", "Constant Cyan", "Constant Purple", "French Flag",
        "Blackbody",     "Uniform Jet",     "Warm Helix",    "Cool Helix",      "Full Helix", "Batlow","Parula"
      };
  
  TransferFunction (*default_fcts[NB_DEFAULT_FCT])()
    = { &TransferFunction::hue_scale,       &TransferFunction::jet,             &TransferFunction::scale_gray,
        &TransferFunction::scale_red,       &TransferFunction::scale_green,     &TransferFunction::scale_blue,
        &TransferFunction::scale_yellow,    &TransferFunction::scale_cyan,      &TransferFunction::scale_purple,
        &TransferFunction::constant_gray,   &TransferFunction::constant_red,    &TransferFunction::constant_green,
        &TransferFunction::constant_blue,   &TransferFunction::constant_yellow, &TransferFunction::constant_cyan,
        &TransferFunction::constant_purple, &TransferFunction::french_flag,
        &TransferFunction::blackbody,       &TransferFunction::uniform_jet,
        &TransferFunction::helix_warm,      &TransferFunction::helix_cool,      &TransferFunction::helix_full,
         &TransferFunction::batlow_10, &TransferFunction::parula
      };
  
  void TransferFunctionDlg::loadSettings(bool changeFunction)
  {
    fct_names.clear();
    fcts.clear();
    for(int i = 0; i < NB_DEFAULT_FCT; ++i) {
      fct_names.push_back(default_fct_names[i]);
      fcts.push_back(default_fcts[i]());
    }
    resetFunctionList();
    if(changeFunction) {
      ui.functionList->setCurrentIndex(0);
    }

    int tw = 10, cs = 20;
    ui.tickWidth->setValue(tw);
    ui.checkSize->setValue(cs);
    ui.fctViewer->setMarkerSize(tw);
    ui.fctViewer->setCheckSize(cs);

    ui.useChecks->setChecked(true);
    ui.fctViewer->setBackgroundType(QTransferFunctionViewer::BG_CHECKS);

    QColor col(200, 200, 200, 200);
    ui.fctViewer->setSelectionColor(col);
    setColor(ui.selectionColor, col);
  }
  
  void TransferFunctionDlg::resetFunctionList()
  {
    QStringList items = fct_names;
    items.sort();
    ui.functionList->clear();
    ui.functionList->addItem(CURRENT_FUNCTION);
    ui.functionList->addItems(items);
  }
  
  QColor TransferFunctionDlg::getColor(QWidget* w) {
    return w->palette().color(QPalette::Window);
  }
  
  void TransferFunctionDlg::setColor(QWidget* w, const QColor& col)
  {
    QPalette p = w->palette();
    p.setColor(QPalette::Window, col);
    w->setPalette(p);
    w->update();
  }
  
  bool TransferFunctionDlg::changeColor(QWidget* w)
  {
    QColor col = w->palette().color(QPalette::Window);
    QColor ncc = QColorDialog::getColor(col, w, "Change color", QColorDialog::ShowAlphaChannel);
    if(ncc.isValid()) {
      setColor(w, ncc);
      return true;
    }
    return false;
  }
  
  void TransferFunctionDlg::on_selectSelectionColor_clicked()
  {
    if(changeColor(ui.selectionColor)) {
      ui.fctViewer->setSelectionColor(getColor(ui.selectionColor));
    }
  }
  
  void TransferFunctionDlg::changeTransferFunction(const QString& name)
  {
    if(name == CURRENT_FUNCTION)
      changeTransferFunction(current);
    else {
      int i = fct_names.indexOf(name);
      if(i >= 0)
        changeTransferFunction(fcts[i]);
    }
  }
  
  void TransferFunctionDlg::changeTransferFunction(const TransferFunction& fct)
  {
    ui.fctViewer->changeTransferFunction(fct);
    switch(fct.interpolation()) {
    case TransferFunction::RGB:
      ui.useRGB->setChecked(true);
      break;
    case TransferFunction::HSV:
      ui.useHSV->setChecked(true);
      break;
    case TransferFunction::CYCLIC_HSV:
      ui.useCyclicHSV->setChecked(true);
      break;
    default:
      err << "Error, unknown interpolation" << endl;
    }
    emit changedTransferFunction(fct);
  }
  
  void TransferFunctionDlg::setDefaultTransferFunction(const TransferFunction& fct)
  {
    default_fct = fct;
    if(fct.empty())
      setTransferFunction(default_fct);
  }
  
  void TransferFunctionDlg::setTransferFunction(const TransferFunction& fct)
  {
    ui.functionList->clearEditText();
    ui.functionList->setCurrentIndex(-1);
    changeTransferFunction(fct);
  }
  
  void TransferFunctionDlg::on_functionList_currentIndexChanged(const QString& name) {
    changeTransferFunction(name);
  }
  
  void TransferFunctionDlg::reset()
  {
    loadSettings();
    current = default_fct;
    ui.fctViewer->changeTransferFunction(current);
  }
  
  void TransferFunctionDlg::reject()
  {
    ui.fctViewer->changeTransferFunction(current);
    QDialog::reject();
  }
  
  void TransferFunctionDlg::apply()
  {
    // int i = fct_names.indexOf(ui.functionList->currentText());
    // if(i >= 0)
    // fcts[i] = ui.fctViewer->transferFunction();
    current = ui.fctViewer->transferFunction();
    emit appliedTransferFunction(ui.fctViewer->transferFunction());
  }
  
  int TransferFunctionDlg::exec()
  {
    current = ui.fctViewer->transferFunction();
    loadSettings(false);
    return QDialog::exec();
  }
  
  void TransferFunctionDlg::accept()
  {
    QDialog::accept();
  }
  
  void TransferFunctionDlg::on_buttonBox_clicked(QAbstractButton* btn)
  {
    switch(ui.buttonBox->buttonRole(btn)) {
    case QDialogButtonBox::ResetRole:
      reset();
      break;
    case QDialogButtonBox::ApplyRole:
      apply();
      break;
    case QDialogButtonBox::AcceptRole:
      apply();
      break;
    default:
      break;
    }
  }
  
  void TransferFunctionDlg::on_exportFunction_clicked()
  {
    QString filename = ui.functionList->currentText();
    filename = QFileDialog::getSaveFileName(this, "Exporting current function", filename,
                                            "Function files (*.fct);;All files (*.*)");
    if(not filename.isEmpty()) {
      if(!filename.endsWith(".fct", Qt::CaseInsensitive)) {
        if(filename.contains("'")) {
          if(QMessageBox::question(this, "Exporting function", "The filename has an extension different from "
                                                               "'.fct'. Do you want to add this standard "
                                                               "extension to the file name?",
                                   QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)
            filename += ".fct";
        } else
          filename += ".fct";
      }
      const TransferFunction& fct = ui.fctViewer->transferFunction();
      QString s = fct.dump();
      QFile f(filename);
      if(!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QMessageBox::critical(this, "Error exporting function",
                              QString("The file '%1' could not be opened for writing").arg(filename));
        return;
      }
      QTextStream ts(&f);
      ts << s;
      ts.flush();
      f.close();
    }
  }
  
  void TransferFunctionDlg::on_importFunction_clicked()
  {
    QString filename = QFileDialog::getOpenFileName(this, "Importing a function", currentPath(),
                                                    "Function files (*.fct);;All files (*.*)");
    if(not filename.isEmpty()) {
      QFile f(filename);
      if(!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QMessageBox::critical(this, "Error importing function",
                              QString("The file '%1' could not be opened for reading").arg(filename));
        return;
      }
      QTextStream ts(&f);
      QString s = ts.readAll();
      f.close();
      changeTransferFunction(TransferFunction::load(s));
    }
  }
  
  void TransferFunctionDlg::on_saveFunction_clicked()
  {
    QString name = ui.functionList->currentText();
    if(name.isEmpty())
      return;
    int i = fct_names.indexOf(name);
    const TransferFunction& fct = ui.fctViewer->transferFunction();
    if(i >= 0) {
      fcts[i] = fct;
    } else {
      fcts.push_back(fct);
      fct_names.push_back(name);
      resetFunctionList();
    }
  }
  
  void TransferFunctionDlg::on_renameFunction_clicked()
  {
    QString name = ui.functionList->currentText();
    int i = fct_names.indexOf(name);
    if(i >= 0) {
      QString new_name = QInputDialog::getText(
          this, "Change Function Name", QString("Enter a new name for '%1':").arg(name), QLineEdit::Normal, name);
      if(new_name != name) {
        if(fct_names.contains(new_name)) {
          QMessageBox::critical(this, "Error renaming function",
                                QString("Error, there is already another function names '%1'").arg(new_name));
          return;
        }
        fct_names[i] = new_name;
      }
      resetFunctionList();
    }
  }
  
  void TransferFunctionDlg::on_deleteFunction_clicked()
  {
    QString name = ui.functionList->currentText();
    int i = fct_names.indexOf(name);
    if(i >= 0) {
      fct_names.erase(fct_names.begin() + i);
      fcts.erase(fcts.begin() + i);
      resetFunctionList();
    } else
      QMessageBox::information(this, "Error deleting function",
                               QString("Error, cannot find function '%1' to be deleted").arg(name));
  }
  
  void TransferFunctionDlg::changeHistogram(const std::vector<double>& h) {
    ui.fctViewer->setHistogram(h);
  }
  
  void TransferFunctionDlg::changeBounds(const std::pair<double, double>& bounds)
  {
    ui.fctViewer->setBounds(bounds.first, bounds.second);
  }
  
  void TransferFunctionDlg::setStickers(const std::vector<double>& pos) {
    ui.fctViewer->setStickers(pos);
  }
  
  const TransferFunction& TransferFunctionDlg::transferFunction() const {
    return ui.fctViewer->transferFunction();
  }
}
