File: Nuget.Frameworks\FrameworkNameProvider.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.ObjectModel\Microsoft.TestPlatform.ObjectModel.csproj (Microsoft.VisualStudio.TestPlatform.ObjectModel)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;

namespace NuGetClone.Frameworks
{
    internal class FrameworkNameProvider : IFrameworkNameProvider
    {
        private static readonly HashSet<NuGetFramework> EmptyFrameworkSet = new();

        /// <summary>
        /// Legacy frameworks that are allowed to have a single digit for the version number.
        /// </summary>
        private static readonly HashSet<string> SingleDigitVersionFrameworks = new(StringComparer.OrdinalIgnoreCase)
        {
            FrameworkConstants.FrameworkIdentifiers.Windows,
            FrameworkConstants.FrameworkIdentifiers.WindowsPhone,
            FrameworkConstants.FrameworkIdentifiers.Silverlight
        };

        /// <summary>
        /// Frameworks that must always include a decimal point (period) between numerical parts.
        /// </summary>
        private static readonly HashSet<string> DecimalPointFrameworks = new(StringComparer.OrdinalIgnoreCase)
        {
            FrameworkConstants.FrameworkIdentifiers.NetCoreApp,
            FrameworkConstants.FrameworkIdentifiers.NetStandard,
            FrameworkConstants.FrameworkIdentifiers.NanoFramework
        };

        /// <summary>
        /// Contains identifier -> identifier
        /// Ex: .NET Framework -> .NET Framework
        /// Ex: NET Framework -> .NET Framework
        /// This includes self mappings.
        /// </summary>
        private readonly Dictionary<string, string> _identifierSynonyms;

        private readonly Dictionary<string, string> _identifierToShortName;
        private readonly Dictionary<string, string> _profilesToShortName;
        private readonly Dictionary<string, string> _identifierShortToLong;
        private readonly Dictionary<string, string> _profileShortToLong;

        // profile -> supported frameworks, optional frameworks
        private readonly Dictionary<int, HashSet<NuGetFramework>> _portableFrameworks;
        private readonly Dictionary<int, HashSet<NuGetFramework>> _portableOptionalFrameworks;

        // PCL compatibility mappings
        private readonly Dictionary<int, HashSet<FrameworkRange>> _portableCompatibilityMappings;

        // equivalent frameworks
        private readonly Dictionary<NuGetFramework, HashSet<NuGetFramework>> _equivalentFrameworks;

        // equivalent profiles
        private readonly Dictionary<string, Dictionary<string, HashSet<string>>> _equivalentProfiles;

        // non-PCL compatibility mappings
        private readonly Dictionary<string, HashSet<OneWayCompatibilityMappingEntry>> _compatibilityMappings;

        // subsets, net -> netcore
        private readonly Dictionary<string, HashSet<string>> _subSetFrameworks;

        // framework ordering (for non-package based frameworks)
        private readonly Dictionary<string, int> _nonPackageBasedFrameworkPrecedence;

        // framework ordering (for package based frameworks)
        private readonly Dictionary<string, int> _packageBasedFrameworkPrecedence;

        // framework ordering (when choosing between equivalent frameworks)
        private readonly Dictionary<string, int> _equivalentFrameworkPrecedence;

        // Rewrite mappings
        private readonly Dictionary<NuGetFramework, NuGetFramework> _shortNameRewrites;
        private readonly Dictionary<NuGetFramework, NuGetFramework> _fullNameRewrites;

        // NetStandard information
        private readonly List<NuGetFramework> _netStandardVersions;
        private readonly List<NuGetFramework> _compatibleCandidates;

        public FrameworkNameProvider(IEnumerable<IFrameworkMappings>? mappings, IEnumerable<IPortableFrameworkMappings>? portableMappings)
        {
            _identifierSynonyms = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            _identifierToShortName = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            _profilesToShortName = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            _identifierShortToLong = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            _profileShortToLong = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            _portableFrameworks = new Dictionary<int, HashSet<NuGetFramework>>();
            _portableOptionalFrameworks = new Dictionary<int, HashSet<NuGetFramework>>();
            _equivalentFrameworks = new Dictionary<NuGetFramework, HashSet<NuGetFramework>>();
            _equivalentProfiles = new Dictionary<string, Dictionary<string, HashSet<string>>>(StringComparer.OrdinalIgnoreCase);
            _subSetFrameworks = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
            _nonPackageBasedFrameworkPrecedence = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
            _packageBasedFrameworkPrecedence = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
            _equivalentFrameworkPrecedence = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
            _compatibilityMappings = new Dictionary<string, HashSet<OneWayCompatibilityMappingEntry>>(StringComparer.OrdinalIgnoreCase);
            _portableCompatibilityMappings = new Dictionary<int, HashSet<FrameworkRange>>();
            _shortNameRewrites = new Dictionary<NuGetFramework, NuGetFramework>();
            _fullNameRewrites = new Dictionary<NuGetFramework, NuGetFramework>();
            _netStandardVersions = new List<NuGetFramework>();
            _compatibleCandidates = new List<NuGetFramework>();

            InitMappings(mappings);

            InitPortableMappings(portableMappings);

            InitNetStandard();
        }

        /// <summary>
        /// Converts a key using the mappings, or if the key is already converted, finds the normalized form.
        /// </summary>
        private static bool TryConvertOrNormalize(string key, IDictionary<string, string> mappings, IDictionary<string, string> reverse, [NotNullWhen(true)] out string? value)
        {
            if (mappings.TryGetValue(key, out value))
            {
                return true;
            }
            else if (reverse.ContainsKey(key))
            {
                value = reverse.Where(p => StringComparer.OrdinalIgnoreCase.Equals(p.Key, key)).Select(s => s.Key).Single();
                return true;
            }

            value = null;
            return false;
        }

        public bool TryGetIdentifier(string framework, [NotNullWhen(true)] out string? identifier)
        {
            return TryConvertOrNormalize(framework, _identifierSynonyms, _identifierToShortName, out identifier);
        }

        public bool TryGetProfile(string frameworkIdentifier, string profileShortName, [NotNullWhen(true)] out string? profile)
        {
            return TryConvertOrNormalize(profileShortName, _profileShortToLong, _profilesToShortName, out profile);
        }

        public bool TryGetShortIdentifier(string identifier, [NotNullWhen(true)] out string? identifierShortName)
        {
            return TryConvertOrNormalize(identifier, _identifierToShortName, _identifierShortToLong, out identifierShortName);
        }

        public bool TryGetShortProfile(string frameworkIdentifier, string profile, [NotNullWhen(true)] out string? profileShortName)
        {
            return TryConvertOrNormalize(profile, _profilesToShortName, _profileShortToLong, out profileShortName);
        }

        public bool TryGetVersion(string versionString, [NotNullWhen(true)] out Version? version)
        {
            if (string.IsNullOrEmpty(versionString))
            {
                version = null;
                return false;
            }
            else
            {
                if (versionString.IndexOf('.') > -1)
                {
                    // parse the version as a normal dot delimited version
                    return Version.TryParse(versionString, out version);
                }
                else
                {
                    // make sure we have at least 2 digits
                    if (versionString.Length < 2)
                    {
                        versionString += "0";
                    }

                    // take only the first 4 digits and add dots
                    // 451 -> 4.5.1
                    // 81233 -> 8123
                    return Version.TryParse(string.Join(".", versionString.ToCharArray().Take(4)), out version);
                }
            }
        }

        public bool TryGetPlatformVersion(string versionString, [NotNullWhen(true)] out Version? version)
        {
            if (string.IsNullOrEmpty(versionString))
            {
                version = null;
                return false;
            }
            else
            {
                if (versionString.IndexOf('.') < 0)
                {
                    versionString += ".0";
                }
                return Version.TryParse(versionString, out version);
            }
        }

        public string GetVersionString(string framework, Version version)
        {
            if (version is null || IsZero(version))
            {
                return string.Empty;
            }

            int major = version.Major > 0 ? version.Major : 0;
            int minor = version.Minor > 0 ? version.Minor : 0;
            int build = version.Build > 0 ? version.Build : 0;
            int revision = version.Revision > 0 ? version.Revision : 0;

            // Remove all trailing zeros beyond the minor version.
            int partCount = (minor == 0, build == 0, revision == 0) switch
            {
                (true, true, true) => 1,
                (false, true, true) => 2,
                (_, false, true) => 3,
                (_, _, false) => 4
            };

            // Only some legacy frameworks are allowed to have one part in their version.
            if (partCount == 1 && !SingleDigitVersionFrameworks.Contains(framework))
            {
                partCount = 2;
            }

            StringBuilder sb = StringBuilderPool.Shared.Rent(256);

            // Some frameworks require a decimal point between parts.
            // If any part is greater than 9 (requiring multiple digits), we add decimal points.
            if (DecimalPointFrameworks.Contains(framework) || HasGreaterThanNinePart())
            {
                // An additional zero is needed for decimals.
                if (partCount == 1)
                    partCount = 2;

                sb.Append(major);
                if (partCount > 1)
                    sb.Append('.').Append(minor);
                if (partCount > 2)
                    sb.Append('.').Append(build);
                if (partCount > 3)
                    sb.Append('.').Append(revision);
            }
            else
            {
                sb.Append(major);
                if (partCount > 1)
                    sb.Append(minor);
                if (partCount > 2)
                    sb.Append(build);
                if (partCount > 3)
                    sb.Append(revision);
            }

            return StringBuilderPool.Shared.ToStringAndReturn(sb);

            bool HasGreaterThanNinePart()
            {
                return major > 9 || minor > 9 || build > 9 || revision > 9;
            }

            static bool IsZero(Version version)
            {
                // Build and Revision can be -1 when only major & minor are specified.
                // Out of caution, check all values for zero or less.
                return version.Major <= 0
                    && version.Minor <= 0
                    && version.Build <= 0
                    && version.Revision <= 0;
            }
        }

        public bool TryGetPortableProfile(IEnumerable<NuGetFramework> supportedFrameworks, out int profileNumber)
        {
            if (supportedFrameworks == null)
            {
                throw new ArgumentNullException(nameof(supportedFrameworks));
            }

            profileNumber = -1;

            // Remove duplicate frameworks, ex: win+win8 -> win
            var profileFrameworks = RemoveDuplicateFramework(supportedFrameworks);

            var reduced = new HashSet<NuGetFramework>();
            foreach (var pair in _portableFrameworks)
            {
                // to match the required set must be less than or the same count as the input
                // if we knew which frameworks were optional in the input we could rule out the lesser ones also
                if (pair.Value.Count <= profileFrameworks.Count)
                {
                    foreach (var curFw in profileFrameworks)
                    {
                        var isOptional = false;

                        foreach (var optional in GetOptionalFrameworks(pair.Key))
                        {
                            // TODO: profile check? Is the version check correct here?
                            if (NuGetFramework.FrameworkNameComparer.Equals(optional, curFw)
                                && StringComparer.OrdinalIgnoreCase.Equals(optional.Profile, curFw.Profile)
                                && curFw.Version >= optional.Version)
                            {
                                isOptional = true;
                            }
                        }

                        if (!isOptional)
                        {
                            reduced.Add(curFw);
                        }
                    }

                    // check all frameworks while taking into account equivalent variations
                    foreach (var permutation in GetEquivalentPermutations(pair.Value))
                    {
                        if (SetEquals(reduced, permutation))
                        {
                            // found a match
                            profileNumber = pair.Key;
                            return true;
                        }
                    }
                }

                reduced.Clear();
            }

            return false;
        }

        private HashSet<NuGetFramework> RemoveDuplicateFramework(IEnumerable<NuGetFramework> supportedFrameworks)
        {
            var result = new HashSet<NuGetFramework>();
            var existingFrameworks = new HashSet<NuGetFramework>();

            foreach (var framework in supportedFrameworks)
            {
                if (!existingFrameworks.Contains(framework))
                {
                    result.Add(framework);

                    // Add in the existing framework (included here) and all equivalent frameworks
                    var equivalentFrameworks = GetAllEquivalentFrameworks(framework);

                    UnionWith(existingFrameworks, equivalentFrameworks);
                }
            }

            return result;
        }

        /// <summary>
        /// Get all equivalent frameworks including the given framework
        /// </summary>
        private HashSet<NuGetFramework> GetAllEquivalentFrameworks(NuGetFramework framework)
        {
            // Loop through the frameworks, all frameworks that are not in results yet
            // will be added to toProcess to get the equivalent frameworks
            var toProcess = new Stack<NuGetFramework>();
            var results = new HashSet<NuGetFramework>();

            toProcess.Push(framework);
            results.Add(framework);

            while (toProcess.Count > 0)
            {
                var current = toProcess.Pop();

                if (_equivalentFrameworks.TryGetValue(current, out HashSet<NuGetFramework>? currentEquivalent))
                {
                    foreach (var equalFramework in currentEquivalent)
                    {
                        if (results.Add(equalFramework))
                        {
                            toProcess.Push(equalFramework);
                        }
                    }
                }
            }

            return results;
        }

        // find all combinations that are equivalent
        // ex: net4+win8 <-> net4+netcore45
        private IEnumerable<HashSet<NuGetFramework>> GetEquivalentPermutations(HashSet<NuGetFramework> frameworks)
        {
            if (frameworks.Count > 0)
            {
                NuGetFramework? current = null;
                var remaining = frameworks.Count == 1 ? null : new HashSet<NuGetFramework>();

                var isFirst = true;
                foreach (var fw in frameworks)
                {
                    if (isFirst)
                    {
                        current = fw;
                        isFirst = false;
                        continue;
                    }

                    remaining!.Add(fw);
                }

                var equalFrameworks = new HashSet<NuGetFramework>();
                // include ourselves
                equalFrameworks.Add(current!);

                // find all equivalent frameworks for the current one
                if (_equivalentFrameworks.TryGetValue(current!, out HashSet<NuGetFramework>? curFrameworks))
                {
                    UnionWith(equalFrameworks, curFrameworks);
                }

                foreach (var fw in equalFrameworks)
                {
                    if (remaining != null && remaining.Count > 0)
                    {
                        foreach (var result in GetEquivalentPermutations(remaining))
                        {
                            // work backwards adding the frameworks into the sets
                            result.Add(fw);
                            yield return result;
                        }
                    }
                    else
                    {
                        var singleFramework = new HashSet<NuGetFramework>();
                        singleFramework.Add(fw);
                        yield return singleFramework;
                    }
                }
            }

            yield break;
        }

        private HashSet<NuGetFramework> GetOptionalFrameworks(int profile)
        {
            if (_portableOptionalFrameworks.TryGetValue(profile, out HashSet<NuGetFramework>? frameworks))
            {
                return frameworks;
            }

            return EmptyFrameworkSet;
        }

        public bool TryGetPortableFrameworks(int profile, [NotNullWhen(true)] out IEnumerable<NuGetFramework>? frameworks)
        {
            return TryGetPortableFrameworks(profile, true, out frameworks);
        }

        public bool TryGetPortableFrameworks(int profile, bool includeOptional, [NotNullWhen(true)] out IEnumerable<NuGetFramework>? frameworks)
        {
            var result = new HashSet<NuGetFramework>();
            if (_portableFrameworks.TryGetValue(profile, out HashSet<NuGetFramework>? tmpFrameworks))
            {
                foreach (var fw in tmpFrameworks)
                {
                    result.Add(fw);
                }
            }

            if (includeOptional)
            {
                if (_portableOptionalFrameworks.TryGetValue(profile, out HashSet<NuGetFramework>? optional))
                {
                    foreach (var fw in optional)
                    {
                        result.Add(fw);
                    }
                }
            }

            frameworks = result;
            return result.Count > 0;
        }

        public bool TryGetPortableFrameworks(string shortPortableProfiles, [NotNullWhen(true)] out IEnumerable<NuGetFramework>? frameworks)
        {
            if (shortPortableProfiles == null)
            {
                throw new ArgumentNullException(nameof(shortPortableProfiles));
            }

            var shortNames = shortPortableProfiles.Split(['+'], StringSplitOptions.RemoveEmptyEntries);

            var result = new List<NuGetFramework>();
            foreach (var name in shortNames)
            {
                var framework = NuGetFramework.Parse(name, this);
                if (framework.HasProfile)
                {
                    // Frameworks within the portable profile are not allowed
                    // to have profiles themselves #1869
                    throw new ArgumentException(string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.InvalidPortableFrameworksDueToHyphen,
                        shortPortableProfiles));
                }

                result.Add(framework);
            }

            frameworks = result;
            return result.Count > 0;
        }

        public bool TryGetPortableCompatibilityMappings(int profile, [NotNullWhen(true)] out IEnumerable<FrameworkRange>? supportedFrameworkRanges)
        {
            if (_portableCompatibilityMappings.TryGetValue(profile, out HashSet<FrameworkRange>? entries))
            {
                supportedFrameworkRanges = entries;
                return supportedFrameworkRanges.Any();
            }

            supportedFrameworkRanges = null;
            return false;
        }

        public bool TryGetPortableProfileNumber(string profile, out int profileNumber)
        {
            // attempt to parse the profile for a number
            if (profile.StartsWith("Profile", StringComparison.OrdinalIgnoreCase))
            {
                var trimmed = profile.Substring(7, profile.Length - 7);
                return int.TryParse(trimmed, out profileNumber);
            }

            profileNumber = -1;
            return false;
        }

        public bool TryGetPortableFrameworks(string profile, bool includeOptional, [NotNullWhen(true)] out IEnumerable<NuGetFramework>? frameworks)
        {
            // attempt to parse the profile for a number
            int profileNum;
            if (TryGetPortableProfileNumber(profile, out profileNum))
            {
                if (TryGetPortableFrameworks(profileNum, includeOptional, out frameworks))
                {
                    return true;
                }

                frameworks = [];
                return false;
            }

            // treat the profile as a list of frameworks
            return TryGetPortableFrameworks(profile, out frameworks);
        }

        public bool TryGetEquivalentFrameworks(NuGetFramework framework, [NotNullWhen(true)] out IEnumerable<NuGetFramework>? frameworks)
        {
            var result = new HashSet<NuGetFramework>();

            // add in all framework aliases
            if (_equivalentFrameworks.TryGetValue(framework, out HashSet<NuGetFramework>? eqFrameworks))
            {
                foreach (var eqFw in eqFrameworks)
                {
                    result.Add(eqFw);
                }
            }

            var baseFrameworks = new List<NuGetFramework>(result);
            baseFrameworks.Add(framework);

            // add in all profile aliases
            foreach (var fw in baseFrameworks)
            {
                if (_equivalentProfiles.TryGetValue(fw.Framework, out Dictionary<string, HashSet<string>>? eqProfiles))
                {
                    if (eqProfiles.TryGetValue(fw.Profile, out HashSet<string>? matchingProfiles))
                    {
                        foreach (var eqProfile in matchingProfiles)
                        {
                            result.Add(new NuGetFramework(fw.Framework, fw.Version, eqProfile));
                        }
                    }
                }
            }

            // do not include the original framework
            result.Remove(framework);

            frameworks = result;
            return result.Count > 0;
        }

        public bool TryGetEquivalentFrameworks(FrameworkRange range, [NotNullWhen(true)] out IEnumerable<NuGetFramework>? frameworks)
        {
            if (range == null)
            {
                throw new ArgumentNullException(nameof(range));
            }

            var relevant = new HashSet<NuGetFramework>();

            foreach (var framework in _equivalentFrameworks.Keys.Where(f => range.Satisfies(f)))
            {
                relevant.Add(framework);
            }

            var results = new HashSet<NuGetFramework>();

            foreach (var framework in relevant)
            {
                if (TryGetEquivalentFrameworks(framework, out IEnumerable<NuGetFramework>? values))
                {
                    foreach (var val in values)
                    {
                        results.Add(val);
                    }
                }
            }

            frameworks = results;
            return results.Count > 0;
        }

        private void InitMappings(IEnumerable<IFrameworkMappings>? mappings)
        {
            if (mappings != null)
            {
                foreach (var mapping in mappings)
                {
                    // eq profiles
                    AddEquivalentProfiles(mapping.EquivalentProfiles);

                    // equivalent frameworks
                    AddEquivalentFrameworks(mapping.EquivalentFrameworks);

                    // add synonyms
                    AddFrameworkSynonyms(mapping.IdentifierSynonyms);

                    // populate short <-> long
                    AddIdentifierShortNames(mapping.IdentifierShortNames);

                    // official profile short names
                    AddProfileShortNames(mapping.ProfileShortNames);

                    // add compatibility mappings
                    AddCompatibilityMappings(mapping.CompatibilityMappings);

                    // add subset frameworks
                    AddSubSetFrameworks(mapping.SubSetFrameworks);

                    // add framework ordering rules
                    AddFrameworkPrecedenceMappings(_nonPackageBasedFrameworkPrecedence, mapping.NonPackageBasedFrameworkPrecedence);
                    AddFrameworkPrecedenceMappings(_packageBasedFrameworkPrecedence, mapping.PackageBasedFrameworkPrecedence);
                    AddFrameworkPrecedenceMappings(_equivalentFrameworkPrecedence, mapping.EquivalentFrameworkPrecedence);

                    // add rewrite rules
                    AddShortNameRewriteMappings(mapping.ShortNameReplacements);
                    AddFullNameRewriteMappings(mapping.FullNameReplacements);
                }
            }
        }

        private void InitPortableMappings(IEnumerable<IPortableFrameworkMappings>? portableMappings)
        {
            if (portableMappings != null)
            {
                foreach (var portableMapping in portableMappings)
                {
                    // populate portable framework names
                    AddPortableProfileMappings(portableMapping.ProfileFrameworks);

                    // populate portable optional frameworks
                    AddPortableOptionalFrameworks(portableMapping.ProfileOptionalFrameworks);

                    // populate portable compatibility mappings
                    AddPortableCompatibilityMappings(portableMapping.CompatibilityMappings);
                }
            }
        }

        private void InitNetStandard()
        {
            // populate the list of frameworks that could be compatible with NetStandard
            AddCompatibleCandidates();

            // populate the list of NetStandard versions
            AddNetStandardVersions();
        }

        private void AddShortNameRewriteMappings(IEnumerable<KeyValuePair<NuGetFramework, NuGetFramework>> mappings)
        {
            if (mappings != null)
            {
                foreach (var mapping in mappings)
                {
                    if (!_shortNameRewrites.ContainsKey(mapping.Key))
                    {
                        _shortNameRewrites.Add(mapping.Key, mapping.Value);
                    }
                }
            }
        }

        private void AddFullNameRewriteMappings(IEnumerable<KeyValuePair<NuGetFramework, NuGetFramework>> mappings)
        {
            if (mappings != null)
            {
                foreach (var mapping in mappings)
                {
                    if (!_fullNameRewrites.ContainsKey(mapping.Key))
                    {
                        _fullNameRewrites.Add(mapping.Key, mapping.Value);
                    }
                }
            }
        }

        private void AddCompatibilityMappings(IEnumerable<OneWayCompatibilityMappingEntry> mappings)
        {
            if (mappings != null)
            {
                foreach (var mapping in mappings)
                {
                    if (!_compatibilityMappings.TryGetValue(mapping.TargetFrameworkRange.Min.Framework, out HashSet<OneWayCompatibilityMappingEntry>? entries))
                    {
                        entries = new HashSet<OneWayCompatibilityMappingEntry>(OneWayCompatibilityMappingEntry.Comparer);
                        _compatibilityMappings.Add(mapping.TargetFrameworkRange.Min.Framework, entries);
                    }

                    entries.Add(mapping);
                }
            }
        }

        private void AddSubSetFrameworks(IEnumerable<KeyValuePair<string, string>> mappings)
        {
            if (mappings != null)
            {
                foreach (var mapping in mappings)
                {
                    if (!_subSetFrameworks.TryGetValue(mapping.Value, out HashSet<string>? subSets))
                    {
                        subSets = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                        _subSetFrameworks.Add(mapping.Value, subSets);
                    }

                    subSets.Add(mapping.Key);
                }
            }
        }

        /// <summary>
        /// 2 way per framework profile equivalence
        /// </summary>
        /// <param name="mappings"></param>
        private void AddEquivalentProfiles(IEnumerable<FrameworkSpecificMapping> mappings)
        {
            if (mappings != null)
            {
                foreach (var profileMapping in mappings)
                {
                    var frameworkIdentifier = profileMapping.FrameworkIdentifier;
                    var profile1 = profileMapping.Mapping.Key;
                    var profile2 = profileMapping.Mapping.Value;

                    if (!_equivalentProfiles.TryGetValue(frameworkIdentifier, out Dictionary<string, HashSet<string>>? profileMappings))
                    {
                        profileMappings = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
                        _equivalentProfiles.Add(frameworkIdentifier, profileMappings);
                    }

                    if (!profileMappings.TryGetValue(profile1, out HashSet<string>? innerMappings1))
                    {
                        innerMappings1 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                        profileMappings.Add(profile1, innerMappings1);
                    }

                    if (!profileMappings.TryGetValue(profile2, out HashSet<string>? innerMappings2))
                    {
                        innerMappings2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                        profileMappings.Add(profile2, innerMappings2);
                    }

                    innerMappings1.Add(profile2);
                    innerMappings2.Add(profile1);
                }
            }
        }

        /// <summary>
        /// 2 way framework equivalence
        /// </summary>
        /// <param name="mappings"></param>
        private void AddEquivalentFrameworks(IEnumerable<KeyValuePair<NuGetFramework, NuGetFramework>> mappings)
        {
            if (mappings != null)
            {
                foreach (var pair in mappings)
                {
                    var remaining = new Stack<NuGetFramework>();
                    remaining.Push(pair.Key);
                    remaining.Push(pair.Value);

                    var seen = new HashSet<NuGetFramework>();
                    while (remaining.Any())
                    {
                        var next = remaining.Pop();
                        if (!seen.Add(next))
                        {
                            continue;
                        }

                        if (!_equivalentFrameworks.TryGetValue(next, out HashSet<NuGetFramework>? eqFrameworks))
                        {
                            // initialize set
                            eqFrameworks = new HashSet<NuGetFramework>();
                            _equivalentFrameworks.Add(next, eqFrameworks);
                        }
                        else
                        {
                            // explore all equivalent
                            foreach (var framework in eqFrameworks)
                            {
                                remaining.Push(framework);
                            }
                        }
                    }

                    // add this equivalency rule, enforcing transitivity
                    foreach (var framework in seen)
                    {
                        foreach (var other in seen)
                        {
                            if (!NuGetFramework.Comparer.Equals(framework, other))
                            {
                                _equivalentFrameworks[framework].Add(other);
                            }
                        }
                    }

                }
            }
        }

        private void AddFrameworkSynonyms(IEnumerable<KeyValuePair<string, string>> mappings)
        {
            if (mappings != null)
            {
                foreach (var pair in mappings)
                {
                    if (!_identifierSynonyms.ContainsKey(pair.Key))
                    {
                        _identifierSynonyms.Add(pair.Key, pair.Value);
                    }
                }
            }
        }

        private void AddIdentifierShortNames(IEnumerable<KeyValuePair<string, string>> mappings)
        {
            if (mappings != null)
            {
                foreach (var pair in mappings)
                {
                    var shortName = pair.Value;
                    var longName = pair.Key;

                    if (!_identifierSynonyms.ContainsKey(pair.Value))
                    {
                        _identifierSynonyms.Add(pair.Value, pair.Key);
                    }

                    _identifierShortToLong.Add(shortName, longName);

                    _identifierToShortName.Add(longName, shortName);
                }
            }
        }

        private void AddProfileShortNames(IEnumerable<FrameworkSpecificMapping> mappings)
        {
            if (mappings != null)
            {
                foreach (var profileMapping in mappings)
                {
                    _profilesToShortName.Add(profileMapping.Mapping.Value, profileMapping.Mapping.Key);
                    _profileShortToLong.Add(profileMapping.Mapping.Key, profileMapping.Mapping.Value);
                }
            }
        }

        // Add supported frameworks for each portable profile number
        private void AddPortableProfileMappings(IEnumerable<KeyValuePair<int, NuGetFramework[]>> mappings)
        {
            if (mappings != null)
            {
                foreach (var pair in mappings)
                {
                    if (!_portableFrameworks.TryGetValue(pair.Key, out HashSet<NuGetFramework>? frameworks))
                    {
                        frameworks = new HashSet<NuGetFramework>();
                        _portableFrameworks.Add(pair.Key, frameworks);
                    }

                    foreach (var fw in pair.Value)
                    {
                        frameworks.Add(fw);
                    }
                }
            }
        }

        // Add optional frameworks for each portable profile number
        private void AddPortableOptionalFrameworks(IEnumerable<KeyValuePair<int, NuGetFramework[]>> mappings)
        {
            if (mappings != null)
            {
                foreach (var pair in mappings)
                {
                    if (!_portableOptionalFrameworks.TryGetValue(pair.Key, out HashSet<NuGetFramework>? frameworks))
                    {
                        frameworks = new HashSet<NuGetFramework>();
                        _portableOptionalFrameworks.Add(pair.Key, frameworks);
                    }

                    foreach (var fw in pair.Value)
                    {
                        frameworks.Add(fw);
                    }
                }
            }
        }

        private void AddPortableCompatibilityMappings(IEnumerable<KeyValuePair<int, FrameworkRange>> mappings)
        {
            if (mappings != null)
            {
                foreach (var mapping in mappings)
                {
                    if (!_portableCompatibilityMappings.TryGetValue(mapping.Key, out HashSet<FrameworkRange>? entries))
                    {
                        entries = new HashSet<FrameworkRange>(FrameworkRangeComparer.Instance);
                        _portableCompatibilityMappings.Add(mapping.Key, entries);
                    }

                    entries.Add(mapping.Value);
                }
            }
        }

        // Ordered lists of framework identifiers
        public void AddFrameworkPrecedenceMappings(IDictionary<string, int> destination, IEnumerable<string> mappings)
        {
            if (mappings != null)
            {
                foreach (var framework in mappings)
                {
                    if (!destination.ContainsKey(framework))
                    {
                        destination.Add(framework, destination.Count);
                    }
                }
            }
        }

        public bool TryGetCompatibilityMappings(NuGetFramework framework, [NotNullWhen(true)] out IEnumerable<FrameworkRange>? supportedFrameworkRanges)
        {
            if (_compatibilityMappings.TryGetValue(framework.Framework, out HashSet<OneWayCompatibilityMappingEntry>? entries))
            {
                supportedFrameworkRanges = entries.Where(m => m.TargetFrameworkRange.Satisfies(framework)).Select(m => m.SupportedFrameworkRange);
                return supportedFrameworkRanges.Any();
            }

            supportedFrameworkRanges = null;
            return false;
        }

        public bool TryGetSubSetFrameworks(string frameworkIdentifier, [NotNullWhen(true)] out IEnumerable<string>? subSetFrameworks)
        {
            if (_subSetFrameworks.TryGetValue(frameworkIdentifier, out HashSet<string>? values))
            {
                subSetFrameworks = values;
                return true;
            }

            subSetFrameworks = null;
            return false;
        }

        public int CompareFrameworks(NuGetFramework? x, NuGetFramework? y)
        {
            if (x is null && y is null) return 0;
            if (x is null) return -1;
            if (y is null) return 1;

            // For the purposes of this compare do not treat netcore50 as packages based
            var xPackagesBased = x.IsPackageBased && !NuGetFrameworkUtility.IsNetCore50AndUp(x);
            var yPackagesBased = y.IsPackageBased && !NuGetFrameworkUtility.IsNetCore50AndUp(y);

            if (xPackagesBased != yPackagesBased)
            {
                // non-package based always come before package based
                return xPackagesBased.CompareTo(yPackagesBased);
            }

            var precedence = xPackagesBased ? _packageBasedFrameworkPrecedence : _nonPackageBasedFrameworkPrecedence;

            return CompareUsingPrecedence(x, y, precedence);
        }

        public int CompareEquivalentFrameworks(NuGetFramework? x, NuGetFramework? y)
        {
            return CompareUsingPrecedence(x, y, _equivalentFrameworkPrecedence);
        }

        private static int CompareUsingPrecedence(NuGetFramework? x, NuGetFramework? y, Dictionary<string, int> precedence)
        {
            if (x is null && y is null) return 0;
            if (x is null) return -1;
            if (y is null) return 1;

            if (StringComparer.OrdinalIgnoreCase.Equals(x.Framework, y.Framework))
            {
                return 0;
            }

            int xIndex;
            if (!precedence.TryGetValue(x.Framework, out xIndex))
            {
                xIndex = int.MaxValue;
            }

            int yIndex;
            if (!precedence.TryGetValue(y.Framework, out yIndex))
            {
                yIndex = int.MaxValue;
            }

            return xIndex.CompareTo(yIndex);
        }


        public NuGetFramework GetShortNameReplacement(NuGetFramework framework)
        {
            // Replace the framework name if a rewrite exists
            if (!_shortNameRewrites.TryGetValue(framework, out NuGetFramework? result))
            {
                result = framework;
            }

            return result;
        }

        public NuGetFramework GetFullNameReplacement(NuGetFramework framework)
        {
            // Replace the framework name if a rewrite exists
            if (!_fullNameRewrites.TryGetValue(framework, out NuGetFramework? result))
            {
                result = framework;
            }

            return result;
        }

        public IEnumerable<NuGetFramework> GetNetStandardVersions()
        {
            return _netStandardVersions.AsReadOnly();
        }

        public IEnumerable<NuGetFramework> GetCompatibleCandidates()
        {
            return _compatibleCandidates.AsReadOnly();
        }

        private void AddNetStandardVersions()
        {
            foreach (var framework in _compatibleCandidates)
            {
                if (StringComparer.OrdinalIgnoreCase.Equals(framework.Framework, FrameworkConstants.FrameworkIdentifiers.NetStandard))
                {
                    _netStandardVersions.Add(framework);
                }
            }

            _netStandardVersions.Sort(NuGetFrameworkSorter.Instance);
        }

        private void AddCompatibleCandidates()
        {
            var set = new HashSet<NuGetFramework>();

            // equivalent
            foreach (var framework in _equivalentFrameworks.Values.SelectMany(x => x))
            {
                set.Add(framework);
            }

            // compatible
            foreach (var mapping in _compatibilityMappings.SelectMany(p => p.Value))
            {
                set.Add(mapping.TargetFrameworkRange.Min);
                set.Add(mapping.TargetFrameworkRange.Max);
                set.Add(mapping.SupportedFrameworkRange.Min);
                set.Add(mapping.SupportedFrameworkRange.Max);
            }

            // portable compatible
            foreach (var pair in _portableCompatibilityMappings)
            {
                var portable = new NuGetFramework(
                    FrameworkConstants.FrameworkIdentifiers.Portable,
                    FrameworkConstants.EmptyVersion,
                    string.Format(NumberFormatInfo.InvariantInfo, "Profile{0}", pair.Key));

                set.Add(portable);
                foreach (var range in pair.Value)
                {
                    set.Add(range.Min);
                    set.Add(range.Max);
                }
            }

            // subset and superset
            var superSetFrameworks = _subSetFrameworks
                .SelectMany(p => p.Value.Select(subset => new { Superset = p.Key, Subset = subset }))
                .GroupBy(p => p.Subset, p => p.Superset, StringComparer.OrdinalIgnoreCase)
                .ToDictionary(g => g.Key, g => new HashSet<string>(g, StringComparer.OrdinalIgnoreCase));

            foreach (var framework in set.ToArray())
            {
                if (framework.HasProfile)
                {
                    continue;
                }

                if (_subSetFrameworks.TryGetValue(framework.Framework, out HashSet<string>? subset))
                {
                    foreach (var subFramework in subset)
                    {
                        set.Add(new NuGetFramework(subFramework, framework.Version, framework.Profile));
                    }
                }

                if (superSetFrameworks.TryGetValue(framework.Framework, out HashSet<string>? superset))
                {
                    foreach (var superFramework in superset)
                    {
                        set.Add(new NuGetFramework(superFramework, framework.Version, framework.Profile));
                    }
                }
            }

            _compatibleCandidates.AddRange(set);
            _compatibleCandidates.Sort(NuGetFrameworkSorter.Instance);
        }

        // Strong typed non-IEnumerator based HashSet functions
        private static bool SetEquals(HashSet<NuGetFramework> left, HashSet<NuGetFramework> right)
        {
            if (left.Count != right.Count)
            {
                return false;
            }

            foreach (var fw in left)
            {
                if (!right.Contains(fw))
                {
                    return false;
                }
            }

            return true;
        }

        private static void UnionWith(HashSet<NuGetFramework> toAccumulate, HashSet<NuGetFramework> toAdd)
        {
            foreach (var fw in toAdd)
            {
                toAccumulate.Add(fw);
            }
        }
    }
}