// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Concurrent;
using Microsoft.Build.Construction;
using Microsoft.Build.Shared;

#nullable disable

namespace Microsoft.Build.Evaluation
{
    /// <summary>
    /// Maintains a cache of all loaded ProjectRootElement instances for design time purposes.
    ///
    /// This avoids the LRU cache and class-wide lock used within ProjectRootElementCache and replaces these mechanisms
    /// with a single ConcurrentDictionary as a tradeoff for increased performance when evaluating projects in parallel.
    /// As a tradeoff, this implementation uses more memory, and is not intended for use when the cache needs to be
    /// long-lived e.g. within Visual Studio.
    ///
    /// SimpleProjectRootElementCache is not currently intended for use outside of evaluation. Several code paths
    /// executed within a full build take a hard dependency on the strong/weak reference behavior used within
    /// ProjectRootElementCache, and further investigation is required to determine the best way to hide these behind
    /// an abstraction. As such, any method unused by evaluation will throw NotImplementedException.
    /// </summary>
    internal class SimpleProjectRootElementCache : ProjectRootElementCacheBase
    {
        private readonly ConcurrentDictionary<string, ProjectRootElement> _cache;

        internal SimpleProjectRootElementCache()
        {
            _cache = new ConcurrentDictionary<string, ProjectRootElement>(StringComparer.OrdinalIgnoreCase);
            LoadProjectsReadOnly = true;
        }

        internal override ProjectRootElement Get(
            string projectFile,
            OpenProjectRootElement loadProjectRootElement,
            bool isExplicitlyLoaded,
            bool? preserveFormatting)
        {
            // Should already have been canonicalized
            ErrorUtilities.VerifyThrowInternalRooted(projectFile);

            return loadProjectRootElement == null
                ? GetFromCache(projectFile)
                : GetFromOrAddToCache(projectFile, loadProjectRootElement);
        }

        private ProjectRootElement GetFromCache(string projectFile)
        {
            if (_cache.TryGetValue(projectFile, out ProjectRootElement projectRootElement))
            {
                return projectRootElement;
            }

            return null;
        }

        private ProjectRootElement GetFromOrAddToCache(string projectFile, OpenProjectRootElement loadFunc)
        {
            return _cache.GetOrAdd(projectFile, key =>
            {
                ProjectRootElement rootElement = loadFunc(key, this);
                ErrorUtilities.VerifyThrowInternalNull(rootElement, "projectRootElement");
                ErrorUtilities.VerifyThrow(
                    rootElement.FullPath.Equals(key, StringComparison.OrdinalIgnoreCase),
                    "Got project back with incorrect path. Expected path: {0}, received path: {1}.",
                    key,
                    rootElement.FullPath);

                AddEntry(rootElement);

                ErrorUtilities.VerifyThrow(_cache.TryGetValue(key, out _),
                    "Project should have been added into cache and boosted");

                return rootElement;
            });
        }

        internal override void AddEntry(ProjectRootElement projectRootElement)
        {
            if (_cache.TryAdd(projectRootElement.FullPath, projectRootElement))
            {
                RaiseProjectRootElementAddedToCacheEvent(projectRootElement);
            }
        }

        internal override void RenameEntry(string oldFullPath, ProjectRootElement projectRootElement)
        {
            throw new NotImplementedException();
        }

        internal override ProjectRootElement TryGet(string projectFile)
        {
            return TryGet(projectFile, null);
        }

        internal override ProjectRootElement TryGet(string projectFile, bool? preserveFormatting)
        {
            return Get(
                projectFile,
                null,
                false,
                preserveFormatting);
        }

        internal override void DiscardStrongReferences()
        {
        }

        internal override void Clear()
        {
            _cache.Clear();
        }

        internal override void DiscardImplicitReferences()
        {
            throw new NotImplementedException();
        }

        internal override void DiscardAnyWeakReference(ProjectRootElement projectRootElement)
        {
            ErrorUtilities.VerifyThrowArgumentNull(projectRootElement);

            // A PRE may be unnamed if it was only used in memory.
            if (projectRootElement.FullPath != null)
            {
                _cache.TryRemove(projectRootElement.FullPath, out _);
            }
        }

        internal override void OnProjectRootElementDirtied(ProjectRootElement sender, ProjectXmlChangedEventArgs e)
        {
            throw new NotImplementedException();
        }

        internal override void OnProjectDirtied(Project sender, ProjectChangedEventArgs e)
        {
            throw new NotImplementedException();
        }
    }
}
