using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JsonRpc.Standard.Contracts;
using LanguageServer.VsCode;
using LanguageServer.VsCode.Contracts;
using Newtonsoft.Json.Linq;
using Upsilon.Binder;
using Upsilon.Binder.VariableSymbols;
using Upsilon.BoundTypes;
using Upsilon.Utilities;
using Type = Upsilon.BaseTypes.Type;

namespace UpsilonLanguageServer.Services
{
    [JsonRpcScope(MethodPrefix = "textDocument/")]
    public class TextDocumentService : UpsilonLanguageServiceBase
    {
        [JsonRpcMethod]
        public async Task<Hover> Hover(TextDocumentIdentifier textDocument, Position position, CancellationToken ct)
        {
            // Note that Hover is cancellable.
            //await Task.Delay(1000, ct);
            if (Session.Documents.TryGetValue(textDocument.Uri, out var doc))
            {
                if (doc.Bound != null && doc.SourceText != null)
                {
                    var findNode = doc.Bound.GetBottomNodeAtPosition(position.Line, position.Character);
                    if (findNode != null)
                    {
                        var contents = new StringBuilder($"Kind: {findNode.Kind}");
                        if (findNode is BoundExpression expression)
                        {
                            contents.Append($"\n\nType: {expression.ValueType}");
                        }

                        if (findNode is BoundVariableSymbol varSymbol)
                        {
                            if (varSymbol.VariableSymbol.CommentValue != null)
                            {
                                contents.Append($"\n\n{string.Join("  \n", varSymbol.VariableSymbol.CommentValue)}");
                            }

                            if (varSymbol.VariableSymbol is FunctionVariableSymbol fVar)
                            {
                                contents.Append($"\n\nReturns: {fVar.FunctionOption.First().ResultType}");
                            }
                        }
                        else if (findNode is BoundVariableAssignment variableAssignment)
                        {
                            contents.Append($"\n\nType: {variableAssignment.Variable.ValueType}");
                        }
                        else if (findNode is BoundFunctionExpression fe)
                        {
                            contents.Append($"\n\nReturns: {fe.ReturnType}");
                        }
                        else if (findNode is BoundFunctionCallExpression functionCall)
                        {
                            contents.Append(
                                $"\n\nParameters: {string.Join(", ", functionCall.Parameters.Select(x => x.ValueType))}");
                            contents.Append($"\n\nReturns: {functionCall.ValueType}");
                        }

                        return new Hover()
                        {
                            Contents = contents.ToString()
                        };
                    }
                }
            }

            return new Hover();
        }

        [JsonRpcMethod]
        public async Task<SignatureHelp> SignatureHelp(TextDocumentIdentifier textDocument, Position position)
        {
            if (Session.Documents.TryGetValue(textDocument.Uri, out var doc))
            {
                await doc.WaitForDocumentBound();
                if (doc.Bound != null && doc.SourceText != null)
                {
                    var findNode = doc.Bound.GetBottomNodeAtPosition(position.Line, position.Character);
                    if (findNode != null)
                    {
                        if (findNode.Kind == BoundKind.BoundFunctionCallExpression)
                        {
                            var functionCall = (BoundFunctionCallExpression)findNode;
                            var exp = Binder.ResolveVariable(functionCall.Identifier, null);
                            if (exp is InternalFunctionVariableSymbol internalFunction)
                            {
                                var n = new SignatureHelp(new List<SignatureInformation>()
                                {
                                    new SignatureInformation(exp.Name, string.Join("   \n", exp.CommentValue),
                                        ((InternalFunctionVariableOption)internalFunction.FunctionOption.First()).FunctionParameters.Select(
                                            x => new ParameterInformation("", $"{x.Name} ({x.ValidTypes})")).ToList())
                                }, 0, functionCall.Parameters.Length);
                                return n;
                            }
                        }
                    }
                }
            }

            return new SignatureHelp();
        }

        [JsonRpcMethod(IsNotification = true)]
        public async Task DidOpen(TextDocumentItem textDocument)
        {
            if (textDocument.Uri.IsUntitled())
            {
                var workspace = Session.Client.Workspace;
                if (workspace != null)
                {

                }
            }

            var doc = new SessionDocument(textDocument);
            var session = Session;
            doc.DocumentChanged += async (sender, args) =>
            {
                // Lint the document when it's changed.
                var sessionDocument = ((SessionDocument) sender);
                var doc1 = sessionDocument.Document;
                var diag1 = DiagnosticProvider.LintDocument(doc1, sessionDocument, session.Settings.MaxNumberOfProblems,
                    session.Settings.ModuleDirectory);
                if (session.Documents.ContainsKey(doc1.Uri))
                {
                    // In case the document has been closed when we were linting…
                    await session.Client.Document.PublishDiagnostics(doc1.Uri, await diag1);
                }
            };
            Session.Documents.TryAdd(textDocument.Uri, doc);
            var diag = DiagnosticProvider.LintDocument(doc.Document, doc, Session.Settings.MaxNumberOfProblems,
                session.Settings.ModuleDirectory);
            await Client.Document.PublishDiagnostics(textDocument.Uri, await diag);
        }

        [JsonRpcMethod(IsNotification = true)]
        public void DidChange(TextDocumentIdentifier textDocument,
            ICollection<TextDocumentContentChangeEvent> contentChanges)
        {
            Session.Documents[textDocument.Uri].NotifyChanges(contentChanges);
        }

        [JsonRpcMethod(IsNotification = true)]
        public void WillSave(TextDocumentIdentifier textDocument, TextDocumentSaveReason reason)
        {
            //Client.Window.LogMessage(MessageType.Log, "-----------");
            //Client.Window.LogMessage(MessageType.Log, Documents[textDocument].Content);
        }

        [JsonRpcMethod(IsNotification = true)]
        public async Task DidClose(TextDocumentIdentifier textDocument)
        {
            if (textDocument.Uri.IsUntitled())
            {
                await Client.Document.PublishDiagnostics(textDocument.Uri, new Diagnostic[0]);
            }
            Session.Documents.TryRemove(textDocument.Uri, out _);
        }

        [JsonRpcMethod]
        public async Task<CompletionList> Completion(TextDocumentIdentifier textDocument, Position position, CompletionContext context)
        {
            if (!Session.Documents.TryGetValue(textDocument.Uri, out var doc))
                return new CompletionList(new CompletionItem[0], false);

            await doc.WaitForDocumentBound();
            if (doc.Bound == null)
                return new CompletionList(new CompletionItem[0], false);

            using (var nodeIterator = doc.Bound.GetNodeAtPosition(position.Line, position.Character).GetEnumerator())
            {
                if (nodeIterator.MoveNext())
                {
                    var node = nodeIterator.Current;
                    if (node.Kind != BoundKind.BoundFullstopIndexExpression && nodeIterator.MoveNext())
                    {
                        node = nodeIterator.Current;
                    }

                    if (node is BoundFullStopIndexExpression indexExpression)
                    {
                        var expression     = indexExpression.Expression;
                        var variableSymbol = Binder.ResolveVariable(expression, null);
                        var result         = await GetListFromVariableSymbol(variableSymbol);
                        if (result != null)
                        {
                            return result;
                        }
                    }
                }
            }

            var scope     = doc.Bound.GetScopeAt(position.Line, position.Character);
            var variables = scope.GetBoundScopeVisibleVariables();
            if (scope != null)
            {
                return new CompletionList(
                    variables.Select(x =>
                    {
                        var (_, value) = x;
                        return BuildItem(value);
                    }));
            }

            return new CompletionList(new CompletionItem[0], false);
        }

        private static async Task<CompletionList> GetListFromVariableSymbol(VariableSymbol variableSymbol)
        {
            if (variableSymbol is UserDataVariableSymbol parameterSymbol &&
                parameterSymbol.BoundTypeDefinition is UserDataBoundTypeDefinition udBoundDef)
            {
                return new CompletionList(
                    udBoundDef.Properties.Select(x =>
                    {
                        var (_, value) = x;
                        return BuildItem(value);
                    }));
            }
            if (variableSymbol is TableVariableSymbol tableSymbol)
            {
                return new CompletionList(
                    tableSymbol.Variables.Select(x =>
                    {
                        var (_, value) = x;
                        return BuildItem(value);
                    }));
            }

            return null;
        }

        private static CompletionItem BuildItem(VariableSymbol symbol)
        {
            var kind = CompletionItemKind.Variable;
            var                data = new JObject();
            var documentation = symbol.CommentValue == null
                ? ""
                : $"{string.Join("  \n", symbol.CommentValue)}";

            if (symbol is FunctionVariableSymbol fun)
            {
                kind = CompletionItemKind.Function;
                switch (fun)
                {
                    case InternalFunctionVariableSymbol internalFunctionVariableSymbol:
                        data.Add("varCount",
                            ((InternalFunctionVariableOption) internalFunctionVariableSymbol.FunctionOption.First())
                            .FunctionParameters.Count(x => !x.IsOptional));
                        break;
                    case ScriptFunctionVariableSymbol scriptFunctionVariableSymbol:
                        data.Add("varCount",
                            ((ScriptFunctionVariableOption) scriptFunctionVariableSymbol.FunctionOption.First())
                            .Parameters.Length);

                        break;
                }
            }
            else if (symbol is UserDataVariableSymbol boundVar)
            {
                if (boundVar.TypeContainer == Type.Function)
                {

                }
                else if (boundVar.TypeContainer == Type.UserData)
                {
                    if (boundVar.BoundTypeDefinition is UserDataBoundEnumDefinition)
                    {
                        kind = CompletionItemKind.Enum;
                    }
                }
            }

            return new CompletionItem(symbol.Name, kind, symbol.TypeContainer.ToString(), documentation, data);
        }

        private static CompletionItem BuildItem(UserDataBoundProperty property)
        {
            var kind = CompletionItemKind.Variable;
            var                data = new JObject();
            if (property.Type == Type.Function)
            {
                var fun = (UserDataBoundMethod)property;
                kind = CompletionItemKind.Function;
                data.Add("varCount", fun.Options.First().Parameters.Count(x => !x.IsOptional));
            }

            const string documentation = "";
            return new CompletionItem(property.Name, kind, property.Type.ToString(), documentation, data);
        }

    }
}
