using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Machinations.FromFlash.utils;

namespace Machinations.FromFlash.machinations.model
{
    /// <summary>
    /// translates texts to formulas for artifitial plazers and their instructions, possibly could be joined with machinations expression, some similar functions
    /// </summary>
    public class MachinationsScriptExpression
    {
        private const string Operators = "()-+*/%&|^=!><";
        private const string Operands = "0123456789.";

        private static List<string> m_postFix;
        private static Stack<string> m_stack;

        private static bool IsOperand(string s)
        {
            return s.Length == 1 && Operands.IndexOf(s.First()) >= 0;
        }


		private static bool IsOperator(string s)
        {
			//if (s.Length > 0) return (Operators.IndexOf(s.Substring(0, 1))>=0);
			return (Operators.IndexOf(s.First()) >= 0);
		}

		public static List<string> ToPostFix(string word)
        {
            List<string> input = new List<string>();
            m_postFix = new List<string>();
            m_stack = new Stack<string>();

            //remove all spaces
            while (word.IndexOf(' ')>=0) word = word.Replace(" ", "");
			
			//parse string
			while (word.Length > 0) {
				//find the first part
				int l = 0;
				char s; 
				string part = ""; 
				int sign = 0;
				
				while (l<word.Length)
                {
                    s = word[l];
                    if (sign == 0) {
                        if (IsOperator(s.ToString()))
                        {
							sign = 1;
                        }
                        else
                        {
						    sign = -1;
                        }
                    }

                    if (sign == 1) {
                        if ((s == '-' || s == '(' || s == ')') && l > 0)
                        {
                            break;
                        }
                        if (!IsOperator(s.ToString()))
                        {
                            break;
                        }
                    }
                    if (sign == -1) {
                        if (IsOperator(s.ToString()))
                        {
                            break;
                        }
                    }
                    part += s;
                    l++;
                    if (s == ')' || s == '(')
                    {
                        break;
                    }
                }
				
				if (sign == 1)
                {
                    input.Add(part);
                }
                else if (sign == -1)
                {
                    input.Add(part);
                }

                word = word.Substring(part.Length);
			}


            //check special cases
            int i = 0;
            while (i < input.Count)
            {
                if (input[i] == "-")
                {
                    if (i == 0 || IsOperator(input[i - 1]))
                    {
                        double forOutput;
                        if (double.TryParse(input[i + 1], out forOutput))
                        {
                            input[i + 1] = ((forOutput)*-1).ToString();
                        }
                        else
                        {
                            input.Insert(i + 1, "-1");
                            input.Insert(i + 2, "*");
                        }
                        input.RemoveAt(i);
                        continue;
                    }
                }
                i++;
            }

            //create postFix
            while (input.Count > 0) {
				string s = input[0];
                s = s?.Trim();
                switch(s) {
					case "||":

                        GotOperator(s, 1);
						break;
					case "&&":

                        GotOperator(s, 2);
						break;
					case "|":

                        GotOperator(s, 3);
						break;
					case "^":

                        GotOperator(s, 4);
						break;
					case "&":

                        GotOperator(s, 5);
						break;
					case "==":
					case "!=":

                        GotOperator(s, 6);
						break;
					case "<":
					case ">":
					case "<=":
					case ">=":

                        GotOperator(s, 7);
						break;
					case "-":
					case "+":

                        GotOperator(s, 8);
						break;
					case "%":
					case "/":
					case "*":

                        GotOperator(s, 9);
						break;
					case "(":
						m_stack.Push(s);
						break;
					case ")":

                        GotParenthesis();
						break;
					default:
						if (! string.IsNullOrEmpty(s)) {
							m_postFix.Add(s);
						} else {
							m_postFix.Add(input[0]);
						}
                        break;
				}
                input.RemoveAt(0);
            }
            while (m_stack.Count > 0)
            {
                m_postFix.Add(m_stack.Pop());
            }
            return m_postFix;
        }

        private static void GotOperator(string opThis, int precedence)
        {
            while (m_stack.Count > 0)
            {
                string opTop = m_stack.Pop();
                if (opTop == "(")
                {
                    m_stack.Push(opTop);
                    break;
                }
                int precedenceTop = 0;
                switch (opTop)
                {
                    case "||":
                        precedenceTop = 1;
                        break;
                    case "&&":
                        precedenceTop = 2;
                        break;
                    case "|":
                        precedenceTop = 3;
                        break;
                    case "^":
                        precedenceTop = 4;
                        break;
                    case "&":
                        precedenceTop = 5;
                        break;
                    case "==":
                    case "!=":
                        precedenceTop = 6;
                        break;
                    case "<":
                    case ">":
                    case "<=":
                    case ">=":
                        precedenceTop = 7;
                        break;
                    case "-":
                    case "+":
                        precedenceTop = 8;
                        break;
                    case "%":
                    case "/":
                    case "*":
                        precedenceTop = 9;
                        break;						
                }
                if (precedenceTop < precedence)
                {
                    m_stack.Push(opTop);
                    break;
                }
                m_postFix.Add(opTop);
            }
            m_stack.Push(opThis);
        }

        private static void GotParenthesis()
        {
            while (m_stack.Count > 0)
            {
                string chx = m_stack.Pop();
                if (chx == "(")
                {
                    break;
                }
                m_postFix.Add(chx);
            }
        }

        public static double Evaluate(List<string> postFix, APInstruction instruction)
        {
			Stack<double> stack = new Stack<double>();
            double interAns;
            double d;
            for (int i = 0; i < postFix.Count; i++)
            {
                if (double.TryParse(postFix[i],NumberStyles.Any, CultureInfo.InvariantCulture, out d))
                {
                    stack.Push(d);
                }
                else
                {
                    var stackLenght = stack.Count;
                    var num2 = stackLenght > 0 ? stack.Pop() : 0;
                    var num1 = stackLenght > 1 ? stack.Pop() : 0;
                    switch (postFix[i])
                    {
                        case "||":
                            interAns = (num1 > 0 || num2 > 0) ? 1 : 0;
                            break;
                        case "&&":
                            interAns = (num1 > 0 && num2 > 0) ? 1 : 0;
                            break;
                        case "|":
                            //interAns = num1 | num2;
                            throw new NotImplementedException();
                        case "^":
                            interAns = Math.Pow(num1, num2);
                            break;
                        case "&":
                            //interAns = num1 & num2;
                            throw new NotImplementedException();
                        case "==":
                            interAns = (Math.Abs(num1 - num2) < 0.00001) ? 1 : 0;
                            break;
                        case "!=":
                            interAns = (Math.Abs(num1 - num2) > 0.00001) ? 1 : 0;
                            break;
                        case "<":
                            interAns = (num1 < num2) ? 1 : 0;
                            break;
                        case ">":
                            interAns = (num1 > num2) ? 1 : 0;
                            break;
                        case "<=":
                            interAns = (num1 <= num2) ? 1 : 0;
                            break;
                        case ">=":
                            interAns = (num1 >= num2) ? 1 : 0;
                            break;
                        case "+":
                            interAns = num1 + num2;
                            break;
                        case "-":
                            interAns = num1 - num2;
                            break;
                        case "*":
                            interAns = num1 * num2;
                            break;
                        case "/":
                            interAns = num1 / num2;
                            break;
                        case "%":
                            interAns = num1 % num2;
                            break;
                        default:
                            interAns = instruction.GetVariable(postFix[i]);
                            if (stackLenght > 1) stack.Push(num1);
                            if (stackLenght > 0) stack.Push(num2);
                            break;
                    }
                    stack.Push(interAns);
                }
			}
			interAns = stack.Pop();
			return interAns;
		}
    }
}
