using System.Text;
using System.Text.RegularExpressions;
using ZelWiki.Engine.Function.Exceptions;

namespace ZelWiki.Engine.Function
{
    public static partial class FunctionParser
    {
        [GeneratedRegex(@"(##|{{|@@)([a-zA-Z_\s{][a-zA-Z0-9_\s{]*)\(((?<BR>\()|(?<-BR>\))|[^()]*)+\)")]
        private static partial Regex FunctionCallParser();

        /// <summary>
        /// 解析函数调用及其参数，并将其与已定义的函数及其原型进行匹配
        /// </summary>
        /// <param name="prototypes"></param>
        /// <param name="functionCall"></param>
        /// <param name="parseEndIndex"></param>
        /// <returns></returns>
        /// <exception cref="WikiFunctionPrototypeNotDefinedException"></exception>
        public static FunctionCall ParseAndGetFunctionCall(FunctionPrototypeCollection prototypes, string functionCall,
            out int parseEndIndex)
        {
            var rawArguments = new List<string>();

            var parsed = ParseFunctionCall(prototypes, functionCall);

            var prototype = prototypes.Get(parsed.Prefix, parsed.Name);
            if (prototype == null)
            {
                throw new WikiFunctionPrototypeNotDefinedException(
                    $"函数 ({parsed.Name}) 没有定义的原型.");
            }

            parseEndIndex = parsed.EndIndex;

            return new FunctionCall(prototype, parsed.RawArguments);
        }

        public static ParsedFunctionCall ParseFunctionCall(FunctionPrototypeCollection prototypes, string functionCall)
        {
            var functionName = string.Empty;
            var parseEndIndex = 0;
            var rawArguments = new List<string>();

            var firstLine = functionCall.Split('\n')?.FirstOrDefault();

            if (firstLine == null || firstLine.Count(x => x == '(') != firstLine.Count(x => x == ')'))
            {
                throw new WikiFunctionPrototypeSyntaxError($"函数括号不匹配.");
            }

            var functionPrefix = functionCall.Substring(0, 2);

            var parameterMatches = FunctionCallParser().Matches(firstLine);
            if (parameterMatches.Count > 0)
            {
                var match = parameterMatches[0];
                int paramStartIndex = match.Value.IndexOf('(');

                functionName = match.Value[..paramStartIndex].ToLower().TrimStart(['{', '#', '@']).Trim();
                parseEndIndex = match.Index + match.Length;

                string rawArgTrimmed = match.ToString()
                    .Substring(paramStartIndex, (match.ToString().Length - paramStartIndex));
                rawArguments = ParseRawArguments(rawArgTrimmed);
            }
            else //函数调用没有参数.
            {
                var endOfLine =
                    functionCall.Substring(2).TakeWhile(c => char.IsLetterOrDigit(c))
                        .Count(); 
                functionName = functionCall.Substring(2, endOfLine).ToLower().TrimStart(['{', '#', '@']).Trim();
                parseEndIndex = endOfLine + 2;
            }

            return new ParsedFunctionCall(functionPrefix, functionName, parseEndIndex, rawArguments);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="paramString"></param>
        /// <returns></returns>
        /// <exception cref="WikiFunctionPrototypeSyntaxError"></exception>
        public static List<string> ParseRawArgumentsAddParenthesis(string paramString)
        {
            if (paramString.StartsWith('(') || paramString.EndsWith(')'))
            {
                throw new WikiFunctionPrototypeSyntaxError($"Unexpected '(' or ')'.");
            }

            return ParseRawArguments($"({paramString})");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="paramString"></param>
        /// <returns></returns>
        /// <exception cref="WikiFunctionPrototypeSyntaxError"></exception>
        public static List<string> ParseRawArguments(string paramString)
        {
            List<string> ps = new();

            var readPos = 0;

            var singleParam = new StringBuilder();

            if (paramString[readPos] != '(')
            {
                throw new WikiFunctionPrototypeSyntaxError($"Expected '('.");
            }

            var parenNest = 1;

            readPos++;

            while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;

            while (true)
            {
                if (paramString[readPos] == '(')
                {
                    parenNest++;
                }
                else if (paramString[readPos] == ')')
                {
                    parenNest--;
                }

                if (readPos == paramString.Length)
                {
                    throw new WikiFunctionPrototypeSyntaxError($"Expected ')'.");
                }
                else if (paramString[readPos] == ')' && parenNest == 0)
                {
                    readPos++;

                    if (parenNest == 0 && readPos != paramString.Length)
                    {
                        throw new WikiFunctionPrototypeSyntaxError($"Expected end of statement.");
                    }

                    if (singleParam.Length > 0)
                    {
                        ps.Add(singleParam.ToString());
                    }

                    singleParam.Clear();

                    if (parenNest == 0)
                    {
                        break;
                    }
                }
                else if (paramString[readPos] == '\"')
                {
                    readPos++; 

                    var escapeChar = false;
                    for (;; readPos++)
                    {
                        if (readPos == paramString.Length)
                        {
                            throw new WikiFunctionPrototypeSyntaxError($"Expected end of string.");
                        }
                        else if (paramString[readPos] == '\\')
                        {
                            escapeChar = true;
                            continue;
                        }
                        else if (paramString[readPos] == '\"' && escapeChar == false)
                        {
                            readPos++;
                            break;
                        }
                        else
                        {
                            singleParam.Append(paramString[readPos]);
                        }

                        escapeChar = false;
                    }

                    while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
                }
                else if (paramString[readPos] == ',')
                {
                    readPos++; 
                    while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;

                    ps.Add(singleParam.ToString());
                    singleParam.Clear();
                    continue;
                }
                else
                {
                    singleParam.Append(paramString[readPos]);

                    if (paramString[readPos] == '(')
                    {
                        readPos++;
                        while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
                    }
                    else if (paramString[readPos] == ')')
                    {
                        readPos++;
                        while (readPos < paramString.Length && char.IsWhiteSpace(paramString[readPos])) readPos++;
                    }
                    else
                    {
                        readPos++;
                    }
                }
            }

            for (var i = 0; i < ps.Count; i++)
            {
                ps[i] = ps[i].Trim();
            }

            return ps;
        }
    }
}