/***************************************************************************
**
**  This file is part of QGpCompatibility.
**
**  QGpCompatibility 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.
**
**  QGpCompatibility 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: 2003-02-11
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>

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

namespace QGpCompatibility {

#define twopi (2*M_PI)

CompatAutocorrCurves::CompatAutocorrCurves() : CompatMultiModalFrequency()
{
  TRACE;
  _values=0;
  _radiusCount=0;
  _radiusMin=0;
  _radiusMax=0;
  _rangeType=Complete;
  _rayleighModesCount=0;
}

CompatAutocorrCurves::CompatAutocorrCurves(int modesCount, int radiusCount, int omegasCount) :
    CompatMultiModalFrequency(modesCount, omegasCount)
{
  TRACE;
  _radiusCount=radiusCount;
  _radiusMin=0;
  _radiusMax=0;
  allocatesRadius();
  _values=0;
  allocatesValues();
  _rangeType=Complete;
  _rayleighModesCount=modesCount;
}

CompatAutocorrCurves::CompatAutocorrCurves(const CompatAutocorrCurves * o) :
    CompatMultiModalFrequency(o)
{
  TRACE;
  _radiusCount=o->_radiusCount;
  _radiusMin=0;
  _radiusMax=0;
  allocatesRadius();
  // Copy the radius min and max
  for(int iRadius=0;iRadius<_radiusCount;iRadius++) {
    _radiusMin[iRadius]=o->radiusMin(iRadius);
    _radiusMax[iRadius]=o->radiusMax(iRadius);
  }  
  _values=0;
  allocatesValues();
  _rangeType=Complete;
  _rayleighModesCount=o->_rayleighModesCount;
}

CompatAutocorrCurves::CompatAutocorrCurves(const CompatAutocorrCurves * o,int modesCount) :
    CompatMultiModalFrequency(o, modesCount)
{
  TRACE;
  _radiusCount=o->_radiusCount;
  _radiusMin=0;
  _radiusMax=0;
  allocatesRadius();
  // Copy the radius min and max
  for(int iRadius=0;iRadius<_radiusCount;iRadius++) {
    _radiusMin[iRadius]=o->radiusMin(iRadius);
    _radiusMax[iRadius]=o->radiusMax(iRadius);
  }  
  _values=0;
  allocatesValues();
  _rangeType=Complete;
  _rayleighModesCount=o->_rayleighModesCount;
  if((int)_rayleighModesCount>_modesCount) _rayleighModesCount=_modesCount;
}

CompatAutocorrCurves::~CompatAutocorrCurves()
{
  TRACE;
  deleteValues();
  delete [] _radiusMin;
  delete [] _radiusMax;
}

void CompatAutocorrCurves::deleteValues()
{
  TRACE;
  if(_values)  {
    for(int imode=0;imode<_modesCount;imode++)
    {
      double ** autocorrMode=_values[imode];
      for(int radius=0; radius<_radiusCount;radius++)
        delete [] autocorrMode[radius];
      delete [] _values[imode];
    }
    delete [] _values; 
    _values=0;
  }
}

void CompatAutocorrCurves::allocatesRadius()
{
  TRACE;
  delete [] _radiusMin;
  delete [] _radiusMax;
  _radiusMin=new double[_radiusCount];
  _radiusMax=new double[_radiusCount];
}

void CompatAutocorrCurves::allocatesValues()
{
  TRACE;
  ASSERT (_radiusCount>0);
  deleteValues();
  _values=new double**[_modesCount];
  for(int imode=0;imode<_modesCount;imode++) {
    double ** autocorrMode=new double*[_radiusCount];
    _values[imode]=autocorrMode;
    for(int iradius=0; iradius<_radiusCount;iradius++)
      autocorrMode[iradius]=new double[_omegasCount];
  }
}

void CompatAutocorrCurves::resetValues()
{
  TRACE;
  for(int mode=0; mode<_modesCount;mode++) {
    double ** valuesMode=_values[mode];
    for(int iradius=0; iradius<_radiusCount;iradius++) {
      double * values=valuesMode[iradius];
      for(int i=0;i<_omegasCount;i++) values[i]=CompatAUTOCORR_INVALID_VALUE;
    }
  }
}

void CompatAutocorrCurves::toStream(FILE * f)
{
  TRACE;
  for(int imode=0; imode<_modesCount;imode++) {
    double ** autocorrMode=_values[imode];
    for(int radius=0; radius<_radiusCount;radius++) {
      double * autocorrValues=autocorrMode[radius];
      for(int i=0;i<_omegasCount;i++) {
        if(autocorrValues[i]!=CompatAUTOCORR_INVALID_VALUE) {
          fprintf(f,"%lf %lf\n",twopi/_omegas[i],autocorrValues[i]);
        }
      }
      fprintf(f,">\n");
    }
  }
}

void CompatAutocorrCurves::valuesToReport(QDataStream& s) const
{
  TRACE;
  for(int imode=0; imode<_modesCount;imode++) {
    double ** autocorrMode=_values[imode];
    for(int iRadius=0; iRadius<_radiusCount;iRadius++) {
      //s.writeRawData((char *)autocorrMode[iRadius],_omegasCount*sizeof(double));
      double * autocorrValues=autocorrMode[iRadius];
      for(int i=0;i<_omegasCount;i++) s << autocorrValues[i];
    }
  }
}

void CompatAutocorrCurves::reportToValues(QDataStream& s)
{
  TRACE;
  ASSERT(_values!=0);
  for(int imode=0; imode<_modesCount;imode++) {
    double ** autocorrMode=_values[imode];
    for(int iRadius=0; iRadius<_radiusCount;iRadius++) {
      //s.readRawData((char *)autocorrMode[iRadius],_omegasCount*sizeof(double));
      double * autocorrValues=autocorrMode[iRadius];
      for(int i=0;i<_omegasCount;i++) s >> autocorrValues[i];
    }
  }
}

void CompatAutocorrCurves::reportToOmega(QDataStream& s)
{
  TRACE;
  deleteValues();
  CompatMultiModalFrequency::reportToOmega(s);
  allocatesValues();
}

void CompatAutocorrCurves::radiusToReport(QDataStream& s) const
{
  TRACE;
  s << _radiusCount;
  for(int i=0;i<_radiusCount;i++) {
    s << _radiusMin[i];
    s << _radiusMax[i];
  }
}

void CompatAutocorrCurves::reportToRadius(QDataStream& s)
{
  TRACE;
  s >> _radiusCount;
  allocatesRadius();
  for(int i=0;i<_radiusCount;i++) {
    s >> _radiusMin[i];
    s >> _radiusMax[i];
  }
}

int CompatAutocorrCurves::toPointVector(int imode, int iradius,Point2D * pointList)
{
  TRACE;
  if(imode>=_modesCount || iradius>=_radiusCount) return 0;
  double * autocorrValues=_values[imode][iradius];
  int count=_omegasCount;
  for(int i=0;i<_omegasCount;i++) {
    double val=autocorrValues[i];
    if(val!=CompatAUTOCORR_INVALID_VALUE) *(pointList++)=Point2D(_omegas[i]/twopi,val);
    else count--;
  }
  return count;
}

int CompatAutocorrCurves::toDistancePointVector(int imode, int iomega, Point2D * pointList)
{
  TRACE;
  if(imode>=_modesCount || iomega>=_omegasCount) return 0;
  double ** autocorrValues=_values[imode];
  int count=_radiusCount;
  for(int i=0;i<_radiusCount;i++) {
    double val=autocorrValues[i][iomega];
    if(val!=CompatAUTOCORR_INVALID_VALUE) *(pointList++)=Point2D(radiusAverage(i),val);
    else count--;
  }
  return count;
}

QVector<double> * CompatAutocorrCurves::radiusAverage() const
{
  QVector<double> * list=new QVector<double>;
  list->reserve(_radiusCount);
  for(int i=0;i<_radiusCount;i++)
    list->push_back(radiusAverage(i));
  return list;
}

void CompatAutocorrCurves::resetAutocorr()
{
  TRACE;
  for(int imode=0; imode<_modesCount;imode++) {
    double ** autocorrMode=_values[imode];
    for(int radius=0; radius<_radiusCount;radius++) {
      double * autocorrValues=autocorrMode[radius];
      for(int i=0;i<_omegasCount;i++)
        autocorrValues[i]=CompatAUTOCORR_INVALID_VALUE;
    }
  }
}


int CompatAutocorrCurves::omegasCount (int imode,int iradius) const
{
  TRACE;
  int n=0;
  double * autocorrValues=_values[imode][iradius];
  for(int i=0;i<_omegasCount;i++) {
    if(autocorrValues[i]!=CompatAUTOCORR_INVALID_VALUE) n++;
  }
  return n;
}

void CompatAutocorrCurves::setRadius(CompatAutocorrCurves * o)
{
  TRACE;
  ASSERT(_radiusCount==o->_radiusCount);   
  for(int iradius=0; iradius<_radiusCount;iradius++) {
    _radiusMin[iradius] =o->_radiusMin[iradius];
    _radiusMax[iradius] =o->_radiusMax[iradius];
  }
}

int CompatAutocorrCurves::getRadiusCount(QString radiusFile)
{
  TRACE;
  QFile f(radiusFile);
  if(!f.open(QIODevice::ReadOnly)) {
    fprintf(stderr," **** Cannot open radius file\n");
    return 0;
  }
  QTextStream s(&f);
  QString str;
  // Count radius
  int radiusCount=0;
  while(!s.atEnd()) {
    str=s.readLine().trimmed();
    if(!str.isEmpty() && str.left(1)!="#") radiusCount++;
  }
  return radiusCount;
}

void CompatAutocorrCurves::setRadius(QString radiusFile)
{
  TRACE;
  ASSERT(getRadiusCount(radiusFile)==_radiusCount);
  QFile f(radiusFile);
  if(!f.open(QIODevice::ReadOnly)) {
    fprintf(stderr," **** Cannot open radius file\n");
    return;
  }
  QTextStream s(&f);
  QString str;
  int iRadius=0;
  while(!s.atEnd()) {
    str=s.readLine().trimmed();
    if(!str.isEmpty() && str.left(1)!="#") {
      _radiusMin[iRadius]=str.section(QRegExp("[ \t]"),0,0).toDouble();
      _radiusMax[iRadius]=str.section(QRegExp("[ \t]"),1,1).toDouble();
      iRadius++;
    }
  }
}

void CompatAutocorrCurves::setRadius(QVector<double>& rmin,
                                  QVector<double>& rmax)
{
  TRACE;
  ASSERT ((int)rmin.count()==_radiusCount && (int)rmax.count()==_radiusCount);
  for(int i=0;i<_radiusCount;i++) {
    _radiusMin[i]=rmin[i];
    _radiusMax[i]=rmax[i];
  }
}

//______________________________________________________________
//
// CALCULATE 
//
void CompatAutocorrCurves::calculate(CompatDispersion& disp)
{
  TRACE;
  int maxMode=disp.modesCount();
  if(maxMode>_modesCount) maxMode=_modesCount;
  int imode;
  for(imode=0;imode<maxMode;imode++) {
    double ** autocorrMode=_values[imode];
    for(int iRadius=0;iRadius<_radiusCount;iRadius++) {
      double rmin=_radiusMin[iRadius];
      double rmax=_radiusMax[iRadius];
      double r0=0.5*(rmin+rmax);
      double dr=rmax-rmin;
      double arg0,arg1;
      double * autocorres =autocorrMode[iRadius];
      // To make as Bruno Bettig, switch from SPAC to MSPAC if the radius range
      // is too large. Threshold is 2%.
      if(fabs(dr) < 0.02*r0) {
        for(int j=0;j<_omegasCount;j++) {
          /********************************************************/
          /* nearly perfect ring - use original Aki's relation    */
          /* between averaged autocorrelation and Bessel function */
          /********************************************************/
          arg0=_omegas[j]*r0*disp.value(j,imode);
          
          switch (_rangeType) {
          case Complete:
            autocorres[j]=j0(arg0);
            break;
          case NoInitialFlat:
            if(arg0 < 0.4)
              autocorres[j]=CompatAUTOCORR_INVALID_VALUE;
            else
              autocorres[j]=j0(arg0);
            break;
          case FirstSlope:
            if(arg0 < 0.4 || arg0 > 3.2)
              autocorres[j]=CompatAUTOCORR_INVALID_VALUE;
            else
              autocorres[j]=j0(arg0);
            break;
          }
        }
      }
      else {
        double rFactor=r0*dr;
        double slow;
        for(int j=0;j<_omegasCount;j++) {
          slow=disp.value(j,imode);
          arg0= _omegas[j]*rmax*slow;
          arg1= _omegas[j]*rmin*slow;
          switch (_rangeType) {
          case Complete:
            autocorres[j]=(rmax*j1(arg0)-rmin*j1(arg1))
                           /(_omegas[j]*rFactor*slow);
            break;
          case NoInitialFlat:
            if(arg0 < 0.4 || arg1 < 0.4)
              autocorres[j]=CompatAUTOCORR_INVALID_VALUE;
            else
              autocorres[j]=(rmax*j1(arg0)-rmin*j1(arg1))
                             /(_omegas[j]*rFactor*slow);
            break;
          case FirstSlope:
            if(arg0 < 0.4 || arg0 > 3.2 || arg1 < 0.4 || arg1 > 3.2)
              autocorres[j]=CompatAUTOCORR_INVALID_VALUE;
            else
              autocorres[j]=(rmax*j1(arg0)-rmin*j1(arg1))
                             /(_omegas[j]*rFactor*slow);
            break;
          }
        }
      }
    }
  }
  // Dispersion contains less modes than this autocorr object, complete with invalid values
  for(imode=maxMode;imode<_modesCount;imode++) {
    double ** autocorrMode=_values[imode];
    for(int iRadius=0;iRadius<_radiusCount;iRadius++) {
      double * autocorres =autocorrMode[iRadius];     
      for(int j=0;j<_omegasCount;j++) autocorres[j]=CompatAUTOCORR_INVALID_VALUE;
    }
  }
}

} // namespace QGpCompatibility
