/*
 *  Copyright (c) 2011 Cyrille Berger <cberger@cberger.net>
 *
 * 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, or (at your option) any later version of the License.
 *
 * This library 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "GenerationVisitor.h"

#include <complex>

#include <Cauchy/String.h>
#include <Cauchy/Variable.h>

#include "ExpressionResult.h"

using namespace MathMLBackend;

#define TO_STR(x) \
  (x).scast<MathMLBackend::ExpressionResult>()->result()

struct GenerationVisitor::Private
{
  Cauchy::String result;
};

GenerationVisitor::GenerationVisitor(const Cauchy::Options& /*options*/) : d(new Private)
{
}

GenerationVisitor::~GenerationVisitor()
{
}

Cauchy::String GenerationVisitor::result() const
{
  return "<math>" + d->result + "</math>";
}

void GenerationVisitor::loadFunctionsDeclarations(Cauchy::DeclarationsRegistry* /*registry*/)
{
  // We do not care about that in the MathML generator
}

void GenerationVisitor::startMainFunction()
{
  // We do not care about that in the MathML generator
}

void GenerationVisitor::startFunction(const Cauchy::AST::FunctionDefinition* /*function*/ )
{
  // We do not care about that in the MathML generator
}

void GenerationVisitor::declareGlobal(Cauchy::Variable* /*global*/)
{
  // We do not care about that in the MathML generator
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateNumber(const Cauchy::String& arg1, Cauchy::Type::DataType _type, const Cauchy::AST::Annotation& _annotation)
{
  return new ExpressionResult("<mn>" + arg1 + "</mn>");
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateComplexNumber(const Cauchy::String& _real, const Cauchy::String& _imag, Cauchy::Type::DataType _type, const Cauchy::AST::Annotation& _annotation)
{
  return new ExpressionResult("<mn>" + _real + "</mn><mo>+</mo><mn>"
                              + _imag + "</mn><mo>&InvisibleTimes;</mo><mi>i</mi>");
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateBoolean(bool arg1, const Cauchy::AST::Annotation& /*_annotation*/)
{
  return new ExpressionResult("<mn>" + Cauchy::String::number( cauchy_int32(arg1) ) + "</mn>");
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateMatrixExpression(const Cauchy::Type* /*_type*/, int size1, int size2, const std::list< Cauchy::AST::ExpressionResultSP >& results, const Cauchy::AST::Annotation&)
{
  Cauchy::String str = "<mfenced open='[' close =']'><mtable>";
  
  std::list< Cauchy::AST::ExpressionResultSP>::const_iterator results_it = results.begin();
  
  for(int i = 0; i < size1; ++i)
  {
    str += "<mtr>";
    for(int j = 0; j < size2; ++j)
    {
      str += "<mtd>" + TO_STR(*results_it) + "</mtd>";
      ++results_it;
    }
    str += "</mtr>";
  }
  
  return new ExpressionResult(str + "</mtable></mfenced>");
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateVariable(Cauchy::Variable* var, Cauchy::AST::ExpressionResultSP idx1, Cauchy::AST::ExpressionResultSP idx2, const Cauchy::AST::Annotation& )
{
  Cauchy::String str = "<mi>" + var->name() + "</mi>";
  if(not idx1.isNull())
  {
    str = "<msub>" + str + "<mrow>" + TO_STR(idx1);
    
    if(not idx2.isNull())
    {
      str += "<mo>,</mo>" + TO_STR(idx2);
    }
    
    str += "</mrow></msub>";
  }
  return new ExpressionResult(str);
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateRangeExpression(Cauchy::AST::ExpressionResultSP startExpr, Cauchy::AST::ExpressionResultSP endExpr, Cauchy::AST::ExpressionResultSP stepExpr, const Cauchy::AST::Annotation& )
{
  if(stepExpr.isNull())
  {
    return new ExpressionResult(TO_STR(startExpr) + "<mo>:</mo>" + TO_STR(endExpr));
  } else {
    return new ExpressionResult(TO_STR(startExpr) + "<mo>:</mo>" + TO_STR(stepExpr) + "<mo>:</mo>" + TO_STR(endExpr));
  }
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateInfiniteRangeExpression()
{
  return new ExpressionResult("<mo>:</mo>");
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateAssignementExpression(Cauchy::Variable* variable, Cauchy::AST::ExpressionResultSP arg2, const Cauchy::AST::Annotation& /*_annotation*/)
{
  return new ExpressionResult("<mi>" + variable->name() + "</mi><mo>=</mo>" + TO_STR(arg2));
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateFunctionCall(const Cauchy::String& _function, const Cauchy::FunctionDeclaration* /*declaration*/, const Cauchy::Variable* /*_variable*/, const std::list<Cauchy::AST::ExpressionResultSP>& _arguments, const std::vector<Cauchy::AST::ExpressionResultSP>& /*_returns*/, const Cauchy::AST::Annotation& /*_annotation*/)
{
  Cauchy::String str = "<mi>" + _function + "</mi><mfenced>";
  
  for(std::list<Cauchy::AST::ExpressionResultSP>::const_iterator it = _arguments.begin();
      it != _arguments.end(); ++it)
  {
    str += "<mrow>" + TO_STR(*it) + "</mrow>";
  }
  
  return new ExpressionResult(str + "</mfenced>");
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateString(const Cauchy::String& arg1, const Cauchy::AST::Annotation&)
{
  return new ExpressionResult("<ms>" + arg1 + "</ms>");
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateFunctionHandle(const Cauchy::String& arg1, const Cauchy::AST::Annotation& /*arg2*/)
{
  return new ExpressionResult("<mo>@</mo><mi>" + arg1 + "</mi>");
}

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateGroupExpression(Cauchy::AST::ExpressionResultSP arg1, const Cauchy::AST::Annotation& /*_annotation*/)
{
  return new ExpressionResult("<mfenced><mrow>" + TO_STR(arg1) + "</mrow></mfenced>");
}

#define GENERATE_BINARY_EXPRESSION(_FNAME_, _OP_) \
    Cauchy::AST::ExpressionResultSP GenerationVisitor::_FNAME_(Cauchy::AST::ExpressionResultSP arg1, Cauchy::AST::ExpressionResultSP arg2, const Cauchy::AST::Annotation& ) \
    { \
      return new ExpressionResult( TO_STR(arg1) + "<mo>" _OP_ "</mo>" + TO_STR(arg2)); \
    }

GENERATE_BINARY_EXPRESSION(generateAndExpresion, "and")
GENERATE_BINARY_EXPRESSION(generateOrExpresion, "or")
GENERATE_BINARY_EXPRESSION(generateEqualExpresion, "=")
GENERATE_BINARY_EXPRESSION(generateDifferentExpresion, "&ne;")
GENERATE_BINARY_EXPRESSION(generateInferiorExpresion, "<")
GENERATE_BINARY_EXPRESSION(generateInferiorEqualExpresion, "&les;")
GENERATE_BINARY_EXPRESSION(generateSupperiorExpresion, ">")
GENERATE_BINARY_EXPRESSION(generateSupperiorEqualExpresion, "&ges;")
GENERATE_BINARY_EXPRESSION(generateAdditionExpresion, "+")
GENERATE_BINARY_EXPRESSION(generateSubtractionExpresion, "-")
GENERATE_BINARY_EXPRESSION(generateMultiplicationExpresion, "&times;")
GENERATE_BINARY_EXPRESSION(generateElementWiseMultiplicationExpresion, ".&times;")

#define GENERATE_TAGED_BINARY_EXPRESSION(_FNAME_, _OPTAG_, _FENCE_ARG1_, _PREFIX_ARG2_) \
    Cauchy::AST::ExpressionResultSP GenerationVisitor::_FNAME_(Cauchy::AST::ExpressionResultSP arg1, Cauchy::AST::ExpressionResultSP arg2, const Cauchy::AST::Annotation& ) \
    { \
      return new ExpressionResult( "<" _OPTAG_ "><" _FENCE_ARG1_ ">" + TO_STR(arg1) + "</" _FENCE_ARG1_ "><mrow>" _PREFIX_ARG2_ + TO_STR(arg2) + "</mrow></" _OPTAG_ + ">"); \
    }

GENERATE_TAGED_BINARY_EXPRESSION(generateDivisionExpresion, "mfrac", "mrow", "")

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateElementWiseDivisionExpresion(Cauchy::AST::ExpressionResultSP arg1, Cauchy::AST::ExpressionResultSP arg2, const Cauchy::AST::Annotation& _annotation)
{
  return generateDivisionExpresion(arg1, arg2, _annotation);
}

GENERATE_TAGED_BINARY_EXPRESSION(generatePowerExpresion, "mfrac", "mfenced", "")
GENERATE_TAGED_BINARY_EXPRESSION(generateElementWisePowerExpresion, "mfrac", "mfenced", "<mo>.</mo>")

#define GENERATE_UNARY_EXPRESSION(_FNAME_, _OP_) \
    Cauchy::AST::ExpressionResultSP GenerationVisitor::_FNAME_(Cauchy::AST::ExpressionResultSP arg1, const Cauchy::AST::Annotation& ) \
    { \
      return new ExpressionResult( "<mo>" _OP_ "</mo>" + TO_STR(arg1) ); \
    }

GENERATE_UNARY_EXPRESSION(generateTildExpression, "~")

Cauchy::AST::ExpressionResultSP GenerationVisitor::generateTransposeExpression(Cauchy::AST::ExpressionResultSP arg1, const Cauchy::AST::Annotation& /*_annotation*/)
{
  return new ExpressionResult( "<msup><mfenced>" + TO_STR(arg1) + "</mfenced> <mo>t</mo></msup>");
}
      
GENERATE_UNARY_EXPRESSION(generateMinusExpression, "-")
GENERATE_UNARY_EXPRESSION(generateNotExpression, "!")
      

void GenerationVisitor::generateExpression(Cauchy::AST::ExpressionResultSP arg1, const Cauchy::String& /*comment*/, const Cauchy::AST::Annotation& /*_annotation*/)
{
  d->result += "<mrow>" + TO_STR(arg1) + "</mrow>";
}

void GenerationVisitor::generateComment(const Cauchy::String& /*arg1*/, const Cauchy::AST::Annotation& /*_annotation*/)
{}

void GenerationVisitor::generatePrintStatement(Cauchy::AST::ExpressionResultSP arg1, const Cauchy::String& comment, const Cauchy::AST::Annotation& _annotation)
{
  generateExpression(arg1, comment, _annotation );
}

void GenerationVisitor::startWhileStatement(Cauchy::AST::ExpressionResultSP /*arg1*/, const Cauchy::String& /*comment*/, const Cauchy::AST::Annotation& )
{}

void GenerationVisitor::endWhileStatement(const Cauchy::AST::Annotation& /*arg1*/)
{}

void GenerationVisitor::generateIfElseStatement(Cauchy::AST::ExpressionResultSP /*arg1*/, Cauchy::AST::Statement* /*ifStatements*/, const std::vector< std::pair<Cauchy::AST::Expression*, Cauchy::AST::Statement*> >& /*_elseIfStatements*/, Cauchy::AST::Statement* /*elseStatement*/, const Cauchy::String& /*comment*/, const Cauchy::AST::Annotation& /*arg3*/)
{}

void GenerationVisitor::generateForStatement(Cauchy::Variable* /*variable*/, Cauchy::AST::ExpressionResultSP /*expr*/, Cauchy::AST::Statement* /*forStatemebts*/, Cauchy::String /*comment*/, const Cauchy::AST::Annotation& /*annotation*/)
{}

void GenerationVisitor::generateBreak(const Cauchy::AST::Annotation& /*arg3*/)
{}

void GenerationVisitor::generateReturnStatement(Cauchy::AST::FunctionDefinition* /*arg1*/, const Cauchy::String& /*arg2*/, const Cauchy::AST::Annotation& /*arg3*/)
{}
