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

#include <DinverCore.h>
#include <QGpCompatibility.h>
#include <QGpCoreWave.h>

#include "TargetList.h"
#include "ParamGroundModel.h"
#include "ParamProfile.h"
#include "PoissonCondition.h"

namespace DinverDCCore {

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

  Full description of class still missing
*/

const QString TargetList::xmlTargetListTag="TargetList";

/*!
  Description of constructor still missing
*/
TargetList::TargetList()
{
  TRACE;
  initFactories();
  initProfiles();
}

TargetList::TargetList(const TargetList& o)
   : XMLClass(),
     _dispersionTarget(o._dispersionTarget),
     _autocorrTarget(o._autocorrTarget),
     _ellipticityCurveTarget(o._ellipticityCurveTarget),
     _ellipticityPeakTarget(o._ellipticityPeakTarget),
     _refractionVpTarget(o._refractionVpTarget),
     _refractionVsTarget(o._refractionVsTarget),
     _magnetoTelluricTarget(o._magnetoTelluricTarget)
{
  TRACE;
  _position=o._position;
  initFactories();
  initProfiles();
  validateTargets();
}

TargetList::~TargetList()
{
  TRACE;
  deleteFactories();
}

void TargetList::initFactories()
{
  TRACE;
  _dispersionFactory=nullptr;
  _autocorrFactory=nullptr;
  _ellipticityFactory=nullptr;
  _refractionVpFactory=nullptr;
  _refractionVsFactory=nullptr;
  _magnetoTelluricFactory=nullptr;
}

void TargetList::initProfiles()
{
  TRACE;
  _vp=nullptr;
  _vs=nullptr;
  _rho=nullptr;
  _pitch=nullptr;
  _res=nullptr;
  _poissonCondition=nullptr;
  _magnetoTelluricStaticShift=nullptr;
}

void TargetList::deleteFactories()
{
  TRACE;
  delete _dispersionFactory;
  delete _autocorrFactory;
  delete _ellipticityFactory;
  delete _refractionVpFactory;
  delete _refractionVsFactory;
  delete _magnetoTelluricFactory;
  initFactories();
}

bool TargetList::isEmpty() const
{
  TRACE;
  return _dispersionTarget.curves().isEmpty() &&
         !_ellipticityPeakTarget.value().isValid() &&
         _ellipticityCurveTarget.curves().isEmpty() &&
         _autocorrTarget.curves().isEmpty() &&
         _refractionVpTarget.curves().isEmpty() &&
         _refractionVsTarget.curves().isEmpty() &&
         _magnetoTelluricTarget.curves().isEmpty();
}

void TargetList::setParamProfiles(ParamGroundModel * gm, RealSpace& ps)
{
  TRACE;
  _vp=gm->find("Vp");
  _vs=gm->find("Vs");
  _rho=gm->find("Rho");
  _pitch=gm->find("Pitch");
  _res=gm->find("Res");
  if(_vp && _vs) {
    _nu=gm->find("Nu");
    ASSERT(_nu && _nu->type()==ParamProfile::Condition);
    _poissonCondition=new PoissonCondition(_vp, _vs, _nu);
    ps.addCondition(_poissonCondition);
  }
  if(_res) {
    _magnetoTelluricStaticShift=ps.parameter("MTStatic");
    if(_magnetoTelluricStaticShift){
      App::log(tr("Magneto-telluric static shift included\n") );
    } else {
      App::log(tr("Magneto-telluric static shift not found (paramter 'MTStatic'\n") );
    }
  }
}

void TargetList::setMisfitType(MisfitType t)
{
  TRACE;
  _dispersionTarget.setMisfitType(t);
  _dispersionTarget.setMisfitType(t);
  _autocorrTarget.setMisfitType(t);
  _ellipticityCurveTarget.setMisfitType(t);
  _ellipticityPeakTarget.setMisfitType(t);
  _refractionVpTarget.setMisfitType(t);
  _refractionVsTarget.setMisfitType(t);
  _magnetoTelluricTarget.setMisfitType(t);
}
/*!
  Build the factories responsible for computing dispersion curves and misfits
*/
void TargetList::validateTargets()
{
  TRACE;
  // Init all factories instead of just dispersion (until 20110404)
  // Bug encountered in gpdcmisfit: load a target with various sub-targets, options
  //                                from command line do not select all of them.
  //                                Hence factories were still allocated but not validated
  //                                however still used by calculate().
  deleteFactories();
  initFactories();

  // Set X from curves
  if(_dispersionTarget.selected() && !_dispersionTarget.curves().isEmpty()) {
    _dispersionFactory=new DispersionFactory;
    _dispersionFactory->setX(_dispersionTarget.curves());
  }
  if(_ellipticityCurveTarget.selected() && !_ellipticityCurveTarget.curves().isEmpty()) {
    _ellipticityFactory=new EllipticityFactory;
    _ellipticityFactory->setX(_ellipticityCurveTarget.curves());
    if(!_dispersionFactory) {
      _dispersionFactory=new DispersionFactory;
    }
  }
  if(_autocorrTarget.selected() && !_autocorrTarget.curves().isEmpty()) {
    _autocorrFactory=new AutocorrFactory(_autocorrTarget.curves().rings());
    int nRings=_autocorrTarget.curves().ringCount();
    for(int iRing=0; iRing<nRings; iRing++) {
      _autocorrFactory->setX(_autocorrTarget.curves().ringCurves(iRing));
    }
    if(!_dispersionFactory) {
      _dispersionFactory=new DispersionFactory;
    }
  }
  if(_ellipticityPeakTarget.selected() && _ellipticityPeakTarget.value().isValid()) {
    if(!_ellipticityFactory) {
      _ellipticityFactory=new EllipticityFactory;
      if(!_dispersionFactory) {
        _dispersionFactory=new DispersionFactory;
        // No sampling defined elsewhere, use a default one from 0.2 to 20 with 100 samples on a log scale
        ModalCurve c;
        c.line(FactoryPoint(0.2, 0.0), FactoryPoint(20.0, 0.0));
        c.resample(100, 0.2, 20.0, LogScale);
        _ellipticityFactory->setX(c);
      }
    }
  }
  // Set consistent X between factories
  if(_autocorrFactory) {
    _autocorrFactory->setX( *_dispersionFactory);
  }
  if(_ellipticityFactory) {
    _ellipticityFactory->setX( *_dispersionFactory);
    if(_autocorrFactory) { // Share again with autocorr if any
      _autocorrFactory->setX( *_dispersionFactory);
    }
  }
  // Set modes
  if(_dispersionFactory) {
    if(_dispersionTarget.selected() && !_dispersionTarget.curves().isEmpty()) {
      _dispersionFactory->linkX(_dispersionTarget.curves());
      _dispersionFactory->setModes(_dispersionTarget.curves());
    }
    _dispersionFactory->setAngularFrequency();
    if(_ellipticityFactory) {
      if(_ellipticityCurveTarget.selected() && !_ellipticityCurveTarget.curves().isEmpty()) {
        _ellipticityFactory->linkX(_ellipticityCurveTarget.curves());
        _ellipticityFactory->setModes(_ellipticityCurveTarget.curves());
      }
      _ellipticityFactory->setAngularFrequency();
      if(_ellipticityPeakTarget.selected() && _ellipticityPeakTarget.value().isValid()) {
        _ellipticityFactory->setMode(Mode(Mode::Absolute, 0));  // TODO Add mode selector in ellipticity peak
      }
    }
    if(_autocorrFactory) {
      _autocorrFactory->linkX(_autocorrTarget.curves().curves());
      _autocorrFactory->setModes(_autocorrTarget.curves().curves());
      _autocorrFactory->setAngularFrequency();
      _autocorrFactory->init();
    }
    _dispersionFactory->validate(_ellipticityFactory, _autocorrFactory);
  }

  if(_refractionVpTarget.selected() && !_refractionVpTarget.curves().isEmpty()) {
    _refractionVpFactory=new RefractionFactory();
    _refractionVpFactory->setX(_refractionVpTarget.curves());
    _refractionVpFactory->linkX(_refractionVpTarget.curves());
  }
  if(_refractionVsTarget.selected() && !_refractionVsTarget.curves().isEmpty()) {
    _refractionVsFactory=new RefractionFactory();
    _refractionVsFactory->setX(_refractionVsTarget.curves());
    _refractionVsFactory->linkX(_refractionVsTarget.curves());
  }
  if(_magnetoTelluricTarget.selected() && !_magnetoTelluricTarget.curves().isEmpty()) {
    delete _magnetoTelluricFactory;
    _magnetoTelluricFactory=new MagnetoTelluricFactory();
    _magnetoTelluricFactory->setX(_magnetoTelluricTarget.curves());
    _magnetoTelluricFactory->linkX(_magnetoTelluricTarget.curves());
    _magnetoTelluricFactory->setAngularFrequency();
  }
}

/*!
  Return the number of sample internally set to dispersion computation. Mainly used to inform the user.
*/
int TargetList::dispersionSampleCount() const
{
  TRACE;
  if(!_dispersionFactory) return 0;
  int nSamples=0;
  Dispersion * d=_dispersionFactory->phaseRayleigh();
  if(d) {
    nSamples=d->xCount();
  } else {
    d=_dispersionFactory->groupRayleigh();
    if(d) {
      nSamples=d->xCount();
    }
  }
  return nSamples;
}

inline bool TargetList::surfaceWave() const
{
  return (_vp && _vs && _nu && _rho) &&
         (!_dispersionTarget.curves().isEmpty() ||
          !_autocorrTarget.curves().isEmpty() ||
          !_ellipticityCurveTarget.curves().isEmpty() ||
          _ellipticityPeakTarget.value().isValid());
}

void TargetList::setVp()
{
  _poissonCondition->setVp();
}

/*!
  \a nDimensions is number of free parameters. This is used only if Akaike misfit is computed.
*/
double TargetList::misfit(int nDimensions, bool& ok)
{
  TRACE;
  double totalMisfit=0;
  double totalWeight=0;
  Seismic1DModel * m=nullptr;

  if(surfaceWave()) {
    m=DCReportBlock::surfaceWaveModel(_vp->resampledProfile().depths(),
                                      _vp->resampledProfile().values(),
                                      _vs->resampledProfile().values(),
                                      _rho->resampledProfile().values(),
                                      &_nu->minResampledProfile().values(),
                                      &_nu->maxResampledProfile().values());
    //fprintf(stdout,"%s\n",surfModel->toString().toLatin1().data());
    surfaceMisfit(totalMisfit, totalWeight, m, nDimensions, ok);
    if((_refractionVpTarget.selected() && !_refractionVpTarget.curves().isEmpty()) ||
       (_refractionVsTarget.selected() && !_refractionVsTarget.curves().isEmpty())) {
      refractionMisfit(totalMisfit, totalWeight, m, _pitch->resampledProfile().values(), nDimensions, ok);
    }
  } else { // No surface wave
    if(_refractionVpTarget.selected() && !_refractionVpTarget.curves().isEmpty()) {
      if(_refractionVsTarget.selected() && !_refractionVsTarget.curves().isEmpty()) {
        m=DCReportBlock::vspModel(_pitch->resampledProfile().depths(),
                                     _vp->resampledProfile().values(),
                                     _vs->resampledProfile().values());
      } else {
        m=DCReportBlock::vpModel(_pitch->resampledProfile().depths(),
                                    _vp->resampledProfile().values());
      }
    } else if(_refractionVsTarget.selected() && !_refractionVsTarget.curves().isEmpty()) {
      m=DCReportBlock::vsModel(_pitch->resampledProfile().depths(),
                                  _vs->resampledProfile().values());
    }
    if(m) refractionMisfit(totalMisfit, totalWeight, m, _pitch->resampledProfile().values(), nDimensions, ok);

  }
  delete m;
  if(_magnetoTelluricTarget.selected() && !_magnetoTelluricTarget.curves().isEmpty()) {
    double staticShift;
    if(_magnetoTelluricStaticShift) {
      staticShift=_magnetoTelluricStaticShift->realValue();
    } else {
      staticShift=1.0;
    }
    Resistivity1DModel m(_res->resampledProfile());
    magnetoTelluricMisfit(totalMisfit, totalWeight, &m, staticShift, nDimensions, ok);
  }
  if(totalWeight > 0) return totalMisfit/totalWeight;
  else return totalMisfit;
}

void TargetList::surfaceMisfit(double& totalMisfit, double& totalWeight,
                               Seismic1DModel * surfModel,
                               int nDimensions, bool& ok)
{
  TRACE;
  double individualMisfit;

  if(!surfModel->initCalculation()) {
    App::log(tr(" *** WARNING *** : bad physical model\n"));
    App::log(surfModel->toString()+"\n");
    ok=false;
    return;
  }

  // Compute dispersion curves; Rayleigh and/or Love modes, phase and/or group
  if(_dispersionTarget.misfitType()!=Determinant &&
     !_dispersionFactory->calculate(surfModel, _ellipticityFactory)) {
    ok=false;
    return;
  }

  if(_dispersionTarget.selected() && !_dispersionTarget.curves().isEmpty()) {
    if(_dispersionTarget.misfitType()==Determinant) {
      individualMisfit=determinantMisfit(_dispersionTarget.curves(), surfModel);
    } else {
      individualMisfit=curveMisfit(_dispersionTarget.curves(), *_dispersionFactory, _dispersionTarget, nDimensions);
      if(individualMisfit<0.0) {
        ok=false;
        return;
      }
    }
    totalMisfit+=_dispersionTarget.misfitWeight()*individualMisfit;
    totalWeight+=_dispersionTarget.misfitWeight();
  }

  if(_autocorrTarget.selected() && !_autocorrTarget.curves().isEmpty()) {
    _autocorrFactory->calculate(_dispersionFactory);
    if(_autocorrFactory->isRadial() || _autocorrFactory->isTransverse()) {
      _autocorrFactory->setAlpha(_autocorrTarget.curves().curves());
      _autocorrFactory->setHorizontalAutocorr();
    }
    individualMisfit=curveMisfit(_autocorrTarget.curves().curves(), *_autocorrFactory, _autocorrTarget, nDimensions);
    if(individualMisfit<0.0) {
      ok=false;
      return;
    }
    totalMisfit+=_autocorrTarget.misfitWeight()*individualMisfit;
    totalWeight+=_autocorrTarget.misfitWeight();
  }

  if(_ellipticityCurveTarget.selected() && !_ellipticityCurveTarget.curves().isEmpty()) {
    individualMisfit=curveMisfit(_ellipticityCurveTarget.curves(), *_ellipticityFactory, _ellipticityCurveTarget, nDimensions);
    if(individualMisfit<0.0) {
      ok=false;
      return;
    }
    totalMisfit+=_ellipticityCurveTarget.misfitWeight()*individualMisfit;
    totalWeight+=_ellipticityCurveTarget.misfitWeight();
  }

  if(_ellipticityPeakTarget.selected() && _ellipticityPeakTarget.value().isValid()) {
    // TODO includes misfitType
    // Ellipticity peak misfit is not a L2 norm but just a L1...
    individualMisfit=_ellipticityFactory->peakMisfit(_ellipticityPeakTarget.value(), *_dispersionFactory, surfModel);
    if(individualMisfit<_ellipticityPeakTarget.minimumMisfit()) {
      individualMisfit=_ellipticityPeakTarget.minimumMisfit();
    }
    totalMisfit+=_ellipticityPeakTarget.misfitWeight()*individualMisfit;
    totalWeight+=_ellipticityPeakTarget.misfitWeight();
  }
}

void TargetList::refractionMisfit(double& totalMisfit, double& totalWeight,
                                  Seismic1DModel * surfModel, const QVector<double>& pitch,
                                  int nDimensions, bool& ok)
{
  TRACE;
  double individualMisfit;
  if(_refractionVpTarget.selected() && !_refractionVpTarget.curves().isEmpty()) {
    RefractionDippingModel m(surfModel->layerCount());
    m.fromSeismic1DModel( *surfModel, pitch, Seismic1DModel::P);
    if(!_refractionVpFactory->calculate(&m)) {
      ok=false;
      return;
    }
    individualMisfit=curveMisfit(_refractionVpTarget.curves(), *_refractionVpFactory, _refractionVpTarget, nDimensions);
    if(individualMisfit<0.0) {
      ok=false;
      return;
    }
    totalMisfit+=_refractionVpTarget.misfitWeight()*individualMisfit;
    totalWeight+=_refractionVpTarget.misfitWeight();
  }
  if(_refractionVsTarget.selected() && !_refractionVsTarget.curves().isEmpty()) {
    RefractionDippingModel m(surfModel->layerCount());
    m.fromSeismic1DModel( *surfModel, pitch, Seismic1DModel::S);
    if( !_refractionVsFactory->calculate(&m) ) {
      ok=false;
      return;
    }
    individualMisfit=curveMisfit(_refractionVsTarget.curves(), *_refractionVsFactory, _refractionVsTarget, nDimensions);
    if(individualMisfit<0.0) {
      ok=false;
      return;
    }
    totalMisfit+=_refractionVsTarget.misfitWeight()*individualMisfit;
    totalWeight+=_refractionVsTarget.misfitWeight();
  }
}

void TargetList::refractionMisfit(double& totalMisfit, double& totalWeight,
                                  RefractionDippingModel * tiltModel,
                                  int nDimensions, bool& ok)
{
  TRACE;
  double individualMisfit;
  if(_refractionVpTarget.selected() && !_refractionVpTarget.curves().isEmpty()) {
    if(!_refractionVpFactory->calculate(tiltModel)) {
      ok=false;
      return;
    }
    individualMisfit=curveMisfit(_refractionVpTarget.curves(), *_refractionVpFactory, _refractionVpTarget, nDimensions);
    if(individualMisfit<0.0) {
      ok=false;
      return;
    }
    totalMisfit+=_refractionVpTarget.misfitWeight()*individualMisfit;
    totalWeight+=_refractionVpTarget.misfitWeight();
  }
  if(_refractionVsTarget.selected() && !_refractionVsTarget.curves().isEmpty()) {
    if( !_refractionVsFactory->calculate(tiltModel) ) {
      ok=false;
      return;
    }
    individualMisfit=curveMisfit(_refractionVsTarget.curves(), *_refractionVsFactory, _refractionVsTarget, nDimensions);
    if(individualMisfit<0.0) {
      ok=false;
      return;
    }
    totalMisfit+=_refractionVpTarget.misfitWeight()*individualMisfit;
    totalWeight+=_refractionVsTarget.misfitWeight();
  }
}

void TargetList::magnetoTelluricMisfit(double& totalMisfit, double& totalWeight,
                                       const Resistivity1DModel * model, double staticShift,
                                       int nDimensions, bool& ok)
{
  TRACE;
  double individualMisfit;
  if(!_magnetoTelluricFactory->calculate(*model, staticShift)) {
    ok=false;
    return;
  }
  individualMisfit=curveMisfit(_magnetoTelluricTarget.curves(), *_magnetoTelluricFactory, _magnetoTelluricTarget, nDimensions);
  if(individualMisfit<0.0) {
    ok=false;
    return;
  }
  totalMisfit+=_magnetoTelluricTarget.misfitWeight()*individualMisfit;
  totalWeight+=_magnetoTelluricTarget.misfitWeight();
}

/*!
  Returns the global misfit for the curve list \a curveList, compared to values
  computed in factory \a f. It returns zero if the curve list contains no data points
  and -1 if no values are valid for a comparison.
*/
double TargetList::curveMisfit(const QList<ModalCurve>& curveList, ModalFactory& f,
                               const Target& target, int nDimensions)
{
  TRACE;
  int nValues=0;
  int nData=0;
  double rms_val=0.0;
  for(QList<ModalCurve>::ConstIterator it=curveList.begin(); it!=curveList.end(); ++it) {
    if(it->isEnabled()) {
      rms_val+=it->misfit(nValues, nData, f, target.misfitType(), target.minimumMisfit());
    }
  }
  if(nValues>0) {
    switch(target.misfitType()) {
    case L1:
    case L1_Normalized:
    case L1_LogNormalized:
    case L1_NormalizedBySigmaOnly:
      rms_val/=nValues;
      break;
    case RMS:
      rms_val=sqrt(rms_val/nValues);
      break;
    case L2:
    case L2_Normalized:
    case L2_LogNormalized:
    case L2_NormalizedBySigmaOnly:
      rms_val=sqrt(rms_val/nValues);
      break;
    case Akaike:
      rms_val=nValues*log(sqrt(rms_val))+2.0*nDimensions;
      break;
    case AkaikeFewSamples:
      rms_val=nValues*log(sqrt(rms_val))
                +2.0*nDimensions+(2.0*nDimensions*(nDimensions+1.0))/(nValues-nDimensions-1.0);
      break;
    case Determinant:
      ASSERT(false);
    }
    // Penalty factor if the number of data is greater than number of values
    rms_val*=1.0+nData-nValues;
  } else if(nData==0) {
    rms_val=0.0; // Case where data curve contains only invalid values
  } else {
    rms_val=-1; // Invalid computed curve
  }
  return rms_val;
}

/*!
  Returns the global misfit for the curve list \a curveList, compared to values
  computed in factory \a f. It returns zero if the curve list contains no data points
  and -1 if no value are valid for a comparison.
*/
double TargetList::curveMisfit(const QList<RefractionCurve>& curveList, RefractionFactory& f,
                               const Target& target, int nDimensions)
{
  TRACE;
  int nValues=0;
  int nData=0;
  double rms_val=0.0;
  for(QList<RefractionCurve>::ConstIterator it=curveList.begin(); it!=curveList.end(); ++it) {
    rms_val+=it->misfit(nValues, nData, f, target.misfitType(), target.minimumMisfit());
  }
  if(nValues>0) {
    switch(target.misfitType()) {
      case L1:
      case L1_Normalized:
      case L1_LogNormalized:
      case L1_NormalizedBySigmaOnly:
        rms_val/=nValues;
        break;
      case RMS:
        rms_val=sqrt(rms_val/nValues);
        break;
      case L2:
      case L2_Normalized:
      case L2_LogNormalized:
      case L2_NormalizedBySigmaOnly:
        rms_val=sqrt(rms_val);
        break;
      case Akaike:
        rms_val=nValues*log10(sqrt(rms_val))+2.0*nDimensions;
        break;
      case AkaikeFewSamples:
        rms_val=nValues*log10(sqrt(rms_val))
                  +2.0*nDimensions+(2.0*nDimensions*(nDimensions+1.0))/(nValues-nDimensions-1.0);
        break;
      case Determinant:
        ASSERT(false);
    }
    // Penalty factor if the number of data is greater than number of values
    rms_val*=1.0+nData-nValues;
  } else if(nData==0) {
    rms_val=0.0; // Case where data curve contains only invalid values
  } else {
    rms_val=-1;
  }
  return rms_val;
}

/*!
  Returns the global misfit for the curve list \a curveList, compared to values
  computed in factory \a f. It returns zero if the curve list contains no data points
  and -1 if no value are valid for a comparison.
*/
double TargetList::curveMisfit(const QList<MagnetoTelluricCurve>& curveList, MagnetoTelluricFactory& f,
                               const Target& target, int nDimensions)
{
  TRACE;
  int nValues=0;
  int nData=0;
  double rms_val=0.0;
  for(QList<MagnetoTelluricCurve>::ConstIterator it=curveList.begin(); it!=curveList.end(); ++it) {
    rms_val+=it->misfit(nValues, nData, f, target.misfitType(), target.minimumMisfit());
  }
  if(nValues>0) {
    switch(target.misfitType()) {
      case L1:
      case L1_Normalized:
      case L1_LogNormalized:
      case L1_NormalizedBySigmaOnly:
        rms_val/=nValues;
        break;
      case RMS:
        rms_val=sqrt(rms_val/nValues);
        break;
      case L2:
      case L2_Normalized:
      case L2_LogNormalized:
      case L2_NormalizedBySigmaOnly:
        rms_val=sqrt(rms_val);
        break;
      case Akaike:
        rms_val=nValues * log10(sqrt( rms_val)) + 2.0*nDimensions;
        break;
      case AkaikeFewSamples:
        rms_val=nValues * log10(sqrt( rms_val))
                  +2.0*nDimensions+(2.0*nDimensions*(nDimensions+1.0))/(nValues-nDimensions-1.0);
        break;
      case Determinant:
        ASSERT(false);
    }
    // Penalty factor if the number of data is greater than number of values
    rms_val*=1.0+nData-nValues;
  } else if(nData==0) {
    rms_val=0.0; // Case where data curve contains only invalid values
  } else {
    rms_val=-1;
  }
  return rms_val;
}


/*!
  Calculate misfit according to Maraschini et al. 2010, based on Thomson-Haskell determinant.
*/
double TargetList::determinantMisfit(const QList<ModalCurve>& curveList, Seismic1DModel * surfModel)
{
  TRACE;
  double rms_val=0.0;
  for(QList<ModalCurve>::ConstIterator it=curveList.begin(); it!=curveList.end(); ++it) {
    rms_val+=it->determinantMisfit(surfModel);
  }
  return rms_val;
}

void TargetList::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  writeProperty(s, "position", _position.toString(20));
}

void TargetList::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  static const QString key("type");
  XMLSaveAttributes att;
  QString& value=att.add(key);
  value="dispersion";
  _dispersionTarget.xml_save(s, context, att);
  _autocorrTarget.xml_save(s, context);
  value="ellipticity";
  _ellipticityCurveTarget.xml_save(s, context, att);
  value="ellipticity peak";
  _ellipticityPeakTarget.xml_save(s, context, att);
  value="Vp";
  _refractionVpTarget.xml_save(s, context, att);
  value="Vs";
  _refractionVsTarget.xml_save(s, context, att);
  _magnetoTelluricTarget.xml_save(s, context);
}

XMLMember TargetList::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  static const QString attName("type");
  if(tag=="DispersionTarget") {
    return XMLMember(&_dispersionTarget);
  } else if(tag=="ModalCurveTarget") {
    XMLRestoreAttributeIterator it=attributes.find(attName);
    if(it!=attributes.end()) {
      if(it.value()=="ellipticity") {
        return XMLMember(&_ellipticityCurveTarget);
      } else if(it.value()=="dispersion") {  // Kept for compatibility before 201902
                                             // See in DispersionTarget.cpp
        return XMLMember(&_dispersionTarget);
      } else {
        App::log(tr("Wrong type of modal curve target: %1\n").arg(it.value().toString()) );
        return XMLMember(XMLMember::Unknown);
      }
    } else {
      App::log(tr("No type defined for modal curve target\n") );
      return XMLMember(XMLMember::Unknown);
    }
  } else if(tag=="AutocorrTarget") {
    return XMLMember(&_autocorrTarget);
  } else if(tag=="ValueTarget") {
    XMLRestoreAttributeIterator it=attributes.find(attName);
    if(it!=attributes.end()) {
      if(it.value()=="ellipticity peak") {
        return XMLMember(&_ellipticityPeakTarget);
      } else {
        App::log(tr("Wrong type of value target: %1\n").arg(it.value().toString()) );
        return XMLMember(XMLMember::Unknown);
      }
    }  else {
      App::log(tr("No type defined for value target\n") );
      return XMLMember(XMLMember::Unknown);
    }
  } else if(tag=="RefractionTarget") {
    XMLRestoreAttributeIterator it=attributes.find(attName);
    if(it!=attributes.end()) {
      if(it.value()=="Vp") {
        return XMLMember(&_refractionVpTarget);
      } else if(it.value()=="Vs") {
        return XMLMember(&_refractionVsTarget);
      } else {
        App::log(tr("Wrong type of refraction target: %1\n").arg(it.value().toString()) );
        return XMLMember(XMLMember::Unknown);
      }
    }  else {
      App::log(tr("No type defined for refraction target\n") );
      return XMLMember(XMLMember::Unknown);
    }
  } else if(tag=="MagnetoTelluricTarget") {
    return XMLMember(&_magnetoTelluricTarget);
  /////////////////////////////////////////////////////////////////////////
  //
  // Code below kept for compatibility with files generated before 20090727
  //
  /////////////////////////////////////////////////////////////////////////
  } else if(tag=="ModalCurve" ) {
    XMLRestoreAttributeIterator it=attributes.find(attName);
    if(it!=attributes.end()) {
      if(it.value()=="disp") {
        _dispersionTarget.curves().append(ModalCurve());
        return XMLMember(&_dispersionTarget.curves().last());
      } else if(it.value()=="ellCurve") {
        _ellipticityCurveTarget.curves().append(ModalCurve());
        return XMLMember(&_ellipticityCurveTarget.curves().last());
      } else {
        App::log(tr("Wrong type of modal curve %1\n").arg(it.value().toString()) );
        return XMLMember(XMLMember::Unknown);
      }
    } else {
      App::log(tr("No type defined for modal curve\n") );
      return XMLMember(XMLMember::Unknown);
    }
  } else if(tag=="AutocorrCurves") {
    return XMLMember(&_autocorrTarget.curves());
  } else if(tag=="StatValue" ) {
    return XMLMember(&_ellipticityPeakTarget.value());
  } else if(tag=="RefractionCurve") {
    XMLRestoreAttributeIterator it=attributes.find(attName);
    if(it!=attributes.end()) {
      if(it.value()=="refraVp") {
        _refractionVpTarget.curves().append(RefractionCurve());
        return XMLMember(&_refractionVpTarget.curves().last());
      } else if(it.value()=="refraVs") {
        _refractionVsTarget.curves().append(RefractionCurve());
        return XMLMember(&_refractionVsTarget.curves().last());
      } else {
        App::log(tr("Wrong type of refraction curve %1\n").arg(it.value().toString()) );
        return XMLMember(XMLMember::Unknown);
      }
    } else {
      App::log(tr("No type defined for refraction curve\n") );
      return XMLMember(XMLMember::Unknown);
    }
  } else if(tag=="position" ) return XMLMember(19);
  else if(tag=="dispWeight" ) return XMLMember(1); // kept for compatibility
  else if(tag=="dispMinMisfit" ) return XMLMember(7); // kept for compatibility
  else if(tag=="dispMisfitType" ) return XMLMember(13); // kept for compatibility
  else if(tag=="autocorrWeight" ) return XMLMember(2); // kept for compatibility
  else if(tag=="autocorrMinMisfit" ) return XMLMember(8); // kept for compatibility
  else if(tag=="autocorrMisfitType" ) return XMLMember(14); // kept for compatibility
  else if(tag=="ellCurveWeight" ) return XMLMember(3); // kept for compatibility
  else if(tag=="ellCurveMinMisfit" ) return XMLMember(9); // kept for compatibility
  else if(tag=="ellCurveMisfitType" ) return XMLMember(15); // kept for compatibility
  else if(tag=="ellPeakWeight" ) return XMLMember(4); // kept for compatibility
  else if(tag=="ellPeakMinMisfit" ) return XMLMember(10); // kept for compatibility
  else if(tag=="ellPeakMisfitType" ) return XMLMember(16); // kept for compatibility
  else if(tag=="refraVpWeight" ) return XMLMember(5); // kept for compatibility
  else if(tag=="refraVpMinMisfit" ) return XMLMember(11); // kept for compatibility
  else if(tag=="refraVpMisfitType" ) return XMLMember(17); // kept for compatibility
  else if(tag=="refraVsWeight" ) return XMLMember(6); // kept for compatibility
  else if(tag=="refraVsMinMisfit" ) return XMLMember(12); // kept for compatibility
  else if(tag=="refraVsMisfitType" ) return XMLMember(18); // kept for compatibility
  else if(tag=="ellWeight" ) return XMLMember(0); // kept for compatibility
  else if(tag=="modeGuess" ) return XMLMember(0); // kept for compatibility
  else if(tag=="modeGuessFreeJumping" ) return XMLMember(0); // kept for compatibility
  else if(tag=="modeGuessCount" ) return XMLMember(0); // kept for compatibility
  else if(tag=="modeGuessRayleighCount" ) return XMLMember(0); // kept for compatibility
  else if(tag=="hodoVpWeight" ) return XMLMember(0); // kept for compatibility
  else if(tag=="hodoVpMinMisfit" ) return XMLMember(0); // kept for compatibility
  else if(tag=="hodoVsWeight" ) return XMLMember(0); // kept for compatibility
  else if(tag=="hodoVsMinMisfit" ) return XMLMember(0); // kept for compatibility
  else if(tag=="ModalDispersion" ) { // kept for compatibility
    CompatModalDispersion * disp=new CompatModalDispersion;
    return XMLMember(disp, true);
  } else return XMLMember(XMLMember::Unknown);
}

bool TargetList::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  switch (memberID) {
  case 19: _position.fromString(content.toString()); return true;
  case 0: return true; // kept for compatibility only
  case 1: _dispersionTarget.setMisfitWeight(content.toDouble()); return true; // kept for compatibility only
  case 2: _autocorrTarget.setMisfitWeight(content.toDouble()); return true; // kept for compatibility only
  case 3: _ellipticityCurveTarget.setMisfitWeight(content.toDouble()); return true; // kept for compatibility only
  case 4: _ellipticityPeakTarget.setMisfitWeight(content.toDouble()); return true; // kept for compatibility only
  case 5: _refractionVpTarget.setMisfitWeight(content.toDouble()); return true; // kept for compatibility only
  case 6: _refractionVsTarget.setMisfitWeight(content.toDouble()); return true; // kept for compatibility only
  case 7: _dispersionTarget.setMinimumMisfit(content.toDouble()); return true; // kept for compatibility only
  case 8: _autocorrTarget.setMinimumMisfit(content.toDouble()); return true; // kept for compatibility only
  case 9: _ellipticityCurveTarget.setMinimumMisfit(content.toDouble()); return true; // kept for compatibility only
  case 10: _ellipticityPeakTarget.setMinimumMisfit(content.toDouble()); return true; // kept for compatibility only
  case 11: _refractionVpTarget.setMinimumMisfit(content.toDouble()); return true; // kept for compatibility only
  case 12: _refractionVsTarget.setMinimumMisfit(content.toDouble()); return true; // kept for compatibility only
  case 13: _dispersionTarget.setMisfitType(RealStatisticalValue::misfitType(content.toString())); return true; // kept for compatibility only
  case 14: _autocorrTarget.setMisfitType(RealStatisticalValue::misfitType(content.toString())); return true; // kept for compatibility only
  case 15: _ellipticityCurveTarget.setMisfitType(RealStatisticalValue::misfitType(content.toString())); return true; // kept for compatibility only
  case 16: _ellipticityPeakTarget.setMisfitType(RealStatisticalValue::misfitType(content.toString())); return true; // kept for compatibility only
  case 17: _refractionVpTarget.setMisfitType(RealStatisticalValue::misfitType(content.toString())); return true; // kept for compatibility only
  case 18: _refractionVsTarget.setMisfitType(RealStatisticalValue::misfitType(content.toString())); return true; // kept for compatibility only
  default: return false;
  }
}

void TargetList::xml_polishChild(XML_POLISHCHILD_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  // Function kept for compatibility only
  if(child->xml_tagName()=="ModalDispersion") {
    CompatModalDispersion * disp=static_cast<CompatModalDispersion *>(child);
    for(int i=0;i<disp->nModes();i++) {
      _dispersionTarget.curves().append(ModalCurve());
      ModalCurve * c=&_dispersionTarget.curves().last();
      if(i<disp->nRayleighModes()) {
        c->addMode(Mode( Mode::Phase, Mode::Rayleigh, i) );
      } else {
        c->addMode(Mode( Mode::Phase, Mode::Love, i-disp->nRayleighModes()) );
      }
      CompatVDataPointVector& m=disp->mode(i);
      for(int i=0;i<m.size();i++) {
        FactoryPoint p;
        p.setX(0.5/M_PI * disp->omega(i));
        p.setMean(m.at(i).mean());
        p.setStddev(m.at(i).stddev());
        p.setWeight(m.at(i).weight());
        c->append(p);
      }
    }
  }
}

bool TargetList::xml_polish(XML_POLISH_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  validateTargets();
  return true;
}

/*!
  Implemented only for testing the possibility of inverting parameter
  values from a 1D model.
*/
double TargetList::modelMisfit()
{
  TRACE;
  Seismic1DModel m1;
  QFile f("/home/wathelem/ref.model");
  f.open(QIODevice::ReadOnly);
  QTextStream s(&f);
  m1.fromStream(s);
  Seismic1DModel * m2=nullptr;
  m2=DCReportBlock::surfaceWaveModel(_vp->resampledProfile().depths(),
                                    _vp->resampledProfile().values(),
                                    _vs->resampledProfile().values(),
                                    _rho->resampledProfile().values(),
                                    &_nu->minResampledProfile().values(),
                                    &_nu->maxResampledProfile().values());
  double maxDepth=m1.depth(m1.layerCount()-1);
  double maxDepth2=m2->depth(m2->layerCount()-1);
  if(maxDepth2>maxDepth) {
    maxDepth=maxDepth2;
  }
  maxDepth*=1.1; // go into the half-space
  double dd=maxDepth*0.01;
  double misfit=0.0;
  double diff;
  for(double d=0.0; d<=maxDepth; d+=dd) {
    diff=m1.slowPAt(d)-m2->slowPAt(d);
    diff/=(m1.slowPAt(d)+m2->slowPAt(d))*0.5;
    misfit+=diff*diff;
    diff=m1.slowSAt(d)-m2->slowSAt(d);
    diff/=(m1.slowSAt(d)+m2->slowSAt(d))*0.5;
    misfit+=diff*diff;
  }
  delete m2;
  return misfit;
}

} // namespace DinverDCCore
