/***************************************************************************
**
**  This file is part of max2curve.
**
**  This file may be distributed and/or modified under the terms of the
**  GNU General Public License version 2 or 3 as published by the Free
**  Software Foundation and appearing in the file LICENSE.GPL included
**  in the packaging of this file.
**
**  This file 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 this program. If not, see <http://www.gnu.org/licenses/>.
**
**  See http://www.geopsy.org for more information.
**
**  Created : 2007-05-29
**  Authors:
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>
#include "MaxEntryList.h"
#include "FKMaxEntry.h"
#include "SPACMaxEntry.h"
#include "TFAMaxEntry.h"
#include "CurveMaxEntry.h"

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

  Full description of class still missing
*/

MaxEntryList::~MaxEntryList()
{
  TRACE;
  qDeleteAll(*this);
}

/*!
  Can be called only once.
*/
bool MaxEntryList::load(QString fileName)
{
  TRACE;
  QFile f(fileName);
  if( !f.open(QIODevice::ReadOnly) ) {
    Message::warning(MSG_ID, tr("Loading max file"),
                         tr("Impossible to access to file %1").arg(fileName), Message::cancel());
    return false;
  }
  QTextStream s(&f);
  // get type of max file
  if(_type==Undefined) {
    QString line;
    while(true) {
      line=s.readLine();
      if(line.isEmpty()) break;
      if(line[0]=='#') {
        line=line.toLower().replace(" ", "");
        if(FKMaxEntry::isHeaderLine(line)) {
          _type=FK;
          break;
        } else if(SPACMaxEntry::isHeaderLine(line)) {
          _type=SPAC;
          break;
        } else if(TFAMaxEntry::isHeaderLine(line)) {
          _type=TFA;
          break;
        } else if(CurveMaxEntry::isHeaderLine(line)) {
          _type=CURVE;
          break;
        }
      }
    }
  }

  // load entries
  MaxEntry * entry;
  ConsoleProgress progress;
  progress.setCaption("Loading...  ");
  while(true) {
    switch (_type) {
    case FK: entry=new FKMaxEntry; break;
    case SPAC: entry=new SPACMaxEntry; break;
    case TFA: entry=new TFAMaxEntry; break;
    case CURVE: entry=new CurveMaxEntry; break;
    default: 
      Message::warning(MSG_ID, "Loading max file",
                          "Impossible to determine file type (FK, SPAC, TFA or CURVE). This type is automatically recognized from the "
                          "comment line containing the field names. You can force the type by using the command line options. See "
                          "-help for more information.", Message::cancel());
      return false;
    }
    if(entry->readLine(s)) {
      append(entry);
    } else {
      delete entry;
      break;
    }
    progress.setValue(count());
  }
  progress.end(count());
  fprintf(stderr,"Sorting...                           \n");
  std::sort(begin(), end(), lessThan);
  return true;
}

bool MaxEntryList::save(QString fileName)
{
  TRACE;
  if(isEmpty()) {
    Message::warning(MSG_ID, tr("Saving max file"),
                         tr("Null number of entries, nothing written"), Message::cancel());
    return false;
  }
  QFile f(fileName);
  if(!f.open(QIODevice::WriteOnly)) {
    Message::warning(MSG_ID, tr("Saving max file"),
                         tr("Impossible to access to file %1").arg(fileName), Message::cancel());
    return false;
  }
  QTextStream s(&f);
  toStream(s);
  return true;
}

void MaxEntryList::toStream(QTextStream& s)
{
  TRACE;
  first()->writeHeader(s);
  ConsoleProgress progress;
  progress.setCaption(tr("Saving...  "));
  progress.setMaximum(count()-1);
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    at(i)->writeLine(s);
    progress.setValue(i);
  }
  progress.end();
}

void MaxEntryList::rejectValue(double minValue, double maxValue)
{
  TRACE;
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption(tr("Filtering(value)...  "));
  progress.setMaximum(count()-1);
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    double s=at(i)->value();
    if(s<minValue || s>maxValue) {
      delete at(i);
    } else {
      newEntryList.append(at(i));
    }
    progress.setValue(i);
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}

void MaxEntryList::rejectTime(double min, double max)
{
  TRACE;
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption(tr("Filtering(time)...  "));
  progress.setMaximum(count()-1);
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    double t=at(i)->time();
    if(t<min || t>max) {
      delete at(i);
    } else {
      newEntryList.append(at(i));
    }
    progress.setValue(i);
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}

/*!
  Return the mean value of the samples at \a x.
*/
RealStatisticalValue MaxEntryList::meanValue(double x, SamplingOption ySampling) const
{
  TRACE;
  ProcessStatistics p;
  QList<MaxEntry *>::const_iterator it;
  if(ySampling & LogScale) {
    for(it=begin();it!=end();++it) {
      MaxEntry& entry=**it;
      if(entry.x()==x && entry.isSelected()) {
        p.addLog(entry.value());
      }
    }
    return p.valueLog();
  } else {
    for(it=begin();it!=end();++it) {
      MaxEntry& entry=**it;
      if(entry.x()==x && entry.isSelected()) {
        p.add(entry.value());
      }
    }
    return p.value();
  }
}

/*!
  Returns the number of samples at \a frequency.
*/
int MaxEntryList::count(double x) const
{
  TRACE;
  int i= 0;
  QList<MaxEntry *>::const_iterator it;
  for(it=begin();it!=end();++it) {
    MaxEntry& entry=**it;
    if(entry.x()==x && entry.isSelected()) {
      i++;
    }
  }
  return i;
}

void MaxEntryList::unselectValue(double x, double minValue, double maxValue)
{
  TRACE;
  QList<MaxEntry *>::iterator it;
  for(it=begin();it!=end();++it) {
    MaxEntry& entry=**it;
    double s=entry.value();
    if(entry.x()==x && (s<minValue || s>maxValue)) {
      entry.setSelected(false);
    }
  }
}

void MaxEntryList::selectAll()
{
  TRACE;
  QList<MaxEntry *>::iterator it;
  for(it=begin();it!=end();++it) {
    (*it)->setSelected(true);
  }
}

void MaxEntryList::selectAll(double x)
{
  TRACE;
  QList<MaxEntry *>::iterator it;
  for(it=begin();it!=end();++it) {
    MaxEntry& entry=**it;
    if(entry.x()==x)
      entry.setSelected(true);
  }
}

QList<double> MaxEntryList::xSamples() const
{
  TRACE;
  QList<double> xList;
  double lastX=0.0;
  QList<MaxEntry *>::const_iterator it;
  for(it=begin();it!=end();++it) {
    MaxEntry& entry=**it;
    if(entry.x()!=lastX) {
      xList.append(entry.x());
      lastX=xList.last();
    }
  }
  return xList;
}

IrregularGrid2D MaxEntryList::initGrid(int nValues, double minValue, double maxValue,
                                       SamplingOption ySampling)
{
  TRACE;
  rejectValue(minValue, maxValue);
  QList<double> xList=xSamples();
  int nBands=xList.count();
  if(nBands==0) {
    Message::critical(MSG_ID, tr("Grid initialization"), tr("Null number of frequency samples: check you filter parameters."));
    xList << 1.0; // Add a fake frequency
    nBands=1;
  }
  IrregularGrid2D grid(nBands, nValues);
  for(int i=0;i<nBands;i++) grid.setX(i, xList.at(i));
  if(ySampling & LogScale) {
    grid.setLog(YAxis, minValue, maxValue);
  } else {
    grid.setLinear(YAxis, minValue, maxValue);
  }
  return grid;
}

void MaxEntryList::fillGrid(IrregularGrid2D& grid)
{
  TRACE;
  grid.init(0.0);
  int ix;
  double x=-1.0;
  double * val=0;
  int nx=grid.nx();
  QList<MaxEntry *>::iterator it;
  for(it=begin();it!=end();++it) {
    MaxEntry& entry=**it;
    if(x!=entry.x()) {
      x=entry.x();
      ix=grid.indexOfX(x);
      val=grid.valuePointer(ix, 0);
    }
    if(entry.isSelected()) {
      int iy=grid.indexOfY(entry.value());
      val[ iy * nx ] ++;
    }
  }
}

/*!
  Specific to FK type
*/
void MaxEntryList::unselectWavenumber(double minWavenumber, double maxWavenumber)
{
  TRACE;
  ASSERT(_type==FK);
  QList<MaxEntry *>::iterator it;
  for(it=begin();it!=end();++it) {
    FKMaxEntry& entry=*static_cast<FKMaxEntry *>(*it);
    double k=entry.wavenumber();
    if(k<minWavenumber || k>maxWavenumber) {
      entry.setSelected(false);
    }
  }
}

void MaxEntryList::minPowerVectors(QMap<double,double>& minRelPow, QMap<double,double>& minAbsPow,
                                   double relativeFactor, double absoluteFactor)
{
  TRACE;
  QMap<double,double> maxRelPow, maxAbsPow;
  // Find min and max semblance for each frequency bands
  QList<MaxEntry *>::iterator it=begin();
  if(it==end()) return;
  FKMaxEntry& entry=*static_cast<FKMaxEntry *>(*it);
  double f=entry.x();
  minRelPow.insert(f, entry.relativePower());
  maxRelPow.insert(f, entry.relativePower());
  minAbsPow.insert(f, entry.absolutePower());
  maxAbsPow.insert(f, entry.absolutePower());
  for(it++;it!=end();++it) {
    FKMaxEntry& entry=*static_cast<FKMaxEntry *>(*it);
    f=entry.x();
    entry.includePower(minRelPow[f], maxRelPow[ f], minAbsPow[f], maxAbsPow[f]);
  }
  for(QMap<double,double>::iterator it=minRelPow.begin(); it!=minRelPow.end(); it++ ) {
    double f=it.key();
    minRelPow[f] += relativeFactor * (maxRelPow[f] - minRelPow[f] );
    minAbsPow[f] += absoluteFactor * (maxAbsPow[f] - minAbsPow[f] );
  }
}

void MaxEntryList::unselectPower(double relativeFactor, double absoluteFactor)
{
  TRACE;
  ASSERT(_type==FK);
  QMap<double,double> minRelPow, minAbsPow;
  minPowerVectors(minRelPow, minAbsPow, relativeFactor, absoluteFactor);
  QList<MaxEntry *>::iterator it;
  for(it=begin();it!=end();++it) {
    FKMaxEntry& entry=*static_cast<FKMaxEntry *>(*it);
    double x=entry.x();
    if(entry.relativePower() < minRelPow[x] && entry.absolutePower() < minAbsPow[x] ) {
      entry.setSelected(false);
    }
  }
}

void MaxEntryList::rejectPower(double relativeFactor, double absoluteFactor)
{
  TRACE;
  ASSERT(_type==FK);
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption("Filtering(power)...  ");
  progress.setMaximum(count()-1);
  QMap<double,double> minRelPow, minAbsPow;
  minPowerVectors(minRelPow, minAbsPow, relativeFactor, absoluteFactor);
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    const FKMaxEntry& m=*static_cast<const FKMaxEntry *>(at(i));
    double x=m.x();
    if(m.relativePower() < minRelPow[x] && m.absolutePower() < minAbsPow[x] ) {
      delete at(i);
    } else {
      newEntryList.append(at(i));
    }
    progress.setValue(i);
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}

void MaxEntryList::minAmplitudeVectors(QMap<double,double>& minVAmplitude, double factor)
{
  TRACE;
  QMap<double,double> maxVAmplitude;
  // Find min and max vertical amplitude for each frequency bands
  QList<MaxEntry *>::iterator it=begin();
  if(it==end()) return;
  TFAMaxEntry& entry=*static_cast<TFAMaxEntry *>(*it);
  double x=entry.x();
  minVAmplitude.insert(x, entry.vAmplitude());
  maxVAmplitude.insert(x, entry.vAmplitude());
  for(it++;it!=end();++it) {
    TFAMaxEntry& entry=*static_cast<TFAMaxEntry *>(*it);
    x=entry.x();
    entry.includeAmplitude(minVAmplitude[x], maxVAmplitude[x]);
  }
  for(QMap<double,double>::iterator it=minVAmplitude.begin(); it!=minVAmplitude.end(); it++ ) {
    double x=it.key();
    minVAmplitude[x] += factor * (maxVAmplitude[x] - minVAmplitude[x] );
  }
}

void MaxEntryList::unselectAmplitude(double factor)
{
  TRACE;
  ASSERT(_type==TFA);
  QMap<double,double> minVAmplitude;
  minAmplitudeVectors(minVAmplitude, factor);
  QList<MaxEntry *>::iterator it;
  for(it=begin();it!=end();++it) {
    TFAMaxEntry& entry=*static_cast<TFAMaxEntry *>(*it);
    double x=entry.x();
    if(entry.vAmplitude() < minVAmplitude[x]) {
      entry.setSelected(false);
    }
  }
}

void MaxEntryList::rejectAmplitudeVerticalFactor(double factor)
{
  TRACE;
  ASSERT(_type==TFA);
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption("Filtering(tfaAmpZFactor)...  ");
  progress.setMaximum(count()-1);
  QMap<double,double> minVAmplitude;
  minAmplitudeVectors(minVAmplitude, factor);
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    const TFAMaxEntry& m=*static_cast<const TFAMaxEntry *>(at(i));
    double x=m.x();
    if(m.vAmplitude() < minVAmplitude[x]) {
      delete at(i);
    } else {
      newEntryList.append(at(i));
    }
    progress.setValue(i);
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}
void MaxEntryList::rejectAmplitudeVerticalAbsolute(double ampZ)
{
  TRACE;
  ASSERT(_type==TFA);
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption("Filtering(tfaAmpZ)...  ");
  progress.setMaximum(count()-1);
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    const TFAMaxEntry& m=*static_cast<const TFAMaxEntry *>(at(i));
    if(m.vAmplitude() < ampZ) {
      delete at(i);
    } else {
      newEntryList.append(at(i));
    }
    progress.setValue(i);
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}

void MaxEntryList::rejectAmplitudeHorizontalAbsolute(double ampH)
{
  TRACE;
  ASSERT(_type==TFA);
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption("Filtering(tfaAmpH)...  ");
  progress.setMaximum(count()-1);
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    const TFAMaxEntry& m=*static_cast<const TFAMaxEntry *>(at(i));
    if(m.hAmplitude() < ampH) {
      delete at(i);
    } else {
      newEntryList.append(at(i));
    }
    progress.setValue(i);
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}

/*!
  Assumes that all entries are sorted by frequency, vAmplitude
*/
void MaxEntryList::rejectNPeaksPerFrequency(int npf)
{
  TRACE;
  ASSERT(_type==TFA);
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption("Filtering(tfaNppf)...  ");
  progress.setMaximum(count()-1);
  int n=0;
  double currentX=first()->x();
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    if(at(i)->x()!=currentX) {
      n=0;
      currentX=at(i)->x();
    }
    if(n>=npf) {
      delete at(i);
    } else {
      newEntryList.append(at(i));
      n++;
    }
    progress.setValue(i);
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}

/*!
  Assumes that all entries are sorted by frequency, vAmplitude.
  If \a nppm is less than 1, the time length is increased until reaching 1
*/
void MaxEntryList::rejectNPeaksPerMinute(double nppm)
{
  TRACE;
  ASSERT(_type==TFA);
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption("Filtering(tfaNppm)...  ");
  progress.setMaximum(count()-1);
  double currentX=first()->x();
  int nEntries=count();
  QMap<double, int> times;
  double timeSegment;
  if(nppm < 1.0) {
    timeSegment=60.0/nppm;
    nppm=1.0;
  } else {
    timeSegment=60.0;
  }
  for(int i=0;i<nEntries;i++) {
    if(at(i)->x()!=currentX) {
      currentX=at(i)->x();
      times.clear();
    }
    // Look back timeSegment seconds before and after this peak and count available peaks
    progress.setValue(i);
    double t=at(i)->time();
    double currentNppmB=0.0, currentNppmF=0.0;
    QMap<double, int>::iterator itForward=times.lowerBound(t);
    QMap<double, int>::iterator itBack=itForward-1;
    QList<double> aroundTimes;
    while(itBack!=times.end() && itBack.key() > t-timeSegment) {
      aroundTimes.prepend(itBack.key());
      itBack--;
      currentNppmB++;
    }
    if(currentNppmB>=nppm) {
      delete at(i);
      continue;
    }
    while(itForward!=times.end() && itForward.key() < t+timeSegment) {
      aroundTimes.append(itForward.key());
      itForward++;
      currentNppmF++;
    }
    if(currentNppmF>=nppm) {
      delete at(i);
      continue;
    }
    // Scan all possible time segment to check if number of time is less than nppm
    int iAroundTimes, iNppm=(int) nppm - 1;
    for(iAroundTimes=aroundTimes.count() - iNppm -1; iAroundTimes>=0; iAroundTimes-- ) {
      if(aroundTimes.at(iAroundTimes+iNppm) - aroundTimes.at(iAroundTimes) < timeSegment) {
        break;
      }
    }
    if(iAroundTimes>=0) {
      delete at(i);
      continue;
    }
    // OK, new time is acceptable
    times.insert(at(i)->time(), i);
    newEntryList.append(at(i));
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}

void MaxEntryList::rejectDelay(double delay)
{
  TRACE;
  ASSERT(_type==TFA);
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption("Filtering(tfaNppm)...  ");
  progress.setMaximum(count()-1);
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    const TFAMaxEntry& m=*static_cast<const TFAMaxEntry *>(at(i));
    if(m.delay()!=delay) {
      delete at(i);
    } else {
      newEntryList.append(at(i));
    }
    progress.setValue(i);
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}

void MaxEntryList::keepRing(int ringIndex)
{
  TRACE;
  ASSERT(_type==SPAC);
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption("Filtering(ring)...  ");
  progress.setMaximum(count()-1);
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    const SPACMaxEntry& m=*static_cast<const SPACMaxEntry *>(at(i));
     if(m.ring()!=ringIndex) {
      delete at(i);
    } else {
      newEntryList.append(at(i));
    }
    progress.setValue(i);
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}

void MaxEntryList::keepComponent(int componentIndex)
{
  TRACE;
  ASSERT(_type==SPAC);
  QList<MaxEntry *> newEntryList;
  ConsoleProgress progress;
  progress.setCaption("Filtering(component)...  ");
  progress.setMaximum(count()-1);
  int nEntries=count();
  for(int i=0;i<nEntries;i++) {
    const SPACMaxEntry& m=*static_cast<const SPACMaxEntry *>(at(i));
     if(m.component()!=componentIndex) {
      delete at(i);
    } else {
      newEntryList.append(at(i));
    }
    progress.setValue(i);
  }
  progress.end();
  clear();
  *static_cast< QList<MaxEntry *>* >(this)=newEntryList;
}
