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


//TODO add parsing for more than single letter

namespace ConsoleLSystem {
    abstract public class LSystemGeneralWordBuilder {
        abstract public LSystemNode createLSystemNode(string name, int values_number);
        abstract public void fillArguments(LSystemNode node, string[] arguments_strings);
    }
    public class LSystemWordBuilder : LSystemGeneralWordBuilder {
        public override LSystemNode createLSystemNode(string name, int values_number) {
            return new LSystemNode(new LSystemNodeLiteral(name, values_number));
        }


        public override void fillArguments(LSystemNode node, string[] arguments_strings) {
            for (int i = 0; i < arguments_strings.Length; i++) {
                node.literal.values[i] = float.Parse(arguments_strings[i], NumberStyles.Any, CultureInfo.InvariantCulture);
            }
        }
    }

    public class LSystemWordGeneratorBuilder : LSystemGeneralWordBuilder {
        public Dictionary<string, int> variableIndex { get;}
        public LSystemWordGeneratorBuilder(Dictionary<string,int> variable_index) {
            this.variableIndex = variable_index;
        }
        public override LSystemNode createLSystemNode(string name, int values_number) {
            return new LSystemNodeGenerator(name, values_number);
        }

        public override void fillArguments(LSystemNode node, string[] arguments_strings) {
            MathExpression[] expressions = arguments_strings.Select(argument => new MathExpression(variableIndex, argument)).ToArray();
            LSystemNodeGenerator ng = (LSystemNodeGenerator)node;
            ng.fillArguments(expressions);
        }
        
    }
    public class LSystemFileParser {
        //LSystemWordParser wordParser;
        //LSystemRulesParser ruleParser;

        //public LSystemFileParser(LSystemWordParser wordParser, LSystemRulesParser ruleParser) {
        //    this.wordParser = wordParser;
        //    this.ruleParser = ruleParser;
        //}
        public static int countParenthesisEnd(string line,char opening_char, char closing_char) {
            var parenthesises_number = 1;
            var length = -1;
            while (parenthesises_number > 0) {
                length++;
                var c = line[1 + length];
                if (c == opening_char) {
                    parenthesises_number++;
                }
                if (c == closing_char) {
                    parenthesises_number--;
                }
            }
            return length;
        }
        public static bool parenthesisCheck(string line) {
            var parenthesises_number = 0;
            var brackets_number = 0;
            foreach (var s in line) {
                if (s == '(') {
                    parenthesises_number++;
                }
                if (s == ')') {
                    parenthesises_number--;
                }
                if (s == '[') {
                    brackets_number++;
                }
                if (s == ']') {
                    brackets_number--;
                }
                if (brackets_number<0 || parenthesises_number < 0) {
                    return false;
                }

            }
            return (brackets_number == 0 && parenthesises_number == 0);
        }

        public static LSystemNode parseWord(string line, LSystemGeneralWordBuilder builder) {
            var read_characters = 0;
            LSystemNode node;
            if (line[0] == '[') {
                node = builder.createLSystemNode("", 0);
            }
            else {
                var literal_name = line.Substring(0, 1);
                if (line.Length>1 && line[1] == '(') {
                    var length = countParenthesisEnd(line.Substring(1), '(', ')');
                    var values_string = line.Substring(2, length).Split(',');
                    node = builder.createLSystemNode(literal_name, values_string.Length);

                    builder.fillArguments(node, values_string);
                    //Leter and: (, )
                    read_characters += length + 3;
                    
                }
                else {
                    read_characters += 1;
                    node = builder.createLSystemNode(line.Substring(0,1),0);
                }
            }
            while (line.Length > read_characters && line[read_characters] == '[') {
                 
                var brackets_number = 1;
                var length = countParenthesisEnd(line.Substring(read_characters), '[', ']');
                var child_node = parseWord(line.Substring(read_characters + 1, length),builder);
                read_characters += length + 2;
                child_node.parent = node;
                node.children.Add(child_node);
            }
            if (read_characters < line.Length) {
                var child_node = parseWord(line.Substring(read_characters),builder);
                child_node.parent = node;
                node.mainChild = (child_node);
            }
            return node;
        }

        public static LSystemNodeLiteralVariable parsePredecesor(string line) {
            var name = line.Substring(0, 1);
            var varable_index = new Dictionary<string, int>();
            if (line.Length > 1) {
                foreach (var i in line.Substring(2, line.Length - 3).Split(',').Select((name, Index) => new { name, Index })) {
                    varable_index.Add(i.name, i.Index);
                };
            }
            return new LSystemNodeLiteralVariable(name, varable_index.Count, varable_index);
        }
        //private static String[] parseIgnore(string line) {
        //    var items = line.Trim().Split(' ');
        //    var resut = List 

        //}

        public static LSystemEvaluator parseLSystem(StreamReader sr) {
            string line;
            int line_number = 0;
            var ignored = new String[0];
            LSystemNode axiom = null;
            List<LSystemRule> rules = null;
            while ((line = sr.ReadLine()) != null) {
                line_number++;
                if (line.Trim() == "#axiom") {
                    line = sr.ReadLine();
                    if (!parenthesisCheck(line)) {
                        throw new Exception(String.Format("In line {0} invalid syntax", line_number));
                    }
                    axiom = parseWord(line,new LSystemWordBuilder());
                }
                if (line.Trim().Split(' ')[0].Trim() == "#ignore") {
                    ignored = line.Trim().Split(' ').Where(x=> x!= "#ignore" && x.Length>0).Select(x => x.Trim()).ToArray();
                }
                if (line.Trim() == "#rules") {
                    rules = parseLSystemRules(sr);
                }

            }
            return new LSystemEvaluator(axiom, rules, ignored);
        }

        private static List<LSystemRule> parseLSystemRules(StreamReader sr) {
            List<LSystemRule> rules = new List<LSystemRule>();
            Regex separator = new Regex(@"-(\d*\.?\d*){1}>");
            Regex weight_regex = new Regex(@"p=(\d+\.?\d*){1}");
            string line;
            while ((line = sr.ReadLine()) != null && line.Trim() != "#rules end") {
                try {
                    if (line.Length == 0 || line[0] == '#') {
                        continue;
                    }
                    MathExpressionComparison[] conditions = new MathExpressionComparison[0];
                    var split = separator.Match(line);
                    var line_parts = line.Split(new string[] { split.Value }, StringSplitOptions.None);
                    // check conditions;
                    //parse predecesor
                    var _t = line_parts[0].Split(':');
                    var predecesor_string = _t[0];
                    LSystemNodeLiteralVariable predecesor;
                    LSystemContext context;
                    //check if contains < or >

                    // check context 
                    getPredecesorContext(predecesor_string, out predecesor, out context);

                    if (_t.Length > 1) {
                        conditions = _t[1].Trim().Split(';').Select(text => MathExpressionComparasionParser.parse(predecesor.variableIndex, text.Trim())).ToArray();
                    }
                    // tochastic rule parsing
                    var sucsessor_string = line_parts[1];
                    var probabilities_list = new List<float>();
                    var consequents_list = new List<LSystemNodeGenerator>();
                    if (sucsessor_string.Trim() == "#stochastic") {
                        while ((line = sr.ReadLine()) != null && line.Trim() != "#stochastic end") {
                            if (line[0] == '#') {
                                continue;
                            }
                            var weight_string = weight_regex.Match(line);
                            var weight = float.Parse(weight_string.Value.Substring(2), NumberStyles.Any, CultureInfo.InvariantCulture);
                            probabilities_list.Add(weight);

                            var prob_len = weight_string.Value.Length;
                            consequents_list.Add((LSystemNodeGenerator)parseWord(line.Substring(prob_len).Trim(), new LSystemWordGeneratorBuilder(predecesor.variableIndex)));
                        }
                    }
                    else {
                        consequents_list.Add((LSystemNodeGenerator)parseWord(sucsessor_string.Trim(), new LSystemWordGeneratorBuilder(predecesor.variableIndex)));
                    }
                    //context 
                    if (context != null) {
                        if (consequents_list.Count > 1) {
                            rules.Add(new LSystemRuleParametricStochasticContext(predecesor, conditions, consequents_list.ToArray(), probabilities_list.ToArray(), context));
                        }
                        else {
                            rules.Add(new LSystemRuleParametricStochasticContext(predecesor, conditions, consequents_list[0], context));
                        }
                    }
                    else {
                        if (consequents_list.Count > 1) {
                            rules.Add(new LSystemRuleParametricStochastic(predecesor, conditions, consequents_list.ToArray(), probabilities_list.ToArray()));
                        }
                        else {
                            rules.Add(new LSystemRuleParametric(predecesor, conditions, consequents_list[0]));
                        }
                    }
                    //var conditions = parts[0];
                    //var result 
                }
                catch (Exception e) {
                    sr.Close();
                    throw new Exception(String.Format("error in {0} with a message \n {1}", line,e.Message));
                }
            }
            return rules;
        }

        private static void getPredecesorContext(string predecesor_string, out LSystemNodeLiteralVariable predecesor, out LSystemContext context) {
            string partent_context_str = null;
            string child_context_str = null;
            string mid_context_str = null;
            int mid_start = 0;

            for (int i = 0; i < predecesor_string.Length; i++) {
                if (predecesor_string[i] == '<' && partent_context_str == null) {
                    partent_context_str = predecesor_string.Substring(0, i).Trim();
                    mid_start = i+1;
                    //Console.WriteLine(i);
                }
                if (predecesor_string[i] == '>' && child_context_str == null) {
                    child_context_str = predecesor_string.Substring(i + 1).Trim();
                    mid_context_str = predecesor_string.Substring(mid_start, i - mid_start).Trim();
                    //Console.WriteLine(i);
                }

            }
            if (mid_context_str == null) {
                mid_context_str = predecesor_string.Substring(mid_start).Trim();
            }
            LSystemNodeLiteralVariable parent = null;
            LSystemNodeLiteralVariable child = null;
            predecesor = LSystemFileParser.parsePredecesor(mid_context_str);
            if (partent_context_str != null) {
                parent = LSystemFileParser.parsePredecesor(partent_context_str);
                foreach (KeyValuePair<string, int> kvp in new Dictionary<string, int>(predecesor.variableIndex)) {
                    //textBox3.Text += ("Key = {0}, Value = {1}", kvp.Key, kvp.Value);
                    predecesor.variableIndex[kvp.Key] = kvp.Value + parent.variableIndex.Count;
                }
                foreach (KeyValuePair<string, int> kvp in parent.variableIndex) {
                    //textBox3.Text += ("Key = {0}, Value = {1}", kvp.Key, kvp.Value);
                    predecesor.variableIndex.Add(kvp.Key, kvp.Value);
                }
            }
            if (child_context_str != null) {
                int size = predecesor.variableIndex.Count;
                child = LSystemFileParser.parsePredecesor(child_context_str);
                foreach (KeyValuePair<string, int> kvp in child.variableIndex) {
                    //textBox3.Text += ("Key = {0}, Value = {1}", kvp.Key, kvp.Value);
                    predecesor.variableIndex.Add(kvp.Key, kvp.Value + size);
                }
            }
            if (child_context_str != null) {
                if (partent_context_str != null) {
                    context = new LSystemContext(parent, child);
                }
                else {
                    context = new LSystemContext();
                    context.setOnlySucceeding(child);
                }
            }
            else if (partent_context_str != null) {
                context = new LSystemContext();
                context.setOnlyPreceding(parent);
            }
            else {
                context = null;
            }
        }
    }
}