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

#include <math.h>

#include "PoissonCondition.h"
#include "ParamProfile.h"
#include "ParamLayer.h"
#include "PowerLawProfile.h"

namespace DinverDCCore {

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

  Full description of class still missing
*/

const QString PoissonCondition::xmlPoissonConditionTag="PoissonCondition";

class ProfileLimits
{
public:
  const ParamLayer * layer;
  QVector<double> depths;
  QVector<double> minP;
  QVector<double> maxP;
  int topIndex;
  int bottomIndex;
  double topDepth;
  double bottomDepth;
  void pMinP();
  void pMaxP();
};

void ProfileLimits::pMinP()
{
  TRACE;
  printf("--minimum profile--\n");
  for(int i=topIndex; i<=bottomIndex; i++) {
    printf("%20.15lg\t%20.15lg\n",depths[i],minP[i]);
  }
}

void ProfileLimits::pMaxP()
{
  TRACE;
  printf("--maximum profile--\n");
  for(int i=topIndex; i<=bottomIndex; i++) {
    printf("%20.15lg\t%20.15lg\n",depths[i],maxP[i]);
  }
}

/*!
  Description of constructor still missing
*/
PoissonCondition::PoissonCondition(ParamProfile * vp, ParamProfile * vs, ParamProfile * nu)
{
  TRACE;
  _vp=vp;
  _vs=vs;
  _nu=nu;
  // Build the list of involved parameters (all value parameters from vp, vs and nu)
  _vp->collectValueParameters(parameterList());
  _vs->collectValueParameters(parameterList());
  _nu->collectValueParameters(parameterList());
}

bool PoissonCondition::operator==(const AbstractCondition& o) const
{
  TRACE;
  if(xml_tagName()==o.xml_tagName()) {
    return AbstractCondition::operator==(o);
  } else return false;
}

void PoissonCondition::getLimits(int paramIndex, double& min, double& max) const
{
  TRACE;
  const GroundParameter& gParam=parameter(paramIndex);
  ProfileLimits pl;
  ASSERT(gParam.count()==1);
  pl.layer=gParam.layer(0); /* Only depth parameters may be linked to several layers from distinct profiles
                               Depth parameters are never associated a poisson condition directly */
  const ParamProfile * profile=pl.layer->profile();
  pl.depths=profile->resampledProfile().depths();
  const QVector<double>& minNu=_nu->minResampledProfile().values();
  const QVector<double>& maxNu=_nu->maxResampledProfile().values();
  int n=pl.depths.count();
  pl.minP.resize(n);
  pl.maxP.resize(n);
  pl.topDepth=pl.layer->gradientProfile().topDepth();
  pl.bottomDepth=pl.layer->gradientProfile().bottomDepth();
  pl.topIndex=-1;
  if(profile==_vp) { // Compute pl.minP and pl.maxP for Vp
    const QVector<double>& values=_vs->resampledProfile().values();
    int i=0;
    for(; i<n && pl.depths[i]<=pl.topDepth; i++) {}
    pl.topIndex=i;
    for(; i<n && pl.depths[i]<=pl.bottomDepth; i++) {
      if(minNu[i]<maxNu[i]) {
        if(minNu[i]==0.5)
          pl.minP[i]=std::numeric_limits<double>::infinity();
        else
          pl.minP[i]=values[i]*sqrt((minNu[i]-1.0)/(minNu[i]-0.5));
        if(maxNu[i]==0.5)
          pl.maxP[i]=std::numeric_limits<double>::infinity();
        else
          pl.maxP[i]=values[i]*sqrt((maxNu[i]-1.0)/(maxNu[i]-0.5));
      } else { // Constant Nu, ignore Vp values
        pl.minP[i]=0.0;
        pl.maxP[i]=std::numeric_limits<double>::infinity();
      }
    }
    pl.bottomIndex=i-1;
  } else if(profile==_vs) { // Compute pl.minP and pl.maxP for Vs
    const QVector<double>& values=_vp->resampledProfile().values();
    int i=0;
    for(; i<n && pl.depths[i]<=pl.topDepth; i++) {}
    pl.topIndex=i;
    for(; i<n && pl.depths[i]<=pl.bottomDepth; i++) {
      if(minNu[i]<maxNu[i]) {
        pl.minP[i]=values[i]*sqrt((maxNu[i]-0.5)/(maxNu[i]-1.0));
        pl.maxP[i]=values[i]*sqrt((minNu[i]-0.5)/(minNu[i]-1.0));
      } else { // Constant Nu, ignore condition forced by Vp values
               // Vp values will be forced later on
        pl.minP[i]=0.0;
        pl.maxP[i]=std::numeric_limits<double>::infinity();
      }
    }
    pl.bottomIndex=i-1;
  }
  // Find min and max for parameter gParam that belongs to layer within minP and maxP profiles
  switch (gParam.type()) {
  case GroundParameter::ValueTop:
    getTopLimits(pl, min, max);
    break;
  case GroundParameter::ValueGradient:
    getGradientLimits(pl, min, max);
    break;
  case GroundParameter::ValueBottom:
    getBottomLimits(pl, min, max);
    break;
  default:
    // Never occur, depth parameters are handled differently: fussy parameters are adjusted by trial and error
    break;
  }
}

void PoissonCondition::getTopLimits(const ProfileLimits& pl, double& min, double& max) const
{
  TRACE;
  GradientProfile * gp=pl.layer->gradientProfile().clone();
  for(int i=pl.topIndex; i<=pl.bottomIndex; i++) {
    // Prominent points are critical: for minimum, values are more critical at the top of each sub-layer
    //                                for maximum, values are more critical at the bottom of each sub-layer
    gp->setMinimumTopValue(pl.minP[i], i>0 ? pl.depths[i-1] : 0.0);
    if(gp->topValue()>min) {
      min=gp->topValue();
    }
    gp->setMaximumTopValue(pl.maxP[i], pl.depths[i]);
    if(gp->topValue()<max) {
      max=gp->topValue();
    }
  }
  delete gp;
}

void PoissonCondition::getGradientLimits(const ProfileLimits& pl, double& min, double& max) const
{
  TRACE;
  if(pl.layer->gradientParam()) {
    GradientProfile * gp=pl.layer->gradientProfile().clone();
    for(int i=pl.topIndex; i<=pl.bottomIndex; i++) {
      // Prominent points are critical: for minimum, values are more critical at the top of each sub-layer
      //                                for maximum, values are more critical at the bottom of each sub-layer
      gp->setMinimumGradient(pl.minP[i], i>0 ? pl.depths[i-1] : 0.0);
      if(gp->gradient()>min) {
        min=gp->gradient();
      }
      gp->setMaximumGradient(pl.maxP[i], pl.depths[i]);
      if(gp->gradient()<max) {
        max=gp->gradient();
      }
    }
    delete gp;
  }
}

void PoissonCondition::getBottomLimits(const ProfileLimits& pl, double& min, double& max) const
{
  TRACE;
  if(pl.layer->bottomParam()) {
    GradientProfile * gp=pl.layer->gradientProfile().clone();
    for(int i=pl.topIndex; i<=pl.bottomIndex; i++) {
      // Prominent points are critical: for minimum, values are more critical at the top of each sub-layer
      //                                for maximum, values are more critical at the bottom of each sub-layer
      gp->setMinimumGradient(pl.minP[i], i>0 ? pl.depths[i-1] : 0.0);
      if(gp->bottomValue()>min) {
        min=gp->bottomValue();
      }
      gp->setMaximumGradient(pl.maxP[i], pl.depths[i]);
      if(gp->bottomValue()<max) {
        max=gp->bottomValue();
      }
    }
    delete gp;
  }
}

/*!
  By design this function is called only for fussy parameters (depth and thickness only).
  All profiles are supposed to be correctly built, it return false if Poisson's ratio is not valid.
*/
bool PoissonCondition::isOk(const GroundParameter * from)
{
  TRACE;
  for(int iLayer=from->count()-1; iLayer>=0; iLayer-- ) {
    const ParamLayer * layer=from->layer(iLayer);
    const ParamProfile * profile=layer->profile();
    QVector<double> depths=profile->resampledProfile().depths();
    int n=depths.count();
    double topDepth=layer->gradientProfile().topDepth();
    double bottomDepth=layer->fixedBottomDepth();
    const QVector<double>& vs=_vs->resampledProfile().values();
    const QVector<double>& vp=_vp->resampledProfile().values();
    const QVector<double>& minNu=_nu->minResampledProfile().values();
    const QVector<double>& maxNu=_nu->maxResampledProfile().values();
    int i=0;
    for( ; i<n && depths[i]<=topDepth; i++ ) {}
    for( ; i<n && depths[i]<=bottomDepth; i++ ) {
      if(minNu[i]<maxNu[i]) {
        /*App::log(tr(" poisson i %1 vp %2 vs %3 minnu %4 maxnu %5")
                         .arg(i)
                         .arg(vp[i])
                         .arg(vs[i])
                         .arg(minNu[i])
                         .arg(maxNu[i]) << endl;*/
        if(vs[i]<vp[i]*sqrt((maxNu[i]-0.5)/(maxNu[i]-1.0))) return false;
        if(vs[i]>vp[i]*sqrt((minNu[i]-0.5)/(minNu[i]-1.0))) return false;
      } // Else constant Nu: no condition from this side
    }
  }
  return true;
}

/*!
  Used for debug
*/
bool PoissonCondition::isOk()
{
  TRACE;
  QVector<double> depths=_vp->resampledProfile().depths();
  int n=depths.count();
  const QVector<double>& vs=_vs->resampledProfile().values();
  const QVector<double>& vp=_vp->resampledProfile().values();
  const QVector<double>& minNu=_nu->minResampledProfile().values();
  const QVector<double>& maxNu=_nu->maxResampledProfile().values();
  for(int i=0; i<n; i++) {
    if(minNu[i]<maxNu[i]) {
      /*App::log(tr(" poisson i %1 vp %2 vs %3 minnu %4 maxnu %5")
                       .arg(i)
                       .arg(vp[i])
                       .arg(vs[i])
                       .arg(minNu[i])
                       .arg(maxNu[i]) << endl;*/
      if(vs[i]<vp[i]*sqrt((maxNu[i]-0.5)/(maxNu[i]-1.0))) return false;
      if(vs[i]>vp[i]*sqrt((minNu[i]-0.5)/(minNu[i]-1.0))) return false;
    } // Else constant Nu: no condition from this side
  }
  return true;
}

/*!
  Return a number that uniquely identify this condition
*/
uint PoissonCondition::internalChecksum() const
{
  TRACE;
  uint cs=0;
  return cs;
}
/*!
  Print human readable information about condition to stream.
*/
void PoissonCondition::humanInfo() const
{
  TRACE;
  App::log("        Poisson's ratio checked\n");
}

/*!
  For all layers with a fixed Poisson's ratio, it calculates Vp from
  Vs profile.
*/
void PoissonCondition::setVp()
{
  TRACE;
  QVector<double>& vp=_vp->resampledProfile().values();
  const QVector<double>& vs=_vs->resampledProfile().values();
  const QVector<double>& minNu=_nu->minResampledProfile().values();
  const QVector<double>& maxNu=_nu->maxResampledProfile().values();
  int n=vs.count();
  for(int i=0; i<n; i++) {
    if(minNu[i]==maxNu[i]) {
      vp[i]=vs[i]*sqrt((minNu[i]-1.0)/(minNu[i]-0.5));
    }
  }
}

} // namespace DinverDCCore
