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

#include "QuadraticCondition.h"

namespace DinverCore {

  /*!
    \class QuadraticCondition QuadraticCondition.h
    \brief A quadratic condition c1*p1^2+c2*p2^2+...<C
  */

  const QString QuadraticCondition::xmlQuadraticConditionTag = "QuadraticCondition";
  static const QString indexKey("index");

  /*!
    Description of constructor still missing
  */
  QuadraticCondition::QuadraticCondition(double factor1, Parameter * p1,
                                         double factor2, Parameter * p2,
                                         Type type, double constant)
  {
    TRACE;
    addParameter(p1);
    addParameter(p2);
    _type=type;
    _factors=new double[2];
    _factors[0]=factor1;
    _factors[1]=factor2;
    _constant=constant;
  }

  /*!
    Description of constructor still missing
  */
  QuadraticCondition::QuadraticCondition(double factor1, Parameter * p1,
                                         double factor2, Parameter * p2,
                                         double factor3, Parameter * p3,
                                         Type type, double constant)
  {
    TRACE;
    addParameter(p1);
    addParameter(p2);
    addParameter(p3);
    _type=type;
    _factors=new double[3];
    _factors[0]=factor1;
    _factors[1]=factor2;
    _factors[2]=factor3;
    _constant=constant;
  }

  /*!
    Description of destructor still missing
  */
  QuadraticCondition::~QuadraticCondition()
  {
    TRACE;
    delete [] _factors;
  }

  bool QuadraticCondition::operator==(const AbstractCondition& o) const
  {
    TRACE;
    if (xml_tagName()==o.xml_tagName()) {
      const QuadraticCondition& qc=static_cast<const QuadraticCondition&>(o);
      if(_type==qc._type &&
         _constant==qc._constant &&
         AbstractCondition::operator==(o)) {
        int n=count();
        for(int i=0; i<n; i++) {
          if(_factors[i]!=qc._factors[i]) {
            return false;
          }
        }
        return true;
      }
    }
    return false;
  }

  /*!
    \fn void QuadraticCondition::getLimits( int paramIndex, double& min, double& max) const
    Adjust \a min or/and \a max to fulfill the condition. \a paramIndex must be less than count().
  */

  /*!
    Return true if original ranges of parameter are touched. Ranges can be reduced, never enlarged
  */
  bool QuadraticCondition::adjustRanges()
  {
    TRACE;
    double min=0.0, max=std::numeric_limits<double>::infinity();
    bool modified=false;
    int n=count();
    switch (_type) {
    case LessThan:
      for(int i=0; i<n; i++) {
        for(int j=0; j<n; j++) {
          if(j!=i) {
            parameter(j).setRealValue(parameter(j).minimum());
          }
        }
        max=std::numeric_limits<double>::infinity();
        getLimits(i, min, max);
        if(parameter(i).maximum()>max) {
          parameter(i).setMaximum(max);
          modified=true;
        }
      }
      break;
    case GreaterThan:
      for(int i=0; i<n; i++) {
        for(int j=0; j<n; j++) {
          if(j!=i) {
            parameter(j).setRealValue(parameter(j).maximum());
          }
        }
        min=0.0;
        getLimits(i, min, max);
        if(parameter(i).minimum()>min) {
          parameter(i).setMinimum(min);
          modified=true;
        }
      }
      break;
    case NoCondition:
      break;
    }
    return modified;
  }

  /*!
    Return a number that uniquely identify this condition
  */
  uint QuadraticCondition::internalChecksum() const
  {
    TRACE;
    uint cs=0;
    for(int i=count()-1; i>=0; i--) {
      cs+=qHash((qint64)_factors[i]);
    }
    cs+=qHash((qint64)_constant);
    cs+=_type+1;
    return cs;
  }

  /*!
    Print human readable information about condition to stream.
  */
  void QuadraticCondition::humanInfo() const
  {
    TRACE;
    App::log("        ");
    int n=count();
    for(int i=0; i<n; i++) {
      if(i>0) {
        App::log("+");
      }
      if(_factors[i]!=1.0) {
        App::log(QString::number(_factors[i])+"*");
      }
      App::log(parameter(i).name()+"^2");
    }
    App::log((_type==LessThan ? " < " : " > " )+QString::number(_constant)+"\n");
  }

  void QuadraticCondition::xml_writeProperties( XML_WRITEPROPERTIES_ARGS ) const
  {
    TRACE;
    writeProperty(s, "type", toString(_type));
    writeProperty(s, "constant", _constant);
    XMLSaveAttributes att;
    att.add(indexKey);
    QString& value=att.value(0);
    int n=count();
    for(int i=0; i<n; i++) {
      value=QString::number(i);
      writeProperty(s, "factor", att, _factors[i]);
    }
    AbstractCondition::xml_writeProperties(s, context);
  }

  XMLMember QuadraticCondition::xml_member(XML_MEMBER_ARGS)
  {
    TRACE;
    if (tag=="factor")
      return XMLMember(0);
    else if (tag=="constant")
      return XMLMember(1);
    else if (tag=="type")
      return XMLMember(2);
    else if (tag=="coef")    // kept for compatibility
      return XMLMember(0);
    else
      return AbstractCondition::xml_member(tag, attributes, context)+3;
  }

  bool QuadraticCondition::xml_setProperty(XML_SETPROPERTY_ARGS)
  {
    TRACE;
    switch (memberID) {
    case 0: {
        XMLRestoreAttributeIterator it=attributes.find(indexKey);
        if(it!=attributes.end()) {
          int i=it.value().toInt();
          if(i<count()) {
            _factors[i]=content.toDouble();
          }
        }
      }
      return true;
    case 1:
      _constant=content.toDouble();
      return true;
    case 2: {
        bool ok=true;
        _type=fromString(content.toString(), ok);
        return ok;
      }
    default:
      return AbstractCondition::xml_setProperty(memberID-3, tag, attributes, content, context);
    }
  }

} // namespace DinverCore
