/***************************************************************************
**
**  This file is part of gpdepths.
**
**  gpdepths 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.
**
**  gpdepths 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: 2009-02-02
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>
#include <QGpCoreMath.h>

#include "DepthDistribution.h"

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
DepthDistribution::DepthDistribution()
{
  TRACE;
  _n=1;
  _minimumWavelength=1.0;
  _a=0.25;
  _maximumWavelength=100.0;
}

void DepthDistribution::setMinimumWavelength(double m)
{
  TRACE;
  _minimumWavelength=m;
  _a=0.25*_minimumWavelength;
}

/*!
  If \a layerCount==1 => returns just a range based on min and max
*/
bool DepthDistribution::setLayerCount(int layerCount)
{
  TRACE;
  if(layerCount<=0) return false;
  _n=layerCount;
  return true;
}

bool DepthDistribution::calculate()
{
  TRACE;
  _depths.clear();
  double z=0.0;
  if(_n==1) {
    _q=1;
    _depths.append(0);
    _depths.append(_a);
    _depths.append(0.5*_maximumWavelength);
  } else {
    // To have one solution, the derivative at 1 must be negative
    // n*a>max
    if(_n*_a>=0.5*_maximumWavelength) {
      App::log(tr("No solution, decrease the number of layers or increase the ratio between min and max wavelength\n") );
      return false;
    }
    // Search root with a step that doubles at each iteration
    double dx=1e-15;
    while(y(1.0+dx)<0.0) {
      dx*=2;
    }
    RootSolver<DepthDistribution> solver(this);
    solver.setPrecision(1e-10);
    solver.setInterval(1.0+dx*0.5, 1.0+dx);
    //solver.newtonRaphson();
    solver.halving();
    _q=solver.lower();
    // Calculate depths at top of each layer
    for(int i=0;i<_n+2;i++) {
      _depths.append(z);
      z+=_a*pow(_q,i);
    }
  }
  return true;
}

double DepthDistribution::y(double q) const
{
  return _a*pow(q,_n)
        -0.5*_maximumWavelength*q+
         0.5*_maximumWavelength-_a;
}

double DepthDistribution::derivative(double q) const
{
  return _n*_a*pow(q,_n-1)
        -0.5*_maximumWavelength;
}

double DepthDistribution::minimumDepth(int i) const
{
  TRACE;
  return _depths[i]+0.5*(_depths[i+1]-_depths[i]);
}

double DepthDistribution::thickness(int i) const
{
  TRACE;
  return _depths[i+1]-_depths[i];
}

double DepthDistribution::maximumDepth(int i) const
{
  TRACE;
  if(i==_n-1) {
    return 0.5*_maximumWavelength;
  } else {
    return _depths[i+1]+0.5*(_depths[i+2]-_depths[i+1]);
  }
}

void DepthDistribution::report(QTextStream& s) const
{
  TRACE;
  s << "# h[i]=" << _a << "*" << _q << "^i" << endl;
  s << "# i | h | z at top | z at bottom | min[z] | max[z]" << endl;
  for(int i=0;i<_n;i++) {
    double z=_depths[i];
    double dz1=_depths[i+1]-z;
    double dz2=_depths[i+2]-_depths[i+1];
    s << i << " " << dz1 << " " << z << " " << z+dz1 << " " << z+0.5*dz1 << " " << z+dz1+0.5*dz2 << endl;
  }
  s << _n << " inf " << _depths[_n] << endl;
}
