/***************************************************************************
**
**  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-05-19
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>
#include <time.h>

#include <QGpCoreWave.h>
#include "CompatDispersionData.h"

namespace QGpCompatibility {

#define twopi (2*M_PI)

/*!
  Added for compatibility with most recent API
*/
ModalCurve CompatDispersionData::curve(int iMode)
{
  TRACE;
  ModalCurve c=CompatMultiModalData::curve(iMode);
  int n=CompatMultiModalFrequency::_omegasCount;
  for(int i=0;i<n;i++) {
    c[i].setX(_omegas[i]);
  }
  if(iMode<static_cast<int>(rayleighModesCount())) {
    c.addMode(Mode(Mode::Phase, Mode::Rayleigh, iMode));
  } else {
    c.addMode(Mode(Mode::Phase, Mode::Love, iMode-rayleighModesCount()));
  }
  c.addLog(_log);
  return c;
}

/**
 The way the stddev is stored has changed from the inverse of velocity
 stddev to the stddev of the slowness.
 new_stddev=old_stddev*slowness*slowness
 All reports with version 0 have the old format for stddev
 This routine is called be CompatInversionReport to change the stddev in case of
 version 0
 */
void CompatDispersionData::convertStddev()
{
  TRACE;
 for(int mode=0;mode<CompatMultiModalFrequency::_modesCount;mode++) {
  double * measurements=_measurements[mode];
  double * stddev=_stddev[mode];
  for(int i=0;i<CompatMultiModalFrequency::_omegasCount;i++)
     stddev[i]=measurements[i]*measurements[i]/stddev[i];
 }
}
/*-------------------------------------------------------------------------------------------------
  
  Misfits computation
  
  --------------------------------------------------------------------------------------------------*/
#define SLOWNESS_MISFIT

double CompatDispersionData::misfit(int iMin, int iMax, FILE * flog)
{
  TRACE;
  // Global RMS is the sum of the RMS of all modes
  double rms_val=0;
  int omegaCountReal=(iMax-iMin+1)*CompatMultiModalFrequency::_modesCount;
  int omegaCountData=omegaCountReal;
  for(int mode=0;mode<CompatMultiModalFrequency::_modesCount;mode++) {
    double * measurements=_measurements[mode];
    double * stddev=_stddev[mode];
    double * calculated=_values[mode];
    for(int i=iMin;i<=iMax;i++) {
      if(measurements[i]!=0) {
        if(calculated[i]!=0) {
          double diff;
#ifdef SLOWNESS_MISFIT
          if(stddev[i]) diff=(measurements[i]-calculated[i]) /stddev[i];
          else diff=(measurements[i]-calculated[i]) /measurements[i];      
#else
          if(stddev[i]) diff=(1/measurements[i]-1/calculated[i])*stddev[i];
          else diff=(1/measurements[i]-1/calculated[i])*measurements[i];
#endif
          rms_val+=diff*diff;
        } else omegaCountReal--;
      } else {omegaCountData--;omegaCountReal--;}
    }
  }
  if(omegaCountReal>0)
    rms_val=sqrt(rms_val/omegaCountReal)*(1+omegaCountData-omegaCountReal);
  else if(omegaCountData==0) rms_val=0;
  else {
    fprintf(flog," *** ERROR *** : no common value between calculated and data, this should never happen!\n");
    rms_val=10000; 
  }
  return rms_val;
}

double CompatDispersionData::closestModeMisfit(bool strictModeJumping, FILE * flog)
{
  TRACE;
  //  RMS is the calculated with the closest mode
  double rms_val=0;
  int omegaCountReal=CompatMultiModalFrequency::_omegasCount;
  int omegaCountData=omegaCountReal;
  // Effective apparent velocity is always in mode 0 (fundamental)
  double * measurements=_measurements[0];
  double * stddev=_stddev[0];
  int maxMode=CompatMultiModalFrequency::_modesCount-1;
  for(int i=CompatMultiModalFrequency::_omegasCount-1;i>=0;i--) {
    if(measurements[i]!=0) {
      double diff,minDiff=std::numeric_limits<double>::infinity();
      int nextSampleMaxMode=maxMode;
      for(int mode=0;mode<=maxMode;mode++) {
        if(_values[mode][i]!=0) {
#ifdef SLOWNESS_MISFIT
          if(stddev[i]) diff=(measurements[i]-_values[mode][i]) /stddev[i];
          else diff=(measurements[i]-_values[mode][i]) /measurements[i];      
#else
          if(stddev[i]) diff=(1/measurements[i]-1/_values[mode][i])*stddev[i];
          else diff=(1/measurements[i]-1/_values[mode][i])*measurements[i];
#endif
          if(diff<0) diff=-diff;
          if(diff<minDiff) {
            minDiff=diff;
            nextSampleMaxMode=mode;
          }
        }
      }
      if(strictModeJumping) maxMode=nextSampleMaxMode;
      if(minDiff!=std::numeric_limits<double>::infinity()) rms_val+=minDiff*minDiff; else omegaCountReal--;
    } 
    else {omegaCountData--;omegaCountReal--;}
  } 
  if(omegaCountReal>0)
    rms_val=sqrt(rms_val/omegaCountReal)*(1+omegaCountData-omegaCountReal);
  else {
    fprintf(flog," *** ERROR *** : no common value between calculated and data, this should never happen!\n");
    rms_val=10000; 
  }
  return rms_val;
}

#if 0
// Code use for comparison with Herrmann code
/*-------------------------------------------------------------------------------------------------
  
  HERRMANN I/O
  
  --------------------------------------------------------------------------------------------------*/
void CompatDispersionData::loadHerrmann(char * filename)
{
  TRACE;
  FILE * s;
  s=fopen(filename,"r");
  // Herrmann's format of dispersion data file
  int imode;
  if(s!=0)
  {
    
    // read 1st line
    int iunit, ifrper;
    fscanf(s,"%i%i",&iunit,&ifrper);
    // read other lines until end to count the number of modes
    // and maximum number of omegas
    int ilvry,iporg;
    double frper,val,dval;
    int modesCount=0,omegasCount;
    fpos_t filePosition;
    fgetpos(s,&filePosition);
    for(omegasCount=0;!feof(s);omegasCount++) {
      if(fscanf(s,"%i%i%i%lf%lf%lf",
                 &ilvry,&iporg,&imode,&frper,&val,&dval)==6
          && imode>modesCount) modesCount=imode;
    }
    // Get all values in a table
    struct dispEntry
    {
      double omega;
      double slowness;
      double stddev;
    };
    dispEntry * entries=new dispEntry [omegasCount];
    CompatDoubleList * omegasPtr=new CompatDoubleList [modesCount];
    fsetpos(s,&filePosition);
    int i;
    for(i=0;!feof(s);i++)
    {
      if(fscanf(s,"%i%i%i%lf%lf%lf",
                 &ilvry,&iporg,&imode,&frper,&val,&dval)==6)
      {
        if(ifrper==0)
          entries[i].omega=twopi/frper;
        else
          entries[i].omega=twopi*frper;
        entries[i].slowness=1/val;
        entries[i].stddev=1/dval;
        omegasPtr[imode].append((double*)entries+i);
      }
    }
    fclose(s);
    // Construct a list of unique omegas for all modes
    CompatDoubleList theOmegas;
    for(imode=0;imode<modesCount;imode++) {
      for(double * it=omegasPtr[imode].first();it;it=omegasPtr[imode].next())
        theOmegas.append(it);
    }
    theOmegas.sort();
    double * it=theOmegas.first();
    double lastOmega=*it;
    for(it=theOmegas.next();it;it=theOmegas.next()) {
      if(*it==lastOmega) {
        theOmegas.remove();
        it=theOmegas.current();
      }
      else it=theOmegas.next();
    } 
    CompatMultiModalFrequency::_omegasCount=theOmegas.count();
    _omegas=new double[CompatMultiModalFrequency::_omegasCount];
    i=0;
    for(it=theOmegas.first();it;it=theOmegas.next(),i++) _omegas[i]=*it;
    // Set the same set of omegas for all modes (interpoling if necessary)
    // Allocates memory for measurements and calculated values
    CompatMultiModalFrequency::_modesCount=modesCount;
    allocatesValues();
    CompatMultiModalData::_omegasCount=omegasCount;
    CompatMultiModalData::_modesCount=modesCount;
    allocatesData();
    for(imode=0;imode<modesCount;imode++)
    {
      double * measurements=_measurements[imode];
      double * stddev=_stddev[imode];
      omegasPtr[imode].sort();
      for(int j=0;j<CompatMultiModalFrequency::_omegasCount;j++)
      {
        if(!omegasPtr[imode].find(_omegas+j))
        {
          if(omegasPtr[imode].current()==omegasPtr[imode].getLast())
            omegasPtr[imode].prev();
          double omega1=*(omegasPtr[imode].current());
          double v1=1/(*(omegasPtr[imode].current()+1));
          double dv1=1/(*(omegasPtr[imode].current()+2));
          double omega2=*(omegasPtr[imode].next());
          double v2=1/(*(omegasPtr[imode].current()+1));
          double dv2=1/(*(omegasPtr[imode].current()+2));
          double v=v1+(v2-v1)/(omega2-omega1)*(_omegas[j]-omega1);
          double dv=dv1+(dv2-dv1)/(omega2-omega1)*(_omegas[j]-omega1);
          measurements[i]=1/v;
          stddev[i]=1/dv;
        }
        else
        {
          measurements[i]=*(omegasPtr[imode].current()+1);
          stddev[i]=*(omegasPtr[imode].current()+2);
        }
      }
    }
  }
}

void CompatDispersionData::saveHerrmann(char * fileName)
{
  TRACE;
  FILE * s;
  s=fopen(fileName,"w");
  // Herrmann's format of dispersion data file
  fprintf(s,"2 0\n");
  for(int mode=0; mode<CompatMultiModalFrequency::_modesCount;mode++)
  {
    double * slownesses=_measurements[mode];
    double * stddev=_stddev[mode];
    for(long i=0;i<CompatMultiModalFrequency::_omegasCount;i++)
      if(slownesses[i]!=0) fprintf(s,"2 1 %i %lf %lf %lf\n",mode+1,
                                    twopi/_omegas[i],1/slownesses[i],1/stddev[i]);
  }
  fclose(s);
}
#endif

double CompatDispersionData::minDataFrequency()
{
  TRACE;
  double minFreq=std::numeric_limits<double>::infinity();
  for(int mode=0; mode<CompatMultiModalFrequency::_modesCount;mode++) {
    double * measurements=_measurements[mode];
    for(int i=0;i<CompatMultiModalFrequency::_omegasCount;i++) {
      if(measurements[i]!=0) {
        if(_omegas[i]<minFreq) minFreq=_omegas[i];
        break;
      }
    }
  }
  return minFreq/twopi;
}


double CompatDispersionData::maxDataFrequency()
{
  TRACE;
  double maxFreq=0;
  for(int mode=0; mode<CompatMultiModalFrequency::_modesCount;mode++) {
    double * measurements=_measurements[mode];
    for(int i=CompatMultiModalFrequency::_omegasCount-1;i>=0;i--) {
      if(measurements[i]!=0) {
        if(_omegas[i]>maxFreq) maxFreq=_omegas[i];
        break;
      }
    }
  }
  return maxFreq/twopi;
}

QVector<double> * CompatDispersionData::groupSlowness(int iMode)
{
  ASSERT(iMode<CompatMultiModalFrequency::_modesCount);
  QVector<double> * list=new QVector<double>;
  double * values=_measurements[iMode];
  int n=CompatMultiModalFrequency::_omegasCount-1;
  for(int i=1;i<n;i++) {
    double derslow=0.5*((values[i]-values[i-1])/(_omegas[i]-_omegas[i-1])+
                        (values[i+1]-values[i])/(_omegas[i+1]-_omegas[i]));
    derslow*=_omegas[i];
    list->push_back(values[i]+derslow);
  }
  return list;
}

#if 0
void CompatDispersionData::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  CompatMultiModalCurves::xml_writeProperties(s,indent);
  s << ("%s<property name=\"%s\">\n",indent,"data");
  for(int mode=0; mode<CompatMultiModalFrequency::_modesCount;mode++) {
    double * measurements=_measurements[mode];
    double * stddev=_stddev[mode];
    double * weight=_weight[mode];
    for(int i=0;i<CompatMultiModalFrequency::_omegasCount;i++)
          s.printf("%s    %3i %12lg %12lg %12lg\n",indent,
                  mode,measurements[i],stddev[i],weight[i]);  
  }
  s.printf("%s</property>\n",indent);
}

bool CompatDispersionData::xml_setProperty(CompatStringSection& propertyName,
                                        CompatStringSection& content)
{
  TRACE;
  if(propertyName=="action") { // overides CompatMultiModalCurve:Action
    if(content=="clear") {
      deleteValues();
      deleteData();
    }
    else if(content=="allocate") {
      allocatesValues();
      CompatMultiModalData::_modesCount=CompatMultiModalCurves::_modesCount;
      CompatMultiModalData::_omegasCount=CompatMultiModalCurves::_omegasCount;
      allocatesData();
    }
  }
  else if(propertyName=="data") {
    const char * ptr=0;
    for(int mode=0; mode<CompatMultiModalFrequency::_modesCount;mode++) {
      double * measurements=_measurements[mode];
      double * stddev=_stddev[mode];
      double * weight=_weight[mode];
      for(int i=0;i<CompatMultiModalFrequency::_omegasCount;i++) {
        CompatStringSection modeStr=content.nextField(ptr);
        CompatStringSection slowStr=content.nextField(ptr);
        CompatStringSection devStr=content.nextField(ptr);
        CompatStringSection weightStr=content.nextField(ptr);
        if(modeStr.isValid() && 
            slowStr.isValid() &&
            devStr.isValid() &&
            weightStr.isValid()) {
          if(modeStr.toInt()==mode) {
            measurements[i]=slowStr.toDouble();
            stddev[i]=devStr.toDouble();
            weight[i]=weightStr.toDouble();
          }
          else {
            fprintf(stderr,"Mode number not consistent with omegasCount\n");
            return false;
          }
        }
        else {
          measurements[i]=0.0;
          stddev[i]=0.0;
          weight[i]=1.0;
        }
      }
    }
  }
  else if(CompatMultiModalCurves::xml_setProperty(propertyName, content));
  else return false;
  return true;  
}
#endif

} // namespace QGpCompatibility
