/***************************************************************************
**
**  This file is part of ArrayCore.
**
**  ArrayCore 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.
**
**  ArrayCore 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: 2019-04-12
**  Copyright: 2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#ifndef FKPOWER_H
#define FKPOWER_H

#include "FKSteering.h"

#include "Wavenumber.h"
#include "ArrayCoreDLLExport.h"

namespace ArrayCore {

  class ARRAYCORE_EXPORT FKPower
  {
  public:
    FKPower(const FKSteering * steering=nullptr) : _steering(steering) {}

    void setSteering(const FKSteering * steering) {_steering=steering;}
    inline void initOneComponent();

    inline void setCaponValue(const ComplexMatrix& crossSpectrum);
    inline void setConventionalValue(const ComplexMatrix& crossSpectrum);
    inline void setThreeComponentValue(const ComplexMatrix& crossSpectrum);

    /*inline void setOneComponentGradient();
    inline void setThreeComponentGradient();
    inline void setOneComponentHessian(const ComplexMatrix& crossSpectrum);
    inline void setThreeComponentHessian(const ComplexMatrix& crossSpectrum);*/

    inline void operator=(const FKPower& o);

    double value() const {return _p.re();}
    //const Point& gradient() const {return _gradp;}
    //const DoubleMatrix& hessian() const {return _hessian;}
    //inline double concavity(const Matrix3x3 * scales=nullptr) const;
    //inline Point newtonStep() const;

    QString toString(char format='g', int precision=6);
  private:
    const FKSteering * _steering;
    ComplexMatrix _fe;
    Complex _p;
    double _2p2;
    //double _3pps, _thfe, _uhfe;
    //Point _gradp;
    //Matrix3x3 _hessian;
  };

  inline void FKPower::operator=(const FKPower& o)
  {
    _fe=o._fe;
    _p=o._p;
    _2p2=o._2p2;
    /*_3pps=o._3pps;
    _thfe=o._thfe;
    _uhfe=o._uhfe;
    _gradp=o._gradp;
    _hessian=o._hessian;*/
  }

  inline void FKPower::setCaponValue(const ComplexMatrix& crossSpectrum)
  {
    _fe=crossSpectrum*_steering->_e;
    // Even if imaginary part is not null and even not negligible compared to the
    // real part, the real part remains correctly estimated. Imaginary part should be
    // null in all cases but numerical errors for ill-posed cross spectral matrices
    // lead to non null imaginary.
    _p=1.0/(_steering->_eh*_fe).at(0, 0);
  }

  inline void FKPower::setConventionalValue(const ComplexMatrix& crossSpectrum)
  {
    _fe=crossSpectrum*_steering->_e;
    _p=(_steering->_eh*_fe).at(0, 0);
  }

  inline void FKPower::setThreeComponentValue(const ComplexMatrix &crossSpectrum)
  {
    setCaponValue(crossSpectrum);
    double ps=pow(_p.re(), 1.5)*_steering->_ell2;
    //_3pps=3.0*_p*ps;
    _p=ps;
  }

  inline void FKPower::initOneComponent()
  {
    //_hessian.zero();
    //_gradp.setZ(0.0);
  }

  /*inline void FKPower::setOneComponentGradient()
  {
    _2p2=2.0*_p*_p;
    _gradp.setX(_2p2*(_steering->_ehrx*_fe).at(0, 0).im());
    _gradp.setY(_2p2*(_steering->_ehry*_fe).at(0, 0).im());
  }

  inline void FKPower::setThreeComponentGradient()
  {
    _thfe=(_steering->_th*_fe).at(0, 0).im();
    _uhfe=(_steering->_uh*_fe).at(0, 0).im();
    _gradp.setX(_3pps*((_steering->_ehrx*_fe).at(0, 0).im()
                        -_steering->_esink*_thfe));
    _gradp.setY(_3pps*((_steering->_ehry*_fe).at(0, 0).im()
                        +_steering->_ecosk*_thfe));
    _gradp.setZ((_3pps*_uhfe+_steering->_2inve*_p)*_steering->_ellFactor);
  }

  inline void FKPower::setOneComponentHessian(const ComplexMatrix& crossSpectrum)
  {
    ComplexMatrix ferx=crossSpectrum*_steering->_rxe;
    ComplexMatrix fery=crossSpectrum*_steering->_rye;
    double inv2p=2.0/_p;
    _hessian.at(0, 0)=inv2p*_gradp.x()*_gradp.x()+_2p2*((_steering->_ehrxrx*_fe).at(0, 0).re()
                                                       -(_steering->_ehrx*ferx).at(0, 0).re());
    _hessian.at(0, 1)=inv2p*_gradp.x()*_gradp.y()+_2p2*((_steering->_ehrxry*_fe).at(0, 0).re()
                                                       -(_steering->_ehrx*fery).at(0, 0).re());
    _hessian.at(1, 0)=_hessian.at(0, 1);
    _hessian.at(1, 1)=inv2p*_gradp.y()*_gradp.y()+_2p2*((_steering->_ehryry*_fe).at(0, 0).re()
                                                       -(_steering->_ehry*fery).at(0, 0).re());
  }

  inline void FKPower::setThreeComponentHessian(const ComplexMatrix& crossSpectrum)
  {
    double inv53ps=5.0/3.0/_p;
    ComplexMatrix ferx=crossSpectrum*_steering->_rxe;
    ComplexMatrix fery=crossSpectrum*_steering->_rye;
    ComplexMatrix ft=crossSpectrum*_steering->_t;

    double thrxfe=(_steering->_thrx*_fe).at(0, 0).re();
    double thryfe=(_steering->_thry*_fe).at(0, 0).re();
    double thft=(_steering->_th*ft).at(0, 0).re();
    double thferx=(_steering->_th*ferx).at(0, 0).re();
    double thfery=(_steering->_th*fery).at(0, 0).re();
    double dthfex=thferx-thrxfe;
    double dthfey=thfery-thryfe;
    double ecossink2thfe=2.0*_steering->_ecossink2*_thfe;
    double uhft=(_steering->_uh*ft).at(0, 0).re();

    _hessian.at(0, 0)=inv53ps*_gradp.x()*_gradp.x()+_3pps*( (_steering->_ehrxrx*_fe).at(0, 0).re()
                                                           -(_steering->_ehrx*ferx).at(0, 0).re()
                                                           +2.0*_steering->_esink*dthfex
                                                           +ecossink2thfe
                                                           -_steering->_esin2k2*_uhfe
                                                           -_steering->_e2sin2k2*thft);
    _hessian.at(0, 1)=inv53ps*_gradp.x()*_gradp.y()+_3pps*( (_steering->_ehrxry*_fe).at(0, 0).re()
                                                           -(_steering->_ehrx*fery).at(0, 0).re()
                                                           -_steering->_ecosk*dthfex
                                                           +_steering->_esink*dthfey
                                                           +(_steering->_esin2k2-_steering->_ecos2k2)*_thfe
                                                           +_steering->_ecossink2*_uhfe
                                                           +_steering->_e2cossink2*thft);
    _hessian.at(1, 1)=inv53ps*_gradp.y()*_gradp.y()+_3pps*( (_steering->_ehryry*_fe).at(0, 0).re()
                                                           -(_steering->_ehry*fery).at(0, 0).re()
                                                           -2.0*_steering->_ecosk*dthfey
                                                           -ecossink2thfe
                                                           -_steering->_ecos2k2*_uhfe
                                                           -_steering->_e2cos2k2*thft);
    _hessian.at(0, 2)=( inv53ps*_gradp.x()*_gradp.z()
                       -_steering->_4inv3e*_gradp.x()+_3pps*( (_steering->_uhrx*_fe).at(0, 0).re()
                                                             -(_steering->_uh*ferx).at(0, 0).re()
                                                             -_steering->_sink*_thfe
                                                             +_steering->_esink*uhft))*_steering->_ellFactor;
    _hessian.at(1, 2)=( inv53ps*_gradp.y()*_gradp.z()
                       -_steering->_4inv3e*_gradp.y()+_3pps*( (_steering->_uhry*_fe).at(0, 0).re()
                                                              -(_steering->_uh*fery).at(0, 0).re()
                                                              +_steering->_cosk*_thfe
                                                              -_steering->_ecosk*uhft))*_steering->_ellFactor;
    _hessian.at(2, 2)=( inv53ps*_gradp.z()*_gradp.z()
                       -_steering->_8inv3e*_gradp.z()
                       +_steering->_2inv3e2*_p
                       -_3pps*(_steering->_uh*crossSpectrum*_steering->_u).at(0, 0).re())*_steering->_ellFactor2
                       +_steering->_2ell*_gradp.z();

    _hessian.at(1, 0)=_hessian.at(0, 1);
    _hessian.at(2, 0)=_hessian.at(0, 2);
    _hessian.at(2, 1)=_hessian.at(1, 2);
  }

  inline double FKPower::concavity(const Matrix3x3 * scales) const
  {
    bool ok=true;
    QVector<double> eigenValues;
    if(scales) {
      Matrix3x3 sh=_hessian.dotMultiply(*scales);
      eigenValues=sh.symetricEigenValues(ok);
    } else {
      eigenValues=_hessian.symetricEigenValues(ok);
    }
    if(ok) {
      for(int i=eigenValues.count()-1; i>=0; i--) {
        if(eigenValues.at(i)>0.0) {
          return 1.0;
        }
      }
      return -1.0;
    } else {
      return 0.0;
    }
  }

  inline Point FKPower::newtonStep() const
  {
    Matrix3x3 hinv=_hessian;
    if(hinv.invert()) {           // Spending a bit of time for OneComponent is not so dramatic
                                  // ThreeComponent is more critical
                                  // newtonStep is used exclusively when concavity is negative
                                  // the Hessian is always invertible.
      return -(hinv*_gradp);
    } else {
      Matrix2x2 hinv=_hessian.subMatrix(0, 0, 1, 1);
      if(hinv.invert()) {
        return -(hinv*_gradp);
      } else {
        return Point();
      }
    }
  }*/

} // namespace ArrayCore

#endif // FKPOWER_H

