﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Extensibility.Testing;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.TableControl;
using Microsoft.VisualStudio.Shell.TableManager;
using Microsoft.VisualStudio.Threading;

namespace Roslyn.VisualStudio.IntegrationTests.InProcess
{
    [TestService]
    internal partial class ErrorListInProcess
    {
        public Task ShowErrorListAsync(CancellationToken cancellationToken)
            => ShowErrorListAsync(ErrorSource.Build | ErrorSource.Other, minimumSeverity: __VSERRORCATEGORY.EC_WARNING, cancellationToken);

        public Task ShowBuildErrorsAsync(CancellationToken cancellationToken)
            => ShowErrorListAsync(ErrorSource.Build, minimumSeverity: __VSERRORCATEGORY.EC_WARNING, cancellationToken);

        public async Task ShowErrorListAsync(ErrorSource errorSource, __VSERRORCATEGORY minimumSeverity, CancellationToken cancellationToken)
        {
            await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            var errorList = await GetRequiredGlobalServiceAsync<SVsErrorList, IErrorList>(cancellationToken);
            ((IVsErrorList)errorList).BringToFront();
            errorList.AreBuildErrorSourceEntriesShown = errorSource.HasFlag(ErrorSource.Build);
            errorList.AreOtherErrorSourceEntriesShown = errorSource.HasFlag(ErrorSource.Other);
            errorList.AreErrorsShown = minimumSeverity >= __VSERRORCATEGORY.EC_ERROR;
            errorList.AreWarningsShown = minimumSeverity >= __VSERRORCATEGORY.EC_WARNING;
            errorList.AreMessagesShown = minimumSeverity >= __VSERRORCATEGORY.EC_MESSAGE;
        }

        public Task<ImmutableArray<string>> GetErrorsAsync(CancellationToken cancellationToken)
            => GetErrorsAsync(ErrorSource.Build | ErrorSource.Other, minimumSeverity: __VSERRORCATEGORY.EC_WARNING, cancellationToken);

        public Task<ImmutableArray<string>> GetBuildErrorsAsync(CancellationToken cancellationToken)
            => GetErrorsAsync(ErrorSource.Build, minimumSeverity: __VSERRORCATEGORY.EC_WARNING, cancellationToken);

        public async Task<ImmutableArray<string>> GetErrorsAsync(ErrorSource errorSource, __VSERRORCATEGORY minimumSeverity, CancellationToken cancellationToken)
        {
            await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            var errorItems = await GetErrorItemsAsync(errorSource, minimumSeverity, cancellationToken);
            var list = errorItems.Select(GetMessage).ToList();

            return list
                .OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
                .ThenBy(x => x, StringComparer.Ordinal)
                .ToImmutableArray();
        }

        public async Task<string> NavigateToErrorListItemAsync(int item, bool isPreview, bool shouldActivate, CancellationToken cancellationToken)
        {
            await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            var errorList = await GetRequiredGlobalServiceAsync<SVsErrorList, IErrorList>(cancellationToken);
            ErrorSource errorSource = 0;
            if (errorList.AreBuildErrorSourceEntriesShown)
                errorSource |= ErrorSource.Build;

            if (errorList.AreOtherErrorSourceEntriesShown)
                errorSource |= ErrorSource.Other;

            var minimumSeverity =
                errorList.AreMessagesShown ? __VSERRORCATEGORY.EC_MESSAGE :
                errorList.AreWarningsShown ? __VSERRORCATEGORY.EC_WARNING :
                __VSERRORCATEGORY.EC_ERROR;

            var items = await GetErrorItemsAsync(errorSource, minimumSeverity, cancellationToken);

            items[item].NavigateTo(isPreview, shouldActivate);
            return GetMessage(items[item]);
        }

        private async Task<ImmutableArray<ITableEntryHandle>> GetErrorItemsAsync(ErrorSource errorSource, __VSERRORCATEGORY minimumSeverity, CancellationToken cancellationToken)
        {
            await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            var errorList = await GetRequiredGlobalServiceAsync<SVsErrorList, IErrorList>(cancellationToken);
            var args = await errorList.TableControl.ForceUpdateAsync().WithCancellation(cancellationToken);
            return args.AllEntries
                .Where(item =>
                {
                    if (item.GetCategory() > minimumSeverity)
                    {
                        return false;
                    }

                    if (item.GetErrorSource() is not { } itemErrorSource
                        || !errorSource.HasFlag(itemErrorSource))
                    {
                        return false;
                    }

                    return true;
                })
                .ToImmutableArray();
        }

        private static string GetMessage(ITableEntryHandle item)
        {
            var source = item.GetBuildTool();
            var document = Path.GetFileName(item.GetPath() ?? item.GetDocumentName()) ?? "<unknown>";
            var line = item.GetLine() ?? -1;
            var column = item.GetColumn() ?? -1;
            var errorCode = item.GetErrorCode() ?? "<unknown>";
            var text = item.GetText() ?? "<unknown>";
            var severity = item.GetCategory() switch
            {
                __VSERRORCATEGORY.EC_ERROR => "error",
                __VSERRORCATEGORY.EC_WARNING => "warning",
                __VSERRORCATEGORY.EC_MESSAGE => "info",
                var unknown => unknown.ToString(),
            };

            var message = $"({source}) {document}({line + 1}, {column + 1}): {severity} {errorCode}: {text}";
            return message;
        }
    }
}
