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

#include "RealSpace.h"

namespace DinverCore {

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

  Full description of class still missing
  TODO: RealSpace must provide a vector of values directly rather
        than fetching each parameter individualy
*/

const QString RealSpace::xmlRealSpaceTag="RealSpace";

/*!
  Description of destructor still missing
*/
RealSpace::~RealSpace()
{
  TRACE;
  qDeleteAll(_allParameters);
  qDeleteAll(_conditions);
}


bool RealSpace::operator==(const RealSpace& o) const
{
  TRACE;
  int nd=variableParameterCount();
  if(nd!=o.variableParameterCount()) return false;
  for(int i=0; i<nd; i++ ) {
    if(_variableParameters[i]->name()!=o._variableParameters[i]->name()) {
      return false;
    }
  }
  return true;
}

/*!
  Returns a number that uniquely identify this parameterization
*/
uint RealSpace::checksum() const
{
  TRACE;
  int ndVar=variableParameterCount();
  int ndAll=allParameterCount();
  uint sum=0;
  sum+=ndVar;
  sum+=ndAll*ndVar;
  for(int i=0;i<ndVar;i++) {
    sum+=(i+1)*_variableParameters[i]->checksum();
  }
  for(int i=0;i<ndAll;i++) {
    sum+=(i+ndVar+1)*_allParameters[i]->checksum();
  }
  return sum;
}

/*!
  Returns the total number of possible models
*/
double RealSpace::possibleCount() const
{
  TRACE;
  int ndVar=variableParameterCount();
  double n=1.0;
  for(int i=0;i<ndVar;i++) {
    n*=_variableParameters[i]->gridCount();
  }
  return n;
}

/*!
  Removes all parameters
*/
void RealSpace::clearParameters()
{
  TRACE;
  qDeleteAll(_allParameters);
  _allParameters.clear();
  _variableParameters.clear();
  qDeleteAll(_conditions);
  _conditions.clear();
}


/*!
  Adds one parameter to space. A standard parameter is allocated.
*/
Parameter * RealSpace::addParameter(QString name, QString unit, double min, double max,
                                    ParameterGrid::Scale scale, double precision)
{
  TRACE;
  Parameter * p=new Parameter;
  p->setName(name);
  p->setUnit(unit);
  p->setMinimum(min);
  p->setMaximum(max);
  p->setScale(scale);
  p->setPrecision(precision);
  if(p->initGrid()) {
    if(!_parameterNamePrefix.isEmpty()) {
      p->setName(_parameterNamePrefix+p->name());
    }
    _allParameters.append(p);
    return p;
  } else {
    App::log(tr("Error initializing parameter '%1'\n").arg(name) );
    delete p;
    return 0;
  }
}

/*!
  Adds one parameter to space. Custom parameters can be added.
  Make sure to call initGrid().
*/
Parameter * RealSpace::addParameter(Parameter * p)
{
  TRACE;
  if(!_parameterNamePrefix.isEmpty()) {
    p->setName(_parameterNamePrefix+p->name());
  }
  _allParameters.append(p);
  return p;
}

/*!
  Fixed and variable parameters are added to space by addParam(). Fixed parameters have their min and max equal.
  This function defines the effective number of dimensions based on the number of variable parameters.
*/
void RealSpace::setVariableParameters()
{
  TRACE;
  _variableParameters.clear();
  double minp, maxp;
  foreach(Parameter * p, _allParameters) {
    p->getRectangularLimits(minp, maxp);
    if(minp==maxp) {
      p->setRealValue(minp);
    } else {
      _variableParameters.append(p);
      if(minp > maxp) {
        App::log(tr("%1: bad parameter range [%2, %3]\n").arg(p->name()).arg(minp).arg(maxp) );
        App::log(tr("   ---> interval reversed\n") );
        p->setMinimum(maxp);
        p->setMaximum(minp);
      }
    }
  }
}

/*!
  With a big number of parameters, if some range of parameter are not adjusted, it might take a very long time to initialize.
  Return true if all range are compatible.
*/
bool RealSpace::adjustRanges()
{
  TRACE;
  bool modified=true;
  while(modified) {
    modified=false;
    foreach(AbstractCondition * ac, _conditions) {
      if(ac->adjustRanges()) {
        modified=true;
      }
    }
    if(modified) {
      // check validity of modified ranges and re-initialize parameter grid
      bool detectedError=false;
      foreach(Parameter * p, _variableParameters) {
        double minp, maxp;
        p->getRectangularLimits(minp, maxp);
        if(minp>=maxp) {
          App::log(tr("%1: conditions require a bad parameter range [%2, %3]\n")
                    .arg(p->name()).arg(minp).arg(maxp));
          detectedError=true;
        }
        if(!p->initGrid()) {
          App::log(tr("%1: error initializing grid in range [%2, %3]\n")
                    .arg(p->name()).arg(minp).arg(maxp));
          detectedError=true;
        }
      }
      if(detectedError) return false;
    }
  }
  return true;
}

/*!
  Print a diagnostic of current model: does it fit will all conditions?
*/
void RealSpace::conditionDiagnostic() const
{
  TRACE;
  foreach(Parameter * p, _allParameters) {
    p->conditionDiagnostic();
  }
}

void RealSpace::humanInfo() const
{
  TRACE;
  App::log(tr( "\n---------------------- List of parameters and conditions\n\n")
            +tr(" Parameterization checksum           =%1\n").arg(checksum())
            +tr(" Dimension of parameter space        =%1\n").arg(variableParameterCount()));
  foreach(Parameter * p, _allParameters) {
    p->humanInfo();
  }
}

void RealSpace::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  foreach(Parameter * p, _allParameters) {
    p->xml_save(s, context);
  }
}

XMLMember RealSpace::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(tag=="Param") {
    Parameter * p=new Parameter;
    _allParameters.append(p);
    return XMLMember(p);
  }
  else return XMLMember(XMLMember::Unknown);
}

bool RealSpace::xml_polish(XML_POLISH_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  setVariableParameters();
  return true;
}

/*!
  Return parameter with \a name, if not found return 0
*/
Parameter * RealSpace::parameter(QString name)
{
  TRACE;
  foreach(Parameter * p, _allParameters) {
    if(p->name()==name) return p;
  }
  return 0;
}

/*!
  Add one condition to define the limit of the parameter sub-space
*/
void RealSpace::addCondition(AbstractCondition * c)
{
  TRACE;
  // Check if this condition already exists
  foreach(AbstractCondition * ac, _conditions) {
    if( *c==*ac) {
      delete c;
      return;
    }
  }
  _conditions.append(c);
  foreach(Parameter * p, _allParameters) {
    p->addCondition(c);
  }
}

QString RealSpace::toString() const
{
  TRACE;
  QString s;
  QString t("    %1=%2 %3\n");
  foreach(Parameter * p, _allParameters) {
    s+=t.arg(p->name()).arg(p->realValue()).arg(p->unit());
  }
  return s;
}

bool RealSpace::isOkDebugVerbose() const
{
  for(int i=0; i<_allParameters.count(); i++) {
    if(!_allParameters.at(i)->isOkDebugVerbose()) return false;
  }
  return true;
}

} // namespace DinverCore
