/***************************************************************************
**
**  This file is part of gpviewmax.
**
**  gpviewmax 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.
**
**  gpviewmax 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: 2018-07-19
**  Copyright: 2018-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "Samples.h"

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

  Full description of class still missing
*/

bool Samples::lessThan(const Sample& s1, const Sample& s2)
{
  if(s1.frequency()<s2.frequency()) {
    return true;
  } else if(s1.frequency()>s2.frequency()) {
    return false;
  } else {
    if(s1.time()<s2.time()) {
      return true;
    } else {
      return false;
    }
  }
}

bool Samples::lessThanSlowness(const Sample& s1, const Sample& s2)
{
  if(s1.frequency()<s2.frequency()) {
    return true;
  } else if(s1.frequency()>s2.frequency()) {
    return false;
  } else {
    if(s1.slowness()<s2.slowness()) {
      return true;
    } else {
      return false;
    }
  }
}

Samples * Samples::filterNullEllipticities() const
{
  ConsoleProgress progress;
  progress.setCaption(tr("Filtering null ellipticities"));
  Samples  * newSamples=new Samples;
  int n=count();
  for(int i=0; i<n; i++) {
    const Sample& s=at(i);
    if(s.ellipticity()!=0.0) {
      newSamples->append(s);
    }
    progress.setValue(i);
  }
  progress.end(n);
  App::log(tr("  remaining %1 samples\n").arg(newSamples->count()));
  return newSamples;
}

Samples * Samples::filterNoise(double threshold) const
{
  if(threshold<std::numeric_limits<double>::infinity()) {
    ConsoleProgress progress;
    progress.setCaption(tr("Filtering noise above %1").arg(threshold));
    Samples  * newSamples=new Samples;
    int n=count();
    for(int i=0; i<n; i++) {
      const Sample& s=at(i);
      if(s.noise()<threshold) {
        newSamples->append(s);
      }
      progress.setValue(i);
    }
    progress.end(n);
    App::log(tr("  remaining %1 samples\n").arg(newSamples->count()));
    return newSamples;
  } else {
    return nullptr;
  }
}

Samples * Samples::filterNoiseSmart(double noiseDev, double slowDev)
{
  if(noiseDev<std::numeric_limits<double>::infinity()) {
    ConsoleProgress progress;
    progress.setCaption(tr("Smart filtering noise, slowness deviation %1, noise deviation %2")
                        .arg(slowDev).arg(noiseDev));
    std::sort(begin(), end(), lessThanSlowness);
    Samples  * newSamples=new Samples;
    int n=count(), j;
    double thSlow, thNoise, freq;
    bool acceptSample=true;
    for(int i=0; i<n; i++) {
      const Sample& s=at(i);
      freq=s.frequency();
      thNoise=s.noise()/(1.0+noiseDev);
      thSlow=s.slowness()*(1.0-slowDev);
      j=i-1;
      while(j>=0) {
        const Sample& st=at(j);
        if(st.frequency()!=freq) {
          break;
        }
        if(st.slowness()<thSlow) {
          break;
        }
        if(st.noise()<thNoise) {
          acceptSample=false;
          break;
        }
        j--;
      }
      if(acceptSample) {
        thSlow=s.slowness()*(1.0+slowDev);
        j=i+1;
        while(j<n) {
          const Sample& st=at(j);
          if(st.frequency()!=freq) {
            break;
          }
          if(st.slowness()>thSlow) {
            break;
          }
          if(st.noise()<thNoise) {
            acceptSample=false;
            break;
          }
          j++;
        }
        if(acceptSample) {
          newSamples->append(s);
        } else {
          acceptSample=true;
        }
      } else {
        acceptSample=true;
      }
      progress.setValue(i);
    }
    progress.end(n);
    App::log(tr("  remaining %1 samples\n").arg(newSamples->count()));
    return newSamples;
  } else {
    return nullptr;
  }
}

Samples * Samples::filterRelativePower(double threshold)
{
  if(threshold>0.0) {
    ConsoleProgress progress;
    progress.setCaption(tr("Filtering relative power below %1").arg(threshold));
    std::sort(begin(), end(), lessThan);
    Samples * newSamples=new Samples;
    double time0=first().time();
    double frequency0=first().frequency();
    double maxPower=0.0;
    int firstSampleIndex=0;
    int n=count();
    for(int i=0; i<n; i++) {
      const Sample& s=at(i);
      if(s.time()==time0 && s.frequency()==frequency0) {
        if(s.power()>maxPower) {
          maxPower=s.power();
        }
      } else {
        selectPower(*newSamples, firstSampleIndex, i, maxPower*threshold);
        maxPower=s.power();
        firstSampleIndex=i;
        time0=s.time();
        frequency0=s.frequency();
      }
      progress.setValue(i);
    }
    selectPower(*newSamples, firstSampleIndex, n, maxPower*threshold);
    progress.end(n);
    App::log(tr("  remaining %1 samples\n").arg(newSamples->count()));
    return newSamples;
  } else {
    return 0;
  }
}

void Samples::selectPower(Samples& samples, int beginIndex, int endIndex, double minPower) const
{
  for(int i=beginIndex; i<endIndex; i++) {
    const Sample& s=at(i);
    if(s.power()>minPower) {
      samples.append(s);
    }
  }
}

Samples * Samples::filterCurve(const ModalCurve& curve, double relativeRange) const
{
  TRACE;
  ConsoleProgress progress;
  progress.setCaption(tr("Filtering around curve '%1' (>%2 %)")
                      .arg(curve.name())
                      .arg(relativeRange*100.0));
  Samples * newSamples=new Samples;
  int n=count();
  for(int i=0; i<n; i++) {
    const Sample& s=at(i);
    if(curve.isInsideRange(s.frequency())) {
      const RealStatisticalPoint& p=curve.at(s.frequency());
      double dy=p.mean()*relativeRange;
      if(s.slowness()>p.mean()-dy && s.slowness()<p.mean()+dy) {
        newSamples->append(s);
      }
    }
    progress.setValue(i);
  }
  progress.end(n);
  App::log(tr("  remaining %1 samples\n").arg(newSamples->count()));
  return newSamples;
}

QVector<Histogram2D::Sample> Samples::dispersionSamples() const
{
  QVector<Histogram2D::Sample> s;
  s.reserve(count());
  for(QVector<Sample>::const_iterator it=begin(); it!=end(); it++) {
    s.append(Histogram2D::Sample(it->frequency(), it->slowness()));
  }
  return s;
}

QVector<Histogram2D::Sample> Samples::ellipticitySamples() const
{
  QVector<Histogram2D::Sample> s;
  s.reserve(count());
  for(QVector<Sample>::const_iterator it=begin(); it!=end(); it++) {
    s.append(Histogram2D::Sample(it->frequency(), it->ellipticity()));
  }
  return s;
}

QVector<Histogram2D::Sample> Samples::noiseSamples() const
{
  QVector<Histogram2D::Sample> s;
  s.reserve(count());
  for(QVector<Sample>::const_iterator it=begin(); it!=end(); it++) {
    s.append(Histogram2D::Sample(it->frequency(), it->noise()));
  }
  return s;
}

QVector<Histogram2D::Sample> Samples::azimuthSamples() const
{
  QVector<Histogram2D::Sample> s;
  s.reserve(count());
  for(QVector<Sample>::const_iterator it=begin(); it!=end(); it++) {
    s.append(Histogram2D::Sample(it->frequency(), it->azimuth()));
  }
  return s;
}

QVector<Histogram2D::Sample> Samples::powerSamples() const
{
  QVector<Histogram2D::Sample> s;
  s.reserve(count());
  for(QVector<Sample>::const_iterator it=begin(); it!=end(); it++) {
    s.append(Histogram2D::Sample(it->frequency(), it->power()));
  }
  return s;
}

bool Samples::save(const QString& fileName, const QString& headerLine, const QString& polarization) const
{
  TRACE;
  QFile f(fileName);
  if(f.open(QIODevice::WriteOnly)) {
    QTextStream s(&f);
    s.setRealNumberPrecision(20);
    if(!headerLine.isEmpty()) {
       s << headerLine;
    }
    for(QVector<Sample>::const_iterator it=begin(); it!=end(); it++) {
      it->write(s, polarization);
    }
    return true;
  } else {
    return false;
  }

}

