﻿// 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.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer
{
    internal partial class CpsDiagnosticItemSource : BaseDiagnosticAndGeneratorItemSource, INotifyPropertyChanged
    {
        private readonly IVsHierarchyItem _item;
        private readonly string _projectDirectoryPath;

        /// <summary>
        /// The analyzer reference that has been found. Once it's been assigned a non-null value, it'll never be assigned null again.
        /// </summary>
        private AnalyzerReference? _analyzerReference;

        public event PropertyChangedEventHandler? PropertyChanged;

        public CpsDiagnosticItemSource(Workspace workspace, string projectPath, ProjectId projectId, IVsHierarchyItem item, IAnalyzersCommandHandler commandHandler, IDiagnosticAnalyzerService analyzerService)
            : base(workspace, projectId, commandHandler, analyzerService)
        {
            _item = item;
            _projectDirectoryPath = Path.GetDirectoryName(projectPath);

            _analyzerReference = TryGetAnalyzerReference(Workspace.CurrentSolution);
            if (_analyzerReference == null)
            {
                // The ProjectId that was given to us was found by enumerating the list of projects in the solution, thus the project must have already
                // been added to the workspace at some point. As long as the project is still there, we're going to assume the reason we don't have the reference
                // yet is because while we have a project, we don't have all the references added yet. We'll wait until we see the reference and then connect to it.
                if (workspace.CurrentSolution.ContainsProject(projectId))
                {
                    Workspace.WorkspaceChanged += OnWorkspaceChangedLookForAnalyzer;
                    item.PropertyChanged += IVsHierarchyItem_PropertyChanged;

                    // Now that we've subscribed, check once more in case we missed the event
                    var analyzerReference = TryGetAnalyzerReference(Workspace.CurrentSolution);

                    if (analyzerReference != null)
                    {
                        _analyzerReference = analyzerReference;
                        UnsubscribeFromEvents();
                    }
                }
            }
        }

        private void UnsubscribeFromEvents()
        {
            Workspace.WorkspaceChanged -= OnWorkspaceChangedLookForAnalyzer;
            _item.PropertyChanged -= IVsHierarchyItem_PropertyChanged;
        }

        private void IVsHierarchyItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // IVsHierarchyItem implements ISupportDisposalNotification, which allows us to know when it's been removed
            if (e.PropertyName == nameof(ISupportDisposalNotification.IsDisposed))
            {
                UnsubscribeFromEvents();
            }
        }

        public IContextMenuController DiagnosticItemContextMenuController => CommandHandler.DiagnosticContextMenuController;

        public override object SourceItem => _item;

        public override AnalyzerReference? AnalyzerReference => _analyzerReference;

        private void OnWorkspaceChangedLookForAnalyzer(object sender, WorkspaceChangeEventArgs e)
        {
            // If the project has gone away in this change, it's not coming back, so we can stop looking at this point
            if (!e.NewSolution.ContainsProject(ProjectId))
            {
                UnsubscribeFromEvents();
                return;
            }

            // Was this a change to our project, or a global change?
            if (e.ProjectId == ProjectId ||
                e.Kind == WorkspaceChangeKind.SolutionChanged)
            {
                var analyzerReference = TryGetAnalyzerReference(e.NewSolution);
                if (analyzerReference != null)
                {
                    _analyzerReference = analyzerReference;
                    UnsubscribeFromEvents();

                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasItems)));
                }
            }
        }

        private AnalyzerReference? TryGetAnalyzerReference(Solution solution)
        {
            var project = solution.GetProject(ProjectId);

            if (project == null)
            {
                return null;
            }

            var canonicalName = _item.CanonicalName;
            var analyzerFilePath = CpsUtilities.ExtractAnalyzerFilePath(_projectDirectoryPath, canonicalName);

            if (string.IsNullOrEmpty(analyzerFilePath))
            {
                return null;
            }

            return project.AnalyzerReferences.FirstOrDefault(r => string.Equals(r.FullPath, analyzerFilePath, StringComparison.OrdinalIgnoreCase));
        }
    }
}
