using System;
using System.Collections.Generic;
using System.IO;

namespace SteamLanguageParser
{
    class Node
    {
        public List<Node> ChildNodes { get; private set; }
        public string Name { get; set; }

        public Node()
        {
            ChildNodes = [];
        }
    }

    class ClassNode : Node
    {
        public Symbol Ident { get; set; }
        public Symbol Parent { get; set; }
        public bool Emit { get; set; }
    }

    class PropNode : Node
    {
        public string Flags { get; set; }
        public string FlagsOpt { get; set; }
        public Symbol Type { get; set; }
        public List<Symbol> Default { get; set; }
        public string Obsolete { get; set; }
        public string Removed { get; set; }
        public bool Emit { get; set; }

        public PropNode()
        {
            Default = [];
            Emit = true;
        }
    }

    class EnumNode : Node
    {
        public string Flags { get; set; }
        public Symbol Type { get; set; }
    }

    class TokenAnalyzer
    {
        public static Node Analyze(Queue<Token> tokens)
        {
            Node root = new Node();

            while (tokens.Count > 0)
            {
                Token cur = tokens.Dequeue();

                switch (cur.Name)
                {
                    case "EOF":
                        break;
                    case "preprocess":
                        Token text = Expect(tokens, "string");

                        if (cur.Value == "import")
                        {
                            Queue<Token> parentTokens = LanguageParser.TokenizeString(File.ReadAllText(text.Value), text.Value);

                            Node newRoot = Analyze(parentTokens);

                            foreach (Node child in newRoot.ChildNodes)
                            {
                                root.ChildNodes.Add(child);
                            }
                        }
                        break;
                    case "identifier":
                        switch (cur.Value)
                        {
                            case "class":
                                {
                                    Token name = Expect(tokens, "identifier");
                                    Token ident = null, parent = null;

                                    Token op1 = Optional(tokens, "operator", "<");
                                    if (op1 != null)
                                    {
                                        ident = Expect(tokens, "identifier");
                                        Expect(tokens, "operator", ">");
                                    }

                                    Token expect = Optional(tokens, "identifier", "expects");
                                    if (expect != null)
                                    {
                                        parent = Expect(tokens, "identifier");
                                    }

                                    Token removed = Optional(tokens, "identifier", "removed");

                                    ClassNode cnode = new ClassNode
                                    {
                                        Name = name.Value
                                    };

                                    if (ident != null)
                                    {
                                        cnode.Ident = SymbolLocator.LookupSymbol(root, ident.Value, false);
                                    }

                                    if (parent != null)
                                    {
                                        //cnode.Parent = SymbolLocator.LookupSymbol(root, parent.Value, true);
                                    }

                                    if (removed != null)
                                    {
                                        cnode.Emit = false;
                                    }
                                    else
                                    {
                                        cnode.Emit = true;
                                    }

                                    root.ChildNodes.Add(cnode);
                                    ParseInnerScope(tokens, cnode, root);
                                }
                                break;
                            case "enum":
                                {
                                    Token name = Expect(tokens, "identifier");
                                    Token datatype = null;

                                    Token op1 = Optional(tokens, "operator", "<");
                                    if (op1 != null)
                                    {
                                        datatype = Expect(tokens, "identifier");
                                        Expect(tokens, "operator", ">");
                                    }

                                    Token flag = Optional(tokens, "identifier", "flags");

                                    EnumNode enode = new EnumNode
                                    {
                                        Name = name.Value
                                    };

                                    if (flag != null)
                                    {
                                        enode.Flags = flag.Value;
                                    }

                                    if (datatype != null)
                                    {
                                        enode.Type = SymbolLocator.LookupSymbol(root, datatype.Value, false);
                                    }


                                    root.ChildNodes.Add(enode);
                                    ParseInnerScope(tokens, enode, root);
                                }
                                break;
                        }
                        break;
                }
            }

            return root;
        }

        private static void ParseInnerScope(Queue<Token> tokens, Node parent, Node root)
        {
            Expect(tokens, "operator", "{");
            Token scope2 = Optional(tokens, "operator", "}");

            while (scope2 == null)
            {
                PropNode pnode = new PropNode();

                Token t1 = tokens.Dequeue();

                Token t1op1 = Optional(tokens, "operator", "<");

                if (t1op1 != null)
                {
                    var flagop = Expect(tokens, "identifier");
                    Expect(tokens, "operator", ">");

                    pnode.FlagsOpt = flagop.Value;
                }

                Token t2 = Optional(tokens, "identifier");
                Token t3 = Optional(tokens, "identifier");

                if (t3 != null)
                {
                    pnode.Name = t3.Value;
                    pnode.Type = SymbolLocator.LookupSymbol(root, t2.Value, false);
                    pnode.Flags = t1.Value;
                }
                else if (t2 != null)
                {
                    pnode.Name = t2.Value;
                    pnode.Type = SymbolLocator.LookupSymbol(root, t1.Value, false);
                }
                else
                {
                    pnode.Name = t1.Value;
                }

                Token defop = Optional(tokens, "operator", "=");

                if (defop != null)
                {
                    while (true)
                    {
                        Token value = tokens.Dequeue();
                        pnode.Default.Add(SymbolLocator.LookupSymbol(root, value.Value, false));

                        if (Optional(tokens, "operator", "|") != null)
                        {
                            continue;
                        }

                        Expect(tokens, "terminator", ";");
                        break;
                    }
                }
                else
                {
                    Expect(tokens, "terminator", ";");
                }

                Token obsolete = Optional(tokens, "identifier", "obsolete");
                if (obsolete != null)
                {
                    // Obsolete identifiers are output when generating the language, but include a warning
                    pnode.Obsolete = "";

                    Token obsoleteReason = Optional(tokens, "string");

                    if (obsoleteReason != null)
                    {
                        pnode.Obsolete = obsoleteReason.Value;
                    }
                }

                Token removed = Optional(tokens, "identifier", "removed");
                if (removed != null)
                {
                    // Removed identifiers are not output when generating the language
                    pnode.Emit = false;

                    // Consume and record the removed reason so it's available in the node graph
                    pnode.Removed = "";

                    Token removedReason = Optional(tokens, "string");

                    if (removedReason != null)
                    {
                        pnode.Removed = removedReason.Value;
                    }
                }

                parent.ChildNodes.Add(pnode);

                scope2 = Optional(tokens, "operator", "}");
            }
        }

        private static Token Expect(Queue<Token> tokens, string name)
        {
            Token peek = tokens.Peek();

            if (peek == null)
            {
                return new Token("EOF", "");
            }

            if (peek.Name != name)
            {
                throw new Exception("Expecting " + name);
            }

            return tokens.Dequeue();
        }

        private static Token Expect(Queue<Token> tokens, string name, string value)
        {
            Token peek = tokens.Peek();

            if (peek == null)
            {
                return new Token("EOF", "");
            }

            if (peek.Name != name || peek.Value != value)
            {
                if (peek.Source.HasValue)
                {
                    var source = peek.Source.Value;
                    throw new Exception($"Expecting {name} '{value}', but got '{peek.Value}' at {source.FileName} {source.StartLineNumber},{source.StartColumnNumber}-{source.EndLineNumber},{source.EndColumnNumber}");
                }
                else
                {
                    throw new Exception("Expecting " + name + " '" + value + "', but got '" + peek.Value + "'");
                }
            }

            return tokens.Dequeue();
        }

        private static Token Optional(Queue<Token> tokens, string name)
        {
            Token peek = tokens.Peek();

            if (peek == null)
            {
                return new Token("EOF", "");
            }

            if (peek.Name != name)
            {
                return null;
            }

            return tokens.Dequeue();
        }

        private static Token Optional(Queue<Token> tokens, string name, string value)
        {
            Token peek = tokens.Peek();

            if (peek == null)
            {
                return new Token("EOF", "");
            }

            if (peek.Name != name || peek.Value != value)
            {
                return null;
            }

            return tokens.Dequeue();
        }
    }
}
