using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace Machinations.FromFlash.machinations.model
{
    /// <summary>
    /// translates texts to formulas for registers, possiblz could be joined with machinations expression, some similar functions
    /// </summary>
    public class MachinationsRegisterExpression
    {
        private const string Operands = "0123456789.";
        private const string Operators = "()-+*/%D";
        private static string Variables = "abcdefghijklmnopqrstuvw";

        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;
        }

        public static bool IsVariable(string s)
        {
			return (s.Length == 1 && Variables.IndexOf(s.First()) >= 0);
		}

        private static bool IsOperator(string s)
        {
            return s.Length == 1 && 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>();

            //parse string
            while (word.Length > 0)
            {
                //find the first part
                int counter = 0;
                char c = '\r';
                string holder = "";
                while (counter < word.Length)
                {
                    c = word.ElementAt(counter);
                    if (!IsOperand(c.ToString()))
                    {
                        break;
                    }
                    holder += c;
                    counter++;
                }
                if (holder.Length == 0 && c != '\r')
                {
                    if (IsOperator(c.ToString())) input.Add(c.ToString());
                    if (IsVariable(c.ToString()))
                    {
                        if (input.Count > 0)
                        {
                            if (!IsOperator(input[input.Count - 1]))
                            {
                                input.Add("*");
                            }
                        }
                        input.Add(c.ToString());
                    }
                    word = word.Substring(1);
                }
                else {
                    input.Add(holder);
                    word = word.Substring(holder.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();
                        }
                        input.RemoveAt(i);
                        continue;
                    }
                }
                if (input[i] == "D")
                {
                    if (i == 0 || IsOperator(input[i - 1]))
                    {
                        input.RemoveAt(i);
                        input.Insert(i - 1, "1");
                        i += 2;
                        continue;
                    }
                }
                i++;
            }

            //create postFix
            while (input.Count > 0)
            {
                string c = input[0];
                switch (c)
                {
                    case "-":
                    case "+":

                        GotOperator(c, 1);
                        break;
                    case "/":
                    case "*":
                    case "%":
                        GotOperator(c, 2);
                        break;
                    case "D":

                        GotOperator(c, 3);
                        break;
                    case "(":
                        m_stack.Push(c);
                        break;
                    case ")":

                        GotParenthesis();
                        break;
                    default:
                        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;
                switch (opTop)
                {
                    case "-":
                    case "+":
                        precedenceTop = 1;
                        break;
                    case "D":
                        precedenceTop = 3;
                        break;
                    case "*":
                    case "/":
                    case "%":
                    default:
                        precedenceTop = 2;
                        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, List<string> values)
        {
            var stack = new Stack<double>();
            double interAns;
            for (int i = 0; i < postFix.Count; i++)
            {
                double d;
                if (double.TryParse(postFix[i], NumberStyles.Number, CultureInfo.InvariantCulture, out d))
                {
                    stack.Push(d);
                }
                else if (IsVariable(postFix[i]))
                {
                    int j = postFix[i][0] - 97; //prvni písmeno stringu na itém místě
                    if (j >= 0 && j < values.Count)
                    {
                        stack.Push(double.Parse(values[j], NumberStyles.Number, CultureInfo.InvariantCulture));
                    }
                    else
                    {
                        stack.Push(0);
                    }
                }
                else
                {
                    var num2 = stack.Count > 0 ? stack.Pop() : 0;
                    var num1 = stack.Count > 0 ? stack.Pop() : 0;
                    switch (postFix[i])
                    {
                        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;
                        case "D":
                            interAns = 0;
                            for (int j = 0; j < num1; j++)
                            {
                                interAns += 1 + Math.Floor(RandomUtils.NextDouble() * num2);
                            }
                            break;
                        default:
                            interAns = 0;
                            break;
                    }
                    stack.Push(interAns);
                }
            }
            interAns = stack.Pop();
            return interAns;
        }
    }
}
