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

namespace Machinations.FromFlash.machinations.model
{
    /// <summary>
    /// class for translating texts to commands and formulas
    /// </summary>
    public class MachinationsExpression
    {
        private const string Operands = "0123456789.";

        private const string Operators = "()-+*/D";

        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)
        {
            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 = '\0';
                string holder = "";
                while (counter < word.Length)
                {
                    c = word.ElementAt(counter);
                    if (!IsOperand(c.ToString()))
                    {
                        break;
                    }
                    holder += c;
                    counter++;
                }
                if (holder.Length == 0 ) //&& c != '\0') nevim kde jsem to vyal
                {
                    if (IsOperator(c.ToString())) 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],NumberStyles.Number, CultureInfo.InvariantCulture,  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);        //splice(i,0,1)
                        input.Insert(i, "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 "*":

                        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 "/":
                    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(string s)
        {
            ToPostFix(s);
            return EvaluatePostFix(m_postFix);
        }

        public static double EvaluatePostFix(List<string> postFix)
        {
            m_stack = new Stack<string>();
            double interAns;
            for (int i = 0; i < postFix.Count; i++)
            {
                double d;
                if (double.TryParse(postFix[i], NumberStyles.Number, CultureInfo.InvariantCulture, out d))
                {
                    m_stack.Push(postFix[i]);
                }
                else
                {
                    var num2 = double.Parse(m_stack.Pop(), CultureInfo.InvariantCulture);
                    var num1 = double.Parse(m_stack.Pop(), CultureInfo.InvariantCulture);
                    switch (postFix[i])
                    {
                        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;
                    }
                    m_stack.Push(interAns + "");
                }
            }
            interAns = m_stack.Count != 0 ? double.Parse(m_stack.Last(), CultureInfo.InvariantCulture):0;
            return interAns;
        }
    }
}