﻿// 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.ComponentModel;
using System.Composition;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.TableManager;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource
{
    [ExportEventListener(WellKnownEventListeners.DiagnosticService, WorkspaceKind.Host), Shared]
    internal partial class VisualStudioDiagnosticListTableWorkspaceEventListener : IEventListener<IDiagnosticService>
    {
        internal const string IdentifierString = nameof(VisualStudioDiagnosticListTable);

        private readonly IAsyncServiceProvider _asyncServiceProvider;
        private readonly IGlobalOptionService _globalOptions;
        private readonly IThreadingContext _threadingContext;
        private readonly ITableManagerProvider _tableManagerProvider;
        private readonly IAsynchronousOperationListener _asynchronousOperationListener;

        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public VisualStudioDiagnosticListTableWorkspaceEventListener(
            [Import("Microsoft.VisualStudio.Shell.Interop.SAsyncServiceProvider")] object asyncServiceProvider,
            IGlobalOptionService globalOptions,
            IThreadingContext threadingContext,
            ITableManagerProvider tableManagerProvider,
            IAsynchronousOperationListenerProvider listenerProvider)
        {
            // MEFv2 doesn't support type based contract for Import above and for this particular contract (SAsyncServiceProvider)
            // actual type cast doesn't work. (https://github.com/microsoft/vs-mef/issues/138)
            // workaround by getting the service as object and cast to actual interface
            _asyncServiceProvider = (IAsyncServiceProvider)asyncServiceProvider;
            _globalOptions = globalOptions;
            _threadingContext = threadingContext;
            _tableManagerProvider = tableManagerProvider;
            _asynchronousOperationListener = listenerProvider.GetListener(FeatureAttribute.DiagnosticService);
        }

        public void StartListening(Workspace workspace, IDiagnosticService diagnosticService)
        {
            _threadingContext.RunWithShutdownBlockAsync(async cancellationToken =>
            {
                using var asyncToken = _asynchronousOperationListener.BeginAsyncOperation(nameof(VisualStudioDiagnosticListTableWorkspaceEventListener) + "." + nameof(StartListening));
                await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

                var errorList = (await _asyncServiceProvider.GetServiceAsync(typeof(SVsErrorList)).ConfigureAwait(true)) as IErrorList;

                if (errorList == null)
                {
                    // nothing to do when there is no error list. 
                    // it can happen if VS ran in command line mode
                    return;
                }

                var table = new VisualStudioDiagnosticListTable(
                    (VisualStudioWorkspaceImpl)workspace,
                    _globalOptions,
                    _threadingContext,
                    diagnosticService,
                    _tableManagerProvider,
                    errorList);
            });
        }

        internal partial class VisualStudioDiagnosticListTable : VisualStudioBaseDiagnosticListTable
        {
            private readonly LiveTableDataSource _liveTableSource;
            private readonly BuildTableDataSource _buildTableSource;

            private readonly IErrorList _errorList;

            public VisualStudioDiagnosticListTable(
                VisualStudioWorkspaceImpl workspace,
                IGlobalOptionService globalOptions,
                IThreadingContext threadingContext,
                IDiagnosticService diagnosticService,
                ITableManagerProvider provider,
                IErrorList errorList)
                : base(workspace, provider)
            {
                _errorList = errorList;

                _liveTableSource = new LiveTableDataSource(workspace, globalOptions, threadingContext, diagnosticService, IdentifierString, workspace.ExternalErrorDiagnosticUpdateSource);
                _buildTableSource = new BuildTableDataSource(workspace, threadingContext, workspace.ExternalErrorDiagnosticUpdateSource);

                AddInitialTableSource(Workspace.CurrentSolution, GetCurrentDataSource());
                ConnectWorkspaceEvents();

                _errorList.PropertyChanged += OnErrorListPropertyChanged;
            }

            private ITableDataSource GetCurrentDataSource()
            {
                if (_errorList == null)
                {
                    return _liveTableSource;
                }

                return _errorList.AreOtherErrorSourceEntriesShown ? _liveTableSource : _buildTableSource;
            }

            /// this is for test only
            private VisualStudioDiagnosticListTable(
                Workspace workspace,
                IGlobalOptionService globalOptions,
                IThreadingContext threadingContext,
                IDiagnosticService diagnosticService,
                ITableManagerProvider provider)
                : base(workspace, provider)
            {
                _liveTableSource = null!;
                _buildTableSource = null!;
                _errorList = null!;

                AddInitialTableSource(workspace.CurrentSolution, new LiveTableDataSource(workspace, globalOptions, threadingContext, diagnosticService, IdentifierString));
            }

            /// this is for test only
            private VisualStudioDiagnosticListTable(Workspace workspace, IThreadingContext threadingContext, ExternalErrorDiagnosticUpdateSource errorSource, ITableManagerProvider provider)
                : base(workspace, provider)
            {
                _liveTableSource = null!;
                _buildTableSource = null!;
                _errorList = null!;

                AddInitialTableSource(workspace.CurrentSolution, new BuildTableDataSource(workspace, threadingContext, errorSource));
            }

            protected override void AddTableSourceIfNecessary(Solution solution)
            {
                if (solution.ProjectIds.Count == 0)
                {
                    // whenever there is a change in solution, make sure we refresh static info
                    // of build errors so that things like project name correctly refreshed
                    _buildTableSource.RefreshAllFactories();
                    return;
                }

                RemoveTableSourcesIfNecessary();
                AddTableSource(GetCurrentDataSource());
            }

            protected override void RemoveTableSourceIfNecessary(Solution solution)
            {
                if (solution.ProjectIds.Count > 0)
                {
                    // whenever there is a change in solution, make sure we refresh static info
                    // of build errors so that things like project name correctly refreshed
                    _buildTableSource.RefreshAllFactories();
                    return;
                }

                RemoveTableSourcesIfNecessary();
            }

            private void RemoveTableSourcesIfNecessary()
            {
                RemoveTableSourceIfNecessary(_buildTableSource);
                RemoveTableSourceIfNecessary(_liveTableSource);
            }

            private void RemoveTableSourceIfNecessary(ITableDataSource source)
            {
                if (!this.TableManager.Sources.Any(s => s == source))
                {
                    return;
                }

                this.TableManager.RemoveSource(source);
            }

            protected override void ShutdownSource()
            {
                _liveTableSource.Shutdown();
                _buildTableSource.Shutdown();
            }

            private void OnErrorListPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (e.PropertyName == nameof(IErrorList.AreOtherErrorSourceEntriesShown))
                {
                    AddTableSourceIfNecessary(this.Workspace.CurrentSolution);
                }
            }

            internal static class TestAccessor
            {
                public static VisualStudioDiagnosticListTable Create(Workspace workspace, IGlobalOptionService globalOptions, IThreadingContext threadingContext, IDiagnosticService diagnosticService, ITableManagerProvider provider)
                    => new(workspace, globalOptions, threadingContext, diagnosticService, provider);

                public static VisualStudioDiagnosticListTable Create(Workspace workspace, IThreadingContext threadingContext, ExternalErrorDiagnosticUpdateSource errorSource, ITableManagerProvider provider)
                    => new(workspace, threadingContext, errorSource, provider);
            }
        }
    }
}
