#include "oslib/types.h"

//#include <iostream.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <stddef.h>
#include <miracl.h>

#include "Eval.h"

#define REPORT                Report
#define REPORTVAR             ReportVar
void    Report                (char * szMessage);
void    ReportVar             (char * szFormat, int nVariable);

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

static bool                     gboError;
static miracl                   *gpsMip;
static char                     gszResult[1024*10];
static char                     gszError[128];

static int                      gnPrecision = 32;
static bool                     gboRadix = ON;

flash Eval (char * szEval, int nLength);
flash GetVariableValue (char * szEval, int nLength);
void CheckMIRACLErrors (void);
void MIRACLErrorString (void);

typedef enum _BINARY
{
  BINARY_INVALID = -1,

  BINARY_MULTIPLY,
  BINARY_DIVIDE,
  BINARY_REMAINDER,
  BINARY_ADD,
  BINARY_SUB,
  BINARY_LESSEQUAL,
  BINARY_GREATEREQUAL,
  BINARY_LESS,
  BINARY_GREATER,
  BINARY_EQUAL,
  BINARY_NOTEQUAL,
  BINARY_LAND,
  BINARY_LOR,
  BINARY_POWER,

  BINARY_NUM
} BINARY;

static char const aszOperator[BINARY_NUM][3] =
{
  "*",
  "/",
  "%",
  "+",
  "-",
  "<=",
  ">=",
  "<",
  ">",
  "==",
  "!=",
  "&&",
  "||",
  "^"
};

typedef enum _UNARY
{
  UNARY_INVALID = -1,

  UNARY_NOT,
  UNARY_LN,
  UNARY_EXP,
  UNARY_ACOSH,
  UNARY_ASINH,
  UNARY_ATANH,
  UNARY_ACOS,
  UNARY_ASIN,
  UNARY_ATAN,
  UNARY_COSH,
  UNARY_SINH,
  UNARY_TANH,
  UNARY_COS,
  UNARY_SIN,
  UNARY_TAN,

  UNARY_NUM
} UNARY;

static char const aszUnary[UNARY_NUM][6] =
{
  "!",
  "ln",
  "exp",
  "acosh",
  "asinh",
  "atanh",
  "acos",
  "asin",
  "atan",
  "cosh",
  "sinh",
  "tanh",
  "cos",
  "sin",
  "tan"
};

//////////////////////////////////////////////////////////////////
// Evaluate an equation and return the result
char * Evaluate (char * szEval, bool * pboError)
{
  int          nLength;
  char         szClean[1024];
  int          nPos;
  int          nTransPos;
  flash        mfResult;

  // Initialse MIRACL
  gpsMip = mirsys (gnPrecision, 10);
  gpsMip->RPOINT = gboRadix;
  gpsMip->ERCON = TRUE;

  strcpy (gszError, "General error");

  nLength = (int)strlen (szEval);

  // Remove any spaces from the expression
  nTransPos = 0;
  for (nPos = 0; ((nPos < nLength)
    && (nTransPos < (int)(sizeof(szClean) - 1))); nPos++)
  {
    if (szEval[nPos] != ' ')
    {
      szClean[nTransPos] = szEval[nPos];
      nTransPos++;
    }
  }

  if (nTransPos < (int)(sizeof(szClean) - 1))
  {
    szClean[nTransPos] = 0;
    gboError = FALSE;
    mfResult = Eval (szClean, nTransPos);
  }
  else
  {
    gboError = TRUE;
    mfResult = mirvar (0);
  }

  if (gpsMip->ERNUM != 0)
  {
    gboError = TRUE;
//    gpsMip->ERNUM = 0;
  }

  if (!gboError)
  {
    otstr (mfResult, gpsMip->IOBUFF);
    CheckMIRACLErrors ();
    strncpy (gszResult, gpsMip->IOBUFF, sizeof (gszResult));
  }

  if (gboError)
  {
    MIRACLErrorString ();
    sprintf (gszResult, "Error %d: %s", gpsMip->ERNUM, gszError);
  }

  if (pboError)
  {
    *pboError = gboError;
  }

  mirkill (mfResult);
  mirexit ();

  return gszResult;
}

//////////////////////////////////////////////////////////////////
// Recursively evaluate an equation string
flash Eval (char * szEval, int nLength)
{
  int                   nBrackets;
  int                   nPos;
  char                  cCurrent;
  bool                  boResult;
  int                   nResult;
  flash                 mfResult;
  char                  szCopy[1024];
  char                  *szLHS;
  int                   nLHSLen;
  char                  *szRHS;
  int                   nRHSLen;
  BINARY                eOperator;
  UNARY                 eUnary;
  int                   nOpLen;
  flash                 mfR1;
  flash                 mfR2;
  flash                 mfTemp;

  if (gboError)
  {
    mfResult = mirvar (0);
    CheckMIRACLErrors ();
    return mfResult;
  }

  if (nLength == 0)
  {
    mfResult = mirvar (0);
    CheckMIRACLErrors ();
    return mfResult;
  }

  strncpy (szCopy, szEval, nLength);
  szCopy[nLength] = 0;

  boResult = FALSE;

  // Binary operators
  eOperator = (BINARY)(0);
  while ((!boResult) && (eOperator < BINARY_NUM))
  {
    nOpLen = (int)strlen (aszOperator[(int)(eOperator)]);
    nBrackets = 0;
    nPos = 0;
    while ((!boResult) && (nPos < nLength))
    {
      cCurrent = szEval[nPos];
      if (cCurrent == '(')
      {
        nBrackets++;
      }
      if (cCurrent == ')')
      {
        nBrackets--;
        if (nBrackets < 0)
        {
          gboError = TRUE;
          mfResult = mirvar (0);
          CheckMIRACLErrors ();
          return mfResult;
        }
      }

      if ((nBrackets == 0) && (nPos <= (nLength - nOpLen)))
      {
        if (strncmp (szEval + nPos, aszOperator[(int)(eOperator)], nOpLen)
          == 0)
        {
          // Do the recursive bit
          szLHS = szEval;
          nLHSLen = nPos;
          szRHS = szEval + nPos + nOpLen;
          nRHSLen = nLength - nPos - nOpLen;
          mfResult = mirvar (0);
          CheckMIRACLErrors ();
          mfR1 = Eval (szLHS, nLHSLen);
          mfR2 = Eval (szRHS, nRHSLen);
          switch (eOperator)
          {
            case BINARY_MULTIPLY:
              fmul (mfR1, mfR2, mfResult);
//              nResult = Eval (szLHS, nLHSLen) * Eval (szRHS, nRHSLen);
              break;
            case BINARY_DIVIDE:
              fdiv (mfR1, mfR2, mfResult);
//              nResult = Eval (szLHS, nLHSLen) / Eval (szRHS, nRHSLen);
              break;
            case BINARY_REMAINDER:
              fmodulo (mfR1, mfR2, mfResult);
//              nResult = Eval (szLHS, nLHSLen) % Eval (szRHS, nRHSLen);
              break;
            case BINARY_ADD:
              fadd (mfR1, mfR2, mfResult);
//              nResult = Eval (szLHS, nLHSLen) + Eval (szRHS, nRHSLen);
              break;
            case BINARY_SUB:
              fsub (mfR1, mfR2, mfResult);
//              nResult = Eval (szLHS, nLHSLen) - Eval (szRHS, nRHSLen);
              break;
            case BINARY_LESS:
              nResult = fcomp (mfR1, mfR2);
              CheckMIRACLErrors ();
              dconv ((double)(nResult < 0), mfResult);
//              nResult = Eval (szLHS, nLHSLen) < Eval (szRHS, nRHSLen);
              break;
            case BINARY_GREATER:
              nResult = fcomp (mfR1, mfR2);
              CheckMIRACLErrors ();
              dconv ((double)(nResult > 0), mfResult);
//              nResult = Eval (szLHS, nLHSLen) > Eval (szRHS, nRHSLen);
              break;
            case BINARY_LESSEQUAL:
              nResult = fcomp (mfR1, mfR2);
              CheckMIRACLErrors ();
              dconv ((double)(nResult <= 0), mfResult);
//              nResult = Eval (szLHS, nLHSLen) <= Eval (szRHS, nRHSLen);
              break;
            case BINARY_GREATEREQUAL:
              nResult = fcomp (mfR1, mfR2);
              CheckMIRACLErrors ();
              dconv ((double)(nResult >= 0), mfResult);
//              nResult = Eval (szLHS, nLHSLen) >= Eval (szRHS, nRHSLen);
              break;
            case BINARY_EQUAL:
              nResult = fcomp (mfR1, mfR2);
              CheckMIRACLErrors ();
              dconv ((double)(nResult == 0), mfResult);
//              nResult = Eval (szLHS, nLHSLen) == Eval (szRHS, nRHSLen);
              break;
            case BINARY_NOTEQUAL:
              nResult = fcomp (mfR1, mfR2);
              CheckMIRACLErrors ();
              dconv ((double)(nResult != 0), mfResult);
//              nResult = Eval (szLHS, nLHSLen) != Eval (szRHS, nRHSLen);
              break;
//            case BINARY_AND:
//              nResult = Eval (szLHS, nLHSLen) & Eval (szRHS, nRHSLen);
//              break;
//            case BINARY_OR:
//              nResult = Eval (szLHS, nLHSLen) | Eval (szRHS, nRHSLen);
//              break;
            case BINARY_LAND:
              mfTemp = mirvar (0);
              CheckMIRACLErrors ();
              nResult = ((fcomp (mfR1, mfTemp) != 0)
                && (fcomp (mfR2, mfTemp) != 0));
              CheckMIRACLErrors ();
              mirkill (mfTemp);
              dconv ((double)(nResult), mfResult);
//              nResult = Eval (szLHS, nLHSLen) && Eval (szRHS, nRHSLen);
              break;
            case BINARY_LOR:
              mfTemp = mirvar (0);
              CheckMIRACLErrors ();
              nResult = ((fcomp (mfR1, mfTemp) != 0)
                || (fcomp (mfR2, mfTemp) != 0));
              CheckMIRACLErrors ();
              mirkill (mfTemp);
              dconv ((double)(nResult), mfResult);
//              nResult = Eval (szLHS, nLHSLen) || Eval (szRHS, nRHSLen);
              break;
            case BINARY_POWER:
              fpowf (mfR1, mfR2, mfResult);
              break;
            default:
              printf ("Error - operator failure\n");
              break;
          }
          CheckMIRACLErrors ();
          boResult = TRUE;
          mirkill (mfR1);
          mirkill (mfR2);
        }
      }
      nPos++;
    }
    eOperator = (BINARY)((int)(eOperator) + 1);
  }

  // Unary operators
  eUnary = (UNARY)(0);
  while ((!boResult) && (eUnary < UNARY_NUM))
  {
    nOpLen = (int)strlen (aszUnary[(int)(eUnary)]);
    nBrackets = 0;
    nPos = 0;
    while ((!boResult) && (nPos < nLength))
    {
      cCurrent = szEval[nPos];
      if (cCurrent == '(')
      {
        nBrackets++;
      }
      if (cCurrent == ')')
      {
        nBrackets--;
        if (nBrackets < 0)
        {
          gboError = TRUE;
          mfResult = mirvar (0);
          return mfResult;
        }
      }

      if ((nBrackets == 0) && (nPos <= (nLength - nOpLen)))
      {
        if (strncmp (szEval + nPos, aszUnary[(int)(eUnary)], nOpLen) == 0)
        {
          // Do the recursive bit
          szLHS = szEval;
          nLHSLen = nPos;
          szRHS = szEval + nPos + nOpLen;
          nRHSLen = nLength - nPos - nOpLen;
          mfResult = mirvar (0);
          CheckMIRACLErrors ();
          mfR2 = Eval (szRHS, nRHSLen);
          switch (eUnary)
          {
            case UNARY_NOT:
              mfTemp = mirvar (0);
              CheckMIRACLErrors ();
              nResult = (fcomp (mfR2, mfTemp) == 0);
              CheckMIRACLErrors ();
              mirkill (mfTemp);
              dconv ((double)(nResult), mfResult);
              break;
            case UNARY_LN:
              flog (mfR2, mfResult);
              break;
            case UNARY_EXP:
              fexp (mfR2, mfResult);
              break;
            case UNARY_COS:
              fcos (mfR2, mfResult);
              break;
            case UNARY_SIN:
              fsin (mfR2, mfResult);
              break;
            case UNARY_TAN:
              ftan (mfR2, mfResult);
              break;
            case UNARY_ACOS:
              facos (mfR2, mfResult);
              break;
            case UNARY_ASIN:
              fasin (mfR2, mfResult);
              break;
            case UNARY_ATAN:
              fatan (mfR2, mfResult);
              break;
            case UNARY_COSH:
              fcosh (mfR2, mfResult);
              break;
            case UNARY_SINH:
              fsinh (mfR2, mfResult);
              break;
            case UNARY_TANH:
              ftanh (mfR2, mfResult);
              break;
            case UNARY_ACOSH:
              facosh (mfR2, mfResult);
              break;
            case UNARY_ASINH:
              fasinh (mfR2, mfResult);
              break;
            case UNARY_ATANH:
              fatanh (mfR2, mfResult);
              break;
            default:
              printf ("Error - operator failure\n");
              break;
          }
          CheckMIRACLErrors ();
          boResult = TRUE;
          mirkill (mfR2);
        }
      }
      nPos++;
    }
    eUnary = (UNARY)((int)(eUnary) + 1);
  }

//  if (!boResult)
//  {
//    switch (szEval[0])
//    {
//      case '!':
//        nResult = !Eval (szEval + 1, nLength - 1);
//        boResult =   TRUE;
//        break;
//      case '~':
//        nResult = ~Eval (szEval + 1, nLength - 1);
//        boResult =   TRUE;
//        break;
//      default:
//        break;
//    }
//  }

  if ((!boResult) && ((szEval[0] == '(') && (szEval[nLength - 1] == ')')))
  {
    mfResult = Eval (szEval + 1, nLength - 2);
    boResult = TRUE;
  }

  if (!boResult)
  {
    if (nBrackets != 0)
    {
      gboError = TRUE;
      mfResult = mirvar (0);
      CheckMIRACLErrors ();
      return mfResult;
    }
    mfResult = GetVariableValue (szCopy, nLength);
  }

  return mfResult;
}

//////////////////////////////////////////////////////////////////
// Evaluate an atom
flash GetVariableValue (char * szEval, int nLength)
{
  int                   nScan;
  flash                 mfResult;

  mfResult = mirvar (0);
  CheckMIRACLErrors ();

  if (nLength == 0)
  {
    dconv ((double)(0.0f), mfResult);
    CheckMIRACLErrors ();
    nScan = 1;
  }
  else
  {
    // Parse the value as a number
    nScan = instr (mfResult, szEval);
    if (gpsMip->ERNUM == MR_ERR_BAD_FORMAT)
    {
      nScan = 0;
      gpsMip->ERNUM = 0;
    }
    CheckMIRACLErrors ();

    if (gpsMip->ERNUM != 0)
    {
      dconv ((double)(0.0f), mfResult);
      CheckMIRACLErrors ();
      gboError = TRUE;
      nScan = 1;
    }
  }
//  nScan = sscanf (szEval, "%d", & nResult);
  if (nScan <= 0)
  {
    // This next line is to fix a peculiar anomaly
    nScan = instr (mfResult, "10");

    if ((nLength == 2) && (strncmp (szEval, "pi", 2) == 0))
    {
      fpi (mfResult);
      CheckMIRACLErrors ();
    }
    else
    {
      gboError = TRUE;
    }
  }

  return mfResult;
}

//////////////////////////////////////////////////////////////////
// Get the precision value
int GetPrecision (void)
{
  return gnPrecision;
}

//////////////////////////////////////////////////////////////////
// Set the precision value
void SetPrecision (int nPrecision)
{
  gnPrecision = nPrecision;
}

//////////////////////////////////////////////////////////////////
// Get the radix status
bool GetRadix (void)
{
  return gboRadix;
}

//////////////////////////////////////////////////////////////////
// Set the radix status
void SetRadix (bool boRadix)
{
  gboRadix = boRadix;
}

//////////////////////////////////////////////////////////////////
// Check for MIRACL errors
void CheckMIRACLErrors (void)
{
  switch (gpsMip->ERNUM)
  {
    case 0: // No error
      break;
    case MR_ERR_OVERFLOW:
    case MR_ERR_FLASH_OVERFLOW:
//      gpsMip->ERNUM = 0;
//      break;
    default:
    case MR_ERR_BASE_TOO_BIG:
    case MR_ERR_DIV_BY_ZERO:
    case MR_ERR_NEG_RESULT:
    case MR_ERR_BAD_FORMAT:
    case MR_ERR_BAD_BASE:
    case MR_ERR_BAD_PARAMETERS:
    case MR_ERR_OUT_OF_MEMORY:
    case MR_ERR_NEG_ROOT:
    case MR_ERR_NEG_POWER:
    case MR_ERR_BAD_ROOT:
    case MR_ERR_INT_OP:
    case MR_ERR_TOO_BIG:
    case MR_ERR_NEG_LOG:
    case MR_ERR_DOUBLE_FAIL:
    case MR_ERR_IO_OVERFLOW:
    case MR_ERR_NO_MIRSYS:
    case MR_ERR_BAD_MODULUS:
    case MR_ERR_NO_MODULUS:
    case MR_ERR_EXP_TOO_BIG:
    case MR_ERR_NOT_SUPPORTED:
    case MR_ERR_NOT_DOUBLE_LEN:
    case MR_ERR_NOT_IRREDUC:
    case MR_ERR_NO_ROUNDING:
      gboError = TRUE;
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Return a MIRACL error string
void MIRACLErrorString (void)
{
  switch (gpsMip->ERNUM)
  {
    default:
    case 0: // No error
      break;
    case MR_ERR_BASE_TOO_BIG:
      strcpy (gszError, "Base too big");
      break;
    case MR_ERR_DIV_BY_ZERO:
      strcpy (gszError, "Divide by zero");
      break;
    case MR_ERR_OVERFLOW:
      strcpy (gszError, "Overflow");
      break;
    case MR_ERR_NEG_RESULT:
      strcpy (gszError, "Neg result");
      break;
    case MR_ERR_BAD_FORMAT:
      strcpy (gszError, "Bad format");
      break;
    case MR_ERR_BAD_BASE:
      strcpy (gszError, "Bad base");
      break;
    case MR_ERR_BAD_PARAMETERS:
      strcpy (gszError, "Bad paramters");
      break;
    case MR_ERR_OUT_OF_MEMORY:
      strcpy (gszError, "Out of memory");
      break;
    case MR_ERR_NEG_ROOT:
      strcpy (gszError, "Neg root");
      break;
    case MR_ERR_NEG_POWER:
      strcpy (gszError, "Neg power");
      break;
    case MR_ERR_BAD_ROOT:
      strcpy (gszError, "Bad root");
      break;
    case MR_ERR_INT_OP:
      strcpy (gszError, "Int op");
      break;
    case MR_ERR_FLASH_OVERFLOW:
      strcpy (gszError, "Flash overflow");
      break;
    case MR_ERR_TOO_BIG:
      strcpy (gszError, "Too big");
      break;
    case MR_ERR_NEG_LOG:
      strcpy (gszError, "Neg log");
      break;
    case MR_ERR_DOUBLE_FAIL:
      strcpy (gszError, "Double fail");
      break;
    case MR_ERR_IO_OVERFLOW:
      strcpy (gszError, "IO overflow");
      break;
    case MR_ERR_NO_MIRSYS:
      strcpy (gszError, "No mirsys");
      break;
    case MR_ERR_BAD_MODULUS:
      strcpy (gszError, "Bad modulus");
      break;
    case MR_ERR_NO_MODULUS:
      strcpy (gszError, "No modulus");
      break;
    case MR_ERR_EXP_TOO_BIG:
      strcpy (gszError, "Exp too big");
      break;
    case MR_ERR_NOT_SUPPORTED:
      strcpy (gszError, "Not supported");
      break;
    case MR_ERR_NOT_DOUBLE_LEN:
      strcpy (gszError, "Nt double length");
      break;
    case MR_ERR_NOT_IRREDUC:
      strcpy (gszError, "Not irreducible");
      break;
    case MR_ERR_NO_ROUNDING:
      strcpy (gszError, "No rounding");
      break;
  }
}

