using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Upsilon.Binder.VariableSymbols;
using Upsilon.BoundTypes;
using Upsilon.StandardLibraries;
using Type = Upsilon.BaseTypes.Type;

namespace UpsilonLanguageServer
{
    public static class BoundTypeParser
    {
        public static void LoadBoundTypes(string fileContent)
        {
            BoundTypeHandler.Reset();
            var json = JObject.Parse(fileContent);
            foreach (var prop in json.Properties())
            {
                var typeName = prop.Name;
                var dic = new Dictionary<string, UserDataBoundProperty>();
                var innerProperties = ((JObject)prop.Value).Properties();
                foreach (var innerProperty in innerProperties)
                {
                    var propertyName = innerProperty.Name;
                    var value = (JObject)innerProperty.Value;
                    var type = value.GetValue("type", StringComparison.InvariantCultureIgnoreCase)?.ToString();
                    var comment = value.GetValue("comment", StringComparison.InvariantCultureIgnoreCase)?.ToString();
                    if (string.Equals("function", type, StringComparison.InvariantCultureIgnoreCase))
                    {
                        ParseMethod(value, dic, propertyName, type, comment);
                    }
                    else
                    {
                        dic.Add(propertyName.ToLowerInvariant(), new UserDataBoundProperty()
                        {
                            Name       = propertyName,
                            ActualType = type,
                            Comment    = comment,
                            Type       = ParseType(type)
                        });
                    }
                }

                BoundTypeHandler.LoadUserDataTypeDefinition(new UserDataBoundTypeDefinition(typeName, dic));
            }
        }

        private static void ParseMethod(JObject value, IDictionary<string, UserDataBoundProperty> dic, string propertyName, string type, string comment)
        {
            var returnType = value.GetValue("returns", StringComparison.InvariantCultureIgnoreCase)?.ToString();
            var parameters = new List<UserDataBoundFunctionParameter>();
            if (value.GetValue("Parameters", StringComparison.InvariantCultureIgnoreCase) is JObject parameterJson)
            {
                var properties = parameterJson.Properties();
                foreach (var property in properties)
                {
                    var parameterName  = property.Name;
                    var parameterValue = (JObject) property.Value;
                    var parType = parameterValue.GetValue("type", StringComparison.InvariantCultureIgnoreCase)
                        ?.ToString();
                    var parComment = parameterValue.GetValue("comment", StringComparison.InvariantCultureIgnoreCase)
                        ?.ToString();
                    var isOptional = parameterValue.GetValue("IsOptional", StringComparison.InvariantCultureIgnoreCase)
                        ?.ToObject<bool>();
                    parameters.Add(new UserDataBoundFunctionParameter()
                    {
                        Name       = parameterName,
                        Type       = ParseType(parType),
                        Comment    = parComment,
                        IsOptional = isOptional.HasValue && isOptional.Value
                    });
                }
            }

            dic.Add(propertyName.ToLowerInvariant(), new UserDataBoundMethod()
            {
                Name       = propertyName,
                ActualType = type,
                Comment    = comment,
                Type       = ParseType(type),
                ResultType = ParseType(returnType),
                Parameters = parameters.ToArray()
            });
        }

        public static void LoadStaticVariables(string fileContent)
        {
            // We call the evaluation scope so that it initializes, and won't overwrite the bound scope later on
            // DONT REMOVE
            var _ = StaticScope.Scope;
            StaticScope.BoundScope = StaticScope.CreateStandardLibrary().Item2;
            var json = JObject.Parse(fileContent);
            foreach (var property in json.Properties())
            {
                var name = property.Name;
                var obj = (JObject)property.Value;
                var stringType = obj.GetValue("type", StringComparison.InvariantCultureIgnoreCase).ToString().ToLowerInvariant();
                var comments = obj.GetValue("comment", StringComparison.InvariantCultureIgnoreCase)?.ToString()
                    .Split('\n');
                var type = ParseType(stringType);
                if (type == Type.UserData)
                {
                    var boundType = BoundTypeHandler.GetTypeDefinition(stringType);
                    if (boundType != null)
                    {
                        StaticScope.BoundScope.AssignToNearest(new UserDataVariableSymbol(name, boundType){CommentValue = comments});
                    }
                    else
                    {
                        StaticScope.BoundScope.AssignToNearest(new UserDataVariableSymbol(name, Type.Unknown){CommentValue = comments});
                    }
                }
                else if (type == Type.Function)
                {
                    var returnType = obj.GetValue("returns", StringComparison.InvariantCultureIgnoreCase).ToString().ToLowerInvariant();
                    var parameters = new List<InternalFunctionVariableSymbol.InternalFunctionParameter>();
                    if (obj.GetValue("Parameters", StringComparison.InvariantCultureIgnoreCase) is JObject parameterJson)
                    {
                        var properties = parameterJson.Properties();
                        foreach (var prop in properties)
                        {
                            var parameterValue = (JObject) prop.Value;
                            var parName = parameterValue.GetValue("name", StringComparison.InvariantCultureIgnoreCase)
                                ?.ToString();
                            var parType = parameterValue.GetValue("type", StringComparison.InvariantCultureIgnoreCase)
                                ?.ToString();
                            var parsedType = ParseType(parType);
                            var isOptional = parameterValue.GetValue("IsOptional", StringComparison.InvariantCultureIgnoreCase)
                                ?.ToObject<bool>();
                            parameters.Add(new InternalFunctionVariableSymbol.InternalFunctionParameter(parName, parsedType,
                                isOptional.HasValue && isOptional.Value));
                        }
                    }
                    var parsedReturnType = ParseType(returnType);
                    StaticScope.BoundScope.AssignToNearest(new InternalFunctionVariableSymbol(name, false,
                        parsedReturnType, parameters.ToArray())
                    {
                        CommentValue = comments
                    });
                }
                else
                {
                    StaticScope.BoundScope.AssignToNearest(new VariableSymbol(property.Name,
                        type, false)
                    {
                        CommentValue = comments
                    });
                }
            }
        }

        private static Type ParseType(string input)
        {
            if (string.Equals(input, "string", StringComparison.InvariantCultureIgnoreCase))
                return Type.String;
            if (string.Equals(input, "number", StringComparison.InvariantCultureIgnoreCase))
                return Type.Number;
            if (string.Equals(input, "bool", StringComparison.InvariantCultureIgnoreCase))
                return Type.Boolean;
            if (string.Equals(input, "table", StringComparison.InvariantCultureIgnoreCase))
                return Type.Table;
            if (string.Equals(input, "function", StringComparison.InvariantCultureIgnoreCase))
                return Type.Function;

            return Type.UserData;
        }
    }
}