#include <Common.h>
#include <System/SysAll.h>
#include <UI/UIAll.h>
#include "MathLib.h"
#include "EvalExpr.h"
#include "ConvStrToDbl.h"

/* defines. */
#define NESTMAX 40
#define PI      3.14159265358979323846

/* global variables */
static double angleunit = PI / 180.;

/* prototypes */
static int  addsub(char**, double*, int);
static int  muldiv(char**, double*, int);
static int  power(char**, double*, int);
static int  sign(char**, double*, int);
static int  afunc(char**, double*, int);
static int  paren(char**, double*, int);
static int  number(char**, double*);
static void chopspace(char**);
static int  compword(char*, char*);


void setdegree(void)
{
 angleunit = PI / 180.;
}


void setradian(void)
{
 angleunit = 1.;
}


static int addsub(char** exprs, double* x, int nest)
{
 double y;
 int error;

 nest++;
 if (nest >= NESTMAX) return ERR_DEEP_NESTING;

 chopspace(exprs);
 error = muldiv(exprs, x, nest);
 if (error) return error;

 for(;;) {
   chopspace(exprs);
   if ((*exprs)[0] == '+') {
     (*exprs)++;
     error = muldiv(exprs, &y, nest);
     if (error) return error;
     *x = *x + y;
   } else if ((*exprs)[0] == '-') {
     (*exprs)++;
     error = muldiv(exprs, &y, nest);
     if (error) return error;
     *x = *x - y;
   } else
     break;
 }

 nest--;
 return 0;
}


static int muldiv(char** exprs, double* x, int nest)
{
 double y;
 int error;

 nest++;
 if (nest >= NESTMAX) return ERR_DEEP_NESTING;

 chopspace(exprs);
 error = sign(exprs, x, nest);
 if (error) return error;

 for(;;) {
   chopspace(exprs);
   if ((*exprs)[0] == '*') {
     (*exprs)++;
     error = sign(exprs, &y, nest);
     if (error) return error;
     *x = *x * y;
   } else if ((*exprs)[0] == '/') {
     (*exprs)++;
     error = sign(exprs, &y, nest);
     if (error) return error;
     *x = *x / y;
   } else
     break;
 }

 nest--;
 return 0;
}


static int sign(char** exprs, double* x, int nest)
{
 double y;
 int error;

 nest++;
 if (nest >= NESTMAX) return ERR_DEEP_NESTING;

 chopspace(exprs);
 if ((*exprs)[0] == '+') {
   (*exprs)++;
   error = power(exprs, &y, nest);
   if (error) return error;
   *x = +y;
 } else if ((*exprs)[0] == '-') {
   (*exprs)++;
   error = power(exprs, &y, nest);
   if (error) return error;
   *x = -y;
 } else {
   error = power(exprs, x, nest);
   if (error) return error;
 }

 nest--;
 return 0;
}


static int power(char** exprs, double* x, int nest)
{
 double y;
 int error;

 nest++;
 if (nest >= NESTMAX) return ERR_DEEP_NESTING;

 chopspace(exprs);
 error = afunc(exprs, x, nest);
 if (error) return error;

 chopspace(exprs);
 if ((*exprs)[0] == '^') {
   (*exprs)++;
   error = power(exprs, &y, nest);
   if (error) return error;
   *x = pow(*x, y);
 }

 nest--;
 return 0;
}


static int afunc(char** exprs, double* x, int nest)
{
 int error;

 nest++;
 if (nest >= NESTMAX) return ERR_DEEP_NESTING;

 chopspace(exprs);
 if (compword(*exprs, "sqrt")) {
   (*exprs) += 4;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = sqrt(*x);
 }
 else if (compword(*exprs, "log")) {
   (*exprs) += 3;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = log10(*x);
 }
 else if (compword(*exprs, "ln")) {
   (*exprs) += 2;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = log(*x);
 }
 else if (compword(*exprs, "exp")) {
   (*exprs) += 3;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = exp(*x);
 }
 else if (compword(*exprs, "asinh")) {
   (*exprs) += 5;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = asinh(*x);
 }
 else if (compword(*exprs, "acosh")) {
   (*exprs) += 5;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = acosh(*x);
 }
 else if (compword(*exprs, "atanh")) {
   (*exprs) += 5;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = atanh(*x);
 }
 else if (compword(*exprs, "sinh")) {
   (*exprs) += 4;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = sinh(*x);
 }
 else if (compword(*exprs, "cosh")) {
   (*exprs) += 4;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = cosh(*x);
 }
 else if (compword(*exprs, "tanh")) {
   (*exprs) += 4;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = tanh(*x);
 }
 else if (compword(*exprs, "asin")) {
   (*exprs) += 4;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = asin(*x) / angleunit;
 }
 else if (compword(*exprs, "acos")) {
   (*exprs) += 4;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = acos(*x) / angleunit;
 }
 else if (compword(*exprs, "atan")) {
   (*exprs) += 4;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = atan(*x) / angleunit;
 }
 else if (compword(*exprs, "sin")) {
   (*exprs) += 3;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = sin(*x * angleunit);
 }
 else if (compword(*exprs, "cos")) {
   (*exprs) += 3;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = cos(*x * angleunit);
 }
 else if (compword(*exprs, "tan")) {
   (*exprs) += 3;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = tan(*x * angleunit);
 }

 else if (compword(*exprs, "ceil")) {
   (*exprs) += 4;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = ceil(*x);
 }
 else if (compword(*exprs, "floor")) {
   (*exprs) += 5;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = floor(*x);
 }
 else if (compword(*exprs, "abs")) {
   (*exprs) += 3;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = fabs(*x);
 }
 else if (compword(*exprs, "rint")) {
   (*exprs) += 4;
   error = afunc(exprs, x, nest);
   if (error) return error;
   *x = rint(*x);
 }

 else {
   error = paren(exprs, x, nest);
   if (error) return error;
 }

 nest--;
 return 0;
}


static int paren(char** exprs, double* x, int nest)
{
 int error;

 nest++;
 if (nest >= NESTMAX) return ERR_DEEP_NESTING;

 chopspace(exprs);
 if ((*exprs)[0] == '(') {
   (*exprs)++;
   error = addsub(exprs, x, nest);
   if (error) return error;
   chopspace(exprs);
   if ((*exprs)[0] != ')') return ERR_PAREN_NOT_MATCH;
   (*exprs)++;
 } else {
   error = number(exprs, x);
   if (error) return error;
 }

 nest--;
 return 0;
}


int number(char** exprs, double* x)
{
 int error;
 char* endptr;

 chopspace(exprs);
 if ((*exprs)[0] == NULL) return ERR_NULL_FOUND;
 error = ConvStrToDbl(x, *exprs, &endptr);
 if (*exprs == endptr) {  /* not a number */
   if (compword(*exprs, "pi") || compword(*exprs, "PI")) {
     /* constant PI */
     (*exprs) += 2;
     *x = PI;
     return 0;
   } else if (**exprs == '$') {
     /* refering variables */
     error = GetVarBySymbol(exprs, x);
     if (error != 0) return ERR_VAR_UNDEFINED;
     return 0;
   } else {
     *exprs = endptr;
     return ERR_NOT_A_NUMBER;
   }
 } else if (error != 0) {
   *exprs = endptr;
   return ERR_NOT_A_NUMBER;
 }

 *exprs = endptr;
 return 0;
}


int evaluate(char** exprs, double* result)
{
 int error;

 error = addsub(exprs, result, 0);
 if (error != 0) return error;

 chopspace(exprs);
 if (**exprs != '\0') return ERR_EXCESS_CHARACTERS;
 return 0;
}


static void chopspace(char** exprs)
{
 while (((*exprs)[0] == ' ') || ((*exprs)[0] == '\t'))
   (*exprs)++;
 return;
}

static int compword(char* str, char* word)
{
 int len = StrLen(word);

 if (StrNCompare(str, word, len) != 0) return false;
 if (*(str + len) == '_') return false;
 if ((*(str + len) >= 'a') && (*(str + len) <= 'z')) return false;
 if ((*(str + len) >= 'A') && (*(str + len) <= 'Z')) return false;

 return true;
}