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

namespace SharpDdl.Parsers
{
    internal static class ParserHelper
    {
        public static string[] ParseIntoTokens(string s, char separator)
        {
            List<string> tokens = new List<string>();

            var builder = new StringBuilder();

            bool isReadingStringLiteral = false;

            for (int i = 0; i < s.Length; i++)
            {
                char c = s[i];

                if (isReadingStringLiteral)
                {
                    if (c == Symbols.EscapeChar)
                    {
                        string escaped = ParseEscape(s, ref i);
                        builder.Append(escaped);
                        continue;
                    }

                    if (c == Symbols.DoubleQuote)
                    {
                        isReadingStringLiteral = false;
                    }

                    builder.Append(c);
                }
                else
                {
                    if (c == separator)
                    {
                        tokens.Add(builder.ToString());
                        builder.Clear();
                        continue;
                    }

                    if (c == Symbols.DoubleQuote)
                    {
                        isReadingStringLiteral = true;
                    }

                    builder.Append(c);
                }
            }

            tokens.Add(builder.ToString());
            return tokens.ToArray();
        }

        private static string ParseEscape(string s, ref int i)
        {
            char c = s[i];

            if (c != Symbols.EscapeChar)
            {
                throw new Exception("Wtf. Error parsing escape char.");
            }

            i++;
            c = s[i];

            switch (c)
            {
                case '\"':
                    return "\\\"";
                case '\'':
                    return "\\\'";
                case '?':
                    return "\\?";
                case '\\':
                    return "\\\\";
                case '\a':
                    return "\\\a";
                case '\b':
                    return "\\\b";
                case '\f':
                    return "\\\f";
                case '\n':
                    return "\\\n";
                case '\r':
                    return "\\\r";
                case '\t':
                    return "\\\t";
                case '\v':
                    return "\\\v";
            }

            if (c == 'x')
            {
                i++;
                char c1 = s[i];
                i++;
                char c2 = s[i];
                return string.Concat("\\x", c1, c2);
            }

            if (c == Symbols.UnicodeChar4)
            {
                i++;
                char c1 = s[i];
                i++;
                char c2 = s[i];
                i++;
                char c3 = s[i];
                i++;
                char c4 = s[i];
                return ParseUnicode(c1, c2, c3, c4);
            }

            if (c == Symbols.UnicodeChar6)
            {
                i++;
                char c1 = s[i];
                i++;
                char c2 = s[i];
                i++;
                char c3 = s[i];
                i++;
                char c4 = s[i];
                return ParseUnicode(c1, c2, c3, c4);
            }

            throw new Exception("Invalid char to escape.");
        }

        public static void SkipTo(StreamReader reader, char target)
        {
#if DEBUG
            StringBuilder debug = new StringBuilder();
#endif
            char c;

            do
            {
                int i = reader.Read();

                if (i == -1)
                {
                    throw new Exception("Reached end of stream");
                }

                c = (char)i;

#if DEBUG
                debug.Append(c);
#endif
            }
            while (c != target);

#if DEBUG
            Debug.WriteLine($"Skipped while skipping to '{target}': '{debug}'.");
#endif
        }

        public static char PeekToNextValidChar(StreamReader reader)
        {
#if DEBUG
            StringBuilder debug = new StringBuilder();
#endif

            while (true)
            {
                int i = reader.Peek();

                if (i == -1)
                {
                    throw new Exception("Reached end of stream");
                }

                char c = (char)i;

                // TODO: Needs to skip comments

                if (char.IsWhiteSpace(c) == false)
                {
#if DEBUG
                    Debug.WriteLine($"Skipped while peeking to next char: '{debug}'.");
#endif
                    return c;
                }

#if DEBUG
                debug.Append(c);
#endif

                reader.Read();
            }
        }

        public static string ReadUntil(StreamReader reader, params char[] matches)
        {
            StringBuilder builder = new StringBuilder();

            bool foundMatch = false;

            char c;
            do
            {
                c = (char)reader.Peek();

                if (c == Symbols.DoubleQuote)
                {
                    string literal = ReadStringLiteral(reader);
                    builder.Append(literal);
                }
                else if (c == Symbols.CommentDash)
                {
                    c = (char)reader.Read();
                    c = (char)reader.Read();
                    if (c == Symbols.CommentDash)
                    {
                        SkipLine(reader);
                    }
                    else if (c == Symbols.CommentStar)
                    {
                        SkipUntilCommentBlockEnd(reader);
                    }
                    else
                    {
                        builder.Append(c);
                    }
                }
                else
                {
                    if (matches.Contains(c))
                    {
                        foundMatch = true;
                        continue;
                    }

                    if (char.IsWhiteSpace(c))
                    {
                        c = ' ';
                    }

                    builder.Append(c);
                    c = (char)reader.Read();
                }
            }
            while (foundMatch == false);

            return builder.ToString();
        }

        private static void SkipUntilCommentBlockEnd(StreamReader reader)
        {
            while(true)
            {
                var c = (char)reader.Read();

                if (c == Symbols.CommentStar)
                {
                    c = (char)reader.Read();
                    if (c == Symbols.CommentDash)
                    {
                        return;
                    }
                }
            }
        }

        private static void SkipLine(StreamReader reader)
        {
            char c;
            do
            {
                int i = reader.Read();

                if (i == -1)
                {
                    throw new Exception("Reached end of stream");
                }

                c = (char)i;
            }
            while (c != '\n' && c != '\r');

            if (c == '\r')
            {
                c = (char)reader.Peek();
                if (c == '\n')
                {
                    reader.Read();
                }
            }
        }

        private static string ReadStringLiteral(StreamReader reader)
        {
            char c = (char)reader.Read();

            if (c != Symbols.DoubleQuote)
            {
                return string.Empty;
            }

            StringBuilder builder = new StringBuilder();

            builder.Append(c);

            do
            {
                c = (char)reader.Peek();

                if (c == Symbols.EscapeChar)
                {
                    string escaped = ReadEscapedSequence(reader);
                    builder.Append(escaped);
                }
                else
                {
                    c = (char)reader.Read();
                    builder.Append(c);
                }
            }
            while (c != Symbols.DoubleQuote);

            return builder.ToString();
        }

        private static string ReadEscapedSequence(StreamReader reader)
        {
            char c = (char)reader.Read();

            if (c != Symbols.EscapeChar)
            {
                return string.Empty;
            }

            c = (char)reader.Read();

            if (c == Symbols.DoubleQuote)
            {
                return "\\\"";
            }

            if (c == Symbols.SingleQuote)
            {
                return "\\\'";
            }

            if (c == Symbols.EscapeChar)
            {
                return "\\\\";
            }

            if (c == 'u')
            {
                char c1 = (char)reader.Read();
                char c2 = (char)reader.Read();
                char c3 = (char)reader.Read();
                char c4 = (char)reader.Read();

                return string.Concat('\\', 'u', c1, c2, c3, c4);
            }

            if (c == 'U')
            {
                char c1 = (char)reader.Read();
                char c2 = (char)reader.Read();
                char c3 = (char)reader.Read();
                char c4 = (char)reader.Read();
                char c5 = (char)reader.Read();
                char c6 = (char)reader.Read();

                return string.Concat('\\', 'U', c1, c2, c3, c4, c5, c6);
            }

            return string.Empty;
        }

        private static string ParseUnicode(params char[] cs)
        {
            var s = string.Concat(cs);
            uint result;
            if (uint.TryParse(s, out result) == false)
            {
                return string.Empty;
            }

            return ((char)result).ToString();
        }
    }
}