/** \defgroup Computation Mathematical computations */

/** @addtogroup Computation
    @{
*/

/** @file CMathExpression.cpp
    \brief This file contains the implementations of the different
    functions that will be used in mathematical calculation.

    It contains the functions that validate an expression, build the
    tree in a CMathExpression and derives an expression.

    The algorithm used it the following.  The function recursively
    calls itself the following way to build the three.

    - Beginning from the right, it looks for an + or - operator.  If
    it finds one, it checks that it is not inside parenthesis and
    returns the sum or difference of the left part of the expression
    parsed by the function and the right part of the expression parsed
    by the function.  Si the unary operator - is found, 0 minus the 
    rest of the expression is returned.

    - It does the same thing for the * and / operators, that are also
    ignored if found in parenthesis.

    - Next, The function looks for some functions like sqrt, sin, cos,
    tan, exp, ln or ^.  If this function is not in parenthesis, the
    function proceeds with the rest of the expression.

    - After that the function looks for an opening parenthesis.  If it
    finds one, it starts from the end looking for a closing parenthesis, 
    then it proceeds inside the parenthesis.

    - Then it looks for x, y or z, and finally for a number.
*/



#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>

#include "popassert.h"
#include "CVector3f.h"
#include "compute.h"
#include "CMathExpression.h"



/**
   \brief This function checks is an operator (+, -, *, /, ^) is
   in parenthesis.

   It counts the number of opening parenthesis '(' before the operator
   and the number of closing parenthesis ')'.  If the two are the same,
   the operator is not between parenthesis.
*/
inline bool In(const char* Formula, int min, int max)
{
  int Counter = 0;
  for (int i = min; i < max; ++i)
    {
      if (Formula[i] == '(')
        ++Counter;
      if (Formula[i] == ')')
        --Counter;
    }
  return (Counter != 0);
}




/**
   It expects a valid expression (formula).
*/
CValue* CMathExpression::BuildFormula(const char* Formula,
                                      int min,
                                      int max)
{
  POP_ASSERT(Formula != NULL, "Using a NULL pointer.");

   if (min < max)
    {
      if (Formula[min] == ' ')
        return BuildFormula(Formula, min+1, max);
      if (Formula[max-1] == ' ')
        return BuildFormula(Formula, min, max-1);
    }


  int i, j;

  // Search for + or -
  for (i = max-1; i > min-1; --i)
    if ((Formula[i] == '+') || (Formula[i] == '-'))
      if ( !(In(Formula, min, i)) )
        if (Formula[i] == '+')
          return(CValue::GetNewAddition(BuildFormula(Formula, min, i), 
                                        BuildFormula(Formula, i+1, max)));
        else
          if (i == min)
            {
              return(CValue::GetNewOpposite(BuildFormula(Formula, i+1, max)));
            }
          else
            return(CValue::GetNewSubstraction(BuildFormula(Formula, min, i), 
                                              BuildFormula(Formula, i+1, max)));

  // Search for * or /
  for (i = max-1; i > min; --i)
    if ((Formula[i] == '*') || (Formula[i] == '/'))
      if ( !(In(Formula, min, i)) )
        if (Formula[i] == '*')
          return(CValue::GetNewProduct(BuildFormula(Formula, min, i), 
                                       BuildFormula(Formula, i+1, max)));
        else
          return(CValue::GetNewDivision(BuildFormula(Formula, min, i),
                                        BuildFormula(Formula, i+1, max)));

  // Search for sqrt, sin, cos, tan, exp or ^
  for (i = min; i < max; ++i)
    if ( (Formula[i] == 's') || (Formula[i] == 'c') ||
         (Formula[i] == 't') || (Formula[i] == 'e') ||
         (Formula[i] == 'l') || (Formula[i] == '^') )
      if ( !(In(Formula, min, i)) )
        switch (Formula[i])
          {
          case('s'):
            if ((Formula[i+1] == 'q') &&
                (Formula[i+2] == 'r') &&
                (Formula[i+3] == 't'))
              return(CValue::GetNewSqrt(BuildFormula(Formula, i+4, max)));

            if ((Formula[i+1] == 'i') &&
                (Formula[i+2] == 'n'))
              return(CValue::GetNewSin(BuildFormula(Formula, i+3, max)));

            break;

          case('c'):
            if ((Formula[i+1] == 'o') &&
                (Formula[i+2] == 's'))
              return(CValue::GetNewCos(BuildFormula(Formula, i+3, max)));
            break;

          case('t'):
            if ((Formula[i+1] == 'a') &&
                (Formula[i+2] == 'n'))
              return(CValue::GetNewTan(BuildFormula(Formula, i+3, max)));
            break;

          case('e'):
            if ((Formula[i+1] == 'x') &&
                (Formula[i+2] == 'p'))
              return(CValue::GetNewExp(BuildFormula(Formula, i+3, max)));
            break;

          case('l'):
            if (Formula[i+1] == 'n')
              return(CValue::GetNewLn(BuildFormula(Formula, i+2, max)));
            break;

          case('^') :
            return(CValue::GetNewPow(BuildFormula(Formula, min, i),
                                     BuildFormula(Formula, i+1, max)));
            break;
          }

  // Search for an opening parenthesis '('
  for (i = min; i < max; ++i)
    if (Formula[i] == '(')
      for (j = max; j > i; --j)
        if (Formula[j-1] == ')')
          return(BuildFormula(Formula, i+1, j));

  // Search for x, y or z
  for (i = min; i < max; ++i)
    if ((Formula[i] == 'x') || (Formula[i] == 'y') || (Formula[i] == 'z'))
      switch (Formula[i])
        {
        case('x'): return(new CVariable(CValue::TYPE_X)); break;
        case('y'): return(new CVariable(CValue::TYPE_Y)); break;
        case('z'): return(new CVariable(CValue::TYPE_Z)); break;
        }

  // Search for a number
  for (i = min; i < max; ++i)
    if ((Formula[i] <= '9') && (Formula[i] >= '0'))
      {
        j = i;
        while ( ( ( (Formula[j] <= '9') &&
                    (Formula[j] >= '0') ) ||
                  (Formula[j] == '.') )&&
                (j < max))
          {
            ++j;
          }
        char* buffer = new char[j-i+1];
        int k;
        for (k = i; k < j; ++k)
          buffer[k-i] = Formula[k];
        buffer[k-i] = '\0';
        float Value = atof(buffer);
        delete []buffer;
        return(new CNumber(Value));
      }

  // Search for spaces
  if (min < max && Formula[min] == ' ')
    return(BuildFormula(Formula, min+1, max));

  // If nothing is found
  POP_ILLEGAL("Nothing has been found in formula construction.");
  return 0;
}


/**
   It uses BuildFormula.
*/
CMathExpression::CMathExpression(const char * Formula)
  : m_pFormula(0), m_pFunction(0)
{
  if (Formula)
    {
      int i;
      std::string strFormula(Formula);

      for (i = 0; i < (int) strFormula.size(); ++i)
        {
          if (strFormula[i] == ' ' || strFormula[i] == '\t')
            {
              strFormula.erase(i, 1);
              --i;
            }
        }

      for (i = strFormula.size() - 1; i >= 0 && strFormula[i] != '='; --i);
      m_pFormula = 
        CValue::GetNewSubstraction(BuildFormula(strFormula.c_str(), 0, i),
                                   BuildFormula(strFormula.c_str(), i+1,
                                                strFormula.size())
                                   );
      Compile();
    }
  else
    {
      m_pFormula = 0;
      m_pFunction = 0;
    }
}


/**
   It accepts two references to ints where are stored the values
   that caused the error, in case there is one.
*/
bool CMathExpression::Validate(const std::string& strFormula,
                               int *value,
                               int min,
                               int max)
{
  if (value)
    {
      value[0] = min;
      value[1] = max;
    }

  int i, j;

  if (min < 0 && max < 0)
    {
      for (i = 0; i < (int) strFormula.size() && strFormula[i] != '='; ++i);
      if (i == (int) strFormula.size())
        {
          return false;
        }
      else
        return (Validate(strFormula, value, 0, i) &&
                Validate(strFormula, value, i+1, strFormula.size()));
    }

  // Take off spaces
  if (min < max)
    {
      if (strFormula[min] == ' ')
        return Validate(strFormula, value, min+1, max);
      if (strFormula[max-1] == ' ')
        return Validate(strFormula, value, min, max-1);
    }

  // Search for + or -
  for (i = max-1; i > min-1; --i)
    if ((strFormula[i] == '+') || (strFormula[i] == '-'))
      if ( !(In(strFormula.c_str(), min, i)) )
        if (strFormula[i] == '+')
          return (Validate(strFormula, value, min, i) &&
                  Validate(strFormula, value, i+1, max));
        else
          if (i == min)
            {
              return(Validate(strFormula, value, i+1, max));
            }
          else 
            return (Validate(strFormula, value, min, i) &&
                    Validate(strFormula, value, i+1, max));

  // Search for * or /
  for (i = max-1; i > min; --i)
    if ((strFormula[i] == '*') || (strFormula[i] == '/'))
      if ( !(In(strFormula.c_str(), min, i)) )
        if (strFormula[i] == '*')
          return (Validate(strFormula, value, min, i) &&
                  Validate(strFormula, value, i+1, max));
        else
          return (Validate(strFormula, value, min, i) &&
                  Validate(strFormula, value, i+1, max));

  // Search for sqrt, sin, cos, tan, exp or ^
  for (i = min; i < max; ++i)
    if ( (strFormula[i] == 's') || (strFormula[i] == 'c') ||
         (strFormula[i] == 't') || (strFormula[i] == 'e') ||
         (strFormula[i] == 'l') || (strFormula[i] == '^') )
      if ( !(In(strFormula.c_str(), min, i)) )
        switch (strFormula[i])
          {
          case('s'):
            if ((strFormula[i+1] == 'q') &&
                (strFormula[i+2] == 'r') &&
                (strFormula[i+3] == 't'))
              return(Validate(strFormula, value, i+4, max));

            if ((strFormula[i+1] == 'i') &&
                (strFormula[i+2] == 'n'))
              return(Validate(strFormula, value, i+3, max));

            break;

          case('c'):
            if ((strFormula[i+1] == 'o') &&
                (strFormula[i+2] == 's'))
              return(Validate(strFormula, value, i+3, max));
            break;

          case('t'):
            if ((strFormula[i+1] == 'a') &&
                (strFormula[i+2] == 'n'))
              return(Validate(strFormula, value, i+3, max));
            break;

          case('e'):
            if ((strFormula[i+1] == 'x') &&
                (strFormula[i+2] == 'p'))
              return(Validate(strFormula, value, i+3, max));
            break;

          case('l'):
            if (strFormula[i+1] == 'n')
              return(Validate(strFormula, value, i+2, max));
            break;

          case('^') :
            return (Validate(strFormula, value, min, i) &&
                    Validate(strFormula, value, i+1, max));
            break;
          }

  // Search for an opening parenthesis '('
  if (strFormula[min] == '(' && strFormula[max-1] == ')')
    return(Validate(strFormula, value, min+1, max-1));

  // Search for x, y or z
  if ((strFormula[min] == 'x') || (strFormula[min] == 'y') || (strFormula[min] == 'z'))
    return (min == max - 1);

  // Search for a number
  int dotfound = 0;
  int digitfound = 0;
  if ( ( (strFormula[min] <= '9') && (strFormula[min] >= '0') ) ||
       (strFormula[min] == '.') )
    {
      j = min;
      while ( ( ( (strFormula[j] <= '9') &&
                  (strFormula[j] >= '0') ) ||
                (strFormula[j] == '.') )&&
              (j < max))
        {
          if (strFormula[j] == '.')
            ++dotfound;
          if (strFormula[j] <= '9' && strFormula[j] >= '0')
            ++digitfound;
          if (dotfound > 1)
            {
              return false;
            }
          ++j;
        }

      return (j == max && digitfound > 0);
    }

  return false;
}


/**
  It "compiles" the mathematical expression by filling
  the buffer with the appropriate Intel instruction byte codes.
*/
void CMathExpression::Compile(std::vector<char>& v) const
{
  v = std::vector<char>();

  // Function initialization
  v.push_back('\x55'); // pushl   %ebp

  v.push_back('\x89');
  v.push_back('\xe5'); // movl    %esp,%ebp

  v.push_back('\x8b');
  v.push_back('\x45');
  v.push_back('\x08'); // movl    0x8(%ebp),%eax

  v.push_back('\x9b');
  v.push_back('\xdb');
  v.push_back('\xe3'); // finit


  // Function compilation
  int i = 0;
  m_pFormula->Compile(v, i);


  // Function clean-up
  v.push_back('\x89');
  v.push_back('\xec'); // movl    %ebp,%esp

  v.push_back('\x5d'); // popl    %ebp

  v.push_back('\xc3'); // ret
}


/** @} */
