/***************************************************************************
**
**  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: 2019-01-17
**  Copyright: 2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "PowerLawProfile.h"

namespace DinverDCCore {

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

    Full description of class still missing
  */

  /*!
    Return true if gradient is modified because depths must be recomputed.
  */
  bool PowerLawProfile::setGradient(double alpha)
  {
    bool mod=alpha!=_alpha;
    _alpha=alpha;
    return mod;
  }

  /*!
    Return true if gradient is modified because depths must be recomputed.
  */
  bool PowerLawProfile::setBottomValue(double v)
  {
    return setGradient(::log(v/_vt)/::log(1.0+thickness()));
  }

  double PowerLawProfile::bottomValue() const
  {
    return _vt*::pow(1+thickness(), _alpha);
  }

  /*!
    Each sub layer has constant value step equal to vt*dv.

    v=v0*(1+z-z0)^a
    Total value variation=v0*(1+h)^a-v0=v0*((1+h)^a-1)
    Value variation per sublayer=v0*((1+h)^a-1)/n=2*v0*dV
    For a particular sublayer i, value=v0+v0*dV+2*i*v0*dV=v0*dVi
    First sublayer value is at v0+v0*dV (half of the usual value variation).

  */
  void PowerLawProfile::setValueStep()
  {
    _dv=0.5*(::pow(1+thickness(), _alpha)-1.0)/static_cast<double>(_n);
  }

  /*!
    \fn double PowerLawProfile::valueAt(int index) const
    Return value of sublayer \a index (dvi)
  */

  /*!
    Mid-depth of each sublayer is approximately on the theoretical power law.
    Travel time is almost kept constant between theoretical power law and discretized one.
    The error is less than 1% on travel time compared to a precise inversion of the depth.
    Works only if gradient is lower than 1.

    \a index of the considered sublayer
  */
  double PowerLawProfile::subLayerBottomDepth(int index, double subLayerTopDepth) const
  {
    double dvi=valueAt(index);
    if(index==_n-1) {
      return _zb;
    } else {
      return 2.0*(_zt+::pow(dvi, 1.0/_alpha)-1.0)-subLayerTopDepth;
    }
#if 0
    // Depth of each sublayer is obtain after a Newton-Raphson iterative process.
    // Convergence is fine in all cases for positive exponent, after a few iterations.
    double z=_zb;
    double dvi=valueAt(index);
    double y;
    // Travel time from _zt to subLayerTopDepth
    double tti=::pow(1.0+subLayerTopDepth-_zt, _alpha+1.0);
    double lastz=subLayerTopDepth;
    int iterations=0;
    do {
      double dz=1.0+z-_zt;
      double dza=::pow(dz, _alpha);
      y=dza*dz-tti-(_alpha+1.0)*dvi*(z-subLayerTopDepth);
      double dy=(_alpha+1.0)*(dza-dvi);
      lastz=z;
      z-=y/dy;
      iterations++;
    } while(fabs(lastz-z)>0.01*lastz && iterations<10); // until less than 1% variation
    if(iterations==10) {
      App::log(tr("Maximum number of iterations (10) while computing power law depths\n"
                  "  Residuals=%1, dz=%2 m\n").arg(y).arg(fabs(lastz-z)));
    }
    ASSERT(std::isnormal(z));
    return z;
#endif
  }

  void PowerLawProfile::setDepths(int topIndex, Profile& p)
  {
    int n1=_n-1;
    setValueStep();
    double z=_zt;
    if(_alpha==0.0) { // Linear distribution of depths
      double dz=thickness()/static_cast<double>(_n);
      for(int i=0; i<n1; i++) {
        p.setDepth(topIndex+i, _zt+static_cast<double>(i+1)*dz);
      }
    } else {
      for(int i=0; i<n1; i++) {
        z=subLayerBottomDepth(i, z);
        p.setDepth(topIndex+i, z);
      }
    }
    p.setDepth(topIndex+n1, _zb);
  }

  void PowerLawProfile::setValues(int topIndex, Profile& p)
  {
    for(int i=0; i<_n; i++) {
      p.setValue(topIndex+i, _vt*valueAt(i));
    }
  }

  /*!
    Return the sub layer index corresponding to \a depth.
    If \a depth matchs one of the predefined depths, the lowest
    index is returned.
  */
  int PowerLawProfile::minimumIndexOf(double depth) const
  {
    double z=_zt;
    int index;
    for(index=0; depth>z; index++) {
      z=subLayerBottomDepth(index, z);
    }
    return index>0 ? index-1 : 0;
  }

  /*!
    Return the sub layer index corresponding to \a depth.
    If \a depth matchs one of the predefined depths, the highest
    index is returned.
  */
  int PowerLawProfile::maximumIndexOf(double depth) const
  {
    double z=_zt;
    int index;
    for(index=0; depth>z; index++) {
      z=subLayerBottomDepth(index, z);
    }
    if(depth==z) {
      return index<_n ? index : _n-1;
    } else {
      return index>0 ? index-1 : 0;
    }
  }

  /*!
    top and bottom depths must be fixed, gradient also.
    If \a depth falls exactly on one of profile's predefined depths,
    choose the highest index to get the lowest possible top value.
  */
  void PowerLawProfile::setMinimumTopValue(double value, double depth)
  {
    ASSERT(depth>=_zt && depth<=_zb);
    int index=maximumIndexOf(depth);
    _vt=value/valueAt(index);
  }

  /*!
    top and bottom depths must be fixed, gradient also.
    If \a depth falls exactly on one of profile's predefined depths,
    choose the lowest index to get the highest possible top value.
  */
  void PowerLawProfile::setMaximumTopValue(double value, double depth)
  {
    ASSERT(depth>=_zt && depth<=_zb);
    int index=minimumIndexOf(depth);
    _vt=value/valueAt(index);
  }

#if 0
  // Getting limits for gradient is complex... not a unique solution
  // The complexity comes from the dependency of sub layer depths with
  // the gradient value.
  // Currently this solution is abandoned and replaced by a fussy option
  // on gradient parameter for powerlaw.

  void PowerLawProfile::scanGradient(double value)
  {
    double dvi1=value/_vt-1.0;
    double n2=2.0*static_cast<double>(_n);
    double invLogH=1.0/::log(1.0+thickness());
    for(int i=0; i<_n; i++) {
      _dv=dvi1/static_cast<double>(1+2*i);
      _alpha=::log(n2*_dv+1.0)*invLogH;
      double z=_zt;
      for(int j=0; j<i; j++) {
        z=subLayerBottomDepth(j, z);
      }
      App::log(tr("depth range for index %1: %2 to %3 => alpha=%4\n")
               .arg(i).arg(z).arg(subLayerBottomDepth(i, z)).arg(_alpha));
    }
  }

  /*!
    top and bottom depths must be fixed, topValue also.

    The solution is not always unique: there may be one or two solutions.
    \a minGradient and \a maxGradient restrict the possibilities.
    True is returned only if there is only one possible solution.
  */
  bool PowerLawProfile::setGradient(double value, double depth,
                                    double minGradient, double maxGradient)
  {
    ASSERT(depth>=_zt && depth<=_zb);
    if(value<_vt) {
      _alpha=0.0;
      return true;
    }
    double dvi1=value/_vt-1.0;
    double n2=2.0*static_cast<double>(_n);
    double invLogH=1.0/::log(1.0+thickness());
    for(int i=0; i<_n; i++) {
      _dv=dvi1/static_cast<double>(1+2*i);
      _alpha=::log(n2*_dv+1.0)*invLogH;
      if(_alpha>=minGradient && _alpha<=maxGradient) {
        int ei=indexOf(depth);
        if(ei==i) {
          i++; // Try next index
          if(i<_n) {
            _dv=dvi1/static_cast<double>(1+2*i);
            _alpha=::log(n2*_dv+1.0)*invLogH;
            if(_alpha>=minGradient && _alpha<=maxGradient) {
              ei=indexOf(depth);
              if(ei==i) {
                printf("alpha= %lf\n", _alpha);
                printDebug();
                i--;
                _dv=dvi1/static_cast<double>(1+2*i);
                _alpha=::log(n2*_dv+1.0)*invLogH;
                printf("alpha= %lf\n", _alpha);
                printDebug();
                return true; // at least two solutions
              }
            }
          }
          i--;
          _dv=dvi1/static_cast<double>(1+2*i);
          _alpha=::log(n2*_dv+1.0)*invLogH;
          return true;
        }
      }
    }
    return false;
#if 0
    // Does not work properly in all cases
    double dvi=value/_vt;
    // First estimation with theoretical profile
    _alpha=::log(dvi)/::log(1.0+depth-_zt);
    if(_alpha>maxGradient) {
      // value is larger than the theoretical profile
      _alpha=maxGradient;
      setValueStep();
      if(value>_vt*valueAt(indexOf(depth))) {
        // value is larger than the discretized profile, maxGradient is fine
        return;
      }
      // Else value is lower than the discretized profile a gradient slightly
      // lower than maxgradient has to be found.
    } else {
      setValueStep();
    }
    // Re-estimation of alpa
    int index=indexOf(depth);
    _alpha=::log(1.0+2.0*_n/(1.0+2.0*index)*(dvi-1.0))/::log(1.0+thickness());
#endif
#if 0
    // index found from value range, which lead to errors...
    printf("value %lf depth %lf\n", value, depth);
    int index=qRound(0.5*(dvi-_dv-1.0)/_dv);
    if(index<0) {
      App::log(tr("PowerLawProfile::setGradient: found negative index\n"));
      index=0;
    }
    setDepths();
    setValues();
    printDebug();
    double topSubLayer=index>0 ? _depths[index-1] : _zt;
    printf("  index %i alpha %lf rel depth %lf %%\n", index, _alpha, 100.0*(depth-topSubLayer)/(_depths[index]-topSubLayer));
    while(true) {
      double oldAlpha=_alpha;
      _alpha=::log(1.0+2.0*_n/(1.0+2.0*index)*(dvi-1.0))/::log(1.0+h);
      setDepths(index);
      //setDepths();
      //setValues();
      //printDebug();
      double topSubLayer=index>0 ? _depths[index-1] : _zt;
      printf("  index %i alpha %lf dAlpha %lf rel depth %lf %%\n", index, _alpha, _alpha-oldAlpha, 100.0*(depth-topSubLayer)/(_depths[index]-topSubLayer));
      if(depth>_depths[index]) {
        index++;
      } else if(index>0 && depth<_depths[index-1]) {
        index--; // From random testing, it does never occur
      } else {
        break;
      }
    }
#endif
  }

  void PowerLawProfile::printDebug() const
  {
    if(_n>0) {
      printf("%5s %15s %15s %15s %15s %12s %12s\n", "index", "value", "depth", "dv", "dz", "tt error", "mid z err");
      double z=_zt, v=_vt;
      for(int i=0; i<_n; i++) {
        double zi=subLayerBottomDepth(i, z);
        double tti=::pow(1.0+z-_zt, _alpha+1.0);
        double dz=1.0+zi-_zt;
        double dza=::pow(dz, _alpha);
        double ttt=_vt*(dza*dz-tti)/(_alpha+1.0);
        double vi=_vt*valueAt(i);
        printf("%5i %15lf %15lf %15.10lg %15.10lg %10.2lf %% %10.2lf %%\n",
               i,
               vi,
               zi,
               vi-v,
               zi-z,
               100.0*(vi*(zi-z)-ttt)/ttt,
               (theoreticalLaw(0.5*(zi+z))-vi)/vi*100.0);
        v=vi;
        z=zi;
      }
    }
  }

  void PowerLawProfile::fineCheckMinimum(double value, double depth)
  {
    printf("fineCheck minimum %lf at %lf m\n", value, depth);
    setValueStep();
    printDebug();
    double aOriginal=_alpha;
    double aEnd=_alpha*1.1;
    for(double a=aOriginal*1.001; a<aEnd; a*=1.001) {
      _alpha=a;
      setValueStep();
      int i=indexOf(depth);
      double vi=_vt*valueAt(i);
      if(value>vi) {
        printf("\n");
      }
    }
  }

  void PowerLawProfile::fineCheckMaximum(double value, double depth)
  {
    printf("fineCheck maximum %lf at %lf m\n", value, depth);
    setValueStep();
    printDebug();
    double aOriginal=_alpha;
    double aEnd=_alpha/1.1;
    for(double a=aOriginal/1.001; a>aEnd; a/=1.001) {
      _alpha=a;
      setValueStep();
      int i=indexOf(depth);
      double vi=_vt*valueAt(i);
      if(value<vi) {
        printf("\n");
      }
    }
  }
#endif

} // namespace DinverDCCore

