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

#include "SumCondition.h"

namespace DinverCore {

  /*!
    \class SumCondition SumCondition.h
    \brief A condition of type c1*p1+c2*p2+...+cn*pn < or > b
  */

  const QString SumCondition::xmlSumConditionTag="SumCondition";
  static const QString indexKey("index");

  /*!
    No parameter attached to this condition with this constructor.
  */
  SumCondition::SumCondition(Type type, double constant)
    : AbstractCondition()
  {
    TRACE;
    _type=type;
    _factors=nullptr;
    _constant=constant;
  }

  SumCondition::~SumCondition()
  {
    TRACE;
    if(_factors) {
      delete [] _factors;
    }
  }

  /*!
    Internal factor list can remain null if all factors are 1
  */
  void SumCondition::setFactor(int index, double value)
  {
    TRACE;
    if(index<count()) {
      if(value==1.0) {
        if(_factors) {
          _factors[index]=1.0;
        }
      } else {
        if(!_factors) {
          int n=count();
          _factors=new double[n];
          for(int i=n-1; i>=0; i--) {
            _factors[i]=1.0;
          }
        }
        _factors[index]=value;
      }
    }
  }

  void SumCondition::addParameter(Parameter * p, double factor)
  {
    TRACE;
    int n=count();
    // _factor can remain null if all factors are 1
    if(_factors) {
      double * newFactors=new double[n+1];
      memcpy(newFactors, _factors, sizeof(double)*n);
      delete [] _factors;
      _factors=newFactors;
    }
    setFactor(n, factor);
    AbstractCondition::addParameter(p);
  }

  bool SumCondition::operator==(const AbstractCondition& o) const
  {
    TRACE;
    if(xml_tagName()==o.xml_tagName()) {
      const SumCondition& sc=static_cast<const SumCondition&>(o);
      if(_type==sc._type &&
         _constant==sc._constant &&
         AbstractCondition::operator==(o)) {
        if(!_factors) {
          return !sc._factors;
        } else if(!sc._factors) {
          return false;
        }
        for(int i=count()-1; i>=0; i--) {
          if(_factors[i]!=sc._factors[i]) {
            return false;
          }
        }
        return true;
      }
    }
    return false;
  }

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

  /*!
    Return true if original ranges of parameter are touched. Ranges can be reduced, never enlarged
  */
  bool SumCondition::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 SumCondition::internalChecksum() const
  {
    TRACE;
    uint cs=0;
    if(_factors) {
      for(int i=count()-1; i>=0; i--) {
        cs+=qHash(static_cast<qint64>(_factors[i]));
      }
    }
    cs+=qHash(static_cast<qint64>(_constant));
    cs+=_type+1;
    return cs;
  }

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

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

  XMLMember SumCondition::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
      return AbstractCondition::xml_member(tag, attributes, context)+3;
  }

  bool SumCondition::xml_setProperty(XML_SETPROPERTY_ARGS)
  {
    TRACE;
    switch (memberID) {
    case 0: {
        XMLRestoreAttributeIterator it=attributes.find(indexKey);
        if(it!=attributes.end()) {
          int i=it.value().toInt();
          setFactor(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

