/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2007-04-10
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "ExpressionAction.h"
#include "Global.h"
#include "CoreApplication.h"
#include "MemoryChecker.h"

namespace QGpCoreTools {

const QString ExpressionAction::xmlExpressionActionTag="ExpressionAction";
const QString ExpressionValue::xmlExpressionValueTag="ExpressionValue";
const QString ExpressionVariable::xmlExpressionVariableTag="ExpressionVariable";
const QString ExpressionBracket::xmlExpressionBracketTag="ExpressionBracket";
const QString ExpressionOperator::xmlExpressionOperatorTag="ExpressionOperator";
const QString ExpressionFunction::xmlExpressionFunctionTag="ExpressionFunction";

/*!
  Priority indexes:

  ExpressionValue      4
  ExpressionVariable   4
  ExpressionFunction   4
  ExpressionBracket    4
  ExpressionOperator   1 to 3

           *,/, IntMul, IntDiv  3
           +,-                  2
           else                 1
*/

void ExpressionValue::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  writeProperty(s,  "value", _val.toString());
}

void ExpressionBracket::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  writeProperty(s,  "open", _open);
}

void ExpressionBracket::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  if(_arg) _arg->xml_save(s, context);
}

void ExpressionOperator::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  switch (_type) {
  case Assign:
    writeProperty(s,  "type", "="); break;
  case SumAssign:
    writeProperty(s,  "type", "+="); break;
  case SubtractAssign:
    writeProperty(s,  "type", "-="); break;
  case MultAssign:
    writeProperty(s,  "type", "*="); break;
  case FloatDivAssign:
    writeProperty(s,  "type", "/="); break;
  case Sum:
    writeProperty(s,  "type", "+"); break;
  case Subtract:
    writeProperty(s,  "type", "-"); break;
  case Mult:
    writeProperty(s,  "type", "*"); break;
  case FloatDiv:
    writeProperty(s,  "type", "/"); break;
  case IntDiv:
    writeProperty(s,  "type", "DIV"); break;
  case IntMod:
    writeProperty(s,  "type", "MOD"); break;
  case Equal:
    writeProperty(s,  "type", "=="); break;
  case NotEqual:
    writeProperty(s,  "type", "!="); break;
  case LessThan:
    writeProperty(s,  "type", "<"); break;
  case LessOrEqual:
    writeProperty(s,  "type", "<="); break;
  case GreaterThan:
    writeProperty(s,  "type", ">"); break;
  case GreaterOrEqual:
    writeProperty(s,  "type", ">="); break;
  case Coma:
    writeProperty(s,  "type", ","); break;
  case OpenBracket:
    writeProperty(s,  "type", "("); break;
  case CloseBracket:
    writeProperty(s,  "type", ")"); break;
  case OpenSquareBracket:
    writeProperty(s,  "type", "["); break;
  case CloseSquareBracket:
    writeProperty(s,  "type", "]"); break;
  case Function:
    writeProperty(s,  "type", "function()"); break;
  case VariableArray:
    writeProperty(s,  "type", "array[]"); break;
  case EndAction:
    writeProperty(s,  "type", "END"); break;
  case NoOperator:
    writeProperty(s,  "type", "noOp"); break;
    break;
  }
}

void ExpressionOperator::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  if(_operand1) _operand1->xml_save(s, context);
  if(_operand2) _operand2->xml_save(s, context);
}

void ExpressionFunction::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  writeProperty(s,  "name", _name);
  writeProperty(s,  "open", _open);
}

void ExpressionFunction::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  for(QVector<ExpressionAction *>::const_iterator it=_args.begin(); it!=_args.end();++it) {
    (*it)->xml_save(s, context);
  }
}

void ExpressionVariable::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  writeProperty(s,  "varIndex", _varIndex);
}

void ExpressionVariable::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  if(_index) _index->xml_save(s, context);
}

/*!
  Calculate the sum of two variants: either the arithmetic sum or concatenation
*/
QVariant ExpressionAction::sum(const QVariant& op1, const QVariant& op2)
{
  TRACE;
  double val;
  bool ok;
  switch (op1.type()) {
  case QVariant::Double:
    switch (op2.type()) {
    case QVariant::Double:
    case QVariant::Int:
      return op1.toDouble()+op2.toDouble();
    case QVariant::String:
      val=op2.toString().toDouble(&ok);
      if(ok)
        return op1.toDouble()+val;
      else
        return op1.toString()+op2.toString();
    default:
      return op1;
    }
  case QVariant::Int:
    switch (op2.type()) {
    case QVariant::Double:
      return op1.toDouble()+op2.toDouble();
    case QVariant::Int:
      return op1.toInt()+op2.toInt();
    case QVariant::String:
      val=op2.toString().toDouble(&ok);
      if(ok)
        return op1.toDouble()+val;
      else
        return op1.toString()+op2.toString();
    default:
      return op1;
    }
  case QVariant::String:
    switch (op2.type()) {
    case QVariant::Double:
      val=op1.toString().toDouble(&ok);
      if(ok)
        return val+op2.toDouble();
      else
        return op1.toString()+op2.toString();
    case QVariant::Int:
      val=op1.toString().toDouble(&ok);
      if(ok)
        return val+op2.toInt();
      else
        return op1.toString()+op2.toString();
    case QVariant::String:
      return op1.toString()+op2.toString();
    default:
      return op1;
    }
  default:
    switch (op2.type()) {
    case QVariant::Double:
    case QVariant::Int:
    case QVariant::String:
      return op2;
    default:
      return QVariant();
    }
  }
}

/*!
  Calculate the subtraction of two variants
*/
QVariant ExpressionAction::subtract(const QVariant& op1, const QVariant& op2)
{
  TRACE;
  double val;
  bool ok;
  switch (op1.type()) {
  case QVariant::Double:
    switch (op2.type()) {
    case QVariant::Double:
    case QVariant::Int:
      return op1.toDouble()-op2.toDouble();
    case QVariant::String:
      val=op2.toString().toDouble(&ok);
      if(ok)
        return op1.toDouble()-val;
      else
        return op1;
    default:
      return op1;
    }
  case QVariant::Int:
    switch (op2.type()) {
    case QVariant::Double:
      return op1.toDouble()-op2.toDouble();
    case QVariant::Int:
      return op1.toInt()-op2.toInt();
    case QVariant::String:
      val=op2.toString().toDouble(&ok);
      if(ok)
        return op1.toDouble()-val;
      else
        return op1;
    default:
      return op1;
    }
  case QVariant::String:
    val=op1.toString().toDouble(&ok);
    if(ok) {
      double val2=op2.toString().toDouble(&ok);
      if(ok)
        return val-val2;
      else
        return val;
    } else {
      val=op2.toString().toDouble(&ok);
      if(ok)
        return -val;
      else
        return QVariant();
    }
  default:
    switch (op2.type()) {
    case QVariant::Double:
    case QVariant::Int:
      return -op2.toDouble();
    default:
      return QVariant();
    }
  }
}

/*!
  Calculate the multiplication of two variants
*/
QVariant ExpressionAction::mult(const QVariant& op1, const QVariant& op2)
{
  TRACE;
  double val;
  bool ok;
  switch (op1.type()) {
  case QVariant::Double:
    switch (op2.type()) {
    case QVariant::Double:
    case QVariant::Int:
      return op1.toDouble()*op2.toDouble();
    case QVariant::String:
      val=op2.toString().toDouble(&ok);
      if(ok)
        return op1.toDouble()*val;
      else
        return 0.0;
    default:
      return QVariant();
    }
  case QVariant::Int:
    switch (op2.type()) {
    case QVariant::Double:
      return op1.toDouble()*op2.toDouble();
    case QVariant::Int:
      return op1.toInt()*op2.toInt();
    case QVariant::String:
      val=op2.toString().toDouble(&ok);
      if(ok)
        return op1.toDouble()*val;
      else
        return 0.0;
    default:
      return QVariant();
    }
  case QVariant::String:
    val=op1.toString().toDouble(&ok);
    if(ok) {
      double val2=op2.toString().toDouble(&ok);
      if(ok)
        return val*val2;
      else
        return 0.0;
    } else {
      return 0.0;
    }
  default:
    return QVariant();
  }
}

/*!
  Calculate the division of two variants
*/
QVariant ExpressionAction::floatDiv(const QVariant& op1, const QVariant& op2)
{
  TRACE;
  double val;
  bool ok;
  switch (op1.type()) {
  case QVariant::Double:
    switch (op2.type()) {
    case QVariant::Double:
    case QVariant::Int:
      return op1.toDouble()/op2.toDouble();
    case QVariant::String:
      val=op2.toString().toDouble(&ok);
      if(ok)
        return op1.toDouble()/val;
      else
        return QVariant();
    default:
      return QVariant();
    }
  case QVariant::Int:
    switch (op2.type()) {
    case QVariant::Double:
      return op1.toDouble()/op2.toDouble();
    case QVariant::Int: {
        int v2=op2.toInt();
        if(v2!=0) {
          return op1.toInt()/v2;
        } else {
          App::log(tr("Division by zero\n") );
          return QVariant();
        }
      }
    case QVariant::String:
      val=op2.toString().toDouble(&ok);
      if(ok)
        return op1.toDouble()/val;
      else {
        App::log(tr("Division by zero\n") );
        return QVariant();
      }
    default:
      App::log(tr("Division by zero\n") );
      return QVariant();
    }
  case QVariant::String:
    val=op1.toString().toDouble(&ok);
    if(ok) {
      double val2=op2.toString().toDouble(&ok);
      if(ok)
        return val/val2;
      else {
        App::log(tr("Division by zero\n") );
        return QVariant();
      }
    } else {
      return 0.0;
    }
  default:
    return QVariant();
  }
}

/*!
  Compares two variants
*/
int ExpressionAction::compare(const QVariant& op1, const QVariant& op2, bool& ok)
{
  TRACE;
  ok=true;
  switch (op1.type()) {
  case QVariant::Double:
  case QVariant::Int:
    switch (op2.type()) {
    case QVariant::Double:
    case QVariant::Int: {
        double op1d=op1.toDouble();
        double op2d=op2.toDouble();
        if(op1d<op2d) return -1;
        else if(op1d>op2d) return 1;
        else return 0;
      }
    case QVariant::String: {
        QString op1s=op1.toString();
        QString op2s=op2.toString();
        if(op1s<op2s) return -1;
        else if(op1s>op2s) return 1;
        else return 0;
      }
    default:
      ok=false;
      return 0;
    }
  case QVariant::String:
    switch (op2.type()) {
    case QVariant::String:
    case QVariant::Double:
    case QVariant::Int: {
        QString op1s=op1.toString();
        QString op2s=op2.toString();
        if(op1s<op2s) return -1;
        else if(op1s>op2s) return 1;
        else return 0;
      }
    default:
      ok=false;
      return 0;
    }
  default:
    ok=false;
    return 0;
  }
}

QVariant ExpressionOperator::value() const
{
  TRACE;
  if(!_operand1 || !_operand2) return QVariant();
  QVariant op1Val=_operand1->value();
  QVariant op2Val=_operand2->value();
  switch(_type) {
  case Assign:
    if(!_operand1->isReadOnly()) {
      _operand1->setValue(op2Val);
      return op2Val;
    }
    break;
  case SumAssign:
    if(!_operand1->isReadOnly()) {
      QVariant res=sum(op1Val, op2Val);
      _operand1->setValue(res);
      return res;
    }
    break;
  case SubtractAssign:
    if(!_operand1->isReadOnly()) {
      QVariant res=subtract(op1Val, op2Val);
      _operand1->setValue(res);
      return res;
    }
    break;
  case MultAssign:
    if(!_operand1->isReadOnly()) {
      QVariant res=mult(op1Val, op2Val);
      _operand1->setValue(res);
      return res;
    }
    break;
  case FloatDivAssign:
    if(!_operand1->isReadOnly()) {
      QVariant res=floatDiv(op1Val, op2Val);
      _operand1->setValue(res);
      return res;
    }
    break;
  case Sum:
    return sum(op1Val, op2Val);
  case Subtract:
    return subtract(op1Val, op2Val);
  case Mult:
    return mult(op1Val, op2Val);
  case FloatDiv:
    return floatDiv(op1Val, op2Val);
  case IntDiv: {
      int v2=op2Val.toInt();
      if(v2!=0) return op1Val.toInt()/v2;
    }
    break;
  case IntMod: {
      int v2=op2Val.toInt();
      if(v2!=0) return op1Val.toInt()%v2;
    }
    break;
  case Equal: {
      bool ok;
      bool res=compare(op1Val,op2Val, ok)==0;
      if(ok) return res; else return QVariant();
    }
  case NotEqual: {
      bool ok;
      bool res=compare(op1Val,op2Val, ok)!=0;
      if(ok) return res; else return QVariant();
    }
  case LessThan: {
      bool ok;
      bool res=compare(op1Val,op2Val, ok)<0;
      if(ok) return res; else return QVariant();
    }
  case LessOrEqual: {
      bool ok;
      bool res=compare(op1Val,op2Val, ok)<=0;
      if(ok) return res; else return QVariant();
    }
  case GreaterThan: {
      bool ok;
      bool res=compare(op1Val,op2Val, ok)>0;
      if(ok) return res; else return QVariant();
    }
  case GreaterOrEqual: {
      bool ok;
      bool res=compare(op1Val,op2Val, ok)>=0;
      if(ok) return res; else return QVariant();
    }
  default:
    break;
  }
  return QVariant();
}

/*!
  If all arguments are already set, the new argument is set as the second and the old second one
  is set as the argument of the new argument. This mechanism is designed for cases
  where an operator with a higher priority is encountered during parse.
*/
bool ExpressionOperator::addArgument(ExpressionAction * a)
{
  TRACE;
  if(!_operand1) {
    a->setParent(this);
    _operand1=a;
  } else if(!_operand2) {
    a->setParent(this);
    _operand2=a;
  } else {
    if(a->priority()<_operand2->priority()) {
      if(a->priority()==priority()) {
        parent()->replaceArgument(this, a);
        if(!a->addArgument(this)) return false;
      } else {
        if(a->addArgument(_operand2)) {
          a->setParent(this);
          _operand2=a;
        } else return false;
      }
    } else return false;
  }
  return true;
}

/*!
  If argument is already set, the new argument is set as the current and the old one
  is set as the argument of the new argument. This mechanism is designed for cases
  where an operator with a higher priority is encountered during parse.
*/
bool ExpressionFunction::addArgument(ExpressionAction * a)
{
  TRACE;
  if(!_currentArg || a->addArgument(_currentArg)) {
    a->setParent(this);
    _currentArg=a;
    return true;
  } else return false;
}

/*!
  If argument is already set, the new argument is set as the current and the old one
  is set as the argument of the new argument. This mechanism is designed for cases
  where an operator with a higher priority is encountered during parse.
*/
bool ExpressionBracket::addArgument(ExpressionAction * a)
{
  TRACE;
  if(!_arg || a->addArgument(_arg)) {
    a->setParent(this);
    _arg=a;
    return true;
  } else return false;
}

/*!
  If argument is already set, the new argument is set as the current and the old one
  is set as the argument of the new argument. This mechanism is designed for cases
  where an operator with a higher priority is encountered during parse.
*/
bool ExpressionVariable::addArgument(ExpressionAction * a)
{
  TRACE;
  if(!_index || a->addArgument(_index)) {
    a->setParent(this);
    _index=a;
    _open=true;
    return true;
  } else return false;
}

void ExpressionOperator::replaceArgument(ExpressionAction * oldA, ExpressionAction * newA)
{
  TRACE;
  newA->setParent(this);
  if(oldA==_operand1) {
    _operand1=newA;
  } else if(oldA==_operand2) {
    _operand2=newA;
  } else {
    ASSERT(_operand1==oldA || _operand2==oldA);
  }
}

void ExpressionFunction::replaceArgument(ExpressionAction * oldA, ExpressionAction * newA)
{
  TRACE;
  ASSERT(_currentArg==oldA);
  _currentArg=newA;
  newA->setParent(this);
}

void ExpressionBracket::replaceArgument(ExpressionAction * oldA, ExpressionAction * newA)
{
  TRACE;
  ASSERT(_arg==oldA);
  _arg=newA;
  newA->setParent(this);
}

void ExpressionVariable::replaceArgument(ExpressionAction * oldA, ExpressionAction * newA)
{
  TRACE;
  ASSERT(_index==oldA);
  _index=newA;
  newA->setParent(this);
}

ExpressionAction * ExpressionAction::superParent()
{
  TRACE;
  if(_parent) return _parent->superParent(); else return this;
}

bool ExpressionAction::addAction(ExpressionAction * a)
{
  TRACE;
  if(parent() && a->priority()<priority()) {
    return parent()->addAction(a);
  } else {
    return addArgument(a);
  }
}

ExpressionAction * ExpressionAction::close(ExpressionAction::Type t)
{
  TRACE;
  if(_parent) return _parent->close(t); else return 0;
}

ExpressionAction * ExpressionBracket::close(ExpressionAction::Type t)
{
  TRACE;
  if(t==CloseBracket && _open) {
    _open=false;
    return _parent;
  }
  else return ExpressionAction::close(t);
}

ExpressionAction * ExpressionFunction::close(ExpressionAction::Type t)
{
  TRACE;
  switch (t) {
  case CloseBracket:
    if(_open) {
      if(_currentArg) {
        _args << _currentArg;
        _currentArg=0;
      }
      _open=false;
      return _parent;
    }
    break;
  case Coma:
    ASSERT(_open);
    if(_currentArg) {
      _args << _currentArg;
      _currentArg=0;
    }
    return this;
  default:
    break;
  }
  return ExpressionAction::close(t);
}

ExpressionVariable::ExpressionVariable(QString name, ExpressionContext * context)
{
  TRACE;
  _context=context;
  _varIndex=_context->addVariable(name, new ExpressionVariableStorage);
  _index=0;
  _open=false;
}

bool ExpressionVariable::isReadOnly() const
{
  TRACE;
  return _context->variable(_varIndex)->isReadOnly();
}

QVariant ExpressionVariable::value() const
{
  TRACE;
  if(_index)
    return _context->variable(_varIndex)->value(_index->value().toString());
  else
    return _context->variable(_varIndex)->value(QString());
}

void ExpressionVariable::setValue(const QVariant& val)
{
  TRACE;
  if(_index)
    _context->variable(_varIndex)->setValue(_index->value().toString(), val);
  else
    _context->variable(_varIndex)->setValue(QString(), val);
}

ExpressionAction * ExpressionVariable::close(ExpressionAction::Type t)
{
  TRACE;
  if(t==CloseSquareBracket && _index && _open) {
    _open=false;
    return _parent;
  }
  else return ExpressionAction::close(t);
}

} // namespace QGpCoreTools
