/***************************************************************************
**
**  This file is part of gpsignal.
**
**  gpsignal 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.
**
**  gpsignal 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: 2008-12-16
**  Copyright: 2008-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>

#include <QGpCoreTools.h>
#include "SignalReader.h"

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
SignalReader::SignalReader()
    : ArgumentStdinReader()
{
  TRACE;
  _nSamples=0;
  _samples=nullptr;
  //_commonFrequency=-1.0;
  _deltaT=0.01;
  _timeLength=600.0; // 10 minutes
  //_uniqueFrequency=true;
  _square=false;
  _dispersionCurve=nullptr;
}

SignalReader::~SignalReader()
{
  TRACE;
  delete [] _samples;
  delete _dispersionCurve;
}

bool SignalReader::setOptions(int& argc, char ** argv)
{
  TRACE;
  // Check arguments
  int i, j=1;
  for(i=1; i<argc; i++) {
    QByteArray arg=argv[i];
    if(arg[0]=='-') {
      if(arg=="-f") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _deltaT=1.0/atof(argv[i] );
      } else if(arg=="-t") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _timeLength=atof(argv[i] );
      } else if(arg=="-x") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _stationCoord.setX(atof( argv[i] ));
      } else if(arg=="-y") {
        CoreApplication::checkOptionArg(i, argc, argv);
        _stationCoord.setY(  atof(argv[i] ));
      } else if(arg=="-grid") {
        CoreApplication::checkOptionArg(i, argc, argv);
        int n=atoi(argv[i]);
        if(n>1) {
          _grid.init(n, n);
        } else {
          App::log(tr("Number of bins must be > 1\n"));
          return 2;
        }
        CoreApplication::checkOptionArg(i, argc, argv);
        double d=0.5*atof(argv[i]);
        if(d>0.0) {
          _grid.setLinear(XAxis, -d, d);
          _grid.setLinear(YAxis, -d, d);
        } else {
          App::log(tr("Grid size must be positive\n"));
          return 2;
        }
      } else if(arg=="-dc") {
        CoreApplication::checkOptionArg(i, argc, argv);
        delete _dispersionCurve;
        QFile f(argv[i]);
        if(f.open(QIODevice::ReadOnly)) {
          _dispersionCurve=new Curve<Point2D>;
          QTextStream s(&f);
          QString line;
          QRegExp sep("[ \t]{1,}");
          bool okX, okY;
          Point2D p;
          int lineNumber=0;
          while(!s.atEnd()) {
            line=s.readLine();
            lineNumber++;
            if(!line.isEmpty() && line[0]!='#') {
              p.setX(line.section(sep, 0, 0).toDouble(&okX));
              p.setY(line.section(sep, 1, 1).toDouble(&okY));
              if(!okX || !okY) {
                App::log(tr("gpsignal: error parsing file '%1' at line %2\n").arg(argv[i]).arg(lineNumber) );
                return false;
              }
              _dispersionCurve->append(p);
            }
          }
        } else {
          App::log(tr("gpsignal: failed to open '%1'\n").arg(argv[i]) );
          return false;
        }
      } else if(arg=="-square") {
        _square=true;
      } else {
        App::log(tr("gpsignal: bad option %1, see -help\n").arg(argv[i]) );
        return false;
      }
    } else {
      argv[j++]=argv[i];
    }
  }
  if(j < argc) {
    argv[j]=nullptr;
    argc=j;
  }
  if(_grid.nx()>0) {
    _wavelets.resize(_grid.nx()*_grid.ny());
  } else {
    _wavelets.resize(1);
  }
  _nSamples=qRound(_timeLength/_deltaT);
  _samples=new double [_nSamples];
  return true;
}

bool SignalReader::addWavelet(int posIndex, const Point2D& pos,
                              const QString& buf)
{
  TRACE;
  Wavelet w;
  StringSection fields(buf);
  StringSection field;
  const QChar * p=nullptr;
  field=fields.nextField(p);
  if(!field.isValid()) {
    App::log(tr("gpsignal: error reading amplitude, \n").arg(buf));
    return false;
  }
  w.amplitude=field.toDouble();
  field=fields.nextField(p);
  if(!field.isValid()) {
    App::log(tr("gpsignal: error reading frequency, \n").arg(buf));
    return false;
  }
  w.frequency=field.toDouble();

  field=fields.nextField(p);
  if(!field.isValid()) {
    App::log(tr("gpsignal: error reading phase, \n").arg(buf));
    return false;
  }
  double phaseDegrees=field.toDouble();
  w.phase=Angle::degreesToRadians(phaseDegrees);

  field=fields.nextField(p);
  if(!field.isValid()) {
    App::log(tr("gpsignal: error reading azimuth, \n").arg(buf));
    return false;
  }
  double azimuth=field.toDouble();
  double angle=pos.azimuth();
  angle-=Angle::geographicToMath(azimuth);

  double distance=Point2D().distanceTo(pos)*cos(angle);
  double velocity;
  if(_dispersionCurve) {
    double slowness=_dispersionCurve->at(w.frequency).y();
    velocity=1.0/slowness;
    w.phase+=2.0*M_PI*w.frequency*distance*slowness;
  } else {
    field=fields.nextField(p);
    if(!field.isValid()) {
      App::log(tr("gpsignal: error reading velocity, \n").arg(buf));
      return false;
    }
    velocity=field.toDouble();
    w.phase+=2.0*M_PI*w.frequency*distance/velocity;
  }
  _wavelets[posIndex].append(w);
  APP_LOG(1, tr("%1   Amplitude          =%2\n"
                "     Frequency          =%3\n"
                "     Phase (theoretical)=%4\n"
                "     Phase (propagation)=%5\n"
                "     Azimuth            =%6 m/s\n"
                "     Velocity           =%7 m/s\n"
                )
              .arg(_wavelets[posIndex].count(), 4, 10)
              .arg(w.amplitude)
              .arg(w.frequency)
              .arg(phaseDegrees)
              .arg(Angle::radiansToDegrees(w.phase))
              .arg(azimuth)
              .arg(velocity));
  //if(_commonFrequency==-1) _commonFrequency=w.frequency;
  //else if(_commonFrequency!=w.frequency) _uniqueFrequency=false;
  return true;

}

bool SignalReader::parse(QTextStream& s)
{
  TRACE;
  QString buf;
  buf=s.readLine();
  if(!buf.isEmpty() && buf[0]!='\n' && buf[0]!='#') {
    if(_grid.nx()==0) {
      return addWavelet(0, _stationCoord, buf);
    } else {
      int nx=_grid.nx();
      for(int ix=nx-1; ix>=0; ix--) {
        for(int iy=_grid.ny()-1; iy>=0; iy--) {
          if(!addWavelet(ix*nx+iy, Point2D(_grid.x(ix), _grid.y(iy)), buf)) {
            return false;
          }
        }
      }
    }
  }
  return true;
}

void SignalReader::exec()
{
  QTextStream sOut(stdout);
  int nx=_grid.nx();
  if(nx>0) {
    for(int it=0; it<_nSamples; it++) {
      double t=static_cast<double>(it)*_deltaT;
      App::log(tr("t=%1\n").arg(t));
      for(int ix=nx-1; ix>=0; ix--) {
        for(int iy=_grid.ny()-1; iy>=0; iy--) {
          double& v=*_grid.valuePointer(ix, iy);
          v=0.0;
          const QVector<Wavelet>& ws=_wavelets.at(ix*nx+iy);
          int nWavelets=ws.count();
          for(int iw=0; iw<nWavelets; iw++) {
            const Wavelet& w=ws.at(iw);
            v+=w.amplitude*cos(2.0*M_PI*w.frequency*t-w.phase);
          }
        }
      }
      sOut << "# t= " << t << "\n";
      sOut << _grid;
    }
  } else {
    const QVector<Wavelet>& ws=_wavelets.first();
    int nWavelets=ws.count();
    double t;
    if(_square) {
      for(int i=0;i<_nSamples;i++) {
        _samples[i]=0.0;
        t=(double)i*_deltaT;
        for(int iw=0; iw<nWavelets; iw++) {
          const Wavelet& w=ws.at(iw);
          double a=w.amplitude*cos(2.0*M_PI*w.frequency*t-w.phase);
          if(a<=0.0) {
            a=0.0;
          } else {
            a=1.0;
          }
          _samples[i]+=a;
        }
      }
    } else {
      for(int i=0;i<_nSamples;i++) {
        _samples[i]=0.0;
        t=(double)i*_deltaT;
        for(int iw=0; iw<nWavelets; iw++) {
          const Wavelet& w=ws.at(iw);
          _samples[i]+=w.amplitude*cos(2.0*M_PI*w.frequency*t-w.phase);
        }
      }
    }
    for(int i=0;i<_nSamples;i++) {
      sOut << _samples[i] << "\n";
    }
  }
  sOut << flush;
}

#if 0
  // Fit of a single cosine if fc[i] are all equals
  if(_uniqueFrequency && nWavelets>1) {
    double ** p=new double *[3];
    double * y=new double [3];
    p[0]=new double [2];
    p[0][0]=_wavelets[0].amplitude;
    p[0][1]=_wavelets[0].phase;
    y[0]=misfit(p[0]);
    p[1]=new double [2];
    p[1][0]=_wavelets[1].amplitude;
    p[1][1]=_wavelets[1].phase;
    y[1]=misfit(p[1]);
    p[2]=new double [2];
    p[2][0]=0;
    p[2][1]=0;
    y[2]=misfit(p[2]);
    int nfunk;
    amoeba(p, y, 2, 1e-5, misfit, &nfunk);
    for(int i=0;i<_nSamples;i++) {
      t=(double)i*_deltaT;
      printf("%lf\t%lf\n",_samples[i],p[0][0]*cos(2.0*M_PI*_commonFrequency*t-p[0][1]));
    }
    App::log(QString("Best fitting cosine:\n"
                      "  Amp=%1\tPhase=%2\tMisfit=%3\n"
                      "  Amp=%4\tPhase=%5\tMisfit=%6\n"
                      "  Amp=%7\tPhase=%8\tMisfit=%9\n")
              .arg(p[0][0]).arg(p[0][1]).arg(misfit(p[0]))
              .arg(p[1][0]).arg(p[1][1]).arg(misfit(p[1]))
              .arg(p[2][0]).arg(p[2][1]).arg(misfit(p[2]));
    /* It is always possible to express a sum of cosine into a single
       A*cos(wt+phi)
       For two cosines:
       A=A1*A1+A2*A2+2*A1*A2*cos(phi1-phi2)
       tan phi=(A1*sin phi1+A2*sin phi2)/(A1*cos phi1+A2*cos phi2)
    */
    delete [] p[0];
    delete [] p[1];
    delete [] p[2];
    delete [] p;
    delete [] y;
  } else {
}

double SignalReader::misfit(double ampPhi[])
{
  double tmp, misfitVal=0, t;
  for(int i=0;i<_nSamples;i++) {
    t=(double)i*_deltaT;
    tmp=_samples[i]-ampPhi[0]*cos(2.0*M_PI*_commonFrequency*t-ampPhi[1]);
    misfitVal+=tmp*tmp;
  }
  misfitVal=sqrt(misfitVal/_nSamples);
  fprintf(stderr,"%lf\t%lf\t%lf\n",ampPhi[0],ampPhi[1],misfitVal);
  return misfitVal;
}
#endif
