using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using LanguageServer.VsCode.Contracts;
using LanguageServer.VsCode.Server;
using Upsilon;
using Upsilon.Evaluator;

namespace UpsilonLanguageServer
{
    public class DiagnosticProvider
    {

        public DiagnosticProvider()
        {
        }


        public static async Task<IEnumerable<Diagnostic>> LintDocument(TextDocument document, SessionDocument session,
            int maxNumberOfProblems, string modulePath)
        {
            var diag = new List<Diagnostic>();
            var content = document.Content;
            if (string.IsNullOrWhiteSpace(content))
            {
                diag.Add(new Diagnostic(DiagnosticSeverity.Hint,
                    new Range(new Position(0, 0), document.PositionAt(content?.Length ?? 0)),
                    "DLS", "DLS0001", "Empty document. Try typing something, such as \".net core\"."));
                return diag;
            }

            var task = RealLint(document, maxNumberOfProblems, session, content, diag, modulePath);
            // either wait until the script is linted, or until a second has passed (timeout)
            await Task.WhenAny(task, Task.Delay(1000));
            session.IsChanging = false;
            return diag;
        }

        private static readonly ScriptOptions Options = new ScriptOptions()
        {
            ThrowExceptionOnError = false,
            SaveDataComments = true,
        };

        private static async Task RealLint(TextDocument document, int maxNumberOfProblems, SessionDocument session,
            string content, ICollection<Diagnostic> diag, string modulePath)
        {
            Options.ScriptLoader.ModulesPath = modulePath;
            try
            {
                using (var script = Executor.ParseInput(content, Options))
                {
                    session.SourceText = script.ScriptString;
                    var bound = script.Bind();
                    foreach (var error in script.Diagnostics.Errors)
                    {
                        diag.Add(ConvertToVsCodeDiagnostic(error));
                        if (diag.Count >= maxNumberOfProblems) break;
                    }
                    foreach (var warning in script.Diagnostics.Warnings)
                    {
                        diag.Add(ConvertToVsCodeDiagnostic(warning));
                        if (diag.Count >= maxNumberOfProblems) break;
                    }

                    session.Bound = bound;
                }
            }
            catch (Exception e)
            {
                diag.Add(new Diagnostic(DiagnosticSeverity.Error,
                    new Range(new Position(0, 0), document.PositionAt(content?.Length ?? 0)),
                    "DLS", "DLS0001", e.ToString()));
            }
        }

        private static Diagnostic ConvertToVsCodeDiagnostic(DiagnosticsMessage message)
        {
            var sourceString = message.Diagnostics.ScriptString;
            var range = new Range(message.Span.StartLine, message.Span.StartPosition, message.Span.EndLine,
                message.Span.EndPosition);

            DiagnosticSeverity severity;
            switch (message.DiagnosticLevel)
            {
                case DiagnosticLevel.Error:
                    severity = DiagnosticSeverity.Error;
                    break;
                case DiagnosticLevel.Warning:
                    severity = DiagnosticSeverity.Warning;
                    break;
                default:
                    severity = DiagnosticSeverity.Information;
                    break;
            }

            return new Diagnostic(severity, range, message.AtError(), "ERR001", message.Message);
        }
    }
}
