/***************************************************************************
**
**  This file is part of DinverCore.
**
**  DinverCore 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.
**
**  DinverCore 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: 2004-10-15
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "Parameter.h"

namespace DinverCore {

  // Rounding errors encountered on a Pentium 4
  #define PRECISION_FACTOR 2.0e-16

  const QString Parameter::xmlParamTag="Param";

  /*!
    \class Parameter Parameter.h
    \brief Represents an inversion parameter (range, scaling, conditions)

    A parameter may be qualified as 'fussy'. If so, each time the value of the parameter is changed, a validation of the model
    can be achieved by re-implementing NaModel::isCurrentModelOk() which returns true by default.
    If NaModel::isCurrentModelOk() returns false, a new value is randomly generated.
  */

  Parameter::Parameter()
  {
    _value=0.5;
    _fussy=false;
  }

  Parameter::~Parameter()
  {
  }

  /*!
    \fn Parameter::setRealValue(double val)

    Set the real value of the parameter, defined() flag is automatically set to true.
  */

  /*!
    Set the real value of the parameter from a binary stream(report).
    Returns true if the value can set properly. Failure may occur for fixed parameters when the constant value is not the same
    as the one of the stream.
  */
  bool Parameter::setRealValue(QDataStream& s)
  {
    double v;
    s >> v;
    if(isFixed()) {
      if(realValue()!=v) {
        App::log(tr("Fixed parameter %1 does not have the correct value (%2 must be %3)\n").arg(name()).arg(v).arg(realValue()) );
        return false;
      }
    } else {
      setRealValue(v);
    }
    return true;
  }

  /*!
    Return a number that uniquely identify this parameter and the attached conditions
  */
  uint Parameter::checksum() const
  {
    TRACE;
    uint sum=0;
    sum += qHash(_name);
    sum += qHash(_unit);
    for(QVector<ParamCondition>::const_iterator it=_conditions.begin(); it!=_conditions.end(); ++it) {
      sum+=it->checksum();
    }
    return sum;
  }

  /*!
    \fn Parameter::defined() const

    A parameter is defined when its realValue() has been set to a correct value. Especially used
    for generating random models with random primary parameter.
  */

  /*!
    \fn Parameter::setDefined(bool d)

    Set the value as undefined. Especially used for generating random models with random
    primary parameter. This function is called every time the generation of model is restarted
  */

  /*!
    Print a diagnostic of this parameter: does it fit will all conditions?
  */
  void Parameter::conditionDiagnostic() const
  {
    TRACE;
    double min, max;
    getRectangularLimits(min, max);
    getLimits(min, max);
    if(isFixed()) {
      App::log(" "+_name+": "+QString::number(_value)+"="+QString::number(min)+"\n");
    } else {
      App::log(" "+_name+": "+QString::number(min)+" <= "+QString::number(_value)+" <= "+QString::number(max)+"\n");
      if(_value<min || _value>max) {
        App::log("   ---> BAD CONDITION!\n");
      }
    }
  }

  bool Parameter::isOkDebugVerbose() const
  {
    double min, max;
    getRectangularLimits(min, max);
    printf("rectangular limits: %10lg %10lg\n", min, max);
    // Get limits
    for(int i=0; i<_conditions.count(); i++) {
      _conditions.at(i).getLimits(min, max);
      printf("condition %3i     : %10lg %10lg\n", i, min, max);
    }
    if(isFixed()) {
      if(_value>=min && _value<=max) return true;
      printf("    %lg <= %lg <= %lg ? oh nooo!\n",min,_value,max);
    } else {
      if(_value>min && _value<max) return true;
      printf("    %lg < %lg < %lg ? oh nooo!\n",min,_value,max);
    }
    return false;
  }

  /*!
    Stream out information about parameter and its condition in a human readable format
  */
  void Parameter::humanInfo() const
  {
    TRACE;
    if(isFixed()) {
      App::log(" "+_name+"="+QString::number(minimum())+" "+_unit+"\n");
    } else {
      App::log(" "+QString::number(minimum())+" <= "+_name+" <= "+QString::number(maximum())+" "+_unit+" ("+QString::number(_grid.count())+" bins)\n");
    }
    for(QVector<ParamCondition>::const_iterator it=_conditions.begin(); it!=_conditions.end(); ++it) {
      it->humanInfo();
    }
  }

  /*!
    Do not call this function directly, use the general RealSpace::addCondition() instead.
    This function is called by RealSpace::addCondition() to initialize the list of condition that include
    this parameter.

    \sa RealSpace::addCondition().
  */
  void Parameter::addCondition(AbstractCondition * c)
  {
    TRACE;
    int index=c->indexOf(this);
    if(index>-1) {
      _conditions.append(ParamCondition( c, index) );
    }
  }

  /*!
    \fn void Parameter::getLimits(double& min, double& max)
    All values of other parameters are assumed to be set correctly.
    This function returns the admissible range for this parameter given the current
    value of all the other parameters.
  */

  void Parameter::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
  {
    TRACE;
    Q_UNUSED(context)
    writeProperty(s, "name", _name);
    writeProperty(s, "unit", _unit);
    writeProperty(s, "min", minimum());
    writeProperty(s, "max", maximum());
  }

  bool Parameter::xml_setProperty(XML_SETPROPERTY_ARGS)
  {
    TRACE;
    Q_UNUSED(tag)
    Q_UNUSED(attributes)
    Q_UNUSED(context)
    switch (memberID) {
    case 0:
      setMinimum(content.toDouble());
      return true;
    case 1:
      setMaximum(content.toDouble());
      return true;
    case 2:
      _name=content.toString();
      return true;
    case 3:
      _unit=content.toString();
      return true;
    default:
      return false;
    }
  }

  XMLMember Parameter::xml_member(XML_MEMBER_ARGS)
  {
    TRACE;
    Q_UNUSED(attributes)
    Q_UNUSED(context)
    if(tag=="min" )
      return XMLMember(0);
    else if(tag=="max" )
      return XMLMember(1);
    else if(tag=="name" )
      return XMLMember(2);
    else if(tag=="unit" )
      return XMLMember(3);
    else
      return XMLMember(XMLMember::Unknown);
  }

} // namespace DinverCore
