/***************************************************************************
**
**  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-21
**  Copyright: 2017-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "FKPeaks.h"
#include "WaveNumberConverter.h"
#include "FKParameters.h"

namespace ArrayCore {

const char * FKPeaks::maxHeaderLine="# abs_time frequency polarization slowness azimuth ellipticity noise power valid";

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  FKPeaks::FKPeaks()
    : IncreaseStorage(1024)
  {
    TRACE;
    _values=static_cast<Value *>(allocateVector(sizeof(Value)));
    _full=false;
  }

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

  void FKPeaks::clear()
  {
    TRACE;
    IncreaseStorage::clear();
    free(_values);
    _values=static_cast<Value *>(allocateVector(sizeof(Value)));
    _full=false;
  }

  void FKPeaks::reallocate()
  {
    TRACE;
    double n=capacity();
    n*=sizeof(Value);
    n/=1024.0*1024.0;
    _dataLock.lockForWrite();
    Value * newValues=static_cast<Value *>(reallocateVector((uchar *)_values, sizeof(Value)));
    if(newValues) {
      _values=newValues;
    } else {
      App::log(tr("Failed to reallocate results for %1 values (size=%2 Mb)\n"
                  "Container is full.")
               .arg(capacity())
               .arg(n));
      _full=true;
    }
    _dataLock.unlock();
    APP_LOG(1, tr("Reallocated results for %1 values (size=%2 Mb)\n")
            .arg(capacity())
            .arg(n))
  }

  /*!
    Ellipticity must be the plain value not the angle.
  */
  bool FKPeaks::add(const Point2D &k, double ell, double noise, double power,
                    const WaveNumberConverter& conv, double minimumSlowness)
  {
    TRACE;
    double slow=conv.slowness(k);
    if(slow>minimumSlowness) {
      if(_full) {
        return false;
      } else {
        int n=size();
        IncreaseStorage::add();
        Value& v=_values[n];
        v.time=_timeReference.secondsTo(conv.time());
        v.frequency=conv.frequency();
        v.slowness=slow;
        v.azimuth=conv.azimuth(k);
        v.polarization=conv.polarization();
        if(v.polarization==Mode::Rayleigh) {
          v.ellipticity=ell;
        } else {
          v.ellipticity=0.0;
        }
        v.noise=noise;
        v.power=power;
        v.valid=true;
        APP_LOG(8, tr("%1 Hz: new peak at %2 m/s propagating towards %3 with ellippticity %4\n")
                            .arg(conv.frequency())
                            .arg(1.0/slow)
                            .arg(conv.azimuth(k))
                            .arg(::atan(ell)))
      }
    }
    return true;
  }

  void FKPeaks::add(const FKPeaks& peaks)
  {
    App::log(tr("Merging results...\n"));
    _addLock.lock();
    int i0=size();
    int n=peaks.size();
    IncreaseStorage::add(n);
    _dataLock.lockForRead();
    _addLock.unlock();
    for(int i=0; i<n; i++) {
      _values[i0+i]=peaks._values[i];
    }
    _dataLock.unlock();
  }

  bool FKPeaks::save(const QString &fileName, const FKParameters& param, const ArraySelection& array) const
  {
    TRACE;
    QFile f(fileName);
    if(f.open(QIODevice::WriteOnly)) {
      QTextStream s(&f);
      s.setRealNumberPrecision(20);
      s << "# MAX FORMAT RELEASE 1.1\n";
      CoreApplication::signature(s);
      s << "#\n"
           "# BEGIN STATION LIST\n"
           "#\n";
      s << array.toString(ArraySelection::Absolute);
      s << "#\n"
           "# END STATION LIST\n"
           "#\n"
           "#\n"
           "# BEGIN PARAMETERS\n"
           "#\n";
      s << param.toString();
      s << "#\n"
           "# END PARAMETERS\n"
           "#\n"
           "#\n"
           "# BEGIN DATA\n"
           "#\n";
      s << maxHeaderLine << "\n";
      int n=size();
      for(int i=0; i<n; i++) {
        Value& v=_values[i];
        s << _timeReference.shifted(v.time).toString() << " "
          << v.frequency << " "
          << Mode::polarization(v.polarization) << " "
          << v.slowness << " "
          << v.azimuth << " "
          << v.ellipticity << " "
          << v.noise << " "
          << v.power << " "
          << (v.valid ? 1 : 0) << "\n";
      }
      return true;
    } else {
      App::log(tr("error writing to file '%1'\n").arg(fileName) );
      return false;
    }
  }

  /*!
    Read until finding the header line.
  */
  bool FKPeaks::readHeader(QTextStream& s)
  {
    TRACE;
    QString line;
    do {
      line=s.readLine();
    } while(!s.atEnd() && line!=maxHeaderLine);
    return !s.atEnd();
  }

  /*!
    Read and parse next available not blank and not starting with '#'.
  */
  bool FKPeaks::read(QTextStream& s, Value& v)
  {
    TRACE;
    QString line;
    do {
      line=s.readLine();
    } while(!line.isEmpty() && line[0]=='#');
    if(!line.isEmpty()) {
      const QChar * ptr=nullptr;
      StringSection field,fields(line);
      field=fields.nextField(ptr);
      v.time=_timeReference.secondsTo(field.toTime(DateTime::defaultFormat));
      field=fields.nextField(ptr);
      v.frequency=field.toDouble();
      field=fields.nextField(ptr);
      v.polarization=Mode::polarization(field.toString());
      field=fields.nextField(ptr);
      v.slowness=field.toDouble();
      field=fields.nextField(ptr);
      v.azimuth=field.toDouble();
      field=fields.nextField(ptr);
      v.ellipticity=field.toDouble();
      field=fields.nextField(ptr);
      v.noise=field.toDouble();
      field=fields.nextField(ptr);
      v.power=field.toDouble();
      field=fields.nextField(ptr);
      v.valid=(field.toInt()==1);
      return true;
    } else {
      return false;
    }
  }

  bool FKPeaks::load(const QString &fileName)
  {
    TRACE;
    QFile f(fileName);
    if(f.open(QIODevice::ReadOnly)) {
      QTextStream s(&f);
      if(!readHeader(s)) {
        App::log(tr("Cannot find header line in file '%1'\n").arg(fileName) );
        return false;
      }
      int n=size();
      IncreaseStorage::add();
      while(true) {
        if(read(s, _values[n])) {
          n=size();
          IncreaseStorage::add();
        } else {
          downSize(n);
          break;
        }
      }
      App::log(tr("Loaded %1 slowness samples from file '%2'\n").arg(size()).arg(fileName) );
      return true;
    } else {
      App::log(tr("Error loading value from '%1'\n").arg(fileName));
      return false;
    }
  }

  void FKPeaks::fillSlowness(Histogram2D * h, Mode::Polarization polarization) const
  {
    TRACE;
    int n=size();
    for(int i=0; i<n; i++) {
      Value& v=_values[i];
      if(v.polarization==polarization && v.valid) {
        h->addSample(Point2D(v.frequency, v.slowness));
      }
    }
  }

  void FKPeaks::fillWaveNumber(Histogram2D * h, Mode::Polarization polarization) const
  {
    TRACE;
    int n=size();
    for(int i=0; i<n; i++) {
      Value& v=_values[i];
      if(v.polarization==polarization && v.valid) {
        h->addSample(Point2D(v.frequency, 2.0*M_PI*v.frequency*v.slowness));
      }
    }
  }

  void FKPeaks::fillSlownessWaveNumber(Histogram2D * h, Mode::Polarization polarization) const
  {
    TRACE;
    int n=size();
    for(int i=0; i<n; i++) {
      Value& v=_values[i];
      if(v.polarization==polarization && v.valid) {
        h->addSample(Point2D(2.0*M_PI*v.frequency*v.slowness, v.slowness));
      }
    }
  }

  void FKPeaks::fillAzimuth(Histogram2D * h, Mode::Polarization polarization) const
  {
    TRACE;
    int n=size();
    for(int i=0; i<n; i++) {
      Value& v=_values[i];
      if(v.polarization==polarization && v.valid) {
        h->addSample(Point2D(v.frequency, v.azimuth));
      }
    }
  }

  void FKPeaks::fillEllipticity(Histogram2D * h) const
  {
    TRACE;
    int n=size();
    for(int i=0; i<n; i++) {
      Value& v=_values[i];
      if(v.polarization==QGpCoreWave::Mode::Rayleigh && v.valid) {
        h->addSample(Point2D(v.frequency, v.ellipticity));
      }
    }
  }

  void FKPeaks::commit(Histogram2D * h, Mode::Polarization polarization)
  {
    TRACE;
    int n=size();
    int ih=0;
    ASSERT(n==h->sampleCount());
    for(int i=0; i<n; i++) {
      Value& v=_values[i];
      if(v.polarization==polarization && v.valid) {
        const Histogram2D::Sample& s=h->sample(ih++);
        v.valid=s.isValid();
      }
    }
  }

  void FKPeaks::reset(Mode::Polarization polarization)
  {
    TRACE;
    int n=size();
    for(int i=0; i<n; i++) {
      Value& v=_values[i];
      if(v.polarization==polarization && !v.valid) {
        v.valid=true;
      }
    }
  }

  /*QHash<double, int> FKPeaks::timeFrequencyHashTable(Mode::Polarization polarization) const
  {
    TRACE;
    QHash<double, int> h;
    int n=size();
    for(int i=0; i<n; i++) {
      Value& v=_values[i];
      if(v.polarization==polarization) {
        h.insert(v.key(), i);
      }
    }
    return h;
  }*/

  void FKPeaks::filterBelowWaveNumber(double minK)
  {
    TRACE;
    double minLambda=minK/(2.0*M_PI);
    double lambda;
    int n=size();
    for(int i=0; i<n; i++) {
      Value& v=_values[i];
      if(v.valid) {
        lambda=v.frequency*v.slowness;
        if(lambda<minLambda) {
          v.valid=false;
        }
      }
    }
  }

  /*!
    \a angleTolerance is in degree like the azimuth

    We can keep all Rayleigh entries that match the single direction in Love
    or,
    We can keep all Love entries that match the single direction in Rayleigh
  */
  void FKPeaks::horizontalSingleDirection(double angleTolerance)
  {
    TRACE;
    sort();
    int n=size();
    if(n==0) {
      return;
    }
    double currentTime=_values[0].time;
    double currentFrequency=_values[0].frequency;
    AverageAngle rayleighAverage, loveAverage;
    QVector<Azimuth> rayleighValues, loveValues;
    int notValidCount=0;
    int totalCount=0;
    for(int i=0; i<n; i++) {
      Value& v=_values[i];
      if(v.valid) {
        if(v.time!=currentTime || v.frequency!=currentFrequency) {
          notValidCount+=validateSingleDirection(rayleighAverage.averageDegrees(), rayleighValues,
                                                 loveAverage.averageDegrees(), loveValues,
                                                 angleTolerance);
          if(v.frequency!=currentFrequency) {
            singleDirectionReport(currentFrequency, notValidCount, totalCount);
            totalCount=0;
            notValidCount=0;
          }
          currentTime=v.time;
          currentFrequency=v.frequency;
          rayleighValues.clear();
          rayleighAverage.clear();
          loveValues.clear();
          loveAverage.clear();
        }
        switch(v.polarization) {
        case Mode::Rayleigh:
        case Mode::Radial:
          rayleighAverage.addDegrees(v.azimuth);
          rayleighValues.append(Azimuth(v.azimuth, i));
          break;
        case Mode::Love:
        case Mode::Transverse:
          loveAverage.addDegrees(v.azimuth);
          loveValues.append(Azimuth(v.azimuth, i));
          break;
        case Mode::Vertical:
          ASSERT(false);
        }
        totalCount++;
      }
    }
    notValidCount+=validateSingleDirection(rayleighAverage.averageDegrees(), rayleighValues,
                                           loveAverage.averageDegrees(), loveValues,
                                           angleTolerance);
    singleDirectionReport(currentFrequency, notValidCount, totalCount);
  }

  /*!
    Return number invalidated entries
  */
  int FKPeaks::validateSingleDirection(double rayleighAverage, const QVector<Azimuth>& rayleighValues,
                                       double loveAverage, const QVector<Azimuth>& loveValues,
                                       double angleTolerance)
  {
    TRACE;
    int notValidCount=0;
    if(isSingleDirection(rayleighAverage, rayleighValues, angleTolerance)) {
      for(QVector<Azimuth>::const_iterator it=loveValues.begin(); it!=loveValues.end(); it++) {
        if(fabs(Angle::differenceDegrees(it->value, rayleighAverage))>angleTolerance) {
          _values[it->index].valid=false;
          notValidCount++;
        }
      }
      if(notValidCount>0) { // several directions for Love
        for(QVector<Azimuth>::const_iterator it=rayleighValues.begin(); it!=rayleighValues.end(); it++) {
          _values[it->index].valid=false;
          notValidCount++;
        }
      }
    } else if(isSingleDirection(loveAverage, loveValues, angleTolerance)) {
      for(QVector<Azimuth>::const_iterator it=rayleighValues.begin(); it!=rayleighValues.end(); it++) {
        if(fabs(Angle::differenceDegrees(it->value, loveAverage))>angleTolerance) {
          _values[it->index].valid=false;
          notValidCount++;
        }
      }
      if(notValidCount>0) { // several directions for Rayleigh
        for(QVector<Azimuth>::const_iterator it=loveValues.begin(); it!=loveValues.end(); it++) {
          _values[it->index].valid=false;
          notValidCount++;
        }
      }
    } else { // Plenty of directions for both polarization
      for(QVector<Azimuth>::const_iterator it=rayleighValues.begin(); it!=rayleighValues.end(); it++) {
        _values[it->index].valid=false;
        notValidCount++;
      }
      for(QVector<Azimuth>::const_iterator it=loveValues.begin(); it!=loveValues.end(); it++) {
        _values[it->index].valid=false;
        notValidCount++;
      }
    }
    return notValidCount;
  }

  /*!
  */
  bool FKPeaks::isSingleDirection(double average, const QVector<Azimuth>& values, double angleTolerance)
  {
    TRACE;
    for(QVector<Azimuth>::const_iterator it=values.begin(); it!=values.end(); it++) {
      if(fabs(Angle::differenceDegrees(it->value, average))>angleTolerance) {
        return false;
      }
    }
    return true;
  }

  /*!
  */
  void FKPeaks::singleDirectionReport(double frequency, int notValidCount, int totalCount)
  {
    TRACE;
    App::log(tr("Multiple horizontal directions at %1 Hz: %2/%3 invalid\n")
        .arg(frequency)
        .arg(notValidCount)
        .arg(totalCount));
  }

  /*!
    Sort as primary key decreasing frequency and secondary increasing time
  */
  void FKPeaks::sort()
  {
    TRACE;
    int n=size();
    QVector<Value *> list(n);
    for(int i=0; i<n; i++) {
      list[i]=_values+i;
    }
    std::sort(list.begin(), list.end(), valueLessThan);
    Value * newValues=static_cast<Value *>(allocateVector(sizeof(Value)));
    for(int i=0; i<n; i++) {
      memcpy(newValues+i, list.at(i), sizeof(Value));
    }
    free(_values);
    _values=newValues;
  }

  bool FKPeaks::valueLessThan(Value * v1, Value * v2)
  {
    if(v1->frequency>v2->frequency) {
      return true;
    } else if(v1->frequency<v2->frequency) {
      return false;
    } else {
      if(v1->time<v2->time) {
        return true;
      } else if(v1->time>v2->time) {
        return false;
      } else {
        if(v1->polarization<v2->polarization) {
          return true;
        } else if(v1->polarization>v2->polarization) {
          return false;
        } else {
          if(v1->slowness<v2->slowness) {
            return true;
          } else if(v1->slowness>v2->slowness) {
            return false;
          } else {
            if(v1->azimuth<v2->azimuth) {
              return true;
            } else if(v1->azimuth>v2->azimuth) {
              return false;
            } else {
              return v1->ellipticity<v2->ellipticity;
            }
          }
        }
      }
    }
  }

} // namespace ArrayCore

