/***************************************************************************
**
**  This file is part of HVGui.
**
**  HVGui 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.
**
**  HVGui 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 Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2003-12-08
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsyGui.h>

#include "AbstractResultSheet.h"
#include "AbstractResultWidget.h"
#include "SetCategory.h"
#include "CategoryGroupItem.h"

namespace HVGui {

  AbstractResultSheet::AbstractResultSheet(QWidget *parent)
    : GraphicSheetMenu(parent)
  {
    TRACE;
  }

  void AbstractResultSheet::addActions()
  {
    TRACE;
    QAction * a;

    menuTools=addMenu(tr("&Tools"));

    a=new QAction(tr("&Save results"), this);
    a->setStatusTip(tr("Save results of spectral analysis in a text file with process information (.log file)"));
    connect(a, SIGNAL(triggered()), this, SLOT(saveResults()));
    menuTools->addAction(a);
    saveResultsAction=a;

    menuTools->addSeparator();

    a=new QAction(tr("&Set categories"), this);
    a->setStatusTip(tr("Sort results into categories."));
    connect(a, SIGNAL(triggered()), this, SLOT(setCategories()));
    menuTools->addAction(a);

    a=new QAction(tr("&Categories to groups"), this);
    a->setStatusTip(tr( "Create signal groups from categories."));
    connect(a, SIGNAL(triggered()), this, SLOT(categoriesToGroups()));
    menuTools->addAction(a);

    a=new QAction(tr("&Groups to categories"), this);
    a->setStatusTip(tr("Set categories from signal groups."));
    connect(a, SIGNAL(triggered()), this, SLOT(groupsToCategories()));
    menuTools->addAction(a);
  }

  /*!
    Add a result widget at the top. Definitive layout is set by setLayout()
  */
  AbstractResultWidget * AbstractResultSheet::addWidget()
  {
    TRACE;
    AbstractResultWidget * w=createWidget();
    w->setValueTitle(_valueTitle);
    sheet()->addObject(w);
    _results.append(w);
    return w;
  }


  void AbstractResultSheet::setStations(const QList<AbstractStation *>& stations)
  {
    TRACE;
    _stations=stations;
    int n=_stations.count();
    sheet()->setStatusBar(GeopsyGuiEngine::instance()->statusBar());
    for(int is=0; is<n; is++) {
      AbstractStation * stat=_stations[is];
      int nres=stat->resultCount();
      for(int ires=0; ires<nres; ires++) {
        AbstractResultWidget * w=addWidget();
        connect(stat, SIGNAL(windowsChanged()), w, SLOT(deepUpdate()));
        w->setValues(stat->results(ires));
      }
    }
  }

  void AbstractResultSheet::showValues()
  {
    for(QList<AbstractResultWidget *>::const_iterator it=_results.begin(); it!=_results.end(); it++) {
      (*it)->showValues();
    }
  }

  const AbstractResults * AbstractResultSheet::firstValidResult() const
  {
    TRACE;
    QList<AbstractStation *>::const_iterator it;
    const AbstractResults * res=nullptr;
    for(it=_stations.begin(); it!=_stations.end(); it++) {
      AbstractStation& s=**it;
      res=s.results(0);
      if(res->windowCount()>0) break;
      res=nullptr;
    }
    return res;
  }

  QList<AbstractStation *> AbstractResultSheet::selectedStations() const
  {
    TRACE;
    QList<AbstractStation *> sel;
    for(QList<AbstractResultWidget *>::const_iterator it=_results.begin(); it!=_results.end(); it++) {
      if((*it)->isSelected()) {
        sel.append((*it)->values()->parent());
      }
    }
    std::sort(sel.begin(), sel.end());
    unique(sel);
    return sel;
  }

  QList<AbstractResultWidget *> AbstractResultSheet::selectedResults() const
  {
    TRACE;
    QList<AbstractResultWidget *> sel;
    for(QList<AbstractResultWidget *>::const_iterator it=_results.begin(); it!=_results.end(); it++) {
      if((*it)->isSelected()) {
        sel.append(*it);
      }
    }
    return sel;
  }

  bool AbstractResultSheet::selectAll(QString title)
  {
    TRACE;
    if(_stations.count()==1) {
      _sheet.selectAll();
      return true;
    } else {
      for(QList<AbstractResultWidget *>::iterator it=_results.begin(); it!=_results.end(); it++) {
        if((*it)->isSelected()) {
          return true;
        }
      }
      return _sheet.selectAll(title);
    }
  }

  void AbstractResultSheet::saveResults()
  {
    TRACE;
    static const QString title=tr("Save results");
    if(!selectAll(title)) return;
    // Checks if all stations have a name, a condition to export everything in a directory
    bool hasEmptyNames=false;
    for(QList<AbstractResultWidget *>::iterator it=_results.begin(); it!=_results.end(); it++) {
      if((*it)->values()->name().isEmpty()) {
        hasEmptyNames=true;
        break;
      }
    }
    // Count selected stations
    QList<AbstractResultWidget *> res=selectedResults();
    QString dirName;
    if(!hasEmptyNames && res.count()>1) {
      dirName=Message::getExistingDirectory(title);
      if(dirName.isEmpty()) {
        return;
      }
    }
    QDir d(dirName);
    for(QList<AbstractResultWidget *>::iterator it=res.begin();it!=res.end();it++) {
      save((*it)->values(), d, res.count()==1 || hasEmptyNames);
    }
  }

  void AbstractResultSheet::save(const AbstractResults * res, QDir outputDir, bool askIndividualFileName)
  {
    TRACE;
    QString suffix=fileSuffix();
    QString fileName=res->name();
    fileName.replace(" ", "_");
    if(askIndividualFileName) {
      fileName=Message::getSaveFileName(tr("Saving results"),
                                        tr("Result file (%1)").arg(suffix),
                                        fileName+suffix);
    } else if(fileName.isEmpty()) {
      if(Message::warning(MSG_ID, tr("Saving results"),
                          tr("Station at (%1): empty station name, please provide a file name to export results")
                            .arg(res->coordinates().toString(0, 'f')),
                          Message::ok(), Message::cancel(), true)==Message::Answer1) {
        return;
      }
      fileName=Message::getSaveFileName(tr("Saving results"), tr("Result file (%1)").arg(suffix));
    } else {
      fileName=outputDir.absoluteFilePath(fileName+suffix);
    }
    if(!fileName.isEmpty()) {
      QString log;
      if(res->parent()) {
        log=res->parent()->log(_parameters);
      }
      res->save(fileName, log);
    }
  }

  AbstractStation * AbstractResultSheet::station(const QString& name) const
  {
    TRACE;
    for(QList<AbstractStation *>::const_iterator it=_stations.begin(); it!=_stations.end(); it++) {
      AbstractStation * s=*it;
      if(s->name()==name) {
        return s;
      }
    }
    return nullptr;
  }

  void AbstractResultSheet::restoreMakeUp(QString fileName)
  {
    TRACE;
    // TODO: make of a group or just the graph? What about the label position if size is changed
    for(QList<AbstractResultWidget *>::iterator it=_results.begin(); it!=_results.end(); it++) {
       (*it)->restoreMakeUp(fileName);
    }
  }

  void AbstractResultSheet::setLayout(int nPlotsPerLine, double pageHeight)
  {
    TRACE;
    double x, y, dx=-1.0, dy=-1.0, minx, maxx, miny;
    SAFE_UNINITIALIZED(x,0);
    SAFE_UNINITIALIZED(y,0);
    SAFE_UNINITIALIZED(minx,0);
    SAFE_UNINITIALIZED(miny,0);
    SAFE_UNINITIALIZED(maxx,0);
    int pageIndex=0;
    for(QList<AbstractResultWidget *>::iterator it=_results.begin(); it!=_results.end(); it++) {
      AbstractResultWidget * w=*it;
      if(dx<0.0) { // Init geometry parameters from geometry of first graph
        minx=w->printLeft();
        miny=w->printTop();
        if(miny>pageHeight) miny=0.5;
        dx=minx+w->printWidth();
        dy=miny+w->printHeight();
        x=minx;
        y=miny;
        maxx=minx+nPlotsPerLine*dx;
      } else {
        x+=dx;
        if(x+dx>maxx) {
          x=minx;
          y+=dy;
        }
        if(y+dy>(pageIndex+1)*pageHeight) {
          pageIndex++;
          y=miny+pageIndex*pageHeight;
        }
      }
      w->setPrintLeft(x);
      w->setPrintTop(y);
      w->updateGeometry();
    }
  }

  /*!

  */
  void AbstractResultSheet::setCategories()
  {
    TRACE;
    if(_stations.isEmpty()) return;
    static const QString title=tr("Categories");
    if(!selectAll(title) ) return;
    QString msg;
    // Build a message with a summary of all categories and search for the most used
    QStringList allCategories;
    QMap<QString, int> selCategories;
    QList<AbstractResultWidget *> selRes;
    for(QList<AbstractResultWidget *>::const_iterator it=_results.begin(); it!=_results.end(); it++) {
      AbstractResultWidget * res=*it;
      QString cat=res->values()->category();
      if(res->isSelected()) {
        selRes.append(res);
        QMap<QString, int>::iterator itcat=selCategories.find(cat);
        if(itcat==selCategories.end()) {
          selCategories.insert(cat, 1);
        } else {
          itcat.value()++;
        }
        msg+=res->values()->name();
        msg+=": ";
        msg+=cat;
        msg+="\n";
      }
      allCategories.append(cat);
    }
    std::sort(allCategories.begin(), allCategories.end());
    unique(allCategories);
    QString cat;
    if(selCategories.count()>1) {
      if(Message::information(MSG_ID, tr("Categories"), tr("Selected stations belong to different categories:\n\n"
                                    "%1\nDo you realy want to change them?").arg(msg),
                                    Message::yes(), Message::no())==Message::Answer1) {
        return;
      }
      int maxi=0;
      for(QMap<QString,int>::iterator it=selCategories.begin(); it!=selCategories.end(); it++ ) {
        if(it.value()>maxi) {
          cat=it.key();
          maxi=it.value();
        }
      }
    } else {
      cat=selCategories.keys().first();
    }
    SetCategory * d=new SetCategory(this);
    d->setCategoryNames(allCategories);
    Settings::getWidget(d);
    d->setCurrentCategory(cat);
    if(d->exec()==QDialog::Accepted) {
      Settings::setWidget(d);
      QString catName=d->currentCategory();
      for(QList<AbstractResultWidget *>::iterator it=selRes.begin(); it!=selRes.end(); it++) {
        AbstractResultWidget * res=*it;
        res->setCategory(catName);
      }
      emit categoryChanged();
    }
  }

  SignalDatabase * AbstractResultSheet::database() const
  {
    TRACE;
    ASSERT(!_stations.isEmpty());
    Signal * s=_stations.first()->originalSignals()->firstValidSignal();
    ASSERT(s);
    return s->database();
  }

  /*!
    Creates groups from categories of selected stations.
  */
  void AbstractResultSheet::categoriesToGroups()
  {
    TRACE;
    if(_stations.isEmpty()) return;
    static const QString title=tr("Categories to groups");
    if(!selectAll(title)) return;

    SignalGroupFolder * baseFolder=new SignalGroupFolder;
    baseFolder->setParent(database()->masterGroup());
    baseFolder->setName(tr("categories-%1").arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")));
    QList<AbstractResultWidget *> res=selectedResults();
    for(QList<AbstractResultWidget *>::iterator it=res.begin(); it!=res.end(); it++) {
      AbstractStation * stat=(*it)->values()->parent();
      if(stat) {
        QString cat=(*it)->values()->category();
        SignalGroupFolder * catFolder;
        if(!cat.isEmpty()) {
          catFolder=static_cast<SignalGroupFolder *>(baseFolder->find(cat));
          if(!catFolder) {
            catFolder=new SignalGroupFolder(baseFolder);
            catFolder->setName(cat);
          }
        } else {
          catFolder=baseFolder;
        }
        StationGroup * g=new StationGroup(catFolder);
        g->setName((*it)->values()->name());
        int n=stat->nComponents();
        for(int i=0; i<n; i++) {
          g->addSignals(stat->originalSignals()->originals(i));
        }
      }
    }
  }

  /*!
    Sets categories from signal groups.
  */
  void AbstractResultSheet::groupsToCategories()
  {
    TRACE;
    if(_stations.isEmpty()) return;
    static const QString title=tr("Group to categories");
    if(!selectAll(title)) return;

    Dialog * d=new Dialog(this);
    QTreeView * w=new QTreeView;
    CategoryGroupItem * model=new CategoryGroupItem(w);
    model->setDatabase(database());
    model->setSignals(_stations);
    w->setModel(model);
    w->setSelectionMode(QAbstractItemView::ExtendedSelection);
    w->setEditTriggers(QAbstractItemView::NoEditTriggers);
    w->setHeaderHidden(true);
    d->setMainWidget(w, Dialog::OkCancel);
    d->setWindowTitle(title);
    Settings::getRect(d, "GroupsToCategories");
    if(d->exec()==QDialog::Accepted) {
      Settings::setRect(d, "GroupsToCategories");
      QModelIndexList sel=w->selectionModel()->selectedIndexes();
      QList<AbstractResultWidget *> res=selectedResults();
      for(QModelIndexList::iterator it=sel.begin(); it!=sel.end(); it++) {
        AbstractSignalGroup * g=model->group(*it);
        for(QList<AbstractResultWidget *>::iterator itr=res.begin();itr!=res.end();itr++) {
          AbstractStation * stat=(*itr)->values()->parent();
          if(stat && CategoryGroupItem::belongsTo(stat, g)) {
            App::log(tr("station %1 belongs to category %2\n").arg(stat->name()).arg(g->name()) );
            (*itr)->setCategory(g->name());
          }
        }
      }
      emit categoryChanged();
    }
  }

  /*!
    \a title must contain %1 that is replaced by units.
    If all results have not the same units, valueTitle is set to "n/a" units.
  */
  void AbstractResultSheet::setUnits(const QString& title)
  {
    TRACE;
    QString unit;
    for(QList<AbstractResultWidget *>::iterator it=_results.begin(); it!=_results.end(); it++) {
      QString stationUnit=(*it)->values()->unit();
      if(unit.isNull()) {
        unit=stationUnit;
      } else if(unit!=stationUnit) {
        unit="n/a";
      }
      (*it)->setValueTitle(title.arg(stationUnit));
    }
    setValueTitle(title.arg(unit));
  }

  void AbstractResultSheet::setValueTitle(const QString& t)
  {
    TRACE;
    _valueTitle=t;
  }


} // namespace HVGui
