using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Data;
using VBNET = Microsoft.VisualBasic;
using gmRTL.Core;
using Microsoft.VisualBasic.CompilerServices;
using System.Linq;
using System.Collections.Generic;

namespace Eval
{
   public class Evaluator
   {
      // Copyright© 1999, Tretyakov Konstantin
      // _____________________________________________________
      // This is the 'Evaluator' class: it inputs a string
      // like "2+2" or "2+4*sin(3.4)^2-8*arccos(0.55)", etc
      // _____________________________________________________
      // You may use the code for free, if you give me credit.
      // If you modify it or make your own program with it,
      // I would VERY APPRECIATE, if you mail me it (or better-
      // a link to it)
      // On the whole - just do not stamp your name on what you haven't
      // done quite alone.
      // This code was written totally by me, and 'it took me about
      // 2 days to code it (and about a year
      // -that is,from the very moment I got interested in programming-
      // I spent dreaming of having such a thing)
      // (BTW this code seems to be quite unique-
      // I searched all over the Internet for such, but NOONE
      // is giving the source for such things)
      // ______________________________________________________
      // Yours Sincerely, Konstantin Tretyakov (kt_ee@yahoo.com)
      // ********************Here we go...********************
      // Well, at the very beginning (when I had only + and -)
      // These constants didplay a role:
      // e.g. I could change the PLUS_SIGN to "plus"
      // and the MINUS_SIGN to "minus", so that I could
      // write an expression like "1 plus 2 minus 3"
      // But now it will not go. :(
      private const string PLUS_SIGN = "+";
      private const string MINUS_SIGN = "-";
      private const string MULTIPLY_SIGN = "*";
      private const string DIVIDE_SIGN = "/";
      private const string POWER_SIGN = "^";
      private const string POINT_SIGN = ",";
      private const string BRACKET_LEFT = "(";
      private const string BRACKET_RIGHT = ")";
      // This is the part to be improved - I mean this error-handling
      public enum EvalError
      {
         ERR_NONE = 0,
         ERR_DBL_POINT = 1,
         ERR_WRONG_SYNTAX = 2,
         ERR_WRONG_SIGN = 4,
         ERR_WRONG_BRACKETS = 8,
         ERR_WRONG_FUNCTION = 16
      }
      // This entry was needed for my other project - Function Analyzer
      // (look for it at the same place, where you found this one)
      private bool m_Assigned = false;
      // I hope you get, what these do
      private string m_Expression = "";
      private double m_Result = 0.0;
      private EvalError m_Error = EvalError.ERR_NONE;
      public string Expression
      {
         set
         {
            m_Expression = ReplaceText(RemoveSpaces(value).UCase(),".",POINT_SIGN);
         }
         get
         {
            return m_Expression;
         }
      }
      public EvalError Error
      {
         get
         {
            return m_Error;
         }
      }
      public double Result
      {
         get
         {
            // Reset the Error
            m_Error = EvalError.ERR_NONE;
            // Calculate
            m_Result = Eval(m_Expression);
            m_Assigned = m_Error == EvalError.ERR_NONE;
            // Return
            return m_Result;
         }
      }
      public bool Assigned
      {
         get
         {
            return m_Assigned;
         }
      }
      public double Evaluate(string Expressn,bool Silent)
      {
         // That's the wrapper for the main procedure
         // You may use this class in 2 ways:
         // 1) Set the 'Expression' property and then read the 'Result' property
         // 2) Call this sub. If you set Silent to False, then the sub will generate a message automatically
         double Res = 0.0;
         Expression = Expressn;
         Res = Result;
         if (!Silent)
         {
            if (m_Error != EvalError.ERR_NONE)
            {
               System.Windows.Forms.MessageBox.Show(ErrorString, "Eval Error", MessageBoxButtons.OK,MessageBoxIcon.Warning);
            }
            else
            {
               System.Windows.Forms.MessageBox.Show("Result: " + Res.ToStr(), "Eval Result", MessageBoxButtons.OK,MessageBoxIcon.Exclamation);
            }
         }
         return m_Result;
      }
      public string ErrorString
      {
         get
         {
            string ErrorString = "";
            switch(m_Error)
            {
            case EvalError.ERR_DBL_POINT:
               ErrorString = "Error: Wrong decimal separator placement!";
               break;
            case EvalError.ERR_WRONG_BRACKETS:
               ErrorString = "Error: Wrong bracket placement!";
               break;
            case EvalError.ERR_WRONG_SIGN:
               ErrorString = "Error: Wrong sign or bracket placement!";
               break;
            case EvalError.ERR_WRONG_SYNTAX:
               ErrorString = "Error: Wrong syntax!";
               break;
            }
            return ErrorString;
         }
      }
      // ***********************************************************
      //  2 helper functions, well they are too 'universal' for this class
      //  (Here we use them only to remove spaces and replace the '.' to ','
      private string RemoveSpaces(string S)
      {
         return ReplaceText(S," ","");
      }
      public string ReplaceText(string SourceText,string StrToReplace,string StrToInsert)
      {
         string ReplaceText = "";

         string RetS = "";
         int I = 0;
         if (StrToReplace == StrToInsert || StrToReplace == "")
         {
            return ReplaceText;
         }
         RetS = SourceText;
         I = RetS.InStr(StrToReplace,1,StringComparison.CurrentCulture);
         while (I != 0)
         {
            RetS = (I == 1 ? "" : RetS.Left((I - 1))) + StrToInsert + (I == RetS.Len() - StrToReplace.Len() + 1 ? "" : RetS.Right((RetS.Len() - I - StrToReplace.Len() + 1)));
            I = RetS.InStr(StrToReplace,1,StringComparison.CurrentCulture);
         }
         return RetS;
      }
      // ***********************************************************
      // The HEART of the class.
      // What it does? - it just splits the expression to monomials
      // (that is: 2*3+3^(3-2)-(2+3) has 3 monomials:
      //       +2*3,  +3^(3-2)  -(2+3)
      // Then it calls the CalcMonomial for each and sums the result
      private double Eval(string Expr)
      {
         string sEval = "";
         int I = 0;
         string[] MonomArray = Array.Empty<string>();
         double dResult = 0.0;
         sEval = Expr;
         MonomArray = SplitToMonomials(sEval,PLUS_SIGN,MINUS_SIGN);
         for(I = MonomArray.GetLowerBound(0); I <= MonomArray.GetUpperBound(0); I += 1)
         {
            dResult = dResult + CalcMonomial(MonomArray[I]);
         }
         return dResult;
      }
      private string[] SplitToMonomials(string EvalStr,string Sign1,string Sign2)
      {
         // Divides the given string in parts using the given sign (Sign1 and Sign2) parameter
         // 
         // Returns an array where each item is a string
         // For example SplitToMonomials("2+3*8-4","+","-") returns [2, +3*8, -4]
         //         and SplitToMonomials("3*2/23","*","/") returns [3, *2, /23]
         // 
         // The function also doesn't split brackets so that
         //       SplitToMonominals("(3+2)*2-3","+","-") will return [(3+2)*2, -3]

         string[] MonomArray = Array.Empty<string>();
         int I = 0;
         int Count = 0;
         string CurMonom = "";
         string sEval = "";
         MonomArray = gmRTL.Core.StringHelper.InitArray(1);
         sEval = EvalStr;
         // Find the first PLUS or MINUS (MUL or DIV) that are not in Bracket
         // (GetSplitPos is Just an Improved Instr, that considers brackets)
         I = GetSplitPos(EvalStr,Sign1,Sign2,1);
         while (I > 0)
         {
            // NOT DONE:
            // Check for expressions of a kind: "2-3*4+6*-5"
            // because we must not split between 6 and 5
            CurMonom = sEval.Left((I - 1));
            // Populate the Array
            Array.Resize(ref MonomArray, Count + 1);
            MonomArray[Count] = CurMonom;
            Count = Count + 1;
            sEval = sEval.Mid(I);
            I = GetSplitPos(sEval,Sign1,Sign2,1);
         }

         CurMonom = sEval;
         Array.Resize(ref MonomArray, Count + 1);
         MonomArray[Count] = CurMonom;
         return MonomArray;
      }
      // Calculates a monomial (expression without PLUSes and MINUSes inside)
      // The work is in fact like of the Eval function:
      // We split it to smaller parts (the ones, that may contain only the ^ sign)
      // and then Calculate each part separately
      private double CalcMonomial(string Monomial)
      {
         double CalcMonomial = 0.0;

         try
         {
            if (m_Error != EvalError.ERR_NONE)
            {
               return CalcMonomial;
            }
            string[] MemberArray = Array.Empty<string>();
            string Sign = "";
            int I = 0;
            double dResult = 0.0;
            double TempRes = 0.0;
            // Split again, but now by * and /
            MemberArray = SplitToMonomials(Monomial,MULTIPLY_SIGN,DIVIDE_SIGN);
            for(I = MemberArray.GetLowerBound(0); I <= MemberArray.GetUpperBound(0); I += 1)
            {
               TempRes = CalcMember(MemberArray[I],ref Sign);
               switch(Sign)
               {
               // Remember - we may have the Plus_sign left in a monomial
               // (like a monomial may be "+2^2*3")
               case PLUS_SIGN:
                  dResult = dResult + TempRes;
                  break;
               case MULTIPLY_SIGN:
                  dResult = dResult * TempRes;
                  break;
               case DIVIDE_SIGN:
                  dResult = dResult / TempRes;
                  break;
               }
            }
            CalcMonomial = dResult;
            return CalcMonomial;
         }
         catch(System.Exception exc)
         {
            gmRTL.Core.GlobalException.Initialize(exc);
            m_Error = EvalError.ERR_WRONG_FUNCTION;
         }
         return CalcMonomial;
      }
      // Calculates an expression, that contains only the operands
      // higher in proirity than * and /
      // TODO: It raises an error on X^Y^Z and calculates only X^Y,
      // That is, for correct calculation you must specify either (X^Y)^Z
      // or X^(Y^Z) (btw which is right ???)
      private double CalcMember(string Member,ref string Sign)
      {
         double CalcMember = 0.0;

         string sSign = "";
         string sEval = "";
         bool HaveMinus = false;
         bool GotNum1 = false;
         double Num1 = 0.0;
         double Num2 = 0.0;
         string Op = "";
         double dResult = 0.0;
         string Func = "";
         string FuncExpr = "";
         if (m_Error != EvalError.ERR_NONE)
         {
            return CalcMember;
         }
         // Here we calculate the results of operations
         // whose priority is higher than * and /
         // The sample given string may be: "+5^2", "*4^2", "/6", "6^2,3"
         // or +(expr)^2, or (expr)^(expr)
         Sign = PLUS_SIGN;
         sEval = Member;
         sSign = sEval.Left(1);
         // Determine the Sign (or find the Bracket or a function)
         if (!gmRTL.Core.InformationHelper.IsNumeric(sSign))
         {
            switch(sSign)
            {
            case MINUS_SIGN:
               HaveMinus = true;
               sEval = sEval.Mid(2);
               if (sEval.Left(1) == BRACKET_LEFT)
               {
                  goto LBrack;
               }
               if (!gmRTL.Core.InformationHelper.IsNumeric(sEval.Left(1)))
               {
                  goto HaveFunc;
               }
               break;
            case PLUS_SIGN:
            case MULTIPLY_SIGN:
            case DIVIDE_SIGN:
               Sign = sSign;
               sEval = sEval.Mid(2);
               if (sEval.Left(1) == BRACKET_LEFT)
               {
                  goto LBrack;
               }
               if (!gmRTL.Core.InformationHelper.IsNumeric(sEval.Left(1)))
               {
                  goto HaveFunc;
               }
               break;
            case BRACKET_LEFT:
            LBrack:
               // That's easy - when we find a bracket - we just 'Eval' the expression in the brackets
               Num1 = Eval(ExtractBrackets(ref sEval));
               GotNum1 = true;
               break;
            default:
               // Here Must make some checks for Functions (like when it's SIN(expr))
            HaveFunc:
               Func = ExtractFunction(ref sEval,ref FuncExpr);
               Num1 = CalcFunction(Func,FuncExpr);
               GotNum1 = true;
               break;
            }
         }
         // Now Do the Calculation
         if (!GotNum1)
         {
            Num1 = ExtractNumber(ref sEval);
         }
         if (sEval.Len() != 0)
         {
            Op = sEval.Left(1);
            sEval = sEval.Mid(2);
            // Check if the second number is a bracketed expression
            if (sEval.Left(1) == BRACKET_LEFT)
            {
               Num2 = Eval(ExtractBrackets(ref sEval));
            }
            else
            {
               if (!gmRTL.Core.InformationHelper.IsNumeric(sEval.Left(1)))
               {
                  Func = ExtractFunction(ref sEval,ref FuncExpr);
                  Num2 = CalcFunction(Func,FuncExpr);
               }
               else
               {
                  Num2 = ExtractNumber(ref sEval);
               }
            }
            switch(Op)
            {
            case POWER_SIGN:
               // UPGRADE_TODO: OnErrorGoto(ErrCalcMember);
               dResult = Math.Pow(Num1,Num2);
               break;
            default:
               m_Error = EvalError.ERR_WRONG_SIGN;
               break;
            }
         }
         else
         {
            dResult = Num1;
         }
         if (sEval.Len() != 0)
         {
            m_Error = EvalError.ERR_WRONG_SYNTAX;
         }
         CalcMember = (HaveMinus ? -dResult : dResult);
         return CalcMember;
      // UPGRADE_TODO: ErrCalcMember:
         m_Error = EvalError.ERR_WRONG_FUNCTION;
         return CalcMember;
      }
      // ***********************************************************
      // This is nearly an equivalent of VAL,
      // only here we may know if there was an error
      // and it also modifies the string by removing the "Extracted" number
      // TODO: It doesn't support the "2.34E+2" notation
      private double ExtractNumber(ref string EvalExpr)
      {
         bool HavePoint = false;
         int I = 0;
         string NewNum = "";
         string TempChar = "";
         string TempSign = "";
         bool HaveMinus = false;
         string sEval = "";
         // Determine whether there is a sign in front of the string
         TempSign = EvalExpr.Left(1);
         if (TempSign == POINT_SIGN)
         {
            sEval = "0" + EvalExpr;
         }
         else
         {
            if (!gmRTL.Core.InformationHelper.IsNumeric(TempSign))
            {
               sEval = EvalExpr.Mid(2);
               HaveMinus = TempSign == MINUS_SIGN;
            }
            else
            {
               sEval = EvalExpr;
            }
         }

         for(I = 1; I <= sEval.Len(); I += 1)
         {
            TempChar = sEval.Mid(I, 1);
            if (gmRTL.Core.InformationHelper.IsNumeric(TempChar))
            {
               NewNum = NewNum + TempChar;
            }
            else
            {
               if (TempChar == POINT_SIGN)
               {
                  if (HavePoint)
                  {
                     // We have already a point, that's an error
                     m_Error = EvalError.ERR_DBL_POINT;
                     break;  // Exit For
                  }
                  else
                  {
                     HavePoint = true;
                     NewNum = NewNum + ".";     // We shall use val in the end
                  }
               }
               else
               {
                  break;  // Exit For
               }
            }
         }
         if (NewNum == "")
         {
            m_Error = EvalError.ERR_WRONG_SYNTAX;
         }
         else
         {
                 // Cut out the number from the string
            EvalExpr = sEval.Mid((NewNum.Len() + 1));
         }
         return (HaveMinus ? -NewNum.ToVal() : NewNum.ToVal());
      }
      // ***********************************************************
      // This is a Helper-func to SplitToMonomials
      // it returns the position in a string of a Sign(1 or 2)
      // it doesn't return the signs that are in brackets and the sign on the 1st place
      private int GetSplitPos(string EvalStr,string Sign1,string Sign2,int StartPos)
      {
         int GetSplitPos = 0;

         int I = 0;
         int InBracket = 0;
         string TempChar = "";

         for(I = StartPos; I <= EvalStr.Len(); I += 1)
         {
            TempChar = EvalStr.Mid(I, 1);
            if (TempChar == Sign1 || TempChar == Sign2)
            {
               if (InBracket == 0 && I > 1)
               {
                  GetSplitPos = I;
                  return GetSplitPos;
               }
            }
            else if (TempChar == BRACKET_LEFT)
            {
               InBracket = InBracket + 1;
            }
            else if (TempChar == BRACKET_RIGHT)
            {
               InBracket = InBracket - 1;
               if (InBracket < 0)
               {
                  m_Error = EvalError.ERR_WRONG_BRACKETS;
                  return GetSplitPos;
               }
            }
         }
         return GetSplitPos;
      }
      // Gets a String, beginning with a Left Bracket and
      // returns the expression in this bracket
      // deletes this expression(with both brackets) from the string
      private string ExtractBrackets(ref string EvalExpr)
      {
         string ExtractBrackets = "";

         int InBracket = 0;
         int I = 0;
         string TempChar = "";
         string RetStr = "";
         // We Suppose that the first sign in the string is BRACKET_LEFT
         InBracket = 1;
         for(I = 2; I <= EvalExpr.Len(); I += 1)
         {
            TempChar = EvalExpr.Mid(I, 1);
            switch(TempChar)
            {
            case BRACKET_LEFT:
               InBracket = InBracket + 1;
               break;
            case BRACKET_RIGHT:
               InBracket = InBracket - 1;
               break;
            }
            if (InBracket == 0)
            {
               RetStr = EvalExpr.Mid(2, I - 2);
               EvalExpr = EvalExpr.Mid((I + 1));
               ExtractBrackets = RetStr;
               return ExtractBrackets;
            }
         }
         m_Error = EvalError.ERR_WRONG_BRACKETS;
         return ExtractBrackets;
      }
      // Process the expression "FUNC(expr)"
      // Returns "FUNC"
      private string ExtractFunction(ref string EvalExpr,ref string FuncExpr)
      {
         string ExtractFunction = "";

         string FuncID = "";
         int I = 0;
         I = EvalExpr.InStr(BRACKET_LEFT,1,StringComparison.CurrentCulture);
         if (I == 0)
         {
            m_Error = EvalError.ERR_WRONG_SYNTAX;
            return ExtractFunction;
         }
         else
         {
            ExtractFunction = EvalExpr.Left((I - 1));
            EvalExpr = EvalExpr.Mid(I);
            FuncExpr = ExtractBrackets(ref EvalExpr);
         }
         return ExtractFunction;
      }
      // You give it a function name and an expression in the brackets after it
      // as 2 separate strings, and it calculates
      // ADD ANY of the Functions you like
      // (E.G. it's interesting to add some 'acting' functions, like, say, MsgBox :)
      // Then there are only several steps towards your own Script-Language
      private double CalcFunction(string FunctionID,string FuncExpr)
      {
         double CalcFunction = 0.0;

         try
         {
            if (m_Error != EvalError.ERR_NONE)
            {
               return CalcFunction;
            }
            double Arg = 0.0;
            Arg = Eval(FuncExpr);
            switch(FunctionID)
            {
            case "ABS":
               CalcFunction = Math.Abs(Arg);
               break;
            case "ATN":
               CalcFunction = Math.Atan(Arg);
               break;
            case "COS":
               CalcFunction = Math.Cos(Arg);
               break;
            case "EXP":
               CalcFunction = Math.Exp(Arg);
               break;
            case "FIX":
               CalcFunction = Arg.Fix();
               break;
            case "INT":
               CalcFunction = ((int)Math.Floor(Arg));
               break;
            case "LOG":
               CalcFunction = Math.Log(Arg);
               break;
            case "RND":
               CalcFunction = gmRTL.Core.MathHelper.Rnd();
               break;
            case "SGN":
               CalcFunction = Math.Sign(Arg);
               break;
            case "SIN":
               CalcFunction = Math.Sin(Arg);
               break;
            case "SQR":
               CalcFunction = Math.Sqrt(Arg);
               break;
            case "TAN":
               CalcFunction = Math.Tan(Arg);
               // Derived
               break;
            case "SEC":
               CalcFunction = 1 / Math.Cos(Arg);
               break;
            case "COSEC":
               CalcFunction = 1 / Math.Sin(Arg);
               break;
            case "COTAN":
               CalcFunction = 1 / Math.Tan(Arg);
               break;
            case "ARCSIN":
               CalcFunction = Math.Atan(Arg / Math.Sqrt(-Arg * Arg + 1));
               break;
            case "ARCCOS":
               CalcFunction = Math.Atan(-Arg / Math.Sqrt(-Arg * Arg + 1)) + 2 * Math.Atan(1);
               break;
            case "ARCSEC":
               CalcFunction = Math.Atan(Arg / Math.Sqrt(Arg * Arg - 1)) + Math.Sign(Arg - 1) * (2 * Math.Atan(1));
               break;
            case "ARCCOSEC":
               CalcFunction = Math.Atan(Arg / Math.Sqrt(Arg * Arg - 1)) + (Math.Sign(Arg) - 1) * (2 * Math.Atan(1));
               break;
            case "ARCCOTAN":
               CalcFunction = Math.Atan(Arg) + 2 * Math.Atan(1);
               break;
            case "HSIN":
               CalcFunction = (Math.Exp(Arg) - Math.Exp(-Arg)) / 2;
               break;
            case "HCOS":
               CalcFunction = (Math.Exp(Arg) + Math.Exp(-Arg)) / 2;
               break;
            case "HTAN":
               CalcFunction = (Math.Exp(Arg) - Math.Exp(-Arg)) / (Math.Exp(Arg) + Math.Exp(-Arg));
               break;
            case "HSEC":
               CalcFunction = 2 / (Math.Exp(Arg) + Math.Exp(-Arg));
               break;
            case "HCOSEC":
               CalcFunction = 2 / (Math.Exp(Arg) - Math.Exp(-Arg));
               break;
            case "HCOTAN":
               CalcFunction = (Math.Exp(Arg) + Math.Exp(-Arg)) / (Math.Exp(Arg) - Math.Exp(-Arg));
               break;
            case "HARCSIN":
               CalcFunction = Math.Log(Arg + Math.Sqrt(Arg * Arg + 1));
               break;
            case "HARCCOS":
               CalcFunction = Math.Log(Arg + Math.Sqrt(Arg * Arg - 1));
               break;
            case "HARCTAN":
               CalcFunction = Math.Log((1 + Arg) / (1 - Arg)) / 2;
               break;
            case "HARCSEC":
               CalcFunction = Math.Log((Math.Sqrt(-Arg * Arg + 1) + 1) / Arg);
               break;
            case "HARCCOSEC":
               CalcFunction = Math.Log((Math.Sign(Arg) * Math.Sqrt(Arg * Arg + 1) + 1) / Arg);
               break;
            case "HARCCOTAN":
               CalcFunction = Math.Log((Arg + 1) / (Arg - 1)) / 2;
               // Not Math functions, but also useful
               break;
            case "TIMER":
               CalcFunction = gmRTL.Core.DateTimeHelper.Timer();
               break;
            case "YEAR":
               CalcFunction = DateTime.Now.Year;
               break;
            case "MONTH":
               CalcFunction = DateTime.Now.Month;
               break;
            case "DAY":
               CalcFunction = DateTime.Now.Day;
               break;
            case "WEEKDAY":
               CalcFunction = ((int)gmRTL.Core.DateTimeHelper.GetDayOfWeek(DateTime.Now)+1);
               break;
            case "HOUR":
               CalcFunction = DateTime.Now.Time().Hour;
               break;
            case "MINUTE":
               CalcFunction = DateTime.Now.Time().Minute;
               break;
            case "SECOND":
               CalcFunction = DateTime.Now.Time().Second;
               // These should be constants, but here you must use them as functions
               // (i.e. with an argument, no matter what)
               break;
            case "PI":
               CalcFunction = 3.14159265358979;
               break;
            case "E":
               CalcFunction = 2.71828182845905;
               break;
            case "ZERO":
               CalcFunction = 0;
               break;
            default:
               m_Error = EvalError.ERR_WRONG_SYNTAX;
               break;
            }
            return CalcFunction;
         }
         catch(System.Exception exc)
         {
            gmRTL.Core.GlobalException.Initialize(exc);
            m_Error = EvalError.ERR_WRONG_FUNCTION;
         }
         return CalcFunction;
      }
   }
}
