/***************************************************************************
**
**  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: 2010-05-24
**  Copyright: 2010-2019
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "MagnetoTelluricFactory.h"
#include "Resistivity1DModel.h"

namespace QGpCoreWave {

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

  Full description of class still missing
*/

  MagnetoTelluricFactory::MagnetoTelluricFactory()
  {
    TRACE;
    _impedanceXY=0;
  }

  MagnetoTelluricFactory::~MagnetoTelluricFactory()
  {
    TRACE;
    delete [] _impedanceXY;
  }

  /*!
    Repeat calls to setX()
  */
  void MagnetoTelluricFactory::setX (const QList<MagnetoTelluricCurve>& curves)
  {
    TRACE;
    for(QList<MagnetoTelluricCurve>::const_iterator it=curves.begin();it!=curves.end();++it) {
      setX(*it);
    }
  }

  /*!
    Repeat calls to linkX()
  */
  void MagnetoTelluricFactory::linkX (QList<MagnetoTelluricCurve>& curves) const
  {
    TRACE;
    for(QList<MagnetoTelluricCurve>::iterator it=curves.begin();it!=curves.end();++it) {
      linkX(*it);
    }
  }

  /*!
  */
  void MagnetoTelluricFactory::setX(const MagnetoTelluricCurve& c)
  {
    TRACE;
    _x.reserve(_x.count()+c.count());
    for(MagnetoTelluricCurve::const_iterator it=c.begin(); it!=c.end(); it++ ) {
      _x.append(it->x());
    }
    std::sort(_x.begin(), _x.end());
    unique(_x);
    // Allocates values for storing calculated impedances
    delete [] _impedanceXY;
    _impedanceXY=new ComplexValue[_x.count()];
  }

  /*!
  */
  void MagnetoTelluricFactory::setX(const QVector<double>& x)
  {
    TRACE;
    _x << x;
    std::sort(_x.begin(), _x.end());
    unique(_x);
    // Allocates values for storing calculated impedances
    delete [] _impedanceXY;
    _impedanceXY=new ComplexValue[_x.count()];
  }

  /*!
  */
  void MagnetoTelluricFactory::linkX(MagnetoTelluricCurve& c) const
  {
    TRACE;
    if(_x.isEmpty()) {
      App::log(tr("### ERROR ### : frequency list not set or empty when linking frequencies.\n") );
      return;
    }
    c.linkX(_x);
  }

  /*!
    Switch to omega from frequency. Must be called only once.
  */
  void MagnetoTelluricFactory::setAngularFrequency()
  {
    TRACE;
    double factor=2*M_PI;
    for(QVector<double>::iterator it=_x.begin(); it!=_x.end(); it++ ) {
      (*it) *= factor;;
    }
  }

  /*!
    Cagniard algorithm written by Max Moorkamp, ported to this
    library by Marc Wathelet.

    Permission to include this code here must be requested from Max
    (GPL into LGPL library-add defines). Easier to include that code
    here than to link to gplib which makes reference to many other
    libraries.
  */
  bool MagnetoTelluricFactory::calculate(const Resistivity1DModel& model, double staticShift)
  {
    if(model.layerCount()==0) {
      return false;
    }
    Complex omegamu, omega;
    Complex kcurr, klow;
    Complex xi;
    QVector<Complex> alpha;
    Complex adm;
    double d;
    double sigmacurr, sigmalow;
    int nx=_x.count();
    int nLayers=model.layerCount();
    const QVector<double>& depths=model.depths();
    const QVector<double>& resistivities=model.resistivities();
    for(int i=0; i<nx; i++) {
      omega.setIm(-_x.at(i));
      omegamu.setIm(-MAGNETIC_CONSTANT*_x.at(i));
      //printf("omegamu[%i]=%lf+i*%lf\n", i, omegamu.re(), omegamu.im());
      alpha.fill(Complex::null, resistivities.count());
      if(nLayers<2) {
        sigmacurr=1.0/resistivities.last();
        kcurr=sqrt(omegamu * sigmacurr);
        //printf("kcurr(HS)=%lf+i*%lf\n",kcurr.re(), kcurr.im());
      } else {
        for(int j=nLayers-2; j>=0; --j) {
          //printf("Layer %i, low %i\n", j, j+1);
          sigmalow=1.0/resistivities.at(j + 1);
          sigmacurr=1.0/resistivities.at(j);
          kcurr=sqrt(omegamu * sigmacurr);
          klow=sqrt(omegamu * sigmalow);
          //printf("kcurr=%lf+i*%lf\n",kcurr.re(), kcurr.im());
          //printf("klow=%lf+i*%lf\n",klow.re(), klow.im());
          if(kcurr.re() < 0.0) {
            kcurr *= -1.0;
            klow *= -1.0;
          }
          Complex km=kcurr + klow;
          km*=km;
          xi=omegamu * (sigmacurr - sigmalow)/km;
          //printf("xi=%lg+i*%lg\n",xi.re(),xi.im());
          alpha[j + 1]=(xi + alpha.at(j + 1))/(1.0+xi * alpha.at(j + 1));
          //printf("alpha[j+1]=%lg+i*%lg\n",alpha.at(j+1).re(),alpha.at(j+1).im());
          d=j>0 ? depths.at(j)-depths.at(j-1) : depths.at(j);
          //printf("d=%lf\n",d);
          alpha[j]=alpha.at(j + 1) * exp(-2. * kcurr * d);
          //printf("alpha[j]=%lg+i*%lg\n",alpha.at(j).re(),alpha.at(j).im());
        }
      }
      //printf("kcurr(surf)=%lf+i*%lf\n",kcurr.re(), kcurr.im());
      adm=kcurr/omega * ((1.0- alpha.at(0))/(1.0 + alpha.at(0)));
      //printf("adm=%lg+i*%lg\n",adm.re(), adm.im());
      _impedanceXY[i].setValue(staticShift*conjugate(1./adm));
      //printf("Zxy=%lg+i*%lg\n",_impedanceXY[i].value().re(), _impedanceXY[i].value().im());
    }
    return true;
  }

  void MagnetoTelluricFactory::writeReport(QDataStream& s) const
  {
    TRACE;
    int nFrequencies=_x.count();
    s << nFrequencies;
    for(int i=0; i<nFrequencies; i++) {
      s << _x.at(i);
    }
    for(int i=0; i<nFrequencies; i++) {
      s << _impedanceXY[i].value();
    }
  }

  void MagnetoTelluricFactory::readReport(QDataStream& s)
  {
    TRACE;
    int nFrequencies;
    s >> nFrequencies;
    _x.resize(nFrequencies);
    for(int i=0; i<nFrequencies; i++) {
      s >> _x[i];
    }
    Complex c;
    delete _impedanceXY;
    _impedanceXY=new ComplexValue[nFrequencies];
    for(int i=0; i<nFrequencies; i++) {
      s >> c;
      _impedanceXY[i].setValue(c);
    }
  }

  void MagnetoTelluricFactory::toStream(QTextStream& sOut, const MagnetoTelluricPointOptions& options) const
  {
    TRACE;
    double factor=0.5/M_PI;
    int n=_x.count();
    Point2D p;
    for(int i=0; i<n; i++) {
      p.setX(factor*_x[i]);
      p.setY(options.toDouble(p.x(), _impedanceXY[i].value()));
      sOut << p << "\n";
    }
    sOut << flush;
  }

  MagnetoTelluricCurve MagnetoTelluricFactory::curve() const
  {
    TRACE;
    MagnetoTelluricCurve c;
    int n=_x.count();
    c.resize(n);
    double factor=0.5/M_PI;
    for(int i=0; i<n; i++) {
      MagnetoTelluricPoint& p=c[i];
      p.setX(factor*_x[i]);
      p.setMean(_impedanceXY[i].value());
    }
    return c;
  }

} // namespace QGpCoreWave
