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

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 parseContextLiteral(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);
    }

    public static LSystemNodeLiteralVariable[] parseSuccessorsContext(string line)
    {
        List<LSystemNodeLiteralVariable> result = new List<LSystemNodeLiteralVariable>();
        bool bracketStarted = false;
        int bracketStartedIndex = 0;
        for (int i = 0; i < line.Length; i++)
        {
            if (line[i] == ']')
            {
                if (!bracketStarted)
                {
                    throw new Exception("invalid syntax");
                }
                else
                {
                    result.Add(parseContextLiteral(line.Substring(bracketStartedIndex, i - bracketStartedIndex)));
                    bracketStarted = false;
                }
            }
            else if (line[i] == '[')
            {
                if (bracketStarted)
                {
                    throw new Exception("invalid syntax");
                }
                else
                {
                    bracketStarted = true;
                    bracketStartedIndex = i + 1;
                }
            }
            else if (!bracketStarted)
            {
                result.Add(parseContextLiteral(line.Substring(i)));
                break;
            }
        }
        return result.ToArray();
    }
    //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[] children = null;
        predecesor = LSystemFileParser.parseContextLiteral(mid_context_str);
        if (partent_context_str != null)
        {
            parent = LSystemFileParser.parseContextLiteral(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;
            children = LSystemFileParser.parseSuccessorsContext(child_context_str);
            int offset = 0;
            foreach (var child in children)
            {

                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 + offset);
                }
                offset += child.variableIndex.Count;
            }
        }
        if (child_context_str != null)
        {
            if (partent_context_str != null)
            {
                context = new LSystemContext(parent, children);
            }
            else
            {
                context = new LSystemContext();
                context.setOnlySucceeding(children);
            }
        }
        else if (partent_context_str != null)
        {
            context = new LSystemContext();
            context.setOnlyPreceding(parent);
        }
        else
        {
            context = null;
        }
    }
}