/***************************************************************************
**
**  This file is part of dinverdc.
**
**  dinverdc 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.
**
**  dinverdc 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-10-27
**  Copyright: 2005-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <DinverCore.h>
#include <DinverDCCore.h>
#include <QGpCoreTools.h>
#include <QGpCoreWave.h>
#include <QGpGuiTools.h>
#include <QGpGuiWave.h>
#include <SciFigs.h>
#include "AutocorrTargetWidget.h"
#include "RingBrowser.h"

/*!
  \class AutocorrTargetWidget AutocorrTargetWidget.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

AutocorrTargetWidget::AutocorrTargetWidget(QWidget * parent)
  : ModalCurveBrowser(parent)
{
  TRACE;

  _rings=new RingBrowser(this);
  qobject_cast<QVBoxLayout *>(frame->layout())->insertWidget(0, _rings);
  connect(_rings, SIGNAL(currentChanged(int)), this, SLOT(setCurrentRing(int) ));
  connect(_rings, SIGNAL(added(int)), this, SLOT(addRing()) );
  connect(_rings, SIGNAL(removed(int)), this, SLOT(removeRing(int) ));
  connect(_rings, SIGNAL(nameChanged(int, QString)), this, SLOT(setRingName(int, QString) ));

  setProxy(new AutocorrProxy);
  setModeType(Mode::Autocorr);
  _sheet=new GraphicSheet(this);
  dock(_sheet);
  setWindowTitle(tr("Autocorr target"));
}

AutocorrTargetWidget::~AutocorrTargetWidget()
{
  TRACE;
  WidgetRect::saveRect(this);
}

void AutocorrTargetWidget::resizeEvent(QResizeEvent * e)
{
  TRACE;
  WidgetRect::record(this);
  ModalCurveBrowser::resizeEvent(e);
}

void AutocorrTargetWidget::setCurrentRing(int index)
{
  TRACE;
  if(index>=0 && index<_ringLayers.count()) {
    setCurrentLayer(_ringLayers.at(index));
  } else {
    setCurrentLayer(nullptr);
  }
}

void AutocorrTargetWidget::clear()
{
  TRACE;
  ModalCurveBrowser::clear();
  setCurrentLayer(nullptr);
  _rings->clear();
  _sheet->clear(true);
  _ringLabels.clear();
  _ringLayers.clear();
}

/*!
  Return total number of curves for all rings
*/
int AutocorrTargetWidget::count()
{
  TRACE;
  int n=_ringLayers.count();
  int total=0;
  for(int i=0; i<n;i++ ) {
    total+=_ringLayers.at(i)->count();
  }
  return total;
}

/*!
  Return total number of curves for all rings
*/
int AutocorrTargetWidget::emptyRingCount() const
{
  TRACE;
  int n=_ringLayers.count();
  int total=0;
  for(int i=0; i<n; i++) {
    if(_ringLayers.at(i)->count()==0) {
      total++;
    }
  }
  return total;
}

void AutocorrTargetWidget::setRingName(int index, QString name)
{
  TRACE;
  TextEdit * c=_ringLabels.at(index);
  c->setText(name);
  c->deepUpdate();
}

void AutocorrTargetWidget::setEditable(bool e)
{
  TRACE;
  _rings->setEditable(e);
  ModalCurveBrowser::setEditable(e);
}

void AutocorrTargetWidget::removeRing(int index)
{
  TRACE;
  _sheet->removeObject(_ringLayers.at(index)->graph(), true);
  _sheet->removeObject(_ringLabels.at(index), true);
  _ringLayers.remove(index);
  _ringLabels.remove(index);
  if(_rings->count()==0) setCurrentRing(-1);
}

/*!
  Add a new ring only if the couple \a min and \a max does not already exist.
  Returns the index of the ring corresponding to \a min and \a max
*/
int AutocorrTargetWidget::addRing(const AutocorrRing& ring)
{
  TRACE;
  int index=_rings->indexOf(ring);
  if(index>-1) return index;
  index=_rings->count();
  _rings->add();
  _rings->setRing(index, ring);
  return index;
}

/*!
  Add a new ring
*/
void AutocorrTargetWidget::addRing()
{
  TRACE;
  int index=_rings->count()-1;
  double x=0.5 + 9 * (index % 2);
  double y=0.5 + (index/2) * 6.5;

  AxisWindow * w=new AxisWindow;
  w->setObjectName( "graph" );
  _sheet->addObject(w);
  _sheet->showObject(w);
  w->xAxis()->setSizeInfo(9);
  w->setPrintXAnchor(x);
  w->yAxis()->setSizeInfo(6);
  w->setPrintYAnchor(y+0.5);
  w->updateExternalGeometry();
  w->updateGeometry();
  w->yAxis()->setRange( -0.5, 1.1);
  LineLayer * ringLayer=new LineLayer(w);
  _ringLayers.append(ringLayer);
  initLayer(ringLayer);

  TextEdit * te=new TextEdit;
  te->setPrintXAnchor(x);
  te->setPrintYAnchor(y);
  te->setPrintWidth(9);
  te->setPrintHeight(1);
  te->setTextAsData(true);
  te->adjustSize();
  _sheet->addObject(te);
  _sheet->showObject(te);
  _ringLabels.append(te);
}

ModalCurve& AutocorrTargetWidget::addCurve(int index)
{
  TRACE;
  LineLayer * plot=_ringLayers.at(index);
  ModalLine * l=static_cast<ModalLine *>(plot->addLine());
  l->setPen(Pen( Qt::black, 0.6));
  l->setSymbol(Symbol(Symbol::Circle, 1.2, Pen(Qt::black, 0.3),
                      Brush(Qt::black, Qt::SolidPattern)));
  plot->deepUpdate();
  return l->curve();
}

void AutocorrTargetWidget::addCurves(const AutocorrCurves& newCurves)
{
  TRACE;
  int curvesCount=count();
  for(int i=0; i<newCurves.ringCount();i++) {
    int ringIndex=addRing(newCurves.ring(i));
    const QList<ModalCurve>& l=newCurves.ringCurves(i);
    for(QList<ModalCurve>::ConstIterator it=l.begin();it!=l.end();it++ ) {
      addCurve(ringIndex)=*it;
    }
  }
  if(curvesCount==0) setLimits();
  setCurrentRing(0);
}

AutocorrCurves AutocorrTargetWidget::curves()
{
  TRACE;
  AutocorrCurves c;
  int nRings=_rings->count();
  for(int iRing=0;iRing<nRings;iRing++) {
    c.addRing(_rings->at(iRing));
    LineLayer * l=_ringLayers.at(iRing);
    int n=l->count();
    for(int j=0;j<n;j++) {
      ModalCurve& curve=static_cast<ModalLine *>(l->line(j))->curve();
      c.addCurve(curve);
      // Ring index is stored inside the modal curve modes, set the definitive index
      QList<Mode>& modes=c.curves().last().modes();
      for(QList<Mode>::Iterator it=modes.begin(); it!=modes.end(); it++ ) {
        it->setRingIndex(iRing);
      }
    }
  }
  return c;
}

void AutocorrTargetWidget::load()
{
  TRACE;
  MessageContext();
  QStringList fileNames=Message::getOpenFileNames(tr("Load autocorr to fit"),
                                                       tr("All files (*);;"
                                                          "Text file (*);;"
                                                          "Dinver targets (*.target);;"
                                                          "Dinver environment (*.dinver);;"
                                                          "Cap SPAC output (*.stmap);;"
                                                          "na_viewer reports (*.report)"));
  if( !fileNames.isEmpty()) {
    // Count the number of rings and the number of rings with no curve for a correct setLimit after load
    int ringCount=_ringLayers.count();
    int nullRingCount=emptyRingCount();
    ColumnTextParser * parser=nullptr;
    int n=fileNames.count();
    for(int i=0;i<n;i++) {
      QString fileName=fileNames.at(i);
      QFileInfo fi(fileName);
      if(fi.suffix()=="target" || fi.suffix()=="dinver") {
        loadTarget(fileName);
      } else if(fi.suffix()=="stmap") {
        loadCap(fileName);
      } else if(fi.suffix()=="report") {
        loadNaViewerReport(fileName);
      } else {
        if(_rings->count()==0) _rings->add();
        ModalCurveBrowser::loadMultiColumns(fileName, i==n-1, parser);
      }
    }
    delete parser;
    if(currentLayer()) {
      if(ringCount!=_ringLayers.count() || nullRingCount!=emptyRingCount()) {
        setLimits();
      }
    }
    // Force refreshment of curve area
    setCurrentRing(_rings->current());
  }
}

/*!
  Re-implementation to extract ring information from section comments. Hence, several rings can be loaded
  from a single text file.
*/
bool AutocorrTargetWidget::loadMultiColumns(const ColumnTextParser * parser, const QString& fileName)
{
  TRACE;
  ColumnTextIterator it(parser);
  QRegExp ringExp("from ([0-9\\.\\-+e]+) to ([0-9\\.\\-+e]+)");
  int nCurves=0;
  while(!it.atEnd()) {
    LineLayer * layer;
    QString comments=parser->sectionComments(it.currentSection());
    if(ringExp.indexIn(comments)>-1) {
      AutocorrRing ring(ringExp.cap(1).toDouble(), ringExp.cap(2).toDouble());
      int ringIndex=addRing(ring);
      layer=_ringLayers.at(ringIndex);
    } else {
      layer=currentLayer();
    }
    if(!ModalCurveBrowser::loadMultiColumns(layer, it, fileName, nCurves)) {
      return false;
    }
  }
  return true;
}

void AutocorrTargetWidget::loadTarget(QString fileName)
{
  TRACE;
  TargetList tl;
  XMLVirtualPlugin plugin(&tl, "DispersionCurve");
  XMLDinverHeader hdr(&plugin);
  if(hdr.xml_restoreFile(fileName)!= XMLClass::NoError) {
    Message::warning(MSG_ID, tr("Loading Dinver target/environment ..."),
                     tr("Error while reading file %1").arg(fileName), Message::cancel());
    return ;
  }
  addCurves(tl.autocorrTarget().curves());
}

void AutocorrTargetWidget::loadCap(QString fileName)
{
  TRACE;
  /* File format from CAP
    Ring_num Freq_Num Frequency Autocorr_value stddev min_radius max_radius
    3 0 0.200000 0.995342 -0.425166 0.104065 0.119740 */
  QFile f(fileName);
  if( !f.open(QIODevice::ReadOnly) ) {
    Message::warning(MSG_ID, tr( "Loading CAP SPAC output ..." ),
                         tr( "Impossible to access to file %1" ).arg(fileName), Message::cancel());
    return ;
  }
  QTextStream s(&f);
  AutocorrCurves newCurves;
  QMap<AutocorrRing, int> lookup;
  FactoryPoint p;
  while( !s.atEnd()) {
    QString str=s.readLine();
    bool ok;
    double rmin=str.section( " ", 5, 5).toDouble(&ok) * 1000.0;
    if( !ok) return loadCapError(s);
    double rmax=str.section( " ", 6, 6).toDouble(&ok) * 1000.0;
    if( !ok) return loadCapError(s);
    p.setX(str.section( " ", 2, 2).toDouble(&ok) );
    if( !ok) return loadCapError(s);
    p.setMean(str.section( " ", 3, 3).toDouble(&ok) );
    if( !ok) return loadCapError(s);
    p.setStddev(str.section( " ", 4, 4).toDouble(&ok) );
    if( !ok) return loadCapError(s);

    int ringIndex;
    AutocorrRing ring(rmin, rmax);
    QMap<AutocorrRing, int>::iterator it=lookup.find(ring);
    if(it!=lookup.end()) {
      ringIndex=it.value();
    } else {
      ringIndex=newCurves.ringCount();
      newCurves.addRing(ring);
      newCurves.ringCurves(ringIndex).append(ModalCurve());
      newCurves.ringCurves(ringIndex).last().addMode(Mode( Mode::Vertical, ringIndex, 0) );
      lookup.insert(ring, ringIndex);
    }
    newCurves.ringCurves(ringIndex).first().append(p);
  }
  for(int i=0;i<newCurves.ringCount(); i++) {
    ModalCurve& c=newCurves.ringCurves(i).first();
    if(c.isEmpty()) {
      newCurves.removeRing(i);
      i--;
    } else {
      c.sort();
      c.addLog(tr("Autocorrelation curves loaded from output CAP file %1.\n"
                   "%2 samples between %3 Hz. to %4 Hz.\n")
                 .arg(fileName).arg(c.count()).arg(c.first().x()).arg(c.last().x()) );
    }
  }
  addCurves(newCurves);
}

void AutocorrTargetWidget::loadCapError(QTextStream& s)
{
  TRACE;
  Message::wrongTextFormat(s, tr("Loading CAP SPAC output"));
  return;
}

void AutocorrTargetWidget::loadNaViewerReport(QString fileName)
{
  TRACE;
  /*QFileInfo fInfo(fileName);
  fileName=fInfo.absoluteFilePath();
  qtbError.reset(false);
  AutocorrReport report(false, fileName);
  if(qtbError.occured()) return ;
  if(report.reportType()!=InversionReport::AutocorrGoal) {
    Message::warning(MSG_ID, tr( "Importing autocorr report" ),
                         tr( "The report you selected contains no autocorrelation curve\n" ), Message::cancel());
    return ;
  }
  AutocorrData * autocorr=report.loadAutocorrGoal();
  _actionLog += autocorr->log();
  int nModes=autocorr->modesCount();
  int nRayleighModes=autocorr->rayleighModesCount();
  for(int iMode=0;iMode < nModes;iMode++ ) {
    for(int iRing=autocorr->radiusCount() - 1;iRing >= 0;iRing-- ) {
      int iDialogRing=addRing(autocorr->radiusMin(iRing), autocorr->radiusMax(iRing) );
      Function& fct=*new Function;
      addCurve(iMode, nRayleighModes, iDialogRing, &fct);
      fct.setX(autocorr->frequencies());
      fct.setY(autocorr->measurements(iMode, iRing) );
      fct.setYErr(autocorr->stddev(iMode, iRing) );
      fct.setWeight(autocorr->weight(iMode, iRing) );
      fct.sort();
    }
  }
  delete autocorr;
  on_ringsScroll_valueChanged(0);*/
}

void AutocorrTargetWidget::setLimits()
{
  TRACE;
  int n=_ringLayers.count();
  for(int i=0; i<n;i++ ) {
    LineLayer * l=_ringLayers.at(i);
    AxisWindow * w=l->graph();
    Rect r=l->boundingRect();
    w->xAxis()->setRange(r.x1()*0.95, r.x2()*1.05);
    w->yAxis()->setRange(-1.05, 1.05);
  }
}
