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

#include <QGpCoreTools.h>

#include "ParamSpaceScript.h"
#include "RealSpace.h"
#include "SimpleCondition.h"
#include "QuadraticCondition.h"

namespace DinverCore {

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

  Full description of class still missing
*/

const QString ParamSpaceScript::xmlParamSpaceScriptTag="ParamSpaceScript";

void ParamSpaceScript::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  writeProperty(s, "text", _text);
}

XMLMember ParamSpaceScript::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(tag=="text" ) return XMLMember(0);
  else return XMLMember(XMLMember::Unknown);
}

bool ParamSpaceScript::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  switch (memberID) {
  case 0: _text=content.toString(); return true;
  default: return false;
  }
}

bool ParamSpaceScript::apply(RealSpace * param) const
{
  TRACE;
  ExpressionParser pEngine;
  ParamExpressionContext context(param);
  ExpressionActions aList=pEngine.parse(_text, &context);
  /* Debug purpose ...
  for(HeaderActionList::iterator ita=aList.begin();ita!=aList.end();++ita) {
    (*ita)->xml_saveFile("/home/mwathele/debugaction.xml");
  }*/
  // Do not return an error if list of actions is empty
  /*if(aList.isEmpty()) {
    App::log(tr("error: no expression found to construct parameter space\n") );
    return false;
  }*/
  for(ExpressionActions::iterator ita=aList.begin();ita!=aList.end();++ita) {
    context.addVariable("nd", param->variableParameterCount());
    (*ita)->value();
  }
  qDeleteAll(aList);
  return true;
}

QStringList ParamExpressionContext::functions() const
{
  TRACE;
  QStringList l;
  l << "linear(<param1>, \">\" | \"<\", <a>, <param2>, <b>)"
    << "quadratic2(<coef1>, <param1>, <coef2>, <param2>, \">\" | \"<\", <c>)"
    << "quadratic3(<coef1>, <param1>, <coef2>, <param2>, <coef3>, <param3>, \">\" | \"<\", <c>)"
    << "parameter(<name>, <unit>, <min>, <max>, <prec>, <scale>)";
  return l;
}

bool ParamExpressionContext::isValidFunction(const QString& name) const
{
  TRACE;
  switch(name[0].unicode()) {
  case 'l':
    if (name=="linear") return true;
    break;
  case 'q':
    if (name=="quadratic2" || name=="quadratic3") return true;
    break;
  case 'p':
    if (name=="parameter") return true;
    break;
  default:
    break;
  }
  return false;
}

QVariant ParamExpressionContext::functionValue(const QString& name, const QVector<ExpressionAction *>& args) const
{
  TRACE;
  switch(name[0].unicode()) {
  case 'l':
    if (name=="linear") {
      if (args.count()==5) {
        Parameter * x1Param=_param->parameter(args.at(0)->value().toString());
        if(!x1Param) {
          App::log(tr("error: parameter %1 not found\n").arg(args.at(0)->value().toString()) );
          return QVariant();
        }
        Parameter * x2Param=_param->parameter(args.at(3)->value().toString());
        if(!x2Param) {
          App::log(tr("error: parameter %1 not found\n").arg(args.at(3)->value().toString()) );
          return QVariant();
        }
        if(args.at(1)->value().toString() ==">") {
          _param->addCondition(new SimpleCondition(x1Param, SimpleCondition::GreaterThan,
                                args.at(2)->value().toDouble(), x2Param, args.at(4)->value().toDouble()) );
        } else {
          _param->addCondition(new SimpleCondition(x1Param, SimpleCondition::LessThan,
                                args.at(2)->value().toDouble(), x2Param, args.at(4)->value().toDouble()) );
        }
      } else {
        App::log(tr("error: function 'linear' requires 5 arguments: param1, \"<\" or \">\", a, param2, b\n") );
      }
    }
    break;
  case 'q':
    if(name=="quadratic2") {
      if (args.count()==6) {
        double coef1=args.at(0)->value().toDouble();
        Parameter * x1Param=_param->parameter(args.at(1)->value().toString());
        if (!x1Param) {
          App::log(tr("error: parameter %1 not found\n").arg(args.at(1)->value().toString()) );
          return QVariant();
        }
        double coef2=args.at(2)->value().toDouble();
        Parameter * x2Param=_param->parameter(args.at(3)->value().toString());
        if (!x2Param) {
          App::log(tr("error: parameter %1 not found\n").arg(args.at(3)->value().toString()) );
          return QVariant();
        }
        double c=args.at(5)->value().toDouble();
        if (args.at(4)->value().toString()==">") {
          _param->addCondition(new QuadraticCondition(coef1, x1Param, coef2, x2Param, QuadraticCondition::GreaterThan, c));
        } else {
          _param->addCondition(new QuadraticCondition(coef1, x1Param, coef2, x2Param, QuadraticCondition::LessThan, c));
        }
      } else {
        App::log(tr("error: function 'quadratic2' requires 6 arguments: coef1, param1, coef2, param2, \"<\" or \">\", c\n") );
      }
    } else if(name=="quadratic3") {
      if (args.count()==8) {
        double coef1=args.at(0)->value().toDouble();
        Parameter * x1Param=_param->parameter(args.at(1)->value().toString());
        if (!x1Param) {
          App::log(tr("error: parameter %1 not found\n").arg(args.at(1)->value().toString()) );
          return QVariant();
        }
        double coef2=args.at(2)->value().toDouble();
        Parameter * x2Param=_param->parameter(args.at(3)->value().toString());
        if (!x2Param) {
          App::log(tr("error: parameter %1 not found\n").arg(args.at(3)->value().toString()) );
          return QVariant();
        }
        double coef3=args.at(4)->value().toDouble();
        Parameter * x3Param=_param->parameter(args.at(5)->value().toString());
        if (!x3Param) {
          App::log(tr("error: parameter %1 not found\n").arg(args.at(5)->value().toString()) );
          return QVariant();
        }
        double c=args.at(7)->value().toDouble();
        if (args.at(6)->value().toString()==">") {
          _param->addCondition(new QuadraticCondition(coef1, x1Param, coef2, x2Param, coef3, x3Param, QuadraticCondition::GreaterThan, c));
        } else {
          _param->addCondition(new QuadraticCondition(coef1, x1Param, coef2, x2Param, coef3, x3Param, QuadraticCondition::LessThan, c));
        }
      } else {
        App::log(tr("error: function 'quadratic3' requires 8 arguments: coef1, param1, coef2, param2, coef3, param3, \"<\" or \">\", c\n") );
      }
    }
    break;
  case 'p':
    if (name=="parameter") {
      if (args.count()==6) {
        ParameterGrid::Scale s;
        QString scaleType=args.at(4)->value().toString().toLower();
        if(scaleType=="log") {
          s=ParameterGrid::Log;
        } else if(scaleType=="linear"){
          s=ParameterGrid::Linear;
        } else {
          App::log(tr("error: bad parameter scale type %1, \"log\" or \"linear\" expected.\n").arg(scaleType) );
          return QVariant();
        }
        if(_param->addParameter(args.at(0)->value().toString(), args.at(1)->value().toString(),
                                args.at(2)->value().toDouble(), args.at(3)->value().toDouble(),
                                s, args.at(5)->value().toDouble())) {
          _param->setVariableParameters();
        } else {
          App::log(tr("error: cannot add parameter %1\n").arg(args.at(0)->value().toString()) );
        }
      } else {
        App::log(tr("error: function 'parameter' requires 6 arguments: name, unit, min, max, scale, prec\n") );
      }
    }
    break;
  default:
    break;
  }
  return QVariant();
}

} // namespace DinverCore
