/***************************************************************************
**
**  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: 2005-12-22
**  Copyright: 2005-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreWave.h>
#include <QGpGuiTools.h>
#include <SciFigs.h>
#include "GroundModelViewer.h"

namespace DinverDCGui {

GroundModelViewer::GroundModelViewer(QWidget * parent)
    : DCModelViewer(parent)
{
  TRACE;
  setObjectName("GroundModelViewer");
  setWindowTitle(tr("Ground profiles:"));

  // Tools menu
  // TODO: move tool menu to DCModelViewer (especially "Save as report")
  // TODO: implement add Reference model
  QMenu * m;
  QAction * a;

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

  a=new QAction(tr( "Add reference model" ), this);
  a->setStatusTip(tr( "Import any .model file and plot its Vp, Vs and density" ));
  connect(a, SIGNAL(triggered()), this, SLOT(setLimits()) );
  m->addAction(a);

  a=new QAction(tr( "Add Min/Max profiles" ), this);
  a->setStatusTip(tr( "Compute the mininum and maximum profiles" ));
  connect(a, SIGNAL(triggered()), this, SLOT(minMaxProfiles()) );
  m->addAction(a);

  m->addSeparator();

  a=new QAction(tr("Save as report"), this);
  a->setStatusTip(tr("Save models and their curves in a .report file"));
  connect(a, SIGNAL(triggered()), this, SLOT(saveAsReport()));
  m->addAction(a);

  a=new QAction(tr("Export models"), this);
  a->setStatusTip(tr("Export profiles in a .model file, e.g. to process them with gpdc"));
  connect(a, SIGNAL(triggered()), this, SLOT(exportModels()));
  m->addAction(a);
}

GroundModelViewer::~GroundModelViewer()
{
  TRACE;
}

AbstractLine * GroundModelViewer::targetReferenceLine() const
{
  TRACE;
  PlotLine2D * line=new PlotLine2D;
  line->setPen(Pen( Qt::black, 0.6) );
  line->setSymbol(Symbol());
  return line;
}

void GroundModelViewer::setTitles(int i)
{
  TRACE;
  AxisWindow * w=_graphs[i];
  w->yAxis()->setTitle( "Depth (m)" );
  w->yAxis()->setTitleInversedScale( "1/Depth (1/m)" );
  w->yAxis()->setReversedScale(true);
  switch (i) {
  case 0:
    w->xAxis()->setTitle( "Vp (m/s)" );
    w->xAxis()->setTitleInversedScale( "Slowness P (s/m)" );
    w->setObjectName("vp");
    break;
  case 1:
    w->xAxis()->setTitle( "Vs (m/s)" );
    w->xAxis()->setTitleInversedScale( "Slowness S (s/m)" );
    w->setObjectName("vs");
    break;
  case 2:
    w->xAxis()->setTitle( "Density (t/m3)" );
    w->xAxis()->setTitleInversedScale( "1/Density (m3/t)" );
    w->setObjectName("rho");
    break;
  case 3:
    w->xAxis()->setTitle( "Pitch" );
    w->xAxis()->setTitleInversedScale( "1/Pitch" );
    w->setObjectName("pitch");
    break;
  default:
    w->xAxis()->setTitle( "Resistivity (ohm m)" );
    w->xAxis()->setTitleInversedScale( "Conductivity (S/m)" );
    w->xAxis()->setScaleType(Scale::Log);
    w->setObjectName("res");
    break;
  }
}

void GroundModelViewer::setSampleCount(DCModelInfo * info, DCReportBlock& dcBlock)
{
  TRACE;
  int ns;
  int n=_nGraphs;
  if(n>5) n=5;
  for(int i=0;i<n;i++) {
    if(dcBlock.profile(i)) {
      dcBlock.stream() >> ns;
      info->setSampleCount(i,2*ns);
    } else {
      info->setSampleCount(i,0);
    }
  }
  // For the sake of security, even if from the graphical interface it is not possible to have a number of
  // graphs greater than 5
  for(int i=n;i<_nGraphs;i++) {
    info->setSampleCount(i,0);
  }
}

void GroundModelViewer::report2plot(DCReportBlock& dcBlock, Point2D ** points, const DCModelInfo& info)
{
  TRACE;
  int n=_nGraphs;
  if(n>5) n=5;
  for(int i=0;i<n;i++) {
    if(dcBlock.profile(i)) {
      Profile::report2plot(dcBlock.stream(), points[i]+info.offset(i));
    } else {
      _graphs[i]->hide();
    }
  }
}

void GroundModelViewer::setLimits(int i, const Rect& r)
{
  TRACE;
  Axis * a=_graphs[i]->xAxis();
  if(r.x1()>0.0) {
    switch(a->scaleType()) {
    case Scale::Log:
    case Scale::InversedLog:
    case Scale::Inversed:
      a->setRange(r.x1() * 0.95, r.x2() * 1.05);
      break;
    case Scale::Linear:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      a->setRange(0.0, r.x2() * 1.05);
      break;
    }
  } else {
    if(r.x2()<0.0) {
      a->setRange(r.x1() * 1.05, r.x2() * 0.95);
    } else {
      a->setRange(r.x1() * 1.05, r.x2() * 1.05);
    }
  }
}

void GroundModelViewer::setGlobalLimits(const Rect& r)
{
  TRACE;
  for(int i=0; i<_nGraphs; i++) {
    _graphs[ i ] ->yAxis()->setRange(0, r.y2() * 1.05);
  }
}

void GroundModelViewer::exportModels()
{
  TRACE;
  QString fileName=Message::getSaveFileName(tr("Export models ..."), tr("Model file (*.model)"));
  if(fileName.length()>0) {
    QFile f(fileName);
    if( !f.open(QIODevice::WriteOnly) ) {
      Message::warning(MSG_ID, tr("Exporting models"),
                            tr("Cannot open file for writing"), Message::cancel());
      return;
    }
    QTextStream s(&f);
    std::sort(_models.begin(),_models.end(), DCModelInfo::loadLessThan);
    int nModels=_models.count();
    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);
        Profile vp, vs, rho;
        if(dcBlock.vp()) vp.readReport(dcBlock.stream()); else continue;
        if(dcBlock.vs()) vs.readReport(dcBlock.stream()); else continue;
        if(dcBlock.rho()) rho.readReport(dcBlock.stream()); else continue;
        // These are raw profiles without the same depth sampling
        QVector<double> depths;
        depths << vp.depths();
        depths << vs.depths();
        depths << rho.depths();
        std::sort(depths.begin(), depths.end());
        unique(depths);
        vp.resample(depths);
        vs.resample(depths);
        rho.resample(depths);
        Seismic1DModel * surfModel =
          DCReportBlock::surfaceWaveModel(depths, vp.values(), vs.values(), rho.values());
        surfModel->toStream(s);
        delete surfModel;
      }
    }
  }
}

/*!
  Save models and their curves to a .report file
*/
void GroundModelViewer::saveAsReport()
{
  TRACE;
  QString fileName=Message::getSaveFileName(tr("Save as report ..."), tr("Report file (*.report)"));
  if (fileName.length()>0) {
    if(!ReportWriter::initReport(fileName, tr("Open report for writing"), ReportWriter::Ask)) {
      return;
    }
    ReportWriter outReport(fileName);
    if(!outReport.open()) {
      Message::warning(MSG_ID, tr("Saving as report"),
                            tr("Cannot open file for writing"), Message::cancel());
      return;
    }
    std::sort(_models.begin(),_models.end(), DCModelInfo::loadLessThan);
    int nModels=_models.count();
    for(int iModel=0;iModel<nModels;iModel++) {
      DCModelInfo& info=*_models[iModel];
      ReportReader& inReport=*info.report();
      QDataStream& s=inReport.stream();
      double misfit=inReport.misfit(info.indexInReport());
      int nParams;
      uint checksum;
      double val;
      qint64 blockOffset=s.device()->pos();
      s >> nParams;
      s >> checksum;
      Parameter * params[nParams];
      for (int id=0 ; id<nParams; id++) {
        s >> val;
        params[id]=new Parameter;
        params[id]->setRealValue(val);
      }
      outReport.addModel(misfit, checksum, nParams, params);
      for (int id=0; id<nParams; id++) delete params[id];
      s.device()->seek(blockOffset);
      DCReportBlock::write(&outReport, &inReport);
    }
  }
}

void GroundModelViewer::minMaxProfiles()
{
  TRACE;
  int n=_nGraphs;
  if(n>5) n=5;
  // Take 100 samples on current axis limits and init vectors for all graphs
  Curve<Point2D> * minCurves=new Curve<Point2D>[n];
  Curve<Point2D> * maxCurves=new Curve<Point2D>[n];
  for(int i=0;i<n;i++) {
    Curve<Point2D>& vMin=minCurves[i];
    vMin.resize(100);
    Curve<Point2D>& vMax=maxCurves[i];
    vMax.resize(100);
    double minDepth=_graphs[i]->yAxis()->minimum();
    double maxDepth=_graphs[i]->yAxis()->maximum();
    double dDepth=(maxDepth-minDepth)*0.01;
    for(int j=0;j<100;j++) {
      double d=minDepth+j*dDepth;
      vMin[j].setX(std::numeric_limits<double>::infinity());
      vMax[j].setX( -std::numeric_limits<double>::infinity());
      vMin[j].setY(d);
      vMax[j].setY(d);
    }
  }
  // Check all profile and update min and max vectors
  std::sort(_models.begin(),_models.end(), DCModelInfo::loadLessThan);
  int nModels=_models.count();
  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);
      for(int i=0;i<n;i++) {
        if(dcBlock.profile(i)) {
          Profile p;
          p.readReport(dcBlock.stream());
          Curve<Point2D>& vMin=minCurves[i];
          Curve<Point2D>& vMax=maxCurves[i];
          for(int j=0;j<100;j++) {
            double v=p.uniformAt(vMin[j].y());
            Point2D& pMin=vMin[j];
            if(v<pMin.x()) pMin.setX(v);
            Point2D& pMax=vMax[j];
            if(v>pMax.x()) pMax.setX(v);
          }
        }
      }
    }
  }
  // Min and max profiles are constructed, now add them as new layers
  for(int i=0;i<n;i++) {
    LineLayer * plot=new LineLayer(_graphs[i] );
    plot->setObjectName("limits");
    PlotLine2D * line=new PlotLine2D;
    line->setPen(Pen(Qt::black, 0.60, Qt::DashLine));
    line->setSymbol(Symbol(Symbol::NoSymbol));
    plot->setReferenceLine(line);
    static_cast<PlotLine2D *>(plot->addLine())->setCurve(minCurves[i]);
    static_cast<PlotLine2D *>(plot->addLine())->setCurve(maxCurves[i]);
    plot->deepUpdate();
  }
  delete [] minCurves;
  delete [] maxCurves;
}


} // namespace DinverDCGui
