/***************************************************************************
**
**  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 "ExpressionParser.h"
#include "CoreApplication.h"
#include "MemoryChecker.h"
#include "Global.h"

namespace QGpCoreTools {

/*!
  Parse a text code and return a list of ExpressionAction strutures which are able to effectively execute the expressions.
*/
ExpressionActions ExpressionParser::parse(QString code, ExpressionContext * context)
{
  TRACE;

  ExpressionActions rootActions(context);
  _row=0;
  _ptr=code.data();
  _beginRowPtr=_ptr;
  while(_ptr->unicode()!=0x0000) {
    ExpressionBracket * a=new ExpressionBracket;
    if(parse(a, context)) {
      rootActions << a;
      //a->xml_saveFile("/home/mwathele/equ.xml");
    } else {
      delete a;
      break;
    }
  }
  return rootActions;
}

bool ExpressionParser::parse(ExpressionAction * parent, ExpressionContext * context)
{
  TRACE;
  ArgumentType argType;
  ExpressionAction::Type opType;
  ExpressionAction * a=nullptr;
  bool doubleOperator=false;
  QString arg;

  while(true) {

    // doubleOperator is true when 2 operators can be consecutive: e.g. )+ or ]*
    if(!doubleOperator) {
      skipBlanks();
      const QChar * argBegin=_ptr;
      if(!skipArgument(argType)) return false;
      arg=QString(argBegin, _ptr-argBegin);
    } else {
      doubleOperator=false;
      argType=NoArgument;
    }

    skipBlanks();
    if(!skipOperator(opType)) return false;

    switch (argType) {
    case NoArgument:
      a=nullptr;
      break;
    case Numeric:
      a=new ExpressionValue(arg.toDouble());
      break;
    case String: // A string may be interrupted by comments, hence arg may contain more than just the string
      // Get the cleaned and recomposed string obtained during the last call to getArgument()
      a=new ExpressionValue(_lastString);
      break;
    case AlphaNumeric:
      if(opType==ExpressionAction::OpenSquareBracket) {
        a=nullptr;
        opType=ExpressionAction::VariableArray;
      } else if(opType==ExpressionAction::OpenBracket && context->isValidFunction(arg)) {
        a=nullptr;
        opType=ExpressionAction::Function;
      } else { // Define new variable if it does not exist yet
        a=new ExpressionVariable(arg, context);
      }
      break;
    }

    if(a) {
      if(!parent->addAction(a)) {
        App::log(tr("Not a valid argument, syntax error %1\n").arg(bufferLocation()) );
        return false;
      }
    }

    switch (opType) {
    case ExpressionAction::Function:
      a=new ExpressionFunction(arg, context);
      break;
    case ExpressionAction::OpenBracket:
      if(argType==NoArgument) a=new ExpressionBracket; else a=nullptr;
      break;
    case ExpressionAction::CloseBracket:
      parent=parent->close(ExpressionAction::CloseBracket);
      if(!parent) {
        App::log(tr("Unmatched bracket %1\n").arg(bufferLocation()) );
        return false;
      }
      a=nullptr;
      doubleOperator=true;
      break;
    case ExpressionAction::VariableArray:
      a=new ExpressionVariable(arg, context);
      break;
    case ExpressionAction::OpenSquareBracket:
      App::log(tr("Square brackets allowed only for arrays: %1\n").arg(bufferLocation()) );
      return false;
    case ExpressionAction::CloseSquareBracket:
      parent=parent->close(ExpressionAction::CloseSquareBracket);
      if(!parent) {
        App::log(tr("Unmatched square bracket %1\n").arg(bufferLocation()) );
        return false;
      }
      a=nullptr;
      doubleOperator=true;
      break;
    case ExpressionAction::Coma:
      parent=parent->close(ExpressionAction::Coma);
      if(!parent) {
        App::log(tr("No coma expected here %1\n").arg(bufferLocation()) );
        return false;
      }
      a=nullptr;
      break;
    case ExpressionAction::EndAction:
      return true;
    case ExpressionAction::NoOperator:
      return false;
    default:
      a=new ExpressionOperator(opType);
      break;
    }

    if(a) {
      if(!parent->addAction(a)) {
        App::log(tr("Not a valid operator, syntax error %1\n").arg(bufferLocation()) );
        return false;
      }
      parent=a;
    }
  }
}

bool ExpressionParser::skipArgument(ArgumentType& type)
{
  TRACE;
  enum ScientificPos {Mantisse, ExponentSign, Exponent};
  ScientificPos scientificPos=Mantisse;
  bool decimal=false;
  bool hasFrontSign=false;
  type=NoArgument;
  while(true) {
    switch (_ptr->unicode()) {
    case 0x000A:   // '\n'
      _beginRowPtr=_ptr+1;
      _row++;
#if __clang__
      [[clang::fallthrough]];
#endif
    case 0x0020:   // ' '
    case 0x0009:   // '\t'
    case 0x000D:   // '\r'
    case 0x0000:   // '\0'
    case 0x002A:   // '*'
    case 0x002F:   // '/'
    case 0x003D:   // '='
    case 0x0021:   // '!'
    case 0x003C:   // '<'
    case 0x003E:   // '>'
    case 0x003B:   // ';'
    case 0x002C:   // ','
    case 0x0028:   // '('
    case 0x005B:   // '['
    case 0x005D:   // ']'
    case 0x0029:   // ')'
      return true;
    case 0x004D:   // 'MID'
      if(_ptr[1].unicode()==0x0049 && _ptr[2].unicode()==0x0044 && _ptr[3].isSpace()) {
        return true;
      }
      break;
    case 0x0044:  // 'DIV'
      if(_ptr[1].unicode()==0x0049 && _ptr[2].unicode()==0x0056 && _ptr[3].isSpace()) {
        return true;
      }
      break;
    case 0x0022:   // '"'
      if(type==NoArgument) {
        _lastString="";
        StringSection section;
        while(_ptr->unicode()==0x0022) {
          _ptr++;
          section.set(_ptr,0);
          if(!skipString()) return false;
          section.setEnd(_ptr);
          section.appendTo(_lastString);
          _ptr++;
          skipBlanks();
        }
        type=String;
        return true;
      } else return badCharacterError();
    case 0x005F:   // '_'
      if(type==Numeric) {
        if(!hasFrontSign) type=AlphaNumeric; else return badCharacterError();
      }
      break;
    case 0x002B:   // '+'
    case 0x002D:   // '-'
      if(type==Numeric && scientificPos==ExponentSign) {
        scientificPos=Exponent;
      } else if(type==NoArgument) {
        hasFrontSign=true; // generate error if something else than Numeric
        type=Numeric;
      } else return true;
      break;
    case 0x0065:   // 'e'
    case 0x0045:   // 'E'
      if(type==Numeric && scientificPos==Mantisse) {
        scientificPos=ExponentSign;
      } else if(!hasFrontSign) type=AlphaNumeric;
      else return badCharacterError();
      break;
    case 0x002E:   // '.'
      if(type==Numeric) {
        if(!decimal) {
          decimal=true;
        } else {
          return badCharacterError();
        }
      } else {
        type=AlphaNumeric; // e.g. variable name like: toto.parent
                           //      Currently not interpreted like data member but just special variable name
                           //      This must be revised to use QJSEngine instead
      }
      break;
    default:
      if((_ptr->unicode()>=0x0041 && _ptr->unicode()<=0x005A) ||
          (_ptr->unicode()>=0x0061 && _ptr->unicode()<=0x007A)) {
        switch (type) {
        case Numeric:
          if(!hasFrontSign) type=AlphaNumeric; else return badCharacterError();
          break;
        case NoArgument:
          type=AlphaNumeric;
          break;
        default:
          break;
        }
      } else if(_ptr->unicode()>=0x0030 && _ptr->unicode()<=0x0039) {
        if(type==NoArgument) type=Numeric;
      } else return badCharacterError();
      break;
    }
    _ptr++;
  }
}

bool ExpressionParser::skipOperator(ExpressionAction::Type& type)
{
  TRACE;
  switch (_ptr->unicode()) {
  case 0x0000:   // '\0'
    type=ExpressionAction::EndAction;
    return true;
  case 0x003B:   // ';'
    type=ExpressionAction::EndAction;
    break;
  case 0x002A:   // '*'
    if(_ptr[1].unicode()==0x003D) {
      _ptr++;
      type=ExpressionAction::MultAssign;
    } else type=ExpressionAction::Mult;
    break;
  case 0x002F:   // '/'
    if(_ptr[1].unicode()==0x003D) {
      _ptr++;
      type=ExpressionAction::FloatDivAssign;
    } else type=ExpressionAction::FloatDiv;
    break;
  case 0x003D:   // '='
    if(_ptr[1].unicode()==0x003D) {
      _ptr++;
      type=ExpressionAction::Equal;
    } else type=ExpressionAction::Assign;
    break;
  case 0x0021:   // '!'
    if(_ptr[1].unicode()==0x003D) {
      _ptr++;
      type=ExpressionAction::NotEqual;
    } else {
      type=ExpressionAction::NoOperator;
      return badCharacterError();
    }
    break;
  case 0x003C:   // '<'
    if(_ptr[1].unicode()==0x003D) {
      _ptr++;
      type=ExpressionAction::LessOrEqual;
    } else type=ExpressionAction::LessThan;
    break;
  case 0x003E:   // '>'
    if(_ptr[1].unicode()==0x003D) {
      _ptr++;
      type=ExpressionAction::GreaterOrEqual;
    } else type=ExpressionAction::GreaterThan;
    break;
  case 0x002C:   // ','
    type=ExpressionAction::Coma;
    break;
  case 0x0028:   // '('
    type=ExpressionAction::OpenBracket;
    break;
  case 0x0029:   // ')'
    type=ExpressionAction::CloseBracket;
    break;
  case 0x005B:   // '['
    type=ExpressionAction::OpenSquareBracket;
    break;
  case 0x005D:   // ']'
    type=ExpressionAction::CloseSquareBracket;
    break;
  case 0x002B:   // '+'
    if(_ptr[1].unicode()==0x003D) {
      _ptr++;
      type=ExpressionAction::SumAssign;
    } else type=ExpressionAction::Sum;
    break;
  case 0x002D:   // '-'
    if(_ptr[1].unicode()==0x003D) {
      _ptr++;
      type=ExpressionAction::SubtractAssign;
    } else type=ExpressionAction::Subtract;
    break;
  case 0x004D:   // "MOD"
    if(_ptr[1].unicode()==0x004F && _ptr[2].unicode()==0x0044 && _ptr[3].isSpace()) {
      _ptr+=2;
      type=ExpressionAction::IntMod;
    } else {
      type=ExpressionAction::NoOperator;
      return badCharacterError();
    }
    break;
  case 0x0044:   // "DIV"
    if(_ptr[1].unicode()==0x0049 && _ptr[2].unicode()==0x0056 && _ptr[3].isSpace()) {
      _ptr+=2;
      type=ExpressionAction::IntDiv;
    } else {
      type=ExpressionAction::NoOperator;
      return badCharacterError();
    }
    break;
  default:
    type=ExpressionAction::NoOperator;
    return badCharacterError();
  }
  _ptr++;
  return true;
}

bool ExpressionParser::badCharacterError()
{
  TRACE;
  App::log(tr("Unrecognized character %1\n").arg(bufferLocation()) );
  return false;
}

QString ExpressionParser::bufferLocation()
{
  TRACE;
  return tr("at line %1 and column %2").arg(_row+1).arg(_ptr-_beginRowPtr+1);
}

bool ExpressionParser::skipString()
{
  TRACE;
  while(true) {
    switch (_ptr->unicode()) {
    case 0x0000:
      App::log(tr("Unmatched \" before reaching the end of buffer\n"));
      return false;
    case 0x005C:   // '\\'
      _ptr++;
      break;
    case 0x000A:   // '\n'
      _beginRowPtr=_ptr+1;
      _row++;
      break;
    case 0x0022:   // '"'
      return true;
    }
    _ptr++;
  }
}

void ExpressionParser::skipBlanks( )
{
  TRACE;
  while(true) {
    switch (_ptr->unicode()) {
    case 0x0000:   // '\0'
      return;
    case 0x000A:   // '\n'
      _beginRowPtr=_ptr+1;
      _row++;
      break;
    case 0x0020:   // ' '
      break;
    case 0x0009:   // '\t'
      break;
    case 0x000D:   // '\r'
      break;
    case 0x002F:   // '/'
      switch ((_ptr+1)->unicode()) {
      case 0x002F: // line comment
        _ptr+=2;
        skipLineComment();
        if(_ptr->unicode()==0x0000) return;
        break;
      case 0x002A: // multi line comment
        _ptr+=2;
        skipMultiLineComment();
        if(_ptr->unicode()==0x0000) return;
        break;
      default:
        return; // else operator '/'
      }
      break;
    default:
      return ;
    }
    _ptr++;
  }
}

/*!
  Skip a C++ like comment over one lines
*/
void ExpressionParser::skipLineComment( )
{
  TRACE;
  while(true) {
    switch (_ptr->unicode()) {
    case 0x000A:   // '\n'
      _beginRowPtr=_ptr+1;
      _row++;
      return;
    case 0x0000:
      return;
    default:
      break;
    }
    _ptr++;
  }
}

/*!
  Skip a C++ like comment over several lines
*/
void ExpressionParser::skipMultiLineComment( )
{
  TRACE;
  while(true) {
    switch (_ptr->unicode()) {
    case 0x000A:   // '\n'
      _beginRowPtr=_ptr+1;
      _row++;
      break;
    case 0x0000:
      return;
    case 0x002A:
      if((_ptr+1)->unicode()==0x002F) {
        _ptr++;
        return;
      }
      break;
    default:
      break;
    }
    _ptr++;
  }
}

} // namespace QGpCoreTools
