/***************************************************************************
**
**  This file is part of QGpCoreWave.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2006-09-22
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>

#include "Seismic1DModel.h"
#include "Rayleigh.h"
#include "Love.h"
#include "ModalCurve.h"
#include "ModalFactory.h"

namespace QGpCoreWave {

const QString ModalCurve::xmlModalCurveTag="ModalCurve";

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

  Full description of class still missing
*/

/*!
  \fn ModalCurve::ModalCurve()
  Construct an empty curve
*/

/*!
  \fn ModalCurve::ModalCurve(const ModalCurve& p)
  Copy constructor
*/

/*!
  \fn ModalCurve::ModalCurve(int n)
  Construct a curve with \a n points.
*/

/*!
  \fn ModalCurve::ModalCurve(const Curve<StatPoint>& o)
  Construct a curve from \a o
*/

ModalCurve::~ModalCurve()
{
  TRACE;
}

void ModalCurve::operator=(const Curve<RealStatisticalPoint>& o)
{
  TRACE;
  int n=o.count();
  resize(n);
  for(int i=0; i<n; i++ ) {
    (*this)[i]=o[i];
  }
  _modes.clear();
  _log="";
  _name="";
}

void ModalCurve::clear()
{
  TRACE;
  Curve<FactoryPoint>::clear();
  _modes.clear();
  _log.clear();
  _name.clear();
}

void ModalCurve::addMode(const Mode& m)
{
  TRACE;
  _modes.append(m);
}

void ModalCurve::removeMode(int index)
{
  TRACE;
  if(index<_modes.count()) {
    _modes.removeAt(index);
  }
}

bool ModalCurve::hasMode(const Mode& m) const
{
  TRACE;
  int n=_modes.count();
  for(int i=0; i<n; i++ ) {
    if(_modes.at(i)==m) {
      return true;
    }
  }
  return false;
}

/*!
  \fn void ModalCurve::sort()
  Sort all samples by increasing X. Necessary for entering into a ModalFactory
*/

/*!
  Set the index in vector \a x of each sample of the curve. Each sample of the curve must have its
  corresponding value in vector \a x.
*/
void ModalCurve::linkX(const ::QVector<double>& x)
{
  TRACE;
  int n=count();
  int nx=x.count();
  int ix=0;
  for(int i=0; i< n; i++) {
    while(ix<nx && x.at(ix) < at(i).x()) ix++;
    ASSERT(at(i).x()==x.at(ix));
    (*this)[i].setIndex(ix);
  }
}

int ModalCurve::indexOf(int factoryIndex) const
{
  TRACE;
  int n=count();
  for(int i=0; i< n; i++) {
    const FactoryPoint& p=at(i);
    if(p.index()==factoryIndex) return i;
  }
  return -1;
}

/*!
  Calculates the squared misfit

  \a nValues is incremented by the number of valid values where the comparison was possible.
  \a nData is incremented by the number of valid data where the comparison is possible. In
  all cases, \a nData >= \a nValues and for the best matching case, \a nValues==\a nData.

  \a minMisfit is the minimum value that can be reached for each sample individualy.
*/
double ModalCurve::misfit(int& nValues, int& nData, const ModalFactory& f, MisfitType type, double minMisfit) const
{
  TRACE;
  double res=0.0;
  int nx=count();
  int nm=_modes.count();
  if(nm==1) {
    const RealValue * values=f.mode(_modes.first());
    for(int i=0; i<nx; i++) {
      const FactoryPoint& p=at(i);
      double pointMisfit=p.misfit(nValues, nData, values[p.index()], type, minMisfit);
      if(pointMisfit>0.0) {
        res+=pointMisfit;
      }
    }
  } else if(nm>1) {
    for(int i=0; i<nx; i++) {
      const FactoryPoint& p=at(i);
      if(!p.isValid()) continue;
      nData++;
      double modalMisfit,minModalMisfit=std::numeric_limits<double>::infinity();
      int localNData=0, localNValues=0;
      for(int im=0; im<nm; im++) {
        const RealValue * values=f.mode(_modes.at(im));
        localNValues=0;
        modalMisfit=p.misfit(localNValues, localNData, values[p.index()], type, minMisfit);
        if(localNValues==1 && modalMisfit>0.0 && modalMisfit<minModalMisfit) {
          minModalMisfit=modalMisfit;
        }
      }
      if(minModalMisfit!=std::numeric_limits<double>::infinity()) {
        res+=minModalMisfit;
        nValues++;
      }
    }
  }
  return res;
}

/*!
  Calculates the misfit based on determinant value.
*/
double ModalCurve::determinantMisfit(const Seismic1DModel * m) const
{
  TRACE;
  double res=0.0;
  int nx=count();
  if(_modes.at(0).polarization()==Mode::Rayleigh) {
    Rayleigh rayleigh(m);
    for(int i=0; i<nx; i++ ) {
      const FactoryPoint& p=at(i);
      if(!p.isValid()) continue;
      double f=p.x();
      rayleigh.setOmega(2.0*M_PI*f);
      double factor=1.0/(f*f);
      res+=fabs(rayleigh.y(p.y())*factor);
    }
  } else {
    Love love(m);
    for(int i=0; i<nx; i++ ) {
      const FactoryPoint& p=at(i);
      if(!p.isValid()) continue;
      double f=p.x();
      love.setOmega(2.0*M_PI*f);
      double factor=1.0/(f*f);
      res+=fabs(love.y(p.y())*factor);
    }
  }
  return res;
}

void ModalCurve::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  writeProperty(s,"name", _name);
  writeProperty(s,"log", _log);
  writeProperty(s,"enabled", _enabled ? "true" : "false");
}

void ModalCurve::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  for(QList<Mode>::ConstIterator it=_modes.begin(); it!=_modes.end(); it++ ) {
    it->xml_save(s, context);
  }
  const_iterator it;
  for(it=begin(); it!=end(); ++it) {
    it->xml_save(s, context);
  }
}

XMLMember ModalCurve::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(tag=="RealStatisticalPoint" || tag=="StatPoint") { // StatPoint kept for compatibility
    append(FactoryPoint());
    return XMLMember(&last());
  } else if(tag=="Mode") {
    _modes.append(Mode());
    return XMLMember(&_modes.last());
  } else if(tag=="name") return XMLMember(0);
  else if(tag=="log") return XMLMember(1);
  else if(tag=="enabled") return XMLMember(2);
  else return XMLMember(XMLMember::Unknown);
}

bool ModalCurve::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  switch (memberID) {
  case 0: _name=content.toString(); return true;
  case 1: _log=content.toString(); return true;
  case 2: _enabled=(content=="true"); return true;
  default: return false;
  }
}

} // namespace QGpCoreWave
