using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using SqlDynamite.Common;

namespace SqlDynamite
{
    public class SyntaxProvider
    {
        public readonly Color FunctionColor = Color.Magenta;
        public readonly Color SearchColor = Color.Red;
        public readonly Color SearchBackgroundColor = Color.LightYellow;

        private readonly Color KeywordColor = Color.Blue;
        private readonly Color TypeColor = Color.Purple;
        private readonly Color CommentColor = Color.Gray;
        private readonly Color VariableColor = Color.MediumPurple;
        private readonly Color TempTableColor = Color.Brown;
        private readonly Color StringColor = Color.SandyBrown;
        private readonly Color NumberColor = Color.Green;
        private readonly Dictionary<string, Color> TagsList = new Dictionary<string, Color>();

        private readonly char[] Separators =
        {
            ' ', '\n', '\r', '\t', ',', '.', '+', '=', '<', '>', '(', ')', '[', ']',
            ';', ':', '?', '!'
        };

        private readonly string[] jsKeywords =
        {
            "arguments", "await", "break", "case", "catch",
            "class", "const", "continue", "debugger", "default", 
            "delete", "do", "else", "enum", "eval",
            "export", "extends", "false", "finally", "for", 
            "function", "if", "implements", "import", "in", 
            "instanceof", "interface", "let", "new", "null", 
            "package", "private", "protected", "public", "return", 
            "static", "super", "switch", "this", "throw", 
            "true", "try", "typeof", "var", "void",
            "while", "with", "yield"
        };

        private readonly string[] jsProperties =
        {
            "Array", "Date", "eval", "function", "hasOwnProperty",
            "Infinity", "isFinite", "isNaN", "isPrototypeOf", "length",
            "Math", "NaN", "name", "Number", "Object",
            "prototype", "String", "toString", "undefined", "valueOf"
        };

        private readonly string[] cqlKeywords =
        {
            "ALL", "AS", "CLUSTERING", "COMPACT", "CONSISTENCY", "COUNT",
            "DISTINCT", "EXISTS", "WRITETIME", "VALUES", "USER", "USERS", "FILTERING", "TTL", "NOSUPERUSER",
            "PERMISSION", "PERMISSIONS", "STATIC", "STORAGE", "SUPERUSER", "KEY", "LEVEL", "TYPE", "ADD", "AGGREGATE",
            "ALLOW", "ALTER", "AND", "ANY", "APPLY", "ASC", "AUTHORIZE", "BATCH", "BEGIN", "BY", "COLUMNFAMILY",
            "CREATE", "DELETE", "DESC", "DROP", "EACH_QUORUM", "ENTRIES", "FROM", "FULL", "GRANT", "IF", "IN", "INDEX",
            "INFINITY", "INSERT", "INTO", "KEYSPACE", "KEYSPACES", "LIMIT", "LOCAL_ONE", "LOCAL_QUORUM",
            "MATERIALIZED", "MODIFY", "NAN", "NORECURSIVE", "NOT", "OF", "ON", "ONE", "ORDER", "PARTITION", "PASSWORD",
            "PER", "PRIMARY", "QUORUM", "RENAME", "REVOKE", "SCHEMA", "SELECT", "TABLE", "THREE", "TO", "TOKEN",
            "TRUNCATE", "TWO", "UNLOGGED", "UPDATE", "USE", "USING", "VIEW", "WHERE", "WITH"
        };

        private readonly string[] cqlTypes =
        {
            "ASCII", "BIGINT", "BLOB", "BOOLEAN",
            "COUNTER", "CUSTOM", "DECIMAL", "DOUBLE",
            "FLOAT", "FROZEN", "INT", "LIST", "MAP",
            "TEXT", "TIMESTAMP", "TIMEUUID", "TUPLE",
            "UUID", "VARCHAR", "VARINT", "SET", "INET",
            "DATE", "TIME"
        };

        private readonly string[] cypherKeywords =
        {
            "CALL", "CREATE", "DELETE", "DETACH", "EXISTS", "FOREACH", "LOAD", "MATCH", "MERGE", "OPTIONAL", "REMOVE",
            "RETURN", "SET", "START", "UNION", "UNWIND", "WITH", "LIMIT", "ORDER", "SKIP", "WHERE", "YIELD", "ASC",
            "ASCENDING", "ASSERT", "BY", "CSV", "DESC", "DESCENDING", "ON", "ALL", "CASE", "ELSE", "END", "THEN",
            "WHEN", "AND", "AS", "CONTAINS", "DISTINCT", "ENDS", "IN", "IS", "NOT", "OR", "STARTS", "XOR",
            "CONSTRAINT", "CREATE", "DROP", "EXISTS", "INDEX", "NODE", "KEY", "UNIQUE", "INDEX", "JOIN", "PERIODIC",
            "COMMIT", "SCAN", "USING", "false", "null", "true", "ADD", "DO", "FOR", "MANDATORY", "OF", "REQUIRE", "SCALAR"
        };

        private readonly string[] cypherTypes =
        {
            "Integer", "Float", "String", "Boolean", "Point",
            "Date", "Time", "LocalTime", "DateTime", "LocalDateTime", "Duration",
            "Node", "Relationship", "Path", "List", "Map"
        };

        private readonly string[] cypherFunctions =
        {
            "all", "any", "exists", "isEmpty", "none", "single", "coalesce", "endNode", "head", "id", "last",
            "length", "properties", "randomUUID", "size", "startNode", "timestamp", "toBoolean", "toBooleanOrNull",
            "toFloat", "toFloatOrNull", "toInteger", "toIntegerOrNull", "type", "avg", "collect", "count", "max",
            "min", "percentileCont", "percentileDisc", "stDev", "stDevP", "sum", "keys", "labels", "nodes", "range",
            "reduce", "relationships", "reverse", "tail", "toBooleanList", "toFloatList", "toIntegerList",
            "toStringList", "abs", "ceil", "floor", "rand", "round", "sign", "e", "exp", "log", "log10", "sqrt",
            "acos", "asin", "atan", "atan2", "cos", "cot", "degrees", "haversin", "pi", "radians", "sin", "tan",
            "left", "lTrim", "replace", "reverse", "right", "rTrim", "split", "substring", "toLower", "toString",
            "toStringOrNull", "toUpper", "trim", "distance", "point", "linenumber", "file"
        };

        public void LoadLanguage(string language)
        {
            string[] keywords = null;
            string[] types = null;
            if (language == "JavaScript")
            {
                keywords = jsKeywords;
                types = jsProperties;
            }
            else if (language == "CQL")
            {
                keywords = cqlKeywords;
                types = cqlTypes;
            }
            else if (language == "Cypher")
            {
                keywords = cypherKeywords;
                types = cypherTypes;
                foreach (string str in cypherFunctions)
                {
                    if (!TagsList.ContainsKey(str))
                    {
                        TagsList[str] = FunctionColor;
                    }
                }
            }

            foreach (string str in keywords)
            {
                if (!TagsList.ContainsKey(str))
                {
                    TagsList[str] = KeywordColor;
                }
            }

            foreach (string str in types)
            {
                if (!TagsList.ContainsKey(str))
                {
                    TagsList[str] = TypeColor;
                }
            }
        }

        public void UnloadLanguage()
        {
            TagsList.Clear();
        }

        public void LoadWords(string words, Color color)
        {
            string[] strs = words.Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries);

            for (int index = 0; index < strs.Length; index++)
            {
                strs[index] = strs[index].Replace("\r", "");
            }

            foreach (string str in strs)
            {
                if (!TagsList.ContainsKey(str.ToLower()) && !TagsList.ContainsKey(str.ToUpper()))
                {
                    TagsList[str.ToLower()] = color;
                    TagsList[str.ToUpper()] = color;
                }
            }
        }

        public void UnloadWords()
        {
            var indexesToDelete = TagsList.Where(pair => pair.Value == FunctionColor).ToList();
            foreach (KeyValuePair<string, Color> pair in indexesToDelete)
            {
                TagsList.Remove(pair.Key);
            }
            TagsList.Clear();
        }
        
        public void InitializeSyntaxHighlighter(SyntaxHighlightingTextBox box, bool syntaxHighlighting)
        {
            box.Separators.Clear();
            box.HighlightDescriptors.Clear();

            foreach (char separator in Separators)
            {
                box.Separators.Add(separator);
            }

            if (syntaxHighlighting)
            {
                foreach (KeyValuePair<string, Color> tag in TagsList)
                {
                    box.HighlightDescriptors.Add(new HighlightDescriptor(tag.Key, tag.Value, null, DescriptorType.Word,
                                                                          DescriptorRecognition.WholeWord));
                }

                box.HighlightDescriptors.Add(new HighlightDescriptor("/*", "*/", CommentColor, null,
                                                                      DescriptorType.ToCloseToken,
                                                                      DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("--", CommentColor, null,
                                                                      DescriptorType.ToEOL,
                                                                      DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("//", CommentColor, null,
                                                                      DescriptorType.ToEOL,
                                                                      DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("@", VariableColor, null,
                                                                      DescriptorType.Word,
                                                                      DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("#", TempTableColor, null,
                                                                      DescriptorType.Word,
                                                                      DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("'", StringColor, null,
                                                                      DescriptorType.Word,
                                                                      DescriptorRecognition.Contains));
                box.HighlightDescriptors.Add(new HighlightDescriptor("`", StringColor, null,
                                                                      DescriptorType.Word,
                                                                      DescriptorRecognition.Contains));
                box.HighlightDescriptors.Add(new HighlightDescriptor("\"", StringColor, null,
                                                                      DescriptorType.Word,
                                                                      DescriptorRecognition.Contains));
                #region Numeric
                box.HighlightDescriptors.Add(new HighlightDescriptor("0", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("1", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("2", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("3", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("4", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("5", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("6", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("7", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("8", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("9", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                box.HighlightDescriptors.Add(new HighlightDescriptor("-", NumberColor, null, DescriptorType.Word, DescriptorRecognition.StartsWith));
                #endregion
            }
        }

        public void SetKeywordColors(MetadataFinder finder)
        {
            if (finder is SsasMetadataFinder ssasMetadataFinder && ssasMetadataFinder._connection is WrapperConnection)
            {
                var schemaReservedWords = ssasMetadataFinder.GetKeywords().Tables[0];
                var keywords = from DataRow row in schemaReservedWords.Rows select row[0].ToString();
                LoadWords(string.Join(Environment.NewLine, keywords), KeywordColor);
                var schemaFunctions = ssasMetadataFinder.GetFunctions().Tables[0];
                var functions = from DataRow row in schemaFunctions.Rows select row[0].ToString();
                LoadWords(string.Join(Environment.NewLine, functions), FunctionColor);
            }
            else
            {
                var schema = finder.GetConnection().GetSchema();
                foreach (DataRow dataRow in schema.Rows)
                {
                    if (dataRow[0].Equals("DataTypes"))
                    {
                        var schemaDataTypes = finder.GetConnection().GetSchema("DataTypes");
                        var dataTypes = from DataRow row in schemaDataTypes.Rows select row[0].ToString();
                        LoadWords(string.Join(Environment.NewLine, dataTypes), TypeColor);
                    }
                    else if (dataRow[0].Equals("ReservedWords"))
                    {
                        var schemaReservedWords = finder.GetConnection().GetSchema("ReservedWords");
                        var keywords = from DataRow row in schemaReservedWords.Rows
                            select string.Join(Environment.NewLine, row[0].ToString().Split(' '));
                        LoadWords(string.Join(Environment.NewLine, keywords), KeywordColor);
                    }
                }
            }
        }
    }
}