/***************************************************************************
**
**  This file is part of HVCore.
**
**  HVCore 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.
**
**  HVCore 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: 2007-08-17
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "CurveResults.h"
#include "HVParameters.h"
#include "AbstractStation.h"

namespace HVCore {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  CurveResults::CurveResults(AbstractStation * parent)
    : AbstractResults(parent)
  {
    TRACE;
    _windows=new XUniqueYColorData;
  }

  CurveResults::~CurveResults()
  {
    TRACE;
    delete _windows;
  }

  void CurveResults::clear()
  {
    TRACE;
    _windows->clear();
    _peaks.clear();
    _average.clear();
    _stddevLow.clear();
    _stddevHigh.clear();
  }

  void CurveResults::setWindows(int winCount, const HVParameters& param)
  {
    TRACE;
    setFrequencySampling(param.sampling());
    int ns=frequencySampling().count();
    _windows->clear();
    _windows->setPointCount(winCount, ns);
    _windows->setColors(new Color[winCount]);

    // Init the frequency axis
    double * f=_windows->x();
    for(int is=0; is<ns; is++) {
      f[is]=frequencySampling().value(is);
    }
    _averageWindowLength.reset();
  }

  void CurveResults::addWindows(int winCount)
  {
    TRACE;
    int oldWinCount=_windows->curveCount();
    _windows->setPointCount(oldWinCount+winCount, frequencySampling().count());
    Color * new_colors=new Color[_windows->curveCount()];
    memcpy(new_colors, _windows->colors(), oldWinCount*sizeof(Color));
    _windows->setColors(new_colors);
  }

  void CurveResults::setWindow(int winIndex, double winLength, const Curve<Point2D>& curve)
  {
    TRACE;
    ASSERT(winIndex<_windows->curveCount());
    const double * x=_windows->x();
    double * y=_windows->y();
    int di=winIndex*frequencySampling().count();
    int n=frequencySampling().count();
    for(int i=0; i<n; i++) {
      y[i+di]=curve.at(x[i]).y();
    }
    _averageWindowLength.add(winLength);
  }

  void CurveResults::setStatistics()
  {
    TRACE;
    const double * x=_windows->x();
    const double * y=_windows->y();

    int ns=frequencySampling().count();
    _average.resize(ns);
    _stddevLow.resize(ns);
    _stddevHigh.resize(ns);
    for(int is=0; is<ns; is++) {
      _average[is].setX(x[is]);
      _stddevLow[is].setX(x[is]);
      _stddevHigh[is].setX(x[is]);
      Statistics s;
      for(int ic=0; ic<_windows->curveCount(); ic++) {
        s.addLog(y[is+ic*ns]);
      }
      double mVal=s.meanLog();
      double sVal=s.stddevLog();
      _average[is].setY(mVal);
      _stddevLow[is].setY(mVal/sVal);
      _stddevHigh[is].setY(mVal*sVal);
    }
    _average.sort();
    _stddevLow.sort();
    _stddevHigh.sort();
  }

  double CurveResults::peak(int index, PeakValue pv) const
  {
    TRACE;
    ASSERT(index<_peaks.count());
    double f=_peaks.at(index).mean();
    switch(pv) {
    case PeakFrequency:
      break;
    case PeakAmplitude:
      return _average.at(f).y();
    }
    return f;
  }

  double CurveResults::peakMinimum(int index) const
  {
    TRACE;
    ASSERT(index<_peaks.count());
    return _peaks.at(index).mean()/_peaks.at(index).stddev();
  }

  double CurveResults::peakMaximum(int index) const
  {
    TRACE;
    ASSERT(index<_peaks.count());
    return _peaks.at(index).mean()*_peaks.at(index).stddev();
  }

  /*!
    Return peak inside \a min and \a max. Return 0 if there is no peak or more than one peak
  */
  double CurveResults::peak(double min, double max, PeakValue pv) const
  {
    TRACE;
    double f0=0.0;
    int n=_peaks.count();
    for(int i=0; i<n; i++) {
      double f=_peaks.at(i).mean();
      if(min<=f && f<=max) {
        if(f0==0.0) {
          f0=f;
        } else {
          App::log(tr("More than one peak between %1 and %2 Hz for station %3.\n")
                   .arg(min).arg(max).arg(name()));
          return 0.0;
        }
      }
    }
    if(f0>0.0) {
      switch(pv) {
      case PeakFrequency:
        break;
      case PeakAmplitude:
        return _average.at(f0).y();
      }
      return f0;
    } else {
      return 0.0;
    }
  }

  double CurveResults::lowestPeak(PeakValue pv) const
  {
    TRACE;
    int n=_peaks.count();
    ASSERT(n>0);
    double minF=std::numeric_limits<double>::infinity();
    for(int index=0; index<n; index++) {
      double f=_peaks.at(index).mean();
      if(f<minF) {
        minF=f;
      }
    }
    switch(pv) {
    case PeakFrequency:
      break;
    case PeakAmplitude:
      return _average.at(minF).y();
    }
    return minF;
  }

  double CurveResults::highestPeak(PeakValue pv) const
  {
    TRACE;
    int n=_peaks.count();
    ASSERT(n>0);
    double maxF=-std::numeric_limits<double>::infinity();
    for(int index=0; index<n; index++) {
      double f=_peaks.at(index).mean();
      if(f>maxF) {
        maxF=f;
      }
    }
    switch(pv) {
    case PeakFrequency:
      break;
    case PeakAmplitude:
      return _average.at(maxF).y();
    }
    return maxF;
  }

  double CurveResults::maximumAmplitudePeak(PeakValue pv) const
  {
    TRACE;
    int n=_peaks.count();
    ASSERT(n>0);
    double maxF, maxAmpl, f, ampl;
    f=_peaks.at(0).mean();
    maxAmpl=_average.at(f).y();
    maxF=f;
    for(int index=0; index<n; index++) {
      f=_peaks.at(index).mean();
      ampl=_average.at(f).y();
      if(ampl>maxAmpl) {
        maxAmpl=ampl;
        maxF=f;
      }
    }
    switch(pv) {
    case PeakFrequency:
      break;
    case PeakAmplitude:
      return maxAmpl;
    }
    return maxF;
  }

  /*!
    Init the frequency average layer: try to calculate at least one peak.
  */
  void CurveResults::setPeaks()
  {
    TRACE;
    if(_windows->curveCount()==0) return;
    double minFreq, maxFreq;
    const double * x=_windows->x();
    int imax=averageMax(_average);
    if(imax==-1) {
      minFreq=x[0];
      maxFreq=x[frequencySampling().count()-1];
    } else {
      double totalMinFreq=x[0];
      double medFreq=x[imax];
      double sigma=1.5-0.25*(medFreq-totalMinFreq) /
                   (x[_windows->xCount()-1]-totalMinFreq);
      minFreq=medFreq/sigma;
      maxFreq=medFreq*sigma;
    }
    clearPeaks();
    addPeak(minFreq, maxFreq);
  }

  /*!
    Clears the list of peaks.
  */
  void CurveResults::clearPeaks()
  {
    TRACE;
    _peaks.clear();
    emit peakChanged();
  }

  /*!
    Adds a peak between \a minFreq and \a maxFreq.
  */
  void CurveResults::addPeak(double minFreq, double maxFreq)
  {
    TRACE;
    if(_windows->curveCount()<2) return;
    if(minFreq>0.0 && maxFreq>0.0) {
      if(minFreq>maxFreq) {
        double tmp=minFreq;
        minFreq=maxFreq;
        maxFreq=tmp;
      }
      int iminFreq=frequencySampling().index(minFreq);
      int imaxFreq=frequencySampling().index(maxFreq);
      _peaks.resize(_peaks.size()+1);
      setPeak(_peaks.count()-1, iminFreq, imaxFreq);
    }
    emit peakChanged();
  }

  /*!
    Sets peak of \a index to be between \a minFreq and \a maxFreq.
  */
  void CurveResults::setPeak(int index, double minFreq, double maxFreq)
  {
    TRACE;
    if(_windows->curveCount()<2) return;
    if(minFreq>0.0 && maxFreq>0.0) {
      if(minFreq>maxFreq) {
        double tmp=minFreq;
        minFreq=maxFreq;
        maxFreq=tmp;
      }
      int iminFreq=frequencySampling().index(minFreq);
      int imaxFreq=frequencySampling().index(maxFreq);
      setPeak(index, iminFreq, imaxFreq);

    }
    emit peakChanged();
  }

  /*!
    Removes peak of \a index.
  */
  void CurveResults::removePeak(int index)
  {
    TRACE;
    _peaks.remove(index);
    emit peakChanged();
  }

  /*!
    Calculates peak with \a index between \a minFreq and \a maxFreq expressed in sample index.
  */
  void CurveResults::setPeak(int index, int minFreq, int maxFreq)
  {
    TRACE;
    const double * x=_windows->x();
    const double * y=_windows->y();
    int imax;
    Statistics s;
    // We must sum fmax[ic] and (fmax[ic])^2
    for(int ic=0; ic<_windows->curveCount(); ic++) {
      imax=max(y+_windows->xCount()*ic, minFreq, maxFreq);
      if(imax!=-1) {
        s.addLog(x[imax]);
      }
    }
    RealStatisticalValue& p=_peaks[index];
    p.setMean(s.meanLog());
    p.setStddev(s.stddevLog());
    p.setWeight(s.count());
    p.setValid(true);
  }

  /*!
    Use the general max() used for each individual time windows.
    See max(double * y, int minFreq, int maxFreq) for the returned value.
  */
  int CurveResults::averageMax(const Curve<Point2D>& meanCurve) const
  {
    TRACE;
    ASSERT(!meanCurve.isEmpty());
    int ns=frequencySampling().count();
    double * y=new double[ns];
    for(int is=0; is<ns; is++) {
      y[is]=meanCurve.at(is).y();
    }
    int imax=max(y, 0, ns-1);
    delete [] y;
    return imax;
  }

  /*!
    Return the index of the samples with the maximum amplitude or -1 if no maximum can be seen (right at edge of the interval
    from \a minFreq to \a maxFreq).
  */
  int CurveResults::max(const double * y, int minFreq, int maxFreq) const
  {
    TRACE;
    ASSERT(minFreq<=maxFreq && maxFreq<frequencySampling().count());
    double valmax=y[ minFreq ], newval;
    int imax=minFreq;
    for(int is=minFreq+1; is<=maxFreq; is++) {
      newval=y[is];
      if(newval>valmax) {
        valmax=newval;
        imax=is;
      }
    }
    // If the max value is found at one extremity, remove the monotoneous slope
    // from the search interval and redo the search on the restricted interval
    if(imax==minFreq) {
      for( ;imax<maxFreq; imax++) {
        newval=y[imax];
        if(newval>valmax) {
          break;
        }
        valmax=newval;
      }
      if(imax==maxFreq) {
        return -1;
      } else {
        return max(y, imax, maxFreq);
      }
    } else if(imax==maxFreq) {
      for( ;imax>minFreq; imax--) {
        newval=y[imax];
        if(newval>valmax) {
          break;
        }
        valmax=newval;
      }
      if(imax==minFreq) {
        return -1;
      } else {
        return max(y, minFreq, imax);
      }
    }
    return imax;
  }

  /*!

  */
  bool CurveResults::save(QString fileName, QString log) const
  {
    TRACE;
    bool success=true;
    if(!saveAverage(fileName)) {
      success=false;
    }
    if(!log.isEmpty()) {
      QFileInfo fi(fileName);
      QDir d(fi.absolutePath());
      QFileInfo filog(d.absoluteFilePath(fi.baseName()+".log"));
      if(!saveLog(filog.filePath(), log)) {
        success=false;
      }
    }
    return success;
  }

  bool CurveResults::saveLog(QString fileName, QString log) const
  {
    TRACE;
    QFile f(fileName);
    if(!f.open(QIODevice::WriteOnly)) {
      App::log(tr("Unable to open file '%1' for writing\n").arg(fileName));
      return false;
    }
    QTextStream s(&f);
    s << log << flush;
    if(s.status()==QTextStream::WriteFailed) {
      App::log(tr("Error while writing to file '%1'\n").arg(fileName));
      return false;
    } else {
      return true;
    }
  }

  bool CurveResults::saveAverage(QString fileName) const
  {
    TRACE;
    if(_windows->curveCount()==0) {
      App::log(tr("No time window available for station '%1'\n").arg(name()));
      return false;
    }
    QFile f(fileName);
    if(!f.open(QIODevice::WriteOnly)) {
      App::log(tr("Unable to open file '%1' for writing\n").arg(fileName));
      return false;
    }
    QTextStream s(&f);
    s << "# GEOPSY output version 1.1\n";
    // Write number of windows
    s << QString("# Number of windows = %1\n").arg(_windows->curveCount());
    // Write frequency of maxima of the average curve
    int iMax=averageMax(_average);
    if(iMax>-1) {
      s << QString("# f0 from average\t%1\n").arg(_average[iMax].x());
    }
    // Write f0s
    if(_peaks.isEmpty()) {
      s << "# Number of windows for f0 = 0\n";
    } else {
      int n=_peaks.count();
      for(int index=0; index<n; index++) {
        const RealStatisticalValue& p=_peaks.at(index);
        s << QString("# Number of windows for f%1 = %2\n").arg(index).arg(p.weight());
        s << QString("# f%1 from windows").arg(index);
        s << QString("\t%1\t%2\t%3\n")
             .arg(p.mean())
             .arg(p.mean()/p.stddev())
             .arg(p.mean()*p.stddev());
        s << QString("# f%1 amplitude\t%2\n").arg(index).arg(_average.at(p.mean()).y());
      }
    }
    s << QString("# Position\t%1\n").arg(coordinates().toString(20));
    s << QString("# Category\t%1\n").arg(category());
    // Write titles
    s << "# Frequency\tAverage\tMin\tMax\n";
    // Write average curves
    int ns=frequencySampling().count();
    for(int is=0; is<ns; is++) {
      s << QString("%1\t%2\t%3\t%4\n").arg(_average[is].x())
                                      .arg(_average[is].y())
                                      .arg(_stddevLow[is].y())
                                      .arg(_stddevHigh[is].y());
    }
    if(s.status()==QTextStream::WriteFailed) {
      App::log(tr("Error while writing to file '%1'\n").arg(fileName));
      return false;
    } else {
      return true;
    }
  }

  bool CurveResults::loadAverage(QString fileName)
  {
    TRACE;
    QFile f(fileName);
    if(!f.open(QIODevice::ReadOnly)) {
      App::log(tr("Unable to open file '%1' for reading\n").arg(fileName));
      return false;
    }
    QTextStream s(&f);
    _average.clear();
    _stddevLow.clear();
    _stddevHigh.clear();
    _peaks.clear();
    bool ok=true;
    QRegExp fi("f([0-9]+)");
    QVector<double> xValues;
    while(!s.atEnd()) {
      QString line=s.readLine();
      if(line.startsWith("#")) {
        if(line.startsWith("# GEOPSY output version", Qt::CaseInsensitive)) {
          if(line.section(QRegExp("[ \t]"), 4, 4).toDouble()>1.1) {
            App::log(tr("Loading results: bad version, expecting version 1.1"));
            return false;
          }
        } else if(line.startsWith("# Number of windows for f", Qt::CaseInsensitive)) {
          if(fi.indexIn(line, 24)==24) {
            int index=fi.cap(1).toInt();
            if(index>=_peaks.count()) {
              _peaks.resize(index+1);
            }
            _peaks[index].setWeight(line.section("=", 1, 1).toInt());
            _peaks[index].setValid(false);
          }
        } else if(line.contains(QRegExp("# f[0-9]+ from windows"))) {
          double f=line.section("\t", 1, 1).toDouble();
          if(fi.indexIn(line, 2)==2) {
            int index=fi.cap(1).toInt();
            if(index>=_peaks.count()) {
              _peaks.resize(index+1);
            }
            _peaks[index].setMean(f);
            _peaks[index].setStddev(f/line.section("\t", 2, 2).toDouble());
            _peaks[index].setValid(true);
          }
        } else if(line.startsWith( "# Category", Qt::CaseInsensitive)) {
          setCategory(line.section("\t", 1));
        } else if(line.startsWith( "# Position", Qt::CaseInsensitive)) {
          Point p;
          p.fromString(line.section("\t", 1));
          setCoordinates(p);
        }
      } else if(!line.isEmpty()) {
        LineParser lp;
        lp.setDelimiters(" \t");
        lp.setSkipEmpty(true);
        lp.setString(line);
        double f=lp.toDouble(0, ok);
        xValues.append(f);
        _average.append(Point(f, lp.toDouble(1, ok)));
        _stddevLow.append(Point(f, lp.toDouble(2, ok)));
        _stddevHigh.append(Point(f, lp.toDouble(3, ok)));
      }
    }
    _average.sort();
    _stddevLow.sort();
    _stddevHigh.sort();
    SamplingParameters samp;
    if(frequencySampling().isValid()) {
      samp=frequencySampling();
    } else {
      std::sort(xValues.begin(), xValues.end());
      unique(xValues);
      // Function available in 3.4
      //samp.bestFit(xValues);
      // Meanwhile use that code:
      samp.setMinimum(xValues.first());
      samp.setMaximum(xValues.last());
      samp.setType(SamplingParameters::Log); // assumes log
      samp.setCount(_average.count());
    }
    samp.setCount(_average.count());
    setFrequencySampling(samp);
    // Avoid T10 area
    _averageWindowLength.reset();
    _averageWindowLength.add(10.0/samp.minimum());
    return true;
  }

  void CurveResults::setWindowColor(int winIndex, const Color &col)
  {
    _windows->setColor(winIndex, col);
  }

  void CurveResults::setWindowColor(const QVector<int>* selCurves, const Color& col)
  {
    TRACE;
    QVector<int>::const_iterator it;
    for(it=selCurves->begin(); it!=selCurves->end(); ++it) {
      _windows->setColor(*it, col);
    }
  }

  void CurveResults::blackWindows(int, const QVector<int>* selCurves)
  {
    TRACE;
    emit colorsToChange(selCurves, Qt::gray);
  }

  /*!
    Reject all windows that does not have a peak inside \a rect.
  */
  void CurveResults::blackWindows(const Rect &rect)
  {
    TRACE;
    QVector<int> selCurves;
    int iminFreq=frequencySampling().index(rect.x1());
    int imaxFreq=frequencySampling().index(rect.x2());
    int ns=frequencySampling().count();
    for(int i=0; i<_windows->curveCount(); i++) {
      const double * y=_windows->y()+ns*i;
      int imax=max(y, iminFreq, imaxFreq);
      if(imax==-1 || y[imax]<rect.y1() || y[imax]>rect.y2()) {
        selCurves.append(i);
      }
    }
    if(!selCurves.isEmpty()) {
      emit colorsToChange(&selCurves, Qt::gray);
    }
  }

  /*!
    Used for the max Y of plots
  */
  double CurveResults::maximumAmplitude() const
  {
    TRACE;
    if(_windows->curveCount()>1) {
      if(_stddevHigh.isEmpty()) {
        return 0.0;
      } else {
        return _stddevHigh.at(_stddevHigh.maximumY()).y();
      }
    } else {
      if(_average.isEmpty()) {
        return 0.0;
      } else {
        return _average.at(_average.maximumY()).y();
      }
    }
  }

  void CurveResults::derivate()
  {
    TRACE;
    _average.yMultiplyByX(2*M_PI);
    _stddevLow.yMultiplyByX(2*M_PI);
    _stddevHigh.yMultiplyByX(2*M_PI);
  }

  void CurveResults::integrate()
  {
    TRACE;
    _average.yDivideByX(2*M_PI);
    _stddevLow.yDivideByX(2*M_PI);
    _stddevHigh.yDivideByX(2*M_PI);
  }

  void CurveResults::printAverage() const
  {
    TRACE;
    App::log(_average.toString()+"\n");
  }

  void CurveResults::printWindows() const
  {
    TRACE;
    const double * x=_windows->x();
    const double * y=_windows->y();
    int ns=frequencySampling().count();
    for(int ic=0; ic<_windows->curveCount(); ic++) {
      printf( "# Window %i\n", ic);
      for(int is=0; is<ns; is++) {
        printf("%lg\t%lg\n", x[is], y[is]);
      }
      y+=ns;
    }
  }

  void CurveResults::printPeaks() const
  {
    TRACE;
    int n=_peaks.count();
    for(int index=0; index<n; index++) {
      const RealStatisticalValue& p=_peaks.at(index);
      printf("%lg\t%lg\t%lg\t%lg\n",
             p.mean(),
             p.mean()/p.stddev(),
             p.mean()*p.stddev(),
             _average.at(p.mean()).y());
    }
  }

  QString CurveResults::comments() const
  {
    TRACE;
    int n=_peaks.count();
    QString str=name();
    if(n==0) {
      str+=tr("\nNo peak");
    } else {
      for(int index=0; index<n; index++) {
        const RealStatisticalValue& p=_peaks.at(index);
        str+=QString("\nf%1=%2 [%3, %4]")
            .arg(index)
            .arg(p.mean())
            .arg(p.mean()/p.stddev())
            .arg(p.mean()*p.stddev());
        str+=QString("\nA%1=%2 [%3, %4]")
             .arg(index)
             .arg(_average.at(p.mean()).y())
             .arg(_stddevLow.at(p.mean()).y())
             .arg(_stddevHigh.at(p.mean()).y());
      }
    }
    str+=tr("\nCategory: %1").arg(category());
    return str;
  }

} // namespace HVCore
