//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2016 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 <LabelEditorDlg.hpp>
#include <ui_LabelEditorDlg.h>
#include <QAbstractButton>
#include <QColorDialog>
#include <QFileDialog>
#include <QDir>
#include <Information.hpp>
#include <QFile>
#include <QTextStream>
#include <QMessageBox>
#include <QMenu>
#include <QPoint>
#include <QItemSelectionModel>
#include <Dir.hpp>
#include <stdlib.h>
#include <Random.hpp>
#include <Geometry.hpp>

using mgx::Colorf;
using mgx::Point3f;
using mgx::Point4f;
using mgx::ran;

LabelModel::LabelModel(std::vector<Colorf>* colors)
  : toChange(colors) , localCopy(*colors) {}

int LabelModel::rowCount(const QModelIndex& parent) const
{
  if(parent.isValid())
    return 0;
  return localCopy.size();
}

Qt::ItemFlags LabelModel::flags(const QModelIndex& ) const {
  return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QVariant LabelModel::data(const QModelIndex& index, int role) const
{
  if(index.column() > 0)
    return QVariant();
  if(index.row() >= (int)localCopy.size())
    return QVariant();
  int n = index.row();
  switch(role) {
  case Qt::DisplayRole:
    return QString::number(n);
  case Qt::DecorationRole:
    return (QColor)localCopy[n];
  }
  return QVariant();
}

void LabelModel::reset()
{
  beginResetModel();
  localCopy.resize(16);
  localCopy[0] = Colorf(1, 0, 0, 1);
  localCopy[1] = Colorf(0, 1, 0, 1);
  localCopy[2] = Colorf(0, 0, 1, 1);
  localCopy[3] = Colorf(1, 1, 0, 1);
  localCopy[4] = Colorf(1, 0, 1, 1);
  localCopy[5] = Colorf(0, 1, 1, 1);
  localCopy[6] = Colorf(.5, 0, 0, 1);
  localCopy[7] = Colorf(0, .5, 0, 1);
  localCopy[8] = Colorf(0, 0, .5, 1);
  localCopy[9] = Colorf(.5, 0, .5, 1);
  localCopy[10] = Colorf(.5, .5, 0, 1);
  localCopy[11] = Colorf(1, 0, .5, 1);
  localCopy[12] = Colorf(.5, .5, 1, 1);
  localCopy[13] = Colorf(0, .5, 1, 1);
  localCopy[14] = Colorf(.5, 0, 1, 1);
  localCopy[15] = Colorf(1, .5, 0, 1);
  endResetModel();
}

void LabelModel::makeGray()
{
  double inc = 1.0 / (localCopy.size() + 1);
  Colorf base(inc, inc, inc, 0);
  for(size_t i = 0; i < localCopy.size(); ++i) {
    localCopy[i] = (i + 1) * base;
    localCopy[i].a() = 1.;
  }
  emit dataChanged(index(0), index(localCopy.size() - 1));
}

void LabelModel::makeRandom()
{
  for(size_t i = 0; i < localCopy.size(); ++i) {
    Colorf col = Point4f(ran(Point3f(1, 1, 1)));
    col.a() = 1.f;
    localCopy[i] = col;
  }
  emit dataChanged(index(0), index(localCopy.size() - 1));
}

void LabelModel::apply() {
  *toChange = localCopy;
}

bool LabelModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
  if(index.column() > 0)
    return false;
  if(index.row() >= (int)localCopy.size())
    return false;
  if(role == Qt::DecorationRole) {
    localCopy[index.row()] = (Colorf)value.value<QColor>();
    emit dataChanged(index, index);
  }
  return false;
}

void LabelModel::setNbColors(int n)
{
  int prev = rowCount();
  if(n > prev)
    insertRows(prev, n - prev);
  else if(n < prev)
    removeRows(n, prev - n);
}

bool LabelModel::insertRows(int row, int count, const QModelIndex& parent)
{
  int prev = rowCount();
  if(row < prev or parent.isValid())
    return false;
  int n = prev + count;
  beginInsertRows(QModelIndex(), prev, n - 1);
  localCopy.resize(n);
  for(int i = prev; i < n; ++i)
    localCopy[i] = localCopy[i - prev];
  endInsertRows();
  return true;
}

bool LabelModel::removeRows(int row, int count, const QModelIndex& parent)
{
  int prev = rowCount();
  if(row != prev - count or parent.isValid())
    return false;
  beginRemoveRows(QModelIndex(), row, prev - 1);
  localCopy.resize(row);
  endRemoveRows();
  return true;
}

LabelEditorDlg::LabelEditorDlg(std::vector<Colorf>* colors, QWidget* parent)
  : QDialog(parent)
{
  ui = new Ui::LabelEditorDlg();
  ui->setupUi(this);
  _model = new LabelModel(colors);
  ui->labelsView->setModel(_model);
  ui->nbColors->setValue(_model->rowCount());
  importButton = ui->buttonBox->addButton("Import", QDialogButtonBox::ActionRole);
  exportButton = ui->buttonBox->addButton("Export", QDialogButtonBox::ActionRole);
  selectLabelAction = new QAction("Select on mesh", this);
  connect(selectLabelAction, SIGNAL(triggered()), this, SLOT(selectLabel()));
  makeLabelCurrentAction = new QAction("Make current", this);
  connect(makeLabelCurrentAction, SIGNAL(triggered()), this, SLOT(makeLabelCurrent()));
  connect(_model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(changeNbItems()));
  connect(_model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(changeNbItems()));
}

LabelEditorDlg::~LabelEditorDlg()
{
  ui->labelsView->setModel(0);
  delete _model;
  delete ui;
}

void LabelEditorDlg::on_makeGray_clicked() {
  _model->makeGray();
}

void LabelEditorDlg::on_makeRandom_clicked() {
  _model->makeRandom();
}

void LabelEditorDlg::on_buttonBox_clicked(QAbstractButton* btn)
{
  switch(ui->buttonBox->buttonRole(btn)) {
  case QDialogButtonBox::ApplyRole:
    _model->apply();
    emit update();
    break;
  case QDialogButtonBox::ResetRole:
    _model->reset();
    emit update();
    break;
  case QDialogButtonBox::ActionRole:
    if(btn->text() == "Import") {
      importLabels();
    } else if(btn->text() == "Export") {
      exportLabels();
    }
    break;
  default:
    break;
  }
}

void LabelEditorDlg::importLabels()
{
  QString filename = QFileDialog::getOpenFileName(this, "Select labels file name", mgx::currentPath(),
                                                  "Palette files (*.mgx.labels);;All files (*.*)");
  if(!filename.isEmpty())
    if(!importLabels(filename))
      QMessageBox::critical(
        this, "Error importing labels",
        QString("Cannot import labels from file '%1'. Check terminal for error.").arg(filename));
}

bool LabelEditorDlg::importLabels(QString filename)
{
  if(!QFile::exists(filename) and !filename.endsWith(".mgx.labels"))
    filename += ".mgx.labels";
  QFile file(filename);
  if(!file.open(QIODevice::ReadOnly)) {
    mgx::Information::out << "Error, cannot open file '" << filename << "' for reading" << endl;
    return false;
  }
  QTextStream ts(&file);
  QString header = ts.readLine();
  if(!header.startsWith("MGX LABELS")) {
    mgx::Information::out << "Error, file '" << filename << "' is not a MorphoGraphX label file" << endl;
    return false;
  }
  QStringList hv = header.split(" ");
  if(hv.size() != 3 or hv[2] != "1.0") {
    mgx::Information::out << "Error, file '" << filename << "' has an unknown version number" << endl;
    return false;
  }
  QString cntString = ts.readLine();
  int cnt;
  bool ok;
  cnt = cntString.trimmed().toInt(&ok);
  if(!ok or cnt < 0) {
    mgx::Information::out << "Error, in file '" << filename << "': Invalid number of colors" << endl;
    return false;
  }
  QAbstractItemModel* m = ui->labelsView->model();
  int n = m->rowCount();
  if(n > cnt)
    m->removeRows(cnt, n - cnt);
  else if(n < cnt)
    m->insertRows(n, cnt - n);
  for(int i = 0; i < cnt; ++i) {
    QString color = ts.readLine().trimmed();
    if(color.isEmpty()) {
      mgx::Information::out << "Error, in file '" << filename << "': Color " << i
                                   << " is invalid or non-existent (expected: " << cnt << " colors)" << endl;
      return false;
    }
    QColor col = QColor(color);
    QModelIndex idx = m->index(i, 0);
    m->setData(idx, col, Qt::DecorationRole);
  }
  return true;
}

void LabelEditorDlg::exportLabels()
{
  QString filename = QFileDialog::getSaveFileName(this, "Select labels file name", "",
                                                  "Palette files (*.mgx.labels);;All files (*.*)");
  if(!filename.isEmpty())
    if(!exportLabels(filename))
      QMessageBox::critical(
        this, "Error exporting labels",
        QString("Cannot export labels to file '%1'. Check terminal for error.").arg(filename));
}

bool LabelEditorDlg::exportLabels(QString filename)
{
  if(!filename.endsWith(".mgx.labels"))
    filename += ".mgx.labels";
  QFile file(filename);
  if(!file.open(QIODevice::WriteOnly)) {
    mgx::Information::out << "Error, cannot open file '" << filename << "' for writing" << endl;
    return false;
  }
  QTextStream ts(&file);
  QAbstractItemModel* m = ui->labelsView->model();
  ts << "MGX LABELS 1.0" << endl;
  int cnt = m->rowCount();
  ts << cnt << endl;
  for(int i = 0; i < cnt; ++i) {
    QModelIndex idx = m->index(i, 0);
    QColor col = m->data(idx, Qt::DecorationRole).value<QColor>();
    ts << col.name() << endl;
  }
  file.close();
  return true;
}

void LabelEditorDlg::on_labelsView_doubleClicked(const QModelIndex& idx)
{
  QColor col = _model->data(idx, Qt::DecorationRole).value<QColor>();
  if(col.isValid()) {
    col = QColorDialog::getColor(col, this, "Change color");
    if(col.isValid()) {
      _model->setData(idx, col, Qt::DecorationRole);
    }
  }
}

void LabelEditorDlg::on_setNbColors_clicked() {
  _model->setNbColors(ui->nbColors->value());
}

void LabelEditorDlg::on_labelsView_customContextMenuRequested(const QPoint& pos)
{
  QModelIndex idx = ui->labelsView->indexAt(pos);
  if(idx.isValid()) {
    selectedLabel = idx.row();
    QMenu* menu = new QMenu(this);
    menu->addAction(selectLabelAction);
    menu->addAction(makeLabelCurrentAction);
    menu->popup(ui->labelsView->mapToGlobal(pos));
  }
}

void LabelEditorDlg::makeLabelCurrent() {
  emit makeLabelCurrent(selectedLabel);
}

void LabelEditorDlg::selectLabel()
{
  int cnt = ui->labelsView->model()->rowCount();
  emit selectLabel(selectedLabel, cnt, true);
}

void LabelEditorDlg::changeNbItems() {
  ui->nbColors->setValue(ui->labelsView->model()->rowCount());
}

void LabelEditorDlg::setCurrentLabel(int label) {
  currentLabel = label;
}

void LabelEditorDlg::on_showCurrent_clicked()
{
  if(currentLabel > 0) {
    QItemSelectionModel* sel = ui->labelsView->selectionModel();
    const QAbstractItemModel* model = sel->model();
    int cnt = model->rowCount();
    QModelIndex midx = sel->model()->index(currentLabel % cnt, 0);
    sel->select(midx, QItemSelectionModel::ClearAndSelect);
    ui->labelsView->scrollTo(midx);
  }
}
