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

namespace ConsoleLSystem {
    public class LSystemNodeLiteralVariable : LSystemNodeLiteral {
        public Dictionary<string, int> variableIndex { get; }

        public LSystemNodeLiteralVariable(string name, int values_number, Dictionary<string, int> variableIndex) : base(name, values_number) {
            this.variableIndex = variableIndex;
        }

    }
    public class LSystemNodeGenerator : LSystemNode {
        MathExpression[] math_expressions;

        public LSystemNodeGenerator(string name, int arguments) : base(new LSystemNodeLiteral(name, arguments)) {
        }
        public void fillArguments(MathExpression[] math_expressions) {
            this.math_expressions = math_expressions;
        }
        public LSystemNode eval(float[] values) {
            var literal = new LSystemNodeLiteral(this.literal.name, this.literal.values_number);
            for (int i = 0; i < literal.values_number; i++) {
                literal.values[i] = math_expressions[i].eval(values);
            }
            var result = new LSystemNode(literal);
            foreach (LSystemNodeGenerator child in children) {
                var _child = child.eval(values);
                _child.parent = result;
                result.children.Add(_child);

            }
            if (mainChild != null) {
                var _mainChild = ((LSystemNodeGenerator)mainChild).eval(values);
                _mainChild.parent = result;
                result.mainChild = _mainChild;
            }
            else {
                result.mainChild = null;
            }
            return result;
        }

    }
    abstract public class LSystemRule {
        abstract public bool is_aplicable(LSystemNode processed_node, String[] ignored);
        abstract public LSystemNode rewrite(LSystemNode processed_node, String[] ignored);
    }

    public class LSystemRuleBasic : LSystemRule {
        LSystemNodeLiteral input;// { get; }
        LSystemNode output;

        public LSystemRuleBasic(LSystemNodeLiteral input, LSystemNode output) {
            this.input = input;
            this.output = output;
        }

        override public bool is_aplicable(LSystemNode processed_node, String[] ignored) {
            if (processed_node.literal == input) {
                return true;
            }
            else {
                return false;
            }
        }
        override public LSystemNode rewrite(LSystemNode processed_node, String[] ignored) {
            if (is_aplicable(processed_node, ignored)) {
                return output.deep_copy();
            }
            else {
                return new LSystemNode(processed_node.literal);
            }
        }
    }

    public class LSystemRuleParametric : LSystemRule {
        LSystemNodeLiteralVariable predecesor;// { get; }
        LSystemNodeGenerator consequent;
        MathExpressionComparison[] conditions;

        public LSystemRuleParametric(LSystemNodeLiteralVariable predecesor, MathExpressionComparison[] conditions, LSystemNodeGenerator consequent) {
            this.predecesor = predecesor;
            this.consequent = consequent;
            this.conditions = conditions;
        }

        public override bool is_aplicable(LSystemNode processed_node, String[] ignored) {
            if (processed_node.literal.name != predecesor.name || processed_node.literal.values_number != predecesor.values_number) {
                return false;
            }
            foreach (var condition in conditions) {
                if (!condition.eval(processed_node.literal.values)) {
                    return false;
                }
            }
            return true;
        }

        public override LSystemNode rewrite(LSystemNode processed_node, String[] ignored) {
            if (is_aplicable(processed_node, ignored)) {
                return consequent.eval(processed_node.literal.values);
            }
            else {
                return new LSystemNode(processed_node.literal);
            }
        }
    }

    public class LSystemRuleParametricStochastic : LSystemRule {
        protected LSystemNodeLiteralVariable predecesor;// { get; }
        protected LSystemNodeGenerator[] consequents;
        protected float[] probabilities;
        protected MathExpressionComparison[] conditions;
        protected Random random;

        public LSystemRuleParametricStochastic(LSystemNodeLiteralVariable predecesor, MathExpressionComparison[] conditions, LSystemNodeGenerator[] consequents, float[] probabilities) {
            this.predecesor = predecesor;
            this.consequents = consequents;
            this.conditions = conditions;
            float probabilities_sum = 0;
            foreach (var probability in probabilities) { probabilities_sum += probability; }
            float acumulator = 0;
            this.probabilities = new float[consequents.Length];
            for (int i = 0; i < consequents.Length; i++) {
                var probability = probabilities[i] / probabilities_sum;
                this.probabilities[i] = probability + acumulator;
                acumulator += probability;
            }
            random = new Random();

        }
        public LSystemRuleParametricStochastic(LSystemNodeLiteralVariable predecesor, MathExpressionComparison[] conditions, LSystemNodeGenerator consequent) {
            this.predecesor = predecesor;
            this.consequents = new LSystemNodeGenerator[] { consequent };
            this.conditions = conditions;
            float probabilities_sum = 0;
            probabilities = new float[1] { 1 };
            random = new Random();

        }
        public override bool is_aplicable(LSystemNode processed_node, String[] ignored) {
            if (processed_node.literal.name != predecesor.name || processed_node.literal.values_number != predecesor.values_number) {
                return false;
            }
            foreach (var condition in conditions) {
                if (!condition.eval(processed_node.literal.values)) {
                    return false;
                }
            }
            return true;
        }

        public override LSystemNode rewrite(LSystemNode processed_node, String[] ignored) {
            var result = random.NextDouble();
            int consequent_index = 0;
            for (int i = 0; i < probabilities.Length; i++) {
                if (result < probabilities[i]) {
                    consequent_index = i;
                    break;
                }
            }
            if (is_aplicable(processed_node, ignored)) {
                return consequents[consequent_index].eval(processed_node.literal.values);
            }
            else {
                return new LSystemNode(processed_node.literal);
            }
        }
    }
    public class LSystemContext {
        public LSystemNodeLiteral parent { get; set; }
        public LSystemNodeLiteral child { get; set; }
        public LSystemNodeLiteral mid { get; set; }
        bool isPreceding = false;
        bool isSucceeding = false;

        public LSystemContext(LSystemNodeLiteral parent, LSystemNodeLiteral child) {
            this.parent = parent;
            this.child = child;
            this.isPreceding = true;
            this.isSucceeding = true;
        }
        public LSystemContext() {

        }
        public void setOnlyPreceding(LSystemNodeLiteral parent) {
            this.parent = parent;
            this.mid = mid;
            this.isPreceding = true;
            this.isSucceeding = false;
        }
        public void setOnlySucceeding(LSystemNodeLiteral child) {
            this.child = child;
            this.isPreceding = false;
            this.isSucceeding = true;
        }
        public bool isAplicable(LSystemNode node, String[] ignored, out float[] variables) {
            var variables_list = new List<float>();
            var currentParent = node.parent;
            while (ignored.Contains(currentParent.literal.name)) {
                currentParent = currentParent.parent;
            }
            if (isPreceding && parent != currentParent.literal) {
                variables = new float[0];
                return false;
            }
            if (isPreceding) {
                variables_list.AddRange(currentParent.literal.values);
            }
            variables_list.AddRange(node.literal.values);
            if (isSucceeding) {
                var result = false;
                LSystemNodeLiteral childLiteral;
                Queue<LSystemNode> ignoredChildren = new Queue<LSystemNode>();
                foreach(var child in node.getAllChildren()) {
                    if (ignored.Contains(child.literal.name)) {
                        ignoredChildren.Enqueue(child);
                    }
                    if (child.literal == this.child) {
                        childLiteral = child.literal;
                        result = true;
                        variables_list.AddRange(child.literal.values);
                        break;
                    }
                }
                while (!result && ignoredChildren.Count>0) {
                    var ignoredChild = ignoredChildren.Dequeue();
                    foreach (var child in ignoredChild.getAllChildren()) {
                        if (ignored.Contains(child.literal.name)) {
                            ignoredChildren.Enqueue(child);
                        }
                        if (child.literal == this.child) {
                            childLiteral = child.literal;
                            result = true;
                            variables_list.AddRange(child.literal.values);
                            break;
                        }
                    }
                }
                if (!result) {
                    variables = new float[0];
                    return false;
                }
            }
            variables = variables_list.ToArray();
            return true;

        }

    }

    public class LSystemRuleParametricStochasticContext : LSystemRuleParametricStochastic {
        LSystemContext context;
        public LSystemRuleParametricStochasticContext(LSystemNodeLiteralVariable predecesor, MathExpressionComparison[] conditions, LSystemNodeGenerator[] consequents, float[] probabilities, LSystemContext context) : base(predecesor, conditions, consequents, probabilities) {
            this.context = context;
        }
        public LSystemRuleParametricStochasticContext(LSystemNodeLiteralVariable predecesor, MathExpressionComparison[] conditions, LSystemNodeGenerator consequent, LSystemContext context) : base(predecesor, conditions, consequent) {
            this.context = context;
        }
        public override bool is_aplicable(LSystemNode processed_node,String[] ignored) {
            if (processed_node.literal.name != predecesor.name || processed_node.literal.values_number != predecesor.values_number) {
                return false;
            }
            var variables = new float[0];
            if (!context.isAplicable(processed_node, ignored, out variables)) {
                return false;
            }
            foreach (var condition in conditions) {
                if (!condition.eval(variables)) {
                    return false;
                }
            }
            return true;
        }
        bool is_aplicable(LSystemNode processed_node, String[] ignored, out float[] variables) {
            variables = new float[0];
            if (processed_node.literal.name != predecesor.name || processed_node.literal.values_number != predecesor.values_number) {
                return false;
            }
            if (!context.isAplicable(processed_node, ignored, out variables)) {
                return false;
            }
            foreach (var condition in conditions) {
                if (!condition.eval(variables)) {
                    return false;
                }
            }
            return true;
        }

        public override LSystemNode rewrite(LSystemNode processed_node, String[] ignored) {
            var result = random.NextDouble();
            int consequent_index = 0;
            for (int i = 0; i < probabilities.Length; i++) {
                if (result < probabilities[i]) {
                    consequent_index = i;
                    break;
                }
            }
            float[] variables;
            if (is_aplicable(processed_node, ignored, out variables)) {
                return consequents[consequent_index].eval(variables);
            }
            else {
                return new LSystemNode(processed_node.literal);
            }
        }

    }
}