/***************************************************************************
**
**  This file is part of DinverDCGui.
**
**  DinverDCGui 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.
**
**  DinverDCGui 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: 2006-05-18
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <DinverCore.h>
#include <DinverDCCore.h>
#include <QGpGuiWave.h>
#include <SciFigs.h>
#include "DCModelViewer.h"
#include "ViewerParam.h"

namespace DinverDCGui {

/*!
  \class DCMOdelViewer DCModelViewer.h
  \brief Abstract class for all model viewers

  Samples offsets stored in _models (list of DCModelInfo) is only used between select() and loadModels()
  The rest of the time only the misfit, report index and report pointer are usefull.
*/

DCModelViewer::DCModelViewer(QWidget * parent)
    : QWidget(parent)
{
  TRACE;
  _sheet.setObjectName("sheet");
  _childLayout=new QVBoxLayout(this);
  _childLayout->setMargin(0);
  _childLayout->setSpacing(0);
#ifndef Q_OS_MAC
  _menuBar=new QMenuBar(this);
  _childLayout->addWidget(_menuBar);
#endif
  _childLayout->addWidget(&_sheet);
  resize(700, 500);

  _colorMap=new ColorMapWidget(nullptr);
  _colorMap->setObjectName("Palette");
  ColorMap map;
  map.setPalette(ColorPalette::preference("DCModelViewer", 20, ColorPalette::Hobi));
  map.setVLinear(0, 1);
  _colorMap->setColorMap(map);
  //_colorMap->setNumberPrecision(2);
  _colorMap->setTitle("Misfit value");
  _colorMap->setPrintXAnchor(6);
  _colorMap->setPrintYAnchor(9);
  _colorMap->setPrintWidth(15);
  _colorMap->setPrintHeight(1.5);
  _sheet.addObject(_colorMap);

  _misfits=nullptr;
  _graphs=nullptr;
  _curveLayers=nullptr;
  _targetLayers=nullptr;
  _pCounts=nullptr;
  _nGraphs=0;

  addActions();
}

DCModelViewer::~DCModelViewer()
{
  TRACE;
  for(int i=0;i<_nGraphs;i++) {
    // Reset the plot widgets before deleting the data vectors
    // If one layer is still being painted while deleting this
    // objects, make sure to stop painting and to prevent other
    // painting to occur.
    _curveLayers[i]->setPointCount(0, 0, nullptr);
    _curveLayers[i]->setValues(nullptr);
    delete [] _pCounts[i];
  }
  delete [] _misfits;
  delete [] _curveLayers;
  delete [] _targetLayers;
  delete [] _graphs;
  delete [] _pCounts;

  for(QList<DCModelInfo *>::iterator  it=_models.begin();it!=_models.end();++it) {
    DCModelInfo::removeReference(*it);
  }
}

QMenu * DCModelViewer::addMenu(QString title)
{
  TRACE;
  QMenu * m=new QMenu(this);
  m->setTitle(title);
#ifndef Q_OS_MAC
  _menuBar->addMenu(m);
#endif
  _sheet.addMenu(m);
  return m;
}

void DCModelViewer::addActions()
{
  TRACE;
  QAction * a;
  QMenu * m;

  // File menu
  m=addMenu(tr( "&File" ));
  _sheet.addFileActions(m, nullptr);

  a=new QAction(tr( "&Close" ), this);
  connect(a, SIGNAL(triggered()), this, SLOT(close()) );
  m->addAction(a);

  // Edit menu
  m=addMenu(tr( "&Edit" ));
  _sheet.addEditActions(m, nullptr);

  // Insert menu
  m=addMenu(tr( "&Insert" ));
  _sheet.addInsertActions(m, nullptr);

  // Format menu
  m=addMenu(tr( "&Format" ));
  _sheet.addFormatActions(m);

  m->addSeparator();

  a=new QAction(tr("Set &limits"), this);
  a->setStatusTip(tr("Adjust limits of plots automatically"));
  connect(a, SIGNAL(triggered()), this, SLOT(setLimits()));
  m->addAction(a);

  a=new QAction(tr("Set default palette"), this);
  a->setStatusTip(tr("Make the current palette the default one"));
  connect(a, SIGNAL(triggered()), this, SLOT(setDefaultPalette()));
  m->addAction(a);
}

void DCModelViewer::initGraphs(int nGraphs)
{
  TRACE;
  AxisWindow * w;

  _nGraphs=nGraphs;
  _graphs=new AxisWindow * [_nGraphs];
  _curveLayers=new XYValueLines * [_nGraphs];
  _targetLayers=new LineLayer * [_nGraphs];
  _pCounts=new int * [_nGraphs];
  for(int i=0; i<_nGraphs; i++) {
    _pCounts[i]=nullptr;
  }

  for(int i=0;i<_nGraphs;i++) {
    // initialize Vp graph
    w=new AxisWindow;
    w->setObjectName(QString("Graph%1").arg(i));
    _sheet.addObject(w);
    _graphs[i]=w;
    _curveLayers[i]=new XYValueLines(w, false);
    _curveLayers[i]->setObjectName("invesionCurves");
    _curveLayers[i]->setColorMap(_colorMap->colorMap());
    _curveLayers[i]->addTrackingAction(tr("Select models"), 0, tr("Select models to create a new plot."));
    _curveLayers[i]->addTrackingAction(tr("Discard models"), 1, tr("Discard models to create a new plot."));
    connect(_curveLayers[i], SIGNAL(dataSelected( int, const QVector<int>* )), this, SLOT(showSelectedModels( int, const QVector<int>* )) );
    setTitles(i);
    // Connect palettes in both ways
    connect(_curveLayers[i], SIGNAL(colorMapChanged(ColorMap)), _colorMap, SLOT(setColorMap(const ColorMap&)));
    connect(_colorMap, SIGNAL(changed(ColorMap)), _curveLayers[i], SLOT(setColorMap(const ColorMap&)));
    _targetLayers[i]=new LineLayer(w);
    _targetLayers[i]->setReferenceLine(targetReferenceLine());
    _targetLayers[i]->setObjectName("target");
    w->setPrintAnchor(Point2D(0.5+i*6.0, 1.0));
    w->setPrintWidth(6.0);
    w->setPrintHeight(8.0);
    w->updateInternalGeometry();
    _sheet.showObject(w);
  }
  _sheet.autoResizeContent();
}

TextEdit * DCModelViewer::addText(int ig, QString text)
{
  TRACE;
  AxisWindow * w=_graphs[ig];
  TextEdit * te=new TextEdit;
  te->setPrintXAnchor(w->printLeft(6) );
  te->setPrintYAnchor(0.5);
  te->setPrintWidth(6);
  te->setPrintHeight(8);
  te->setTextAsData(true);
  te->setText(text);
  te->adjustSize();
  _sheet.addObject(te);
  _sheet.showObject(te);
  return te;
}

void DCModelViewer::setLayerComments(const QStringList& reportList)
{
  TRACE;
  QString c;
  for(QStringList::const_iterator it=reportList.begin(); it!=reportList.end(); it++) {
    QFileInfo fi(*it);
    if(!c.isEmpty()) {
      c+=", ";
    }
    c+=fi.baseName();
  }
  for(int i=0;i<_nGraphs;i++) {
    _curveLayers[i]->setComments(c);
  }
}

void DCModelViewer::selectModels(QString reportFileName, double maxMisfit)
{
  TRACE;
  ReportReader * report=new ReportReader(reportFileName);
  if(!report->open()) {
    delete report;
    return;
  }
  maxMisfit*=1.0+1e-15;
  report->synchronize(); // make sure that report is synchronized on disk
  int nModels=report->nModels();
  // Fill in ReportModelInfo structure (counting of samples)
  int initialNModels=_models.count();
  DCReportBlock dcBlock(report->stream());
  for(int iModel=0;iModel<nModels;iModel++) {
    double misfit=report->misfit(iModel);
    if(misfit<maxMisfit) {
      // Read user block to get the number of samples
      int version=report->userBlockVersion("DISP");
      if(version>=0) {
        dcBlock.readProfiles(version);
        if(hasCurves(dcBlock, version)) {
          DCModelInfo * info=new DCModelInfo;
          info->setCurveCount(_nGraphs);
          setSampleCount(info, dcBlock);
          info->setReport(report);
          info->setIndexInReport(iModel);
          info->setMisfit(misfit);
          info->addReference();
          _models << info;
        }
      }
    }
  }
  if(_models.count()==initialNModels) { /* Means that no model were added with a reference to this report
                                            Hence the report won't be deleted automatically upon deletion of model infos */
    delete report;
  }
}

void DCModelViewer::selectModels(DCModelViewer * w, const QVector<int> * indexes)
{
  TRACE;
  // Make sure models are properly ordered (plotting index)
  std::sort(w->_models.begin(),w->_models.end(), DCModelInfo::misfitLessThan);
  int n=indexes->count();
  for(int i=0;i<n;i++) {
    DCModelInfo& info=*w->_models.at(indexes->at(i));
    ReportReader& report=*info.report();
    int version=report.userBlockVersion(info.indexInReport(), "DISP");
    if(version>=0) {
      DCReportBlock dcBlock(report.stream());
      dcBlock.readProfiles(version);
      if(hasCurves(dcBlock, version)) {
        info.setCurveCount(_nGraphs);
        setSampleCount(&info, dcBlock);
        info.addReference();
        _models << &info;
      }
    }
  }
}

void DCModelViewer::rejectModels(DCModelViewer * w, const QVector<int> * indexes)
{
  TRACE;
  QSet<int> rejectLookup;
  int n=indexes->count();
  for (int i=0;i<n;i++) {
    rejectLookup.insert(indexes->at(i));
  }
  // Make sure models are properly ordered (plotting index)
  std::sort(w->_models.begin(),w->_models.end(), DCModelInfo::misfitLessThan);
  n=w->modelCount();
  for (int i=0; i<n; i++) {
    if (!rejectLookup.contains(i)) {
      DCModelInfo& info = *w->_models.at(i);
      ReportReader& report=*info.report();
      int version=report.userBlockVersion(info.indexInReport(), "DISP");
      if(version>=0) {
        DCReportBlock dcBlock(report.stream());
        dcBlock.readProfiles(version);
        if(hasCurves(dcBlock, version)) {
          info.setCurveCount(_nGraphs);
          setSampleCount(&info, dcBlock);
          info.addReference();
          _models << &info;
        }
      }
    }
  }
}

void DCModelViewer::loadModels()
{
  TRACE;
  // Set curve offset for each model
  std::sort(_models.begin(),_models.end(), DCModelInfo::misfitLessThan);
  int nModels=_models.count();
  int * offsets=new int[_nGraphs];
  for(int i=0;i<_nGraphs;i++) offsets[i]=0;
  for(int iModel=0;iModel<nModels;iModel++) {
    DCModelInfo& info=*_models[iModel];
    for(int i=0;i<_nGraphs;i++) {
      info.setOffset(i, offsets[i]);
      offsets[i]+=info.sampleCount(i);
    }
  }
  // Initialize the count vectors for the five plots
  for(int i=0;i<_nGraphs;i++) {
    if(offsets[i]>0) {
      delete _pCounts[i];
      _pCounts[i]=new int[ nModels ];
      for(int iModel=0;iModel<nModels;iModel++) {
        _pCounts[i][iModel]=_models[iModel]->sampleCount(i);
      }
    }
  }
  // Set misfit values in plots
  delete [] _misfits;
  _misfits=new double [ nModels ];
  for(int iModel=0;iModel<nModels;iModel++) {
    _misfits[iModel]=_models[iModel]->misfit();
  }
  // Lock plots
  for(int i=0;i<_nGraphs;i++) {
    _curveLayers[i]->setValues(_misfits);
    _curveLayers[i]->lockDelayPainting();
  }
  // Set plots
  Point2D ** points=new Point2D *[_nGraphs];
  for(int i=0;i<_nGraphs;i++) {
    XYValueLines * curveLayer=_curveLayers[i];
    if(offsets[i]>0)
      curveLayer->setPointCount(offsets[i], nModels, _pCounts[i]);
    else
      curveLayer->setPointCount(0, 0, nullptr);
    points[i]=curveLayer->points();
  }
  // Load points into vectors
  std::sort(_models.begin(),_models.end(), DCModelInfo::loadLessThan);
  for(int iModel=0;iModel<nModels;iModel++) {
    DCModelInfo& info=*_models[iModel];
    ReportReader& report=*info.report();
    int version=report.userBlockVersion(info.indexInReport(), "DISP");
    if(version>=0) {
      DCReportBlock dcBlock(report.stream());
      dcBlock.readProfiles(version);
      if(hasCurves(dcBlock, version)) {
        report2plot(dcBlock, points, info);
      }
    }
  }
  delete [] points;
  // unlock plots
  for(int i=0;i<_nGraphs;i++) {
    _curveLayers[i]->unlock();
  }
  // Force redrawing
  setLimits();
  _sheet.deepUpdate();
  // Cleanup model info: save space for offset and number of samples
  for(QList<DCModelInfo *>::iterator  it=_models.begin();it!=_models.end();++it) {
    (*it)->setCurveCount(0);
  }
  delete [] offsets;
}

void DCModelViewer::setLimits()
{
  TRACE;
  double minMisfit=std::numeric_limits<double>::infinity();
  double maxMisfit=-std::numeric_limits<double>::infinity();
  Rect rmax;
  for(int i=0;i<_nGraphs;i++) {
    XYValueLines * curveLayer=_curveLayers[i];
    curveLayer->valueRange(minMisfit, maxMisfit);
    Rect r=curveLayer->boundingRect();
    if(i==0) rmax=r; else rmax.add(r);
    setLimits(i,r);
  }
  setGlobalLimits(rmax);
  // Adjust palette to effective misfit value range
  if(minMisfit>0.0) {
    _colorMap->setVLog(minMisfit, 0.5*(minMisfit+maxMisfit));
    _colorMap->setScaleType(Scale::Log);
  } else {
    _colorMap->setVLinear(minMisfit, 0.5*(minMisfit+maxMisfit));
  }

  _sheet.deepUpdate();
}

void DCModelViewer::setDefaultPalette()
{
  TRACE;
  ColorPalette::setPreference("DCModelViewer", _colorMap->colorMap().palette());
}

void DCModelViewer::setTarget(const TargetList *)
{
}

/*!
  indexes refers to the curve index
*/
void DCModelViewer::showSelectedModels(int id, const QVector<int>* indexes)
{
  TRACE;
  ViewerParam * d=new ViewerParam(QApplication::activeWindow());
  if(id==0)
    d->select(this, indexes);
  else
    d->reject(this, indexes);
  delete d;
}

void DCModelViewer::showTarget(bool isVisible)
{
  TRACE;
  for(int i=0;i<_nGraphs;i++) {
    if(_targetLayers[i]) {
      _targetLayers[i]->setOpacity(isVisible ? 1.0 : 0.0);
      _targetLayers[i]->deepUpdate();
    }
  }
}

} // namespace DinverDCGui
