/***************************************************************************
**
**  This file is part of ArrayCore.
**
**  ArrayCore 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.
**
**  ArrayCore 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: 2017-03-13
**  Copyright: 2017-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "FKResults.h"
#include "FKParameters.h"
#include "ArrayStations.h"

namespace ArrayCore {

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

    Full description of class still missing
  */

#define KMIN_LIMIT_FACTOR 1.0
#define KMAX_LIMIT_FACTOR 0.5

  /*!
    Description of constructor still missing
  */
  FKResults::FKResults()
    : AbstractArrayResults()
  {
    TRACE;
    _binCount=200;
    _minimumSlowness=0.0;
    _mode=ArrayStations::Vertical;
  }

  /*!
    Description of destructor still missing
  */
  FKResults::~FKResults()
  {
    TRACE;
  }

  void FKResults::clear()
  {
    TRACE;
    _peaks.clear();
  }

  void FKResults::setArray(const ArraySelection * array)
  {
    AbstractArrayResults::setArray(array);
    _mode=_array->array()->mode();
    // Set a "good" time reference as close as possible to the effective time range
    // Avoid loss of time precision in storage of peaks... finally not so important.
    _peaks.setTimeReference(_array->array()->first()->minTime());
  }

  Histogram2D * FKResults::initSlownessHistogram() const
  {
    TRACE;
    const SamplingParameters &freqParam=parameters()->frequencySampling();
    Histogram2D * h=new Histogram2D(freqParam.count(), _binCount);
    h->setXSampling(freqParam);
    SamplingParameters slowParam;
    slowParam.setMinimum(_minimumSlowness);
    slowParam.setMaximum(parameters()->maximumSlowness());
    slowParam.setType(SamplingParameters::Log);
    slowParam.setCount(_binCount); // Required only for log
    h->setYSampling(slowParam);
    App::log(tr("Slowness histogram\n"
                "  X axis %1\n"
                "  Y axis %2\n")
             .arg(freqParam.toUserString())
             .arg(slowParam.toUserString()));
    return h;
  }

  Histogram2D * FKResults::initSlownessWaveNumberHistogram() const
  {
    TRACE;
    Histogram2D * h=new Histogram2D(50, 100);
    SamplingParameters kParam;
    kParam.setMinimum(parameters()->kmin());
    kParam.setMaximum(parameters()->kmax());
    h->setXSampling(kParam);
    SamplingParameters slowParam;
    slowParam.setMinimum(_minimumSlowness);
    slowParam.setMaximum(parameters()->maximumSlowness());
    slowParam.setType(SamplingParameters::Log);
    h->setYSampling(slowParam);
    return h;
  }

  Histogram2D * FKResults::initWaveNumberHistogram() const
  {
    TRACE;
    const SamplingParameters &freqParam=parameters()->frequencySampling();
    int yCount=qCeil(4.0*parameters()->kmax()/parameters()->kmin());
    Histogram2D * h=new Histogram2D(freqParam.count(), yCount);
    h->setXSampling(freqParam);
    SamplingParameters kParam;
    kParam.setMinimum(0.25*parameters()->kmin());
    kParam.setMaximum(parameters()->kmax());
    kParam.setCount(yCount); // Required only for log
    h->setYSampling(kParam);
    App::log(tr("Wavenumber histogram\n"
                "  X axis %1\n"
                "  Y axis %2\n")
             .arg(freqParam.toUserString())
             .arg(kParam.toUserString()));
    return h;
  }

  Histogram2D * FKResults::initAzimuthHistogram() const
  {
    TRACE;
    const SamplingParameters &freqParam=parameters()->frequencySampling();
    Histogram2D * h=new Histogram2D(freqParam.count(), _binCount);
    h->setXSampling(freqParam);
    SamplingParameters azParam;
    azParam.setMaximum(360.0);
    h->setYSampling(azParam);
    return h;
  }

  Histogram2D * FKResults::initEllipticityHistogram() const
  {
    TRACE;
    const SamplingParameters &freqParam=parameters()->frequencySampling();
    Histogram2D * h=new Histogram2D(freqParam.count(), _binCount);
    h->setXSampling(freqParam);
    SamplingParameters ellParam;
    ellParam.setMinimum(-0.5*M_PI);
    ellParam.setMaximum(0.5*M_PI);
    h->setYSampling(ellParam);
    return h;
  }

  Curve<RealStatisticalPoint> FKResults::removeNonLinearPoints(Curve<RealStatisticalPoint> c, double maxNonLinear)
  {
    // TODO: test the NL on many random theoretical curve, and get max possible NL
    Curve<RealStatisticalPoint> cr;
    int n=c.count();
    if(n>1) {
      for(int i=2; i<n; i++) {
        double nl=fabs((c.at(i-2).y()+
                        (c.at(i-1).x()-c.at(i-2).x())
                        /(c.at(i).x()-c.at(i-2).x())
                        *(c.at(i).y()-c.at(i-2).y())-c.at(i-1).y())
                        /(0.5*(c.at(i).y()+c.at(i-2).y())));
        if(nl<maxNonLinear) {
          cr.append(c.at(i-1));
        }
      }
    }
    App::log(tr("    %1 samples set as invalid\n").arg(c.count()-cr.count()) );
    App::log(tr("    remaining %1 samples\n").arg(cr.count()) );
    return cr;
  }

  /*!
    All samples falling in a class less than \a smallFraction are filtered out.
    All samples outside \a curveRange (fraction) around mode are filtered out.
  */
  /*void FKResults::filter(Histogram2D * h, double smallFraction, double curveRange)
  {
    TRACE;
    h->normalizeAmplitude(YAxis);
    App::log(tr("  Filtering small fraction (<%1 %) ...\n").arg(smallFraction*100.0) );
    h->filterThreshold(smallFraction);
    if(curveRange<1.0) {
      Curve<RealStatisticalPoint> c=h->modeCurve();
      App::log(tr("  Filtering strong non-linearities on mode curve (>%1 %) ...\n").arg(20) );
      c=removeNonLinearPoints(c, 0.2);
      App::log(tr("  Filtering outside curve (>%1 %) ...\n").arg(curveRange*100.0) );
      h->filterCurveRelativeRange(c, curveRange);
    }
  }*/

  FKResults::SmallFractionFilter::SmallFractionFilter(double value)
  {
    _value=value;
  }

  void FKResults::SmallFractionFilter::run(Histogram2D * h)
  {
    TRACE;
    h->normalizeAmplitude(YAxis);
    h->filterThreshold(_value);
  }

  FKResults::HitCountFilter::HitCountFilter(double value)
  {
    _value=value;
  }

  void FKResults::HitCountFilter::run(Histogram2D * h)
  {
    TRACE;
    h->filterThreshold(_value);
  }

  /*!
    \a maxMisfit is the maximum misfit when comparing the observed histogram and the
    theoretical Gaussian curve. If it is 0, the misfit is ignored.
  */
  FKResults::MedianCurve::MedianCurve(double maxMisfit,
                                      FKResults * results,
                                      const QString& baseName)
  {
    _results=results;
    _maxMisfit=maxMisfit;
    _baseName=baseName;
  }

  /*!
    Used only for ellipticity
  */
  void FKResults::MedianCurve::run(Histogram2D * h)
  {
    TRACE;
    Curve<RealStatisticalPoint> c=h->medianCurve();
    if(_maxMisfit>0.0) {
      h->validateCurve(c, _maxMisfit);
    }
    saveCurve(c, _baseName+".ell");
  }

  /*!
    Used only for slowness
  */
  void FKResults::MedianCurve::run(Histogram2D * h, QGpCoreWave::Mode::Polarization polarization)
  {
    Curve<RealStatisticalPoint> c=h->medianCurve();
    if(_maxMisfit>0.0) {
      h->validateCurve(c, _maxMisfit);
    }
    _results->filterArrayLimits(c);
    _results->_rayleighCurves.clear();
    _results->_loveCurves.clear();
    if(!c.isEmpty()) {
      saveCurve(c, _baseName+"-"+QGpCoreWave::Mode::userPolarization(polarization)+".disp");
      switch (polarization) {
      case QGpCoreWave::Mode::Vertical:
      case QGpCoreWave::Mode::Radial:
      case QGpCoreWave::Mode::Rayleigh:
        _results->_rayleighCurves.append(c);
        break;
      case QGpCoreWave::Mode::Transverse:
      case QGpCoreWave::Mode::Love:
        _results->_loveCurves.append(c);
        break;
      }
    }
  }

  FKResults::GaussianMixture::GaussianMixture(FKResults * results,
                                              const QString& baseName)
  {
    _results=results;
    _baseName=baseName;
  }

  QList<Curve<RealStatisticalPoint>> FKResults::GaussianMixture::scan(Histogram2D * h, QGpCoreWave::Mode::Polarization polarization)
  {
    TRACE;
    HistogramDensities hd;
    Curve<RealStatisticalPoint> curve;

    hd.setHistogram(h);
    GaussianMixtureParameters param;
    param.setModeCount(4);
    param.setInversionType(GaussianMixtureParameters::MeanOnly);
    param.setAbsoluteError(0.125*_results->parameters()->kmin());
    param.setKmin(0.25*_results->parameters()->kmin());
    param.setKmax(_results->parameters()->kmax());
    hd.setParameters(param);
    hd.start();
    hd.wait();

    const SamplingParameters& frequencies=_results->parameters()->frequencySampling();
    int nf=frequencies.count();
    RealStatisticalPoint p;

    for(int f=0; f<nf; f++) {
      p.setX(frequencies.value(f));
      Densities d=hd.densities(f);
      d.filterWeight(0.05);
      d.filterRange(1.01*param.kmin(), 0.99*param.kmax());
      d.filterWeight(0.1);
      for(int i=0; i<d.count(); i++) {
        p.setMean(d.density(i).mean());
        p.setStddev(d.density(i).stddev());
        p.setWeight(d.weight(i));
        curve.append(p);
      }
    }

    saveCurve(curve, _baseName+"-gm-"+QGpCoreWave::Mode::userPolarization(polarization)+".disp");

    // maxX for splitting must include around 5 samples, hence step^5
    // Variation of slope along a curve does not exceed 2.5% in terms of Y amplitude
    //Curve<RealStatisticalPoint> stepCurve=curve;
    //stepCurve.sort();
    //double maxX=pow(stepCurve.minimumStep(LogScale), 5);
    //App::log(1, tr("Split curve max-x %1 error %2 min-count %3\n").arg(maxX).arg(0.025).arg(5));
    QList<Curve<RealStatisticalPoint>> curves=curve.split(1.2, LogScale, 0.05, 5);
    QString baseName=_baseName+"-gm-"+QGpCoreWave::Mode::userPolarization(polarization);
    QString index("_%1.disp");
    App::log(tr("Splitting into %1 sub-curve(s)\n").arg(curves.count()));
    for(int i=curves.count()-1; i>=0; i--) {
      Curve<RealStatisticalPoint>& c=curves[i];
      // Filling holes in the curve sampling by re-interpolation
      double min=c.at(c.minimumX()).x();
      double max=c.at(c.maximumX()).x();
      double step=c.minimumStep(LogScale);
      int n=1+qRound(log(max/min)/log(step));
      c.resample(n, min, max, LogScale);
      saveCurve(c, baseName+index.arg(i, 2, 10, QChar('0')));
    }
    return curves;
  }

  void FKResults::run(AbstractAction * a)
  {
    TRACE;
    // When switching between the 2 alternatives do not forget to change fill functions
    Histogram2D * h=initSlownessHistogram();
    //Histogram2D * h=initSlownessWaveNumberHistogram();
    switch(_mode) {
    case ArrayStations::Vertical:
      App::log(tr("%1 for vertical ...\n").arg(a->title()));
      _peaks.fillSlowness(h, QGpCoreWave::Mode::Vertical);
      //_peaks.fillSlownessWaveNumber(h, QGpCoreWave::Mode::Vertical);
      a->run(h, QGpCoreWave::Mode::Vertical);
      _peaks.commit(h, QGpCoreWave::Mode::Vertical);
      break;

    case ArrayStations::Horizontal:
      App::log(tr("%1 for radial ...\n").arg(a->title()) );
      _peaks.fillSlowness(h, QGpCoreWave::Mode::Radial);
      //_peaks.fillSlownessWaveNumber(h, QGpCoreWave::Mode::Radial);
      a->run(h, QGpCoreWave::Mode::Radial);
      _peaks.commit(h, QGpCoreWave::Mode::Radial);
      delete h;

      h=initSlownessHistogram();
      //h=initSlownessWaveNumberHistogram();
      App::log(tr("%1 for transverse ...\n").arg(a->title()) );
      _peaks.fillSlowness(h, QGpCoreWave::Mode::Transverse);
      a->run(h, QGpCoreWave::Mode::Transverse);
      _peaks.commit(h, QGpCoreWave::Mode::Transverse);
      break;

    case ArrayStations::ThreeComponents:
      App::log(tr("%1 for Rayleigh ...\n").arg(a->title()) );
      _peaks.fillSlowness(h, QGpCoreWave::Mode::Rayleigh);
      a->run(h, QGpCoreWave::Mode::Rayleigh);
      _peaks.commit(h, QGpCoreWave::Mode::Rayleigh);
      delete h;

      h=initEllipticityHistogram();
      App::log(tr("%1 for ellipticity ...\n").arg(a->title()) );
      _peaks.fillEllipticity(h);
      a->run(h);
      _peaks.commit(h, QGpCoreWave::Mode::Rayleigh);
      delete h;

      h=initSlownessHistogram();
      //h=initSlownessWaveNumberHistogram();
      App::log(tr("%1 for Love ...\n").arg(a->title()) );
      _peaks.fillSlowness(h, QGpCoreWave::Mode::Love);
      //_peaks.fillSlownessWaveNumber(h, QGpCoreWave::Mode::Love);
      a->run(h, QGpCoreWave::Mode::Love);
      _peaks.commit(h, QGpCoreWave::Mode::Love);
      break;
    }
    delete h;
  }

  void FKResults::aviosPick(const QString &groupName)
  {
    TRACE;
    Histogram2D * h;
    switch(_mode) {
    case ArrayStations::Vertical: {
        h=initWaveNumberHistogram();
        _peaks.fillWaveNumber(h, QGpCoreWave::Mode::Vertical);
        h->save(groupName+"-beforefilter.max");
        App::log(tr("Filter hit count=1 for vertical ...\n"));
        h->filterThreshold(1.0);
        App::log(tr("Filter 20% from max for vertical ...\n"));
        h->normalizeAmplitude(YAxis);
        h->filterThreshold(0.2);
        h->save(groupName+"-filter.max");
        App::log(tr("Gaussian mixture for vertical ...\n"));
        GaussianMixture gm(this, groupName);
        _rayleighCurves=gm.scan(h, Mode::Rayleigh);
        _peaks.reset(QGpCoreWave::Mode::Vertical);
        for(int i=_rayleighCurves.count()-1; i>=0; i--) {
          h->filterCurveOutsideSigmaRange(_rayleighCurves.at(i), 1.0);
          _peaks.commit(h, QGpCoreWave::Mode::Vertical);
          save((groupName+"-rayleigh_%1.max").arg(i, 2, 10, QChar('0')));
          delete h;
          h=initSlownessHistogram();
          _peaks.fillSlowness(h, QGpCoreWave::Mode::Vertical);
          _rayleighCurves[i]=h->medianCurve();
          filterArrayLimits(_rayleighCurves[i], 1.0, 0.75);
          saveCurve(_rayleighCurves[i], (groupName+"-rayleigh-median_%1.disp").arg(i, 2, 10, QChar('0')));
          delete h;
          _peaks.reset(QGpCoreWave::Mode::Vertical);
          h=initWaveNumberHistogram();
          _peaks.fillWaveNumber(h, QGpCoreWave::Mode::Vertical);
        }
        delete h;
      }
      break;
    case ArrayStations::Horizontal:
      break;
    case ArrayStations::ThreeComponents:
      break;
    }
  }

  /*!
   If \a baseName does not have any extension, it is added automatically
  */
  bool FKResults::save(QString fileName)
  {
    TRACE;
    _peaks.sort();
    if(!fileName.endsWith(".max")) {
      fileName+=".max";
    }
    if(!_peaks.save(fileName, *parameters(), *_array)) {
      App::log(tr("Error saving results to '%1'.\n").arg(fileName));
      return false;
    }
    App::log(tr("results saved in '%1'.\n").arg(fileName));
    return true;
  }

  /*!
   \a baseName must be without extension. Suffix is added according to the type of output.
  */
  bool FKResults::load(QString fileName)
  {
    TRACE;
    if(!fileName.endsWith(".max")) {
      fileName+=".max";
    }
    return _peaks.load(fileName);
  }

  bool FKResults::saveCurve(const Curve<RealStatisticalPoint>& c, const QString &fileName)
  {
    TRACE;
    QFile f(fileName);
    if(f.open(QIODevice::WriteOnly)) {
      QTextStream s(&f);
      s.setRealNumberPrecision(20);
      int n=c.count();
      for(int i=0; i<n; i++) {
        s << c.at(i).toString(20) << "\n";
      }
      return true;
    } else {
      return false;
    }
  }

  bool FKResults::loadCurve(Curve<RealStatisticalPoint>& c, const QString &fileName)
  {
    TRACE;
    QFile f(fileName);
    App::log(1, tr("Loading curve '%1'...\n").arg(fileName));
    if(f.open(QIODevice::ReadOnly)) {
      QTextStream s(&f);
      RealStatisticalPoint p;
      while(!s.atEnd()) {
        QString line=s.readLine();
        if(!line.startsWith("#") && !line.isEmpty()) {
          if(p.fromString(line)) {
            c.append(p);
          } else {
            App::log(tr("Error parsing line '%1'\n").arg(line) );
            return false;
          }
        }
      }
      return true;
    } else {
      return false;
    }
  }

  void FKResults::filterArrayLimits(Curve<RealStatisticalPoint>& c,
                                    double kminFactor, double kmaxFactor) const
  {
    TRACE;
    int stat=0;
    double kmin=kminFactor*parameters()->kmin();
    double kmax=kmaxFactor*parameters()->kmax();
    int  n=c.count();
    for(int i=0; i<n; i++) {
      RealStatisticalPoint &p=c[i];
      if(p.isValid()) {
        double k=2.0*M_PI*p.x()*p.mean();
        if(k<kmin || k>kmax) {
          p.setValid(false);
          stat++;
        }
      }
    }
  }

  void FKResults::horizontalSingleDirection(double angleTolerance)
  {
    TRACE;
    _peaks.horizontalSingleDirection(angleTolerance);
  }

  /*!
    Define limits in \a param for the FK search with the current results.
    medianCurves() must be called before.

    Restrict search around the median curves with a possible variation of \a fraction.
  */
  void FKResults::setCurveRange(FKParameters& param, const Curve<RealStatisticalPoint>& curve, double fraction,
                                QGpCoreWave::Mode::Polarization polarization) const
  {
    TRACE;
    App::log(tr("Restricting search around curves (<%1 stddev)...\n").arg(fraction) );

    param.clearCurveRefine();
    switch (polarization) {
    case QGpCoreWave::Mode::Vertical:
    case QGpCoreWave::Mode::Radial:
    case QGpCoreWave::Mode::Rayleigh:
      param.setRefineGridIndex(0);
      break;
    case QGpCoreWave::Mode::Transverse:
    case QGpCoreWave::Mode::Love:
      param.setRefineGridIndex(1);
      break;
    }
    for(int i=0; i<curve.count(); i++) {
      const RealStatisticalPoint& p=curve.at(i);
      if(p.isValid()) {
        param.addCurveRefine(p.x(), p.mean()-fraction*p.stddev(), p.mean()+fraction*p.stddev());
      }
    }
  }

  void FKResults::range(const QList<Curve<RealStatisticalPoint> >& curves, double& fMin, double& fMax, double& sMax) const
  {
    TRACE;
    double kmin=KMIN_LIMIT_FACTOR*parameters()->kmin();
    double kmax=KMAX_LIMIT_FACTOR*parameters()->kmax();
    double f, s;
    QList<Curve<RealStatisticalPoint> >::const_iterator it;
    for(it=curves.begin(); it!=curves.end(); it++) {
      Curve<RealStatisticalPoint> c=*it;
      const Curve<RealStatisticalPoint>& cc=c;
      // Extrapolate the curve until reaching the kmin and kmax limits
      bool extrapolated=true;
      while(2.0*M_PI*cc.first().x()*cc.first().y()>kmin && extrapolated) {
        // too risky to extrapolate small curves and decreasing slopes, skip extrapolation.
        extrapolated=c.extrapolateFromStart(4, true, LogScale);
      }
      extrapolated=true;
      while(2.0*M_PI*cc.last().x()*cc.last().y()<kmax && extrapolated) {
        // too risky to extrapolate small curves and decreasing slopes, skip extrapolation.
        extrapolated=c.extrapolateFromEnd(4, true, LogScale);
      }
      filterArrayLimits(c, KMIN_LIMIT_FACTOR, KMAX_LIMIT_FACTOR);
      if(c.count()>200) {
        App::log(tr("Too many samples %1, there must be a problem somewhere before!\n").arg(c.count()) );
        exit(2);
      }
      App::log(1, c.toString()+"\n");
      f=c.at(c.minimumX()).x();
      if(f<fMin) {
        fMin=f;
      }
      f=c.at(c.maximumX()).x();
      if(f>fMax) {
        fMax=f;
      }
      s=c.at(c.maximumY()).y();
      if(s>sMax) {
        sMax=s;
      }
      App::log(1, tr("min/max from %1 to %2 Hz, min velocity %3 m/s\n")
                        .arg(fMin).arg(fMax).arg(1.0/sMax));
    }
  }

  /*!
    Define limits in \a param for the FK search with the current results.

    Set global range in frequency and slowness
  */
  bool FKResults::setRange(FKParameters& param) const
  {
    TRACE;
    double fMinR=std::numeric_limits<double>::infinity(), fMaxR=-std::numeric_limits<double>::infinity(), sMaxR=-std::numeric_limits<double>::infinity();
    double fMinL=std::numeric_limits<double>::infinity(), fMaxL=-std::numeric_limits<double>::infinity(), sMaxL=-std::numeric_limits<double>::infinity();
    range(_rayleighCurves, fMinR, fMaxR, sMaxR);
    range(_loveCurves, fMinL, fMaxL, sMaxL);

    if((fMinR==std::numeric_limits<double>::infinity() || fMaxR==-std::numeric_limits<double>::infinity() || sMaxR==-std::numeric_limits<double>::infinity()) &&
       (fMinL==std::numeric_limits<double>::infinity() || fMaxL==-std::numeric_limits<double>::infinity() || sMaxL==-std::numeric_limits<double>::infinity())) {
      App::log(tr("Rayleigh and Love curves are empty, cannot set frequency range.\n") );
      return false;
    }

    // We are trying to obtain the largest frequency range that fit with Rayleigh and Love
    double fMin=fMinR, fMax=fMaxR, sMax=sMaxR;
    if(fMinL<fMin) {
      fMin=fMinL;
    }
    if(fMaxL>fMax) {
      fMax=fMaxL;
    }
    if(sMaxL>sMax) {
      sMax=sMaxL;
    }

    SamplingParameters& samplingParam=param.frequencySampling();
    samplingParam.setType(SamplingParameters::Log);
    if(fMin<samplingParam.minimum()) {
      fMin=samplingParam.minimum();
    }
    if(fMax>samplingParam.maximum()) {
      fMax=samplingParam.maximum();
    }
    samplingParam.setRange(fMin, fMax);
    samplingParam.setStep(1.025);
    App::log(tr("Sampling from %1 Hz to %2 Hz with %3 samples\n")
                     .arg(samplingParam.minimum())
                     .arg(samplingParam.maximum())
                     .arg(samplingParam.count()));
    param.setMaximumSlowness(sMax*1.1);
    App::log(tr("Minimum velocity %1 m/s\n")
                     .arg(1.0/param.maximumSlowness()));

    param.setMinimumFrequency(0, fMinR); // Rayleigh
    param.setMaximumFrequency(0, fMaxR);
    param.setMinimumFrequency(1, fMinL); // Love
    param.setMaximumFrequency(1, fMaxL);
    return true;
  }

  /*!
    Help debugging: restarting from Rayleigh and Love saved curves
  */
  bool FKResults::loadCurves(const QString& path)
  {
    TRACE;
    QFileInfo fi(path);
    App::log(tr("Loading curves from dir '%1' with pattern '%2'\n")
                     .arg(fi.absolutePath())
                     .arg(fi.baseName()));
    QDir d=fi.absoluteDir();
    QStringList filters, files;

    _rayleighCurves.clear();
    filters << fi.baseName()+"-Rayleigh-??.disp";
    files=d.entryList(filters);
    for(QStringList::iterator it=files.begin(); it!=files.end(); it++) {
      Curve<RealStatisticalPoint> c;
      if(loadCurve(c, d.absoluteFilePath(*it))) {
        _rayleighCurves.append(c);
      }
    }
    App::log(tr("%1 curves loaded for Rayleigh\n").arg(_rayleighCurves.count()));

    _loveCurves.clear();
    filters.clear();
    filters << fi.baseName()+"-Love-??.disp";
    files=d.entryList(filters);
    for(QStringList::iterator it=files.begin(); it!=files.end(); it++) {
      Curve<RealStatisticalPoint> c;
      if(loadCurve(c, d.absoluteFilePath(*it))) {
        _loveCurves.append(c);
      }
    }
    App::log(tr("%1 curves loaded for Love\n").arg(_loveCurves.count()) );
    return true;
  }

} // namespace ArrayCore

