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

namespace ConsoleLSystem {
    public class MathExpressionComparison{
        MathExpression leftSide;
        MathExpression rightSide;
        Func<float, float,bool> comparisonFunction;

        public MathExpressionComparison(MathExpression leftSide, MathExpression rightSide, Func<float, float, bool> comparisonFunction) {
            this.leftSide = leftSide;
            this.rightSide = rightSide;
            this.comparisonFunction = comparisonFunction;
        }
        public bool eval(float[] variables) {
            return comparisonFunction(leftSide.eval(variables), rightSide.eval(variables));
        }


    }
    public class MathExpressionNode {
        Func<float[], float[], float> function;
        MathExpressionNode[] childNodes;

        public MathExpressionNode(Func<float[], float[], float> function, MathExpressionNode[] childNodes) {
            this.function = function;
            this.childNodes = childNodes;
        }

        public float eval(float[] variables) {
            var x = childNodes.Select(node => node.eval(variables)).ToArray();
            return function(x, variables);
        }
    }
    public class MathExpression {
        private static Random random = new Random();

        private static Dictionary<char, int> expression_hierarchy = new Dictionary<char, int>{
            {'*', 3},
            {'/', 3},
            {'+', 1},
            {'-', 1},
        };
        MathExpressionNode rootNode;
        public float eval(float [] variables) {
            return rootNode.eval(variables);
        }
        private static float negate(float[] children, float[] variables) {
            return -children[0];
        }
        private static float mulitply(float[] children, float[] variables) {
            return children[0] * children[1];
        }
        private static float divide(float[] children, float[] variables) {
            return children[0] / children[1];
        }
        private static float add(float[] children, float[] variables) {
            return children[0] + children[1];
        }
        private static float subtract(float[] children, float[] variables) {
            return children[0] - children[1];
        }

        //private MathExpressionNode parse(Dictionary<string, int> variableNames, string expression_string) {


        //    var read_characters = 0;
        //    var node = new MathExpressionNode();
        //    if (expression_string[0] == '(') {
        //        var length = LSystemFileParser.countParenthesisEnd(expression_string, '(', ')');
        //        if (length == expression_string.Length - 2) {
        //            return parse(variableNames, expression_string.Substring(1, length));
        //        }

        //        var left_child_node = parse(variableNames, expression_string.Substring(1, length));
        //        read_characters += length + 2;

        //        node.function = getFunction(expression_string[read_characters]);
        //        read_characters += 1;

        //        if (expression_string[read_characters] == '(')
        //    }



        //}

        private static MathExpressionNode parse(Dictionary<string, int> variableNames, string expression_string) {
            expression_string = expression_string.Trim();
            var expression_pos = -1;
            var current_level = 1000;
            for (int i = 0; i < expression_string.Length; i++) {
                if (expression_string[i] == '(') {
                    var length = LSystemFileParser.countParenthesisEnd(expression_string.Substring(i), '(', ')');
                    if (length == expression_string.Length - 2) {
                        return parse(variableNames, expression_string.Substring(1, length));
                    }
                    //skip parenthesis
                    // if expression is covered in ( )
                    if (i == 0 && length == expression_string.Length - 2) {
                        return parse(variableNames, expression_string.Substring(1, length));
                    }
                    else {
                        i += length + 1;
                        continue;
                    }
                }
                int new_level;
                if (expression_hierarchy.TryGetValue(expression_string[i],out new_level)) {
                    if (i>0 && !(i - 1 == expression_pos && expression_string[i] == '-')) {
                        if (new_level <= current_level) {
                            current_level = new_level;
                            expression_pos = i;
                        }
                    }
                }
            }
            if (expression_pos > 0) {
                var left_node = parse(variableNames, expression_string.Substring(0, expression_pos));
                var right_node = parse(variableNames, expression_string.Substring(expression_pos + 1));
                MathExpressionNode[] child_nodes = { left_node, right_node };
                switch (expression_string[expression_pos]) {
                    case '*':
                        return new MathExpressionNode(mulitply, child_nodes);
                        break;
                    case '/':
                        return new MathExpressionNode(divide, child_nodes);
                        break;
                    case '+':
                        return new MathExpressionNode(add, child_nodes);
                        break;
                    case '-':
                        return new MathExpressionNode(subtract, child_nodes);
                        break;
                    default:
                        throw new Exception(String.Format("unknown operation {0} in {1}", expression_string[expression_pos], expression_string));
                        break;
                }
            }
            else if (expression_string[0] == '-') {
                MathExpressionNode[] a = { parse(variableNames, expression_string.Substring(1)) };
                return new MathExpressionNode(negate, a);
            }
            else {
                int index;
                if (expression_string == "RANDOM") {
                    return new MathExpressionNode((float[] children, float[] arguments) => (float)random.NextDouble(), new MathExpressionNode[0]);
                }
                if (variableNames.TryGetValue(expression_string.Trim(),out index)){
                    return new MathExpressionNode((float[] children, float[] arguments) => arguments[index], new MathExpressionNode[0]);
                }
                else {
                    try {
                        float value = float.Parse(expression_string, NumberStyles.Any, CultureInfo.InvariantCulture);
                        return new MathExpressionNode((float[] children, float[] arguments) => value, new MathExpressionNode[0]);
                    }
                    catch (Exception) {

                        throw new Exception(String.Format("can't parse ``{0}``. It's not a number nor a known variable", expression_string));
                    }
                }
            }
        }
        public MathExpression(Dictionary<string,int> variableNames, string expression_string) {
            rootNode = parse(variableNames, expression_string);
        }
    }
    public class MathExpressionComparasionParser {
        private static bool le(float a, float b) { return a < b; }
        private static bool leq(float a, float b) { return a <= b; }
        private static bool eq(float a, float b) { return a == b; }

        public static MathExpressionComparison parse(Dictionary<string, int> variableNames, string expression_string) {
            //var operations = new string[]{ "<=", ">=", "<", ">"};
            if (expression_string.Contains("<=")) {
                
                var parts = expression_string.Split(new string[] { "<=" }, StringSplitOptions.None).Select(s => s.Trim()).ToArray();
                return new MathExpressionComparison(new MathExpression(variableNames, parts[0]), new MathExpression(variableNames, parts[1]), leq);
            }
            if (expression_string.Contains(">=")) {
                var parts = expression_string.Split(new string[] { ">=" }, StringSplitOptions.None).Select(s => s.Trim()).ToArray();
                return new MathExpressionComparison(new MathExpression(variableNames, parts[1]), new MathExpression(variableNames, parts[0]), leq);
            }
            if (expression_string.Contains("==")) {
                var parts = expression_string.Split(new string[] { "==" }, StringSplitOptions.None).Select(s => s.Trim()).ToArray();
                return new MathExpressionComparison(new MathExpression(variableNames, parts[1]), new MathExpression(variableNames, parts[0]), eq);
            }
            if (expression_string.Contains("<")) {
                var parts = expression_string.Split('<').Select(s => s.Trim()).ToArray();
                return new MathExpressionComparison(new MathExpression(variableNames, parts[0]), new MathExpression(variableNames, parts[1]), le);
            }
            if (expression_string.Contains(">")) {
                var parts = expression_string.Split('>').Select(s => s.Trim()).ToArray();
                return new MathExpressionComparison(new MathExpression(variableNames, parts[1]), new MathExpression(variableNames, parts[0]), le);
            }

            throw new Exception(String.Format("comparison operation not recognized in {}", expression_string));
        }
    }
}