namespace ZelWiki.Engine.Function
{
    /// <summary>
    /// 包含有关实际函数调用及其提供的参数的信息，并与定义的函数相匹配。
    /// </summary>
    public class FunctionCall
    {
        /// <summary>
        /// 
        /// </summary>
        public string Name { get; private set; }

        public FunctionPrototype Prototype { get; set; }

        /// <summary>
        /// T
        /// </summary>
        public FunctionParameters Parameters { get; private set; }

        public FunctionCall(FunctionPrototype prototype, List<string> args)
        {
            Prototype = prototype;
            Parameters = new FunctionParameters(this);
            Name = prototype.FunctionName;

            foreach (var arg in args)
            {
                if (arg.StartsWith(':') && arg.Contains('='))
                {
                    var parsed = arg.Substring(1);
                    int index = parsed.IndexOf('=');
                    var name = parsed.Substring(0, index).Trim().ToLower();
                    var value = parsed.Substring(index + 1).Trim();

                    Parameters.Named.Add(new NamedParameter(name, value));
                }
                else
                {
                    Parameters.Ordinals.Add(new OrdinalParameter(arg));
                }
            }

            ApplyPrototype();
        }

        /// <summary>
        /// 对照函数原型检查传递的值,以确保变量的类型,值等正确
        /// </summary>
        /// <param name="param"></param>
        /// <param name="value"></param>
        /// <exception cref="Exception"></exception>
        private void EnforcePrototypeParamValue(PrototypeParameter param, string value)
        {
            if (param.Type == "bool")
            {
                if (bool.TryParse(value, out bool _) == false)
                {
                    throw new Exception(
                        $"函数 [{Name}] 传递给 [{param.Name}] 的值 [{value}] 无法转化成布尔");
                }
            }

            if (param.Type == "integer")
            {
                if (int.TryParse(value, out int _) == false)
                {
                    throw new Exception(
                        $"函数 [{Name}] 传递给 [{param.Name}] 的值 [{value}] 无法转化成整数.");
                }
            }
            else if (param.Type == "float")
            {
                if (double.TryParse(value, out double _) == false)
                {
                    throw new Exception(
                        $"函数 [{Name}] 传递给 [{param.Name}] 的值 [{value}] 无法转化成小数.");
                }
            }

            if (param.AllowedValues != null && param.AllowedValues.Count > 0)
            {
                if (param.AllowedValues.Contains(value.ToLower()) == false)
                {
                    throw new Exception(
                        $"函数 [{Name}] 传递给 [{param.Name}] 的值 [{value}] 为非法数据. 合法值为 [{string.Join(",", param.AllowedValues)}].");
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <exception cref="Exception"></exception>
        private void ApplyPrototype()
        {
            var index = 0;

            var namedToAddLater = new List<NamedParameter>();

            for (; index < Prototype.Parameters.Count; index++)
            {
                var param = Prototype.Parameters[index];

                if (param.IsRequired == false)
                {
                    break;
                }

                if (param.IsInfinite)
                {
                    break;
                }

                if (Parameters.Ordinals.Count > index)
                {
                    var value = Parameters.Ordinals[index].Value;
                    Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
                    EnforcePrototypeParamValue(param, value.ToLower());

                    namedToAddLater.Add(new NamedParameter(param.Name, value));
                }
                else
                {
                    throw new Exception($"Function [{Name}], the required parameter [{param.Name}] was not specified.");
                }
            }

            bool hasEncounteredOptionalParameter = false;

            for (; index < Prototype.Parameters.Count; index++)
            {
                var param = Prototype.Parameters[index];

                if (param.IsInfinite)
                {
                    if (param.IsRequired)
                    {
                        if (Parameters.Ordinals.Count > index)
                        {
                            var value = Parameters.Ordinals[index].Value;
                            Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
                            EnforcePrototypeParamValue(param, value.ToLower());
                        }
                        else
                        {
                            throw new Exception(
                                $"函数 [{Name}], 参数 [{param.Name}] 未通过.");
                        }
                    }

                    for (; index < Parameters.Ordinals.Count; index++)
                    {
                        var value = Parameters.Ordinals[index].Value;
                        Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
                        EnforcePrototypeParamValue(param, value.ToLower());
                        namedToAddLater.Add(new NamedParameter(param.Name, value));
                    }

                    break;
                }

                if (param.IsRequired == false)
                {
                    hasEncounteredOptionalParameter = true;
                }

                if (param.IsRequired && hasEncounteredOptionalParameter)
                {
                    throw new Exception(
                        $"函数 [{Name}], 所必参数 [{param.Name}] 在其他可选参数之后找到.");
                }
                else if (param.IsInfinite)
                {
                    throw new Exception(
                        $"函数 [{Name}], 参数溢出 [{param.Name}].");
                }

                if (Parameters.Ordinals.Count > index)
                {
                    var value = Parameters.Ordinals[index].Value;
                    Parameters.Ordinals[index].AssociateWithPrototypeParam(param.Name);
                    EnforcePrototypeParamValue(param, value.ToLower());
                    namedToAddLater.Add(new NamedParameter(param.Name, value));
                }
            }

            foreach (var named in Parameters.Named)
            {
                var param = Prototype.Parameters
                                .FirstOrDefault(o => o.Name.Equals(named.Name, StringComparison.InvariantCultureIgnoreCase))
                            ?? throw new Exception(
                                $"函数 [{Name}], 命名参数 [{named.Name}] 未在函数原型中定义.");

                EnforcePrototypeParamValue(param, named.Value);
            }

            Parameters.Named.AddRange(namedToAddLater);

            var unmatchedParams = Parameters.Ordinals.Where(o => o.IsMatched == false).ToList();
            if (unmatchedParams.Count != 0)
            {
                throw new Exception($"函数 [{Name}], 不匹配的参数值 [{unmatchedParams.First().Value}].");
            }

            var nonInfiniteParams =
                Prototype.Parameters.Where(o => o.IsInfinite == false).Select(o => o.Name.ToLower());
            var groups = Parameters.Named.Where(o => nonInfiniteParams.Contains(o.Name.ToLower()))
                .GroupBy(o => o.Name.ToLower()).Where(o => o.Count() > 1);

            if (groups.Any())
            {
                var group = groups.First();
                throw new Exception(
                    $"函数 [{Name}], 多次指定参数: [{group.Key}].");
            }
        }
    }
}