/***************************************************************************
**
**  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: 2007-02-27
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "AutocorrFactory.h"
#include "AutocorrEngine.h"
#include "DispersionFactory.h"
#include "ModalCurve.h"
#include "Mode.h"

namespace QGpCoreWave {

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
AutocorrFactory::AutocorrFactory(const QVector<AutocorrRing> * rings)
{
  TRACE;
  _rings=rings;
  _vertical=0;
  _radial=0;
  _transverse=0;

  _jRR=0;
  _jLR=0;
  _jRT=0;
  _jLT=0;
  _alpha=0;
}

/*!
  Description of destructor still missing
*/
AutocorrFactory::~AutocorrFactory()
{
  TRACE;
  int nRings=_rings->count();
  for(int i=0;i<nRings;i++) {
    if(_vertical) delete _vertical[i];
    if(_radial) delete _radial[i];
    if(_transverse) delete _transverse[i];

    if(_jRR) delete _jRR[i];
    if(_jLR) delete _jLR[i];
    if(_jRT) delete _jRT[i];
    if(_jLT) delete _jLT[i];
  }
  delete [] _vertical;
  delete [] _radial;
  delete [] _transverse;
  delete [] _jRR;
  delete [] _jLR;
  delete [] _jRT;
  delete [] _jLT;
  if(_alpha) {
    for(int i=0; i<_nHorizontalModes; i++) {
      delete [] _alpha[i];
    }
    delete [] _alpha;
  }
}

void AutocorrFactory::upgrade(ModalStorage **& s, int ringIndex, int requestedMode)
{
  TRACE;
  if( !s) {
    int nRings=_rings->count();
    s=new ModalStorage * [nRings];
    for(int i=0;i<nRings;i++) {
      s[i]=0;
    }
  }
  ModalFactory::upgrade<ModalStorage>(s[ringIndex], requestedMode);
}

void AutocorrFactory::setMode (const Mode& m)
{
  TRACE;
  switch (m.polarization()) {
  case Mode::Radial:
    upgrade(_radial, m.ringIndex(), m.index()); break;
  case Mode::Transverse:
    upgrade(_transverse, m.ringIndex(), m.index()); break;
  default:
    upgrade(_vertical, m.ringIndex(), m.index()); break;
  }
}

const RealValue * AutocorrFactory::mode(const Mode& m) const
{
  switch (m.polarization()) {
  case Mode::Radial:
    return _radial ? _radial[m.ringIndex()]->mode(m.index()) : 0;
  case Mode::Transverse:
    return _transverse ? _transverse[m.ringIndex()]->mode(m.index()) : 0;
  default:
    return _vertical ? _vertical[m.ringIndex()]->mode(m.index()) : 0;
  }
}

int AutocorrFactory::maxModeCount(ModalStorage ** s) const
{
  TRACE;
  if(s) {
    int nr=_rings->count();
    int maxModeCount=0;
    for(int iRing=0;iRing<nr; iRing++) {
      if(s[iRing]) {
        int nm=s[iRing]->modeCount();
        if(nm>maxModeCount) maxModeCount=nm;
      }
    }
    return maxModeCount;
  } else return 0;
}

int AutocorrFactory::horizontalModeCount() const
{
  TRACE;
  int nm=maxModeCount(_radial);
  int nTransverse=maxModeCount(_transverse);
  if(nTransverse<nm) nm=nTransverse;
  return nm;
}

ModalStorage ** AutocorrFactory::newHorizontalStorage()
{
  TRACE;
  int nRings=_rings->count();
  ModalStorage ** s=new ModalStorage*[nRings];
  for(int i=0; i<nRings; i++) {
    if(_radial[i] && _transverse[i]) {
      s[i]=newStorage(_nHorizontalModes);
    } else {
      s[i]=0;
    }
  }
  return s;
}

void AutocorrFactory::setInvalid(ModalStorage ** s)
{
  TRACE;
  if(s) {
    int nx=_x.count();
    int nr=_rings->count();
    for(int iRing=0;iRing < nr; iRing++ ) {
      if(s[iRing] ) {
        int nm=s[iRing]->modeCount();
        for(int iMode=0;iMode < nm;iMode++ ) {
          RealValue * points=s[iRing]->mode(iMode);
          for(int i=0;i < nx;i++ ) {
            points[i].setValid(false);
          }
        }
      }
    }
  }
}

void AutocorrFactory::init()
{
  TRACE;
  if(isRadial() || isTransverse()) {
    _nHorizontalModes=horizontalModeCount();
    setInvalid(_radial);
    setInvalid(_transverse);
    _jRR=newHorizontalStorage();
    _jLR=newHorizontalStorage();
    _jRT=newHorizontalStorage();
    _jLT=newHorizontalStorage();
    _alpha=new RealStatisticalValue*[_nHorizontalModes];
    int nx=_x.count();
    for(int i=0; i<_nHorizontalModes; i++) {
      RealStatisticalValue * s=new RealStatisticalValue[nx];
      for(int j=0; j<nx; j++) {
        s[j].setValid(false);
      }
      _alpha[i]=s;
    }
  }
  if(isVertical()) {
    _nVerticalModes=verticalModeCount();
    setInvalid(_vertical);
  }
}

/*!
  Calculate the autocorrelation curves from a dispersion curve for all possible modes
  Min(nModes(),disp.nModes())
*/
void AutocorrFactory::calculateVertical(DispersionFactory * disp)
{
  TRACE;
  Dispersion& dispRayleigh=*disp->phaseRayleigh();
  int nr=_rings->count();
  int nx=_x.count();
  ASSERT(_nVerticalModes<=dispRayleigh.modeCount());
  AutocorrEngine eng;
  for(int iMode=0; iMode<_nVerticalModes; iMode++) {
    RealValue * dispR=dispRayleigh.mode(iMode);
    for(int iRing=0; iRing<nr; iRing++) {
      if(_vertical[iRing]) {
        eng.setVerticalRing(_rings->at(iRing));
        RealValue * autocorrV=vertical(iRing).mode(iMode);
        if(eng.isThinRing()) {
          for(int ix=0; ix<nx; ix++)
            autocorrV[ix]=eng.verticalThinRing(x()->at(ix), dispR[ix]);
        } else {
          for(int ix=0; ix<nx; ix++)
            autocorrV[ix]=eng.verticalThickRing(x()->at(ix), dispR[ix]);
        }
      }
    }
  }
}

void AutocorrFactory::calculateHorizontal(double alphaR, DispersionFactory * disp)
{
  TRACE;
  double alphaL=1.0-alphaR;
  Dispersion& dispRayleigh=*disp->phaseRayleigh();
  Dispersion& dispLove=*disp->phaseLove();
  int nr=_rings->count();
  int nx=_x.count();
  ASSERT(_nHorizontalModes<=dispRayleigh.modeCount());
  ASSERT(_nHorizontalModes<=dispLove.modeCount());
  AutocorrEngine eng;
  for(int iMode=0; iMode<_nHorizontalModes; iMode++) {
    RealValue * dispR=dispRayleigh.mode(iMode);
    RealValue * dispL=dispLove.mode(iMode);
    for(int iRing=0; iRing<nr; iRing++) {
      if(_radial[iRing] && _transverse[iRing]) {
        eng.setHorizontalRing(_rings->at(iRing));
        RealValue * autocorrR=radial(iRing).mode(iMode);
        RealValue * autocorrT=transverse(iRing).mode(iMode);
        if(eng.isThinRing()) {
          for(int ix=0; ix<nx; ix++) {
            if(eng.initHorizontalThinRing(x()->at(ix), dispR[ix], dispL[ix])) {
              autocorrR[ix]=eng.radial(alphaR, alphaL);
              autocorrT[ix]=eng.transverse(alphaR, alphaL);
            } else {
              autocorrR[ix].setValid(false);
              autocorrT[ix].setValid(false);
            }
          }
        } else {
          for(int ix=0; ix < nx; ix++ ) {
            if(eng.initHorizontalThickRing(x()->at(ix), dispR[ix], dispL[ix])) {
              autocorrR[ix]=eng.radial(alphaR, alphaL);
              autocorrT[ix]=eng.transverse(alphaR, alphaL);
            } else {
              autocorrR[ix].setValid(false);
              autocorrT[ix].setValid(false);
            }
          }
        }
      }
    }
  }
}

/*!
  Calculation radial and transverse autocorrelations for the \a iOmega th frequency and for fundamental mode
*/
void AutocorrFactory::calculateHorizontal(int iOmega, double alphaR, DispersionFactory * disp)
{
  TRACE;
  double alphaL=1.0-alphaR;
  Dispersion& dispRayleigh=*disp->phaseRayleigh();
  Dispersion& dispLove=*disp->phaseLove();
  int nr=_rings->count();
  ASSERT(_nHorizontalModes<=dispRayleigh.modeCount());
  ASSERT(_nHorizontalModes<=dispLove.modeCount());
  AutocorrEngine eng;
  RealValue * dispR=dispRayleigh.mode(0);
  RealValue * dispL=dispLove.mode(0);
  for(int iRing=0; iRing<nr; iRing++) {
    if(_radial[iRing] && _transverse[iRing]) {
      eng.setHorizontalRing(_rings->at(iRing));
      RealValue * autocorrR=radial(iRing).mode(0);
      RealValue * autocorrT=transverse(iRing).mode(0);
      if(eng.isThinRing()) {
        if(eng.initHorizontalThinRing(x()->at(iOmega), dispR[iOmega], dispL[iOmega])) {
          autocorrR[iOmega]=eng.radial(alphaR, alphaL);
          autocorrT[iOmega]=eng.transverse(alphaR, alphaL);
        } else {
          autocorrR[iOmega].setValid(false);
          autocorrT[iOmega].setValid(false);
        }
      } else {
        if(eng.initHorizontalThickRing(x()->at(iOmega), dispR[iOmega], dispL[iOmega])) {
          autocorrR[iOmega]=eng.radial(alphaR, alphaL);
          autocorrT[iOmega]=eng.transverse(alphaR, alphaL);
        } else {
          autocorrR[iOmega].setValid(false);
          autocorrT[iOmega].setValid(false);
        }
      }
    }
  }
}

void AutocorrFactory::calculate(double alphaR, DispersionFactory * disp)
{
  TRACE;
  if(isVertical()) calculateVertical(disp);
  if(isRadial() || isTransverse()) calculateHorizontal(alphaR, disp);
}

void AutocorrFactory::calculate(DispersionFactory * disp)
{
  TRACE;
  if(isVertical()) calculateVertical(disp);
  if(isRadial() || isTransverse()) {
    Dispersion& dispRayleigh=*disp->phaseRayleigh();
    Dispersion& dispLove=*disp->phaseLove();
    int nr=_rings->count();
    int nx=_x.count();
    ASSERT(_nHorizontalModes<=dispRayleigh.modeCount());
    ASSERT(_nHorizontalModes<=dispLove.modeCount());
    AutocorrEngine eng;
    for(int iMode=0; iMode<_nHorizontalModes; iMode++) {
      RealValue * dispR=dispRayleigh.mode(iMode);
      RealValue * dispL=dispLove.mode(iMode);
      for(int iRing=0; iRing<nr; iRing++) {
        if(_radial[iRing] && _transverse[iRing]) {
          eng.setHorizontalRing(_rings->at(iRing));
          RealValue * jRR=_jRR[iRing]->mode(iMode);
          RealValue * jLR=_jLR[iRing]->mode(iMode);
          RealValue * jRT=_jRT[iRing]->mode(iMode);
          RealValue * jLT=_jLT[iRing]->mode(iMode);
          if(eng.isThinRing()) {
            for(int ix=0; ix<nx; ix++) {
              if(eng.initHorizontalThinRing(x()->at(ix), dispR[ix], dispL[ix])) {
                jRR[ix]=eng.rayleighRadial();
                jRT[ix]=eng.rayleighTransverse();
                jLR[ix]=eng.loveRadial();
                jLT[ix]=eng.loveTransverse();
              } else {
                jRR[ix].setValid(false);
                jRT[ix].setValid(false);
                jLR[ix].setValid(false);
                jLT[ix].setValid(false);
              }
            }
          } else {
            for(int ix=0; ix < nx; ix++ ) {
              if(eng.initHorizontalThickRing(x()->at(ix), dispR[ix], dispL[ix])) {
                jRR[ix]=eng.rayleighRadial();
                jRT[ix]=eng.rayleighTransverse();
                jLR[ix]=eng.loveRadial();
                jLT[ix]=eng.loveTransverse();
              } else {
                jRR[ix].setValid(false);
                jRT[ix].setValid(false);
                jLR[ix].setValid(false);
                jLT[ix].setValid(false);
              }
            }
          }
        }
      }
    }
  }
}

/*!
  Direct inversion of 3C SPAC.
*/
void AutocorrFactory::setAlpha(const QList<ModalCurve> curves)
{
  TRACE;
  for(QList<ModalCurve>::ConstIterator itCurve=curves.begin(); itCurve!= curves.end(); itCurve++ ) {
    const ModalCurve& curve=*itCurve;
    const QList<Mode>& modes=curve.modes();
    for(QList<Mode>::ConstIterator itMode=modes.begin(); itMode!= modes.end(); itMode++ ) {
      const Mode& mode=*itMode;
      double factor;
      int iRing=mode.ringIndex();
      int iMode=mode.index();
      RealValue *j1, *j2;
      RealStatisticalValue alpha;
      switch (mode.polarization()) {
      case Mode::Radial:
        j1=_jLR[iRing]->mode(iMode);
        j2=_jRR[iRing]->mode(iMode);
        for(ModalCurve::const_iterator it=curve.begin(); it!=curve.end(); it++) {
          const FactoryPoint& p=*it;
          int index=p.index();
          double j1Value=j1[index].value();
          factor=1.0/(j1Value-j2[index].value());
          alpha.setMean(factor*(j1Value-p.mean()));
          alpha.setStddev(factor*p.stddev());
          alpha.setWeight(p.weight());
          if(alpha.mean()>1.0) alpha.setMean(1.0);
          else if(alpha.mean()<0.0) alpha.setMean(0.0);
          printf("radial alpha mode %i index %i %lg/%lg\n",iMode, index, alpha.mean(),alpha.stddev());
          _alpha[iMode][index].average(alpha);
        }
        break;
      case Mode::Transverse:
        j1=_jLT[iRing]->mode(iMode);
        j2=_jRT[iRing]->mode(iMode);
        for(ModalCurve::const_iterator it=curve.begin(); it!=curve.end(); it++ ) {
          const FactoryPoint& p=*it;
          int index=p.index();
          double j1Value=j1[index].value();
          factor=1.0/(j1Value - j2[index].value());
          alpha.setMean(factor * (j1Value - p.mean()) );
          alpha.setStddev(factor * p.stddev());
          alpha.setWeight(p.weight());
          if(alpha.mean()>1.0) alpha.setMean(1.0);
          else if(alpha.mean()<0.0) alpha.setMean(0.0);
          printf("transv alpha mode %i index %i %lg/%lg\n",iMode, index, alpha.mean(),alpha.stddev());
          _alpha[iMode][index].average(alpha);
        }
        break;
      default:
        break;
      }
    }
  }
}

/*!
  Direct inversion of 3C SPAC
*/
void AutocorrFactory::setHorizontalAutocorr()
{
  TRACE;
  int nr=_rings->count();
  int nx=_x.count();
  for(int iMode=0; iMode<_nHorizontalModes; iMode++) {
    RealStatisticalValue * alpha=_alpha[iMode];
    for(int iRing=0; iRing<nr; iRing++) {
      if(_radial[iRing] && _transverse[iRing]) {
        RealValue * autocorrR=radial(iRing).mode(iMode);
        RealValue * autocorrT=transverse(iRing).mode(iMode);
        RealValue * jRR=_jRR[iRing]->mode(iMode);
        RealValue * jLR=_jLR[iRing]->mode(iMode);
        RealValue * jRT=_jRT[iRing]->mode(iMode);
        RealValue * jLT=_jLT[iRing]->mode(iMode);
        for(int j=0; j<nx; j++) {
          double alphaR=alpha[j].mean();
          if(alphaR<0.0) alphaR=0.0;
          else if(alphaR>1.0) alphaR=1.0;
          double alphaL=1.0-alphaR;
          autocorrR[j].setValue(alphaR*jRR[j].value()+alphaL*jLR[j].value());
          autocorrT[j].setValue(alphaR*jRT[j].value()+alphaL*jLT[j].value());
        }
      }
    }
  }
}

/*!
  Unoptimized litteral impletation, for testing only
*/
double AutocorrFactory::radialAutocorr(double omega, double r1, double r2, double alpha, double cr, double cl)
{
  TRACE;
  //printf("%lf: %lf %lf %lf %lf %lf\n",omega, r1, r2, alpha, cr,cl);
  double omegaR1=omega*r1/cr; 
  double omegaR2=omega*r2/cr; 
  double omegaL1=omega*r1/cl; 
  double omegaL2=omega*r2/cl; 
  return 4/(r2*r2-r1*r1)*(alpha*cr/omega*(cr/omega*(j0(omegaR2)-j0(omegaR1))+(r2*j1(omegaR2)-r1*j1(omegaR1)))-(1-alpha)*cl*cl/(omega*omega)*(j0(omegaL2)-j0(omegaL1)));
}

/*!
  Unoptimized litteral impletation, for testing only
*/
double AutocorrFactory::transverseAutocorr(double omega, double r1, double r2, double alpha, double cr, double cl)
{
  TRACE;
  double omegaR1=omega*r1/cr; 
  double omegaR2=omega*r2/cr; 
  double omegaL1=omega*r1/cl; 
  double omegaL2=omega*r2/cl; 
  return 4/(r2*r2-r1*r1)*(-alpha*cr*cr/(omega*omega)*(j0(omegaR2)-j0(omegaR1))+(1-alpha)*cl/omega*(cl/omega*(j0(omegaL2)-j0(omegaL1))+(r2*j1(omegaL2)-r1*j1(omegaL1))));
}

} // namespace QGpCoreWave
