//
// 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 <MeshProcessAttributes.hpp>
#include <Attributes.hpp>

#include <Information.hpp>

#include <QHash>
#include <QRegExp>
#include <QTreeWidget>
#include <QWidget>

namespace mgx
{
  bool ManageAttributes::setUpTree(const QStringList &attr)
  {
    ui.attrTreeWidget->clear();
    forall(const QString &text, attr) {
      QTreeWidgetItem *item = new QTreeWidgetItem(QStringList() << text << QString::number(attributes->size(text)));
			item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
      if(attributes->saveRequired(text))
        item->setCheckState(2, Qt::Checked);
      else
        item->setCheckState(2, Qt::Unchecked);
			ui.attrTreeWidget->addTopLevelItem(item);
    }
    ui.attrTreeWidget->sortItems(0, Qt::AscendingOrder);
    for(int i = 0; i < 3; i++)
      ui.attrTreeWidget->resizeColumnToContents(i);
    return true;
  }

  bool ManageAttributes::initialize(QWidget *parent)
  {
    Mesh *m;
    int meshId = parm("Mesh number").toInt();
    if(meshId < 0)
      m = currentMesh();
    else if(meshId == 0 or meshId == 1)
      m = this->mesh(meshId);
    else
      throw(QString("Invalid Mesh id, must be 0 or 1."));

    attributes = &m->attributes();
    QDialog dlg(parent);
    ui.setupUi(&dlg);
    QStringList attr = attributes->getAttrList();
    setUpTree(attr);
    connect(ui.attrTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem *, int)),
            this, SLOT(on_attrTreeWidget_itemClicked(QTreeWidgetItem *, int )));
    connect(ui.clearPushButton, SIGNAL(clicked()), this, SLOT(on_clearPushButton_clicked()));
    connect(ui.clearAllPushButton, SIGNAL(clicked()), this, SLOT(on_clearAllPushButton_clicked()));
    connect(ui.savePushButton, SIGNAL(clicked()), this, SLOT(on_savePushButton_clicked()));
    dlg.exec();

    return true;
  }

  void ManageAttributes::on_attrTreeWidget_itemClicked(QTreeWidgetItem *item, int column)
  {
  }

  void ManageAttributes::on_clearPushButton_clicked()
  {
    QList<QTreeWidgetItem *> list = ui.attrTreeWidget->selectedItems();
    forall(QTreeWidgetItem *i, list)
      attributes->erase(i->text(0));
    setUpTree(attributes->getAttrList());
  }

  void ManageAttributes::on_clearAllPushButton_clicked()
  {
    QList<QTreeWidgetItem *> list = ui.attrTreeWidget->selectedItems();
    setUpTree(attributes->clearNotparsedAttrList());
  }
  void ManageAttributes::on_savePushButton_clicked(){
    for(int itemCount = 0; itemCount < ui.attrTreeWidget->topLevelItemCount(); ++itemCount){
      if(Qt::Checked == ui.attrTreeWidget->topLevelItem(itemCount)->checkState(2))
        attributes->saveRequired(ui.attrTreeWidget->topLevelItem(itemCount)->text(0));
    }
  }
  REGISTER_PROCESS(ManageAttributes);


  void SaveAttributesCSVExtended::selectAll()
  {
    for(int itemCount = 0; itemCount < ui.attrMapTree->topLevelItemCount(); ++itemCount)
      ui.attrMapTree->topLevelItem(itemCount)->setCheckState(0, Qt::Checked);
  }

  void SaveAttributesCSVExtended::unselectAll()
  {
    for(int itemCount = 0; itemCount < ui.attrMapTree->topLevelItemCount(); ++itemCount)
      ui.attrMapTree->topLevelItem(itemCount)->setCheckState(0, Qt::Unchecked);
  }



 // file dialogue for save data
 bool SaveAttributesCSVExtended::initialize(QWidget* parent)
  {
    Mesh *m = currentMesh();
    QString filename = parm("File Name");
    if(parent)
      filename = QFileDialog::getSaveFileName(0, "Choose spreadsheet file to save", QDir::currentPath(), "CSV files (*.csv)");
    if(filename.isEmpty())
      return false;
    if(!filename.endsWith(".csv", Qt::CaseInsensitive))
      filename += ".csv";
    setParm("File Name", filename);

    if(!parent) return true;

    // now the new GUI
    QDialog dlg(parent);
    ui.setupUi(&dlg);

    connect(ui.selectAllButton, SIGNAL(clicked()), this, SLOT(selectAll()));
    connect(ui.unselectAllButton, SIGNAL(clicked()), this, SLOT(unselectAll()));

    fillTreeWidgetWithAttrMaps(m,ui.attrMapTree);

    if(dlg.exec() == QDialog::Accepted){
      QStringList newList;
      for(int itemCount = 0; itemCount < ui.attrMapTree->topLevelItemCount(); ++itemCount){
        QString currentAttr = ui.attrMapTree->topLevelItem(itemCount)->text(1);
        if(Qt::Checked == ui.attrMapTree->topLevelItem(itemCount)->checkState(0)){
          newList << currentAttr;
        }
      }
      attrMapsToBeSaved = newList;

    }

    return true;
  }


  bool SaveAttributesCSVExtended::run(Mesh* m, QString filename, QString missingData, QString genotype, QString sample, QString timepoint, QString stage)
  {
qDebug() << "ext\n";
    QFile file(filename);
    if(!file.open(QIODevice::WriteOnly)) {
      setErrorMessage(QString("File '%1' cannot be opened for writing").arg(filename));
      return false;
    }
    QTextStream out(&file);

    QString missing = "";
    if(missingData == "NA") missing = "NA";
    if(missingData == "0") missing = "0";

    QString headerFile = "";
    headerFile += "Label,Parent";

    if(genotype != ""){
      headerFile+= ",Genotype";
    }
    if(sample != ""){
      headerFile+= ",Sample";
    }
    if(timepoint != ""){
      headerFile+= ",Timepoint";
    }
    if(stage != ""){
      headerFile+= ",Stage";
    }

    QString measure = "Measure ";
    QString header = "Label ";
    QString doub = "Double";
    QString in = "Int";
    QString vect = "Vector";
    QString tens = "Tensor";

    for(QString s : attrMapsToBeSaved){
      if(s.startsWith(header + doub)){
        headerFile+= "," + s.remove(header + doub).trimmed();
      } else if(s.startsWith(header + in)){
        QString sTrimmed = s.remove(header + in).trimmed();
        headerFile+= "," + sTrimmed;

        AttrMap<int, QString> &parentMap = m->attributes().attrMap<int, QString>("Measure Parent ID " + sTrimmed);

        QStringList filterList = attrMapsToBeSaved.filter("Parent ID " + sTrimmed);
        
        if(!parentMap.empty() and !filterList.empty()){
          sTrimmed = sTrimmed + "_ID";
          headerFile+= "," + sTrimmed;
        }

      } else if(s.startsWith(header + vect)){
        s=s.remove(header + vect).trimmed();
        headerFile+= "," + s + "(x)," + s + "(y)," + s + "(z)";
      } else if(s.startsWith(header + tens)){
        s=s.remove(header + tens).trimmed();
        headerFile+= "," + s + "(ev1x)," + s + "(ev1y)," + s + "(ev1z)";
        headerFile+= "," + s + "(ev2x)," + s + "(ev2y)," + s + "(ev2z)";
        headerFile+= "," + s + "(evalsx)," + s + "(evalsy)," + s + "(evalsz)";
      }
    }
    out << headerFile << endl;

    // get all the keys from the maps
    // (label from the mesh doesn't do it, as the maps could be parent label maps)
    //std::set<int> allLabels = findAllLabelsSet(m->graph());

    std::set<int> allLabels;

    for(QString s : attrMapsToBeSaved){
      if(s.startsWith(header + doub)){
        AttrMap<int, double> &currentMap = m->attributes().attrMap<int, double>(measure + s);
        for(auto &p : currentMap)
          allLabels.insert(p.first);
      } else if(s.startsWith(header + in)){
        AttrMap<int, int> &currentMap = m->attributes().attrMap<int, int>(measure + s);
        for(auto &p : currentMap)
          allLabels.insert(p.first);
      } else if(s.startsWith(header + vect)){
        AttrMap<int, Point3d> &currentMap = m->attributes().attrMap<int, Point3d>(measure + s);
        for(auto &p : currentMap)
          allLabels.insert(p.first);
      } else if(s.startsWith(header + tens)){
        AttrMap<int, SymmetricTensor> &currentMap = m->attributes().attrMap<int, SymmetricTensor>(measure + s);
        for(auto &p :currentMap)
          allLabels.insert(p.first);
      }
    }

    for(int label : allLabels){
      if(label < 1) 
        continue;
      QString data = "";
      data += QString::number(label);
      data += ",";
      if(m->parents().find(label) != m->parents().end()) data += QString::number(m->parents()[label]);
      else data += missing;

      if(genotype != ""){
        data += ",";
        data += genotype;
      }
      if(sample != ""){
        data += ",";
        data+= sample;
      }
      if(timepoint != ""){
        data += ",";
        data += timepoint;
      }
      if(stage != ""){
        data += ",";
        data += stage;
      }

      forall(QString s, attrMapsToBeSaved){
        if(s.startsWith(header + doub)){
          AttrMap<int, double>& currentMap = m->attributes().attrMap<int, double>(measure + s);
          data += ",";
          if(currentMap.find(label) != currentMap.end()) data += QString::number(currentMap[label]);
          else data += missing;
        } else if(s.startsWith(header + in)){
          AttrMap<int, int>& currentMap = m->attributes().attrMap<int, int>(measure + s);
          QString sTrimmed = s.remove(header + in).trimmed();
          data += ",";
          if(currentMap.find(label) != currentMap.end()){
            data += QString::number(currentMap[label]);
            QStringList filterList = attrMapsToBeSaved.filter("Parent ID " + sTrimmed);
            AttrMap<int, QString> &parentMap = m->attributes().attrMap<int, QString>("Measure Parent ID " + sTrimmed);
            if(!parentMap.empty() and !filterList.empty()){
              data += ",";
              data += parentMap[currentMap[label]];
            }
          } else {
            data += missing;
            QStringList filterList = attrMapsToBeSaved.filter("Parent ID " + sTrimmed);
            AttrMap<int, QString> &parentMap = m->attributes().attrMap<int, QString>("Measure Parent ID " + sTrimmed);
            if(!parentMap.empty() and !filterList.empty()){
              data += ",";
              data += missing;
            }
          }
        } else if(s.startsWith(header + vect)){
          AttrMap<int, Point3d>& currentMap = m->attributes().attrMap<int, Point3d>(measure + s);
          data += "," + QString::number(currentMap[label].x()) + "," + QString::number(currentMap[label].y()) + "," + QString::number(currentMap[label].z());
        } else if(s.startsWith(header + tens)){
          AttrMap<int, SymmetricTensor>& currentMap = m->attributes().attrMap<int, SymmetricTensor>(measure + s);
          data += "," + QString::number(currentMap[label].ev1().x())+ "," + QString::number(currentMap[label].ev1().y())+ "," + QString::number(currentMap[label].ev1().z());
          data += "," + QString::number(currentMap[label].ev2().x())+ "," + QString::number(currentMap[label].ev2().y())+ "," + QString::number(currentMap[label].ev2().z());
          data += "," + QString::number(currentMap[label].evals().x())+ "," + QString::number(currentMap[label].evals().y())+ "," + QString::number(currentMap[label].evals().z());
        }
      }
      out << data << endl;
    }

    SETSTATUS(QString("Wrote data file with %1 labels and %2 attribute maps").arg(allLabels.size(), attrMapsToBeSaved.size()));

    return true;
  }
  REGISTER_PROCESS(SaveAttributesCSVExtended);


    void SaveAttributesCSV::selectAll()
    {
      for(int itemCount = 0; itemCount < ui.attrMapTree->topLevelItemCount(); ++itemCount)
        ui.attrMapTree->topLevelItem(itemCount)->setCheckState(0, Qt::Checked);
    }

    void SaveAttributesCSV::unselectAll()
    {
      for(int itemCount = 0; itemCount < ui.attrMapTree->topLevelItemCount(); ++itemCount)
        ui.attrMapTree->topLevelItem(itemCount)->setCheckState(0, Qt::Unchecked);
    }



   // file dialogue for save data
   bool SaveAttributesCSV::initialize(QWidget* parent)
    {
      Mesh *m = currentMesh();
      QString filename = parm("File Name");
      if(parent)
        filename = QFileDialog::getSaveFileName(0, "Choose spreadsheet file to save", QDir::currentPath() + "/" + filename, "CSV files (*.csv)");
      if(filename.isEmpty())
        return false;
      if(!filename.endsWith(".csv", Qt::CaseInsensitive))
        filename += ".csv";
      setParm("File Name", filename);

      auto attrNames = parm("Attribute Name").split(",");
      attrMapsToBeSaved.clear();
      for(auto s : attrNames)
        attrMapsToBeSaved += s.trimmed();

      if(!parent) 
        return true;

      // now the new GUI
      QDialog dlg(parent);
      ui.setupUi(&dlg);

      connect(ui.selectAllButton, SIGNAL(clicked()), this, SLOT(selectAll()));
      connect(ui.unselectAllButton, SIGNAL(clicked()), this, SLOT(unselectAll()));

      fillTreeWidgetWithAttrMaps(m, ui.attrMapTree, &attrMapsToBeSaved);

      if(dlg.exec() == QDialog::Accepted){
        QStringList newList;
        for(int itemCount = 0; itemCount < ui.attrMapTree->topLevelItemCount(); ++itemCount){
          QString currentAttr = ui.attrMapTree->topLevelItem(itemCount)->text(1);
          if(Qt::Checked == ui.attrMapTree->topLevelItem(itemCount)->checkState(0)){
            newList << currentAttr;
          }
        }
        attrMapsToBeSaved = newList;
      }

      return true;
    }


    bool SaveAttributesCSV::run(Mesh* m, QString filename)
    {
      SaveAttributesCSVExtended *proc;
      if(!getProcess("Mesh/Attributes/Save to CSV Extended", proc))
        throw(QString("MeshProcessAttributes:: Unable to make SaveAttributesCSVExtended process"));
      proc->setAttrs(attrMapsToBeSaved);
      return proc->run(m, filename, "Empty", "", "", "", "");
    }
    REGISTER_PROCESS(SaveAttributesCSV);


  bool ClearAttrMap::run(Mesh* m, QString type, QString name)
  {

    if(name == "") return setErrorMessage("Name cannot be empty");

    if(type == "Double"){
      AttrMap<int, double>& currentMap = m->attributes().attrMap<int, double>("Measure Label Double " + name);
      currentMap.clear();
    } else if(type == "Int"){
      AttrMap<int, int>& currentMap = m->attributes().attrMap<int, int>("Measure Label Int " + name);
      currentMap.clear();
    } else if(type == "Vector"){
      AttrMap<int, Point3d>& currentMap = m->attributes().attrMap<int, Point3d>("Measure Label Vector " + name);
      currentMap.clear();
    } else { // Tensor
      AttrMap<int, SymmetricTensor>& currentMap = m->attributes().attrMap<int, SymmetricTensor>("Measure Label Tensor " + name);
      currentMap.clear();
    }

    return true;
  }
  REGISTER_PROCESS(ClearAttrMap);

}
