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

namespace ConsoleLSystem {
    public class LSystemNodeLiteral {
        public string name { get; }
        public int values_number { get; }

        public float[] values { get; set; }

        public LSystemNodeLiteral(string name, int values_number) {
            this.name = name;
            this.values_number = values_number;
            //in case it would be 0 
            values = new float[values_number];
        }
        public LSystemNodeLiteral(LSystemNodeLiteral other) {
            this.name = other.name;
            this.values_number = other.values_number;
            //in case it would be 0 
            values = new float[this.values_number];
            other.values.CopyTo(values, 0);
        }

        public static bool operator ==(LSystemNodeLiteral thisLiteral, LSystemNodeLiteral other) {
            return thisLiteral.name == other.name && thisLiteral.values_number == other.values_number;
        }
        public static bool operator !=(LSystemNodeLiteral thisLiteral, LSystemNodeLiteral other) {
            return !(thisLiteral.name == other.name && thisLiteral.values_number == other.values_number);
        }

        public override string ToString() {
            if (values_number == 0) {
                return name;
            }
            else {
                StringBuilder sb = new StringBuilder(name, name.Length + values_number * 7 + 4);
                sb.Append("(");
                for (int i = 0; i < values_number; i++) {
                    var v = values[i];
                    sb.AppendFormat("{0:0.##}", v);
                    if (i < values_number - 1) {
                        sb.Append(",");
                    }
                }
                sb.Append(")");
                return sb.ToString();
            }
        }
    }

    public class LSystemNode {
        public LSystemNodeLiteral literal { get; set; }
        public List<LSystemNode> children { get; set; }
        public LSystemNode mainChild { get; set; }

        private LSystemNode _parent;
        public LSystemNode parent {
            get { if (_parent is null) { return new LSystemNode(new LSystemNodeLiteral(" _ ", 0)); }
                else { return _parent; } }
            set { _parent = value; } }

        public LSystemNode(LSystemNodeLiteral nodeLiteral) {
            literal = nodeLiteral;
            children = new List<LSystemNode>();
            mainChild = null;
        }

        public LSystemNode(LSystemNodeLiteral nodeLiteral, List<LSystemNode> children, LSystemNode mainChild) {
            literal = nodeLiteral;
            this.children = children;
            this.mainChild = mainChild;
        }
        public LSystemNode(LSystemNodeLiteral nodeLiteral, List<LSystemNode> children) {
            literal = nodeLiteral;
            this.children = children;
            this.mainChild = null;
        }
        public List<LSystemNode> getAllChildren() {
            var result = new List<LSystemNode>(children);
            if (mainChild != null){
                result.Add(mainChild);
            }
            return result;
        }

        public LSystemNode deep_copy() {
            if (children.Count() == 0 && mainChild == null) {
                return new LSystemNode(new LSystemNodeLiteral(literal));
            }
            else if (mainChild != null) {
                var result = new LSystemNode(new LSystemNodeLiteral(literal));
                result.mainChild = mainChild.deep_copy();
                return result;
            }
            else {
                var new_children = new List<LSystemNode>();
                foreach (var x in children) {
                    new_children.Add(x.deep_copy());
                }
                return new LSystemNode(literal, new_children, this.mainChild.deep_copy());
            }
        }

        private static LSystemNode deepestNode(LSystemNode node) {
            while (node.mainChild != null) {
                node = node.mainChild;
            }
            return node;
        }

        public LSystemNode newParent() {
            return deepestNode(this);
        }

        public override string ToString() {
            StringBuilder sb = new StringBuilder(literal.ToString());
            var node = this;
            while (true) {
                foreach (var child in node.children) {
                    sb.Append("[");
                    sb.Append(child.ToString());
                    sb.Append("]");
                }
                if (node.mainChild != null) {
                    sb.Append(node.mainChild.literal.ToString());
                    node = node.mainChild;
                }
                else {
                    return sb.ToString();
                }
            }
        }
    }


    public class LSystemEvaluator {
        public LSystemNode lSystemString { get; set; }
        public List<LSystemRule> lSystemRules { get; set; }
        public String[] ignored { get; set; }

        public LSystemEvaluator(LSystemNode startingString, List<LSystemRule> rules) {
            lSystemString = startingString;
            lSystemRules = rules;
            ignored = new String[0];
        }
        public LSystemEvaluator(LSystemNode startingString, List<LSystemRule> rules, String[] ignored) {
            lSystemString = startingString;
            lSystemRules = rules;
            this.ignored = ignored;
        }
        private LSystemNode _rewrite(LSystemNode node) {
            foreach (var rule in lSystemRules) {
                if (rule.is_aplicable(node, ignored)) {
                    return rule.rewrite(node, ignored);
                }
            }
            return new LSystemNode(node.literal);
        }
        //private void _rewrite_recursive(LSystemNode node, LSystemNode parent) {
        //    var new_node = _rewrite(node);
        //    parent.children.Add(new_node);
        //    new_node.parent = parent;
        //    var new_parent = new_node.newParent();

        //    foreach (var child in node.children) {
        //        _rewrite_recursive(child, new_parent);
        //    }
        //}


        private void _rewrite_recursive(LSystemNode node, LSystemNode parent) {
            var new_node = _rewrite(node);
            parent.children.Add(new_node);
            new_node.parent = parent;
            var new_parent = new_node.newParent();

            foreach (var child in node.children) {
                _rewrite_recursive(child, new_parent);
            }
            while (node.mainChild != null) {
                new_node = _rewrite(node.mainChild);
                new_node.parent = new_parent;

                new_parent.mainChild = new_node;
                new_parent = new_node.newParent();
                foreach (var child in node.mainChild.children) {
                    _rewrite_recursive(child, new_parent);
                }
                node = node.mainChild;
            }
        }
        public void rewrite() {
            var new_root = _rewrite(lSystemString);
            var new_parent = new_root.newParent();
            foreach (var child in lSystemString.children) {
                _rewrite_recursive(child, new_parent);
            }
            var node = lSystemString;

            while (node.mainChild != null) {
                var new_node = _rewrite(node.mainChild);
                new_node.parent = new_parent;

                new_parent.mainChild = new_node;
                new_parent = new_node.newParent();
                foreach (var child in node.mainChild.children) {
                    _rewrite_recursive(child, new_parent);
                }
                node = node.mainChild;
            }
            lSystemString = new_root;
        }
        //private void rewrite(ref LSystemNode currentNode)
    }
    //abstract class LSystemWordParser { }
    //abstract class LSystemRulesParser { }

    class A {
        public int B { get; set; }
        public A[] AA { get; set; }
    }
    class Program {
        static void runParametric() {

            Dictionary<string, int> variableIndex = new Dictionary<string, int> {
                {"a", 0},
                {"b", 1},

            };
            var predecesor1 = LSystemFileParser.parsePredecesor("B(a,b)");
            var predecesor2 = LSystemFileParser.parsePredecesor("A(a)");
            var condition1 = MathExpressionComparasionParser.parse(variableIndex, "a>b");
            var condition2 = MathExpressionComparasionParser.parse(variableIndex, "a>5");

            var builder = new LSystemWordGeneratorBuilder(variableIndex);

            var consequent1 = (LSystemNodeGenerator)LSystemFileParser.parseWord("B(0,b)[A(a/2)]A(a/2)", builder);
            var consequent2 = (LSystemNodeGenerator)LSystemFileParser.parseWord("B(a+3.5,b+1)", builder);
            var consequent3 = (LSystemNodeGenerator)LSystemFileParser.parseWord("B(0,a)", builder);
            var consequent4 = (LSystemNodeGenerator)LSystemFileParser.parseWord("A(a+1)", builder);

            var rule1 = new LSystemRuleParametric(predecesor1, new MathExpressionComparison[] { condition1 }, consequent1 );
            var rule2 = new LSystemRuleParametric(predecesor1, new MathExpressionComparison[] { }, consequent2 );
            var rule3 = new LSystemRuleParametric(predecesor2, new MathExpressionComparison[] { condition2 }, consequent3 );
            var rule4 = new LSystemRuleParametric(predecesor2, new MathExpressionComparison[] { }, consequent4);

            var axiom = LSystemFileParser.parseWord("A(6)", new LSystemWordBuilder());

            var rules = new List<LSystemRule> { rule1, rule2, rule3, rule4 };
            var evaluator = new LSystemEvaluator(axiom, rules);
            Console.WriteLine(evaluator.lSystemString.ToString());
            for (int i = 0; i < 20; i++) {
                evaluator.rewrite();
                Console.WriteLine(evaluator.lSystemString.ToString());
            }
        }
        static void runContext() {
            var axiom = LSystemFileParser.parseWord("A(1)A(1)A(1)A(1)A(1)A(1)B(2)", new LSystemWordBuilder());
            var predecesor = LSystemFileParser.parsePredecesor("A(a)");
            var predecesor1 = LSystemFileParser.parsePredecesor("B(b)");

            var contex = new LSystemContext();
            contex.setOnlySucceeding(new LSystemNodeLiteral("B", 1));

            var contex1 = new LSystemContext();
            contex1.setOnlyPreceding(new LSystemNodeLiteral("A", 1));


            Dictionary<string, int> variableIndex = new Dictionary<string, int> {
                { "a", 0 },
                { "b", 1 },
            };
            var builder = new LSystemWordGeneratorBuilder(variableIndex);
            var consequent = (LSystemNodeGenerator)LSystemFileParser.parseWord("B(a)", builder);
            var consequent1 = (LSystemNodeGenerator)LSystemFileParser.parseWord("A(1)", builder);
            var consequent2 = (LSystemNodeGenerator)LSystemFileParser.parseWord("A(a+1)", builder);

            MathExpressionComparison comparison = MathExpressionComparasionParser.parse(variableIndex, "a>b");
            MathExpressionComparison comparison1 = MathExpressionComparasionParser.parse(variableIndex, "a<=b");

            var rule = new LSystemRuleParametricStochasticContext(predecesor, new MathExpressionComparison[] { comparison }, consequent, contex);
            var rule1 = new LSystemRuleParametricStochasticContext(predecesor, new MathExpressionComparison[] { }, consequent2, contex);
            var rule2 = new LSystemRuleParametricStochasticContext(predecesor1, new MathExpressionComparison[] { comparison }, consequent1, contex1);


            var rules = new List<LSystemRule> { rule, rule1, rule2 };
            var evaluator = new LSystemEvaluator(axiom, rules);
            Console.WriteLine(evaluator.lSystemString.ToString());
            for (int i = 0; i < 20; i++) {
                evaluator.rewrite();
                Console.WriteLine(evaluator.lSystemString.ToString());
            }

        }
        static void runFile() {
            Console.WriteLine("Enter filepath:");
            string filepath = Console.ReadLine();
            var evaluator = LSystemFileParser.parseLSystem(new StreamReader(filepath));
            Console.WriteLine(evaluator.lSystemString.ToString());
            while (true) {
                Console.ReadLine();
                evaluator.rewrite();
                Console.WriteLine(evaluator.lSystemString.ToString());
            }

        }
        static void Main(string[] args) {
            runFile();
            //runContext();
            //var Al = new LSystemNodeLiteral("Al", 0);
            //var Ar = new LSystemNodeLiteral("Ar", 0);
            //var Bl = new LSystemNodeLiteral("Bl", 0);
            //var Br = new LSystemNodeLiteral("Br", 0);

            //var root = new LSystemNode(Ar);
            //var rule_list = new List<LSystemRule>();

            //var result = new LSystemNode(Al);
            //result.children.Add(new LSystemNode(Br));

            //rule_list.Add(new LSystemRuleBasic(Ar, result));

            //result = new LSystemNode(Bl);
            //result.children.Add(new LSystemNode(Ar));
            //rule_list.Add(new LSystemRuleBasic(Al, result));

            //result = new LSystemNode(Ar);
            //rule_list.Add(new LSystemRuleBasic(Br, result));

            //result = new LSystemNode(Al);
            //rule_list.Add(new LSystemRuleBasic(Bl, result));


            //var evaluator = new LSystemEvaluator(root, rule_list);
            //for (int i=0; i < 5; i++) {
            //    Console.WriteLine(evaluator.lSystemString.ToString());
            //    evaluator.rewrite();
            //}
            //Console.WriteLine(evaluator.lSystemString.ToString());




            //var node = LSystemFileParser.parseWord("A[AA][A]AB(1,2)[A(1)]C", new LSystemWordBuilder());
            //Console.WriteLine(node.ToString());
        }
    }
}