﻿// 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.

#nullable disable

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Microsoft.CodeAnalysis.UnitTests
{
    internal sealed class FusionAssemblyIdentityComparer
    {
        // internal for testing
        internal enum AssemblyComparisonResult
        {
            Unknown = 0,

            EquivalentFullMatch = 1,      // all fields match
            EquivalentWeakNamed = 2,      // match based on weak-name, version numbers ignored
            EquivalentFxUnified = 3,      // match based on FX-unification of version numbers
            EquivalentUnified = 4,        // match based on legacy-unification of version numbers
            NonEquivalentVersion = 5,     // all fields match except version field
            NonEquivalent = 6,            // no match

            EquivalentPartialMatch = 7,
            EquivalentPartialWeakNamed = 8,
            EquivalentPartialUnified = 9,
            EquivalentPartialFxUnified = 10,
            NonEquivalentPartialVersion = 11
        }

        private static readonly object s_assemblyIdentityGate = new object();

        internal static AssemblyIdentityComparer.ComparisonResult CompareAssemblyIdentity(string fullName1, string fullName2, bool ignoreVersion, FusionAssemblyPortabilityPolicy policy, out bool unificationApplied)
        {
            unificationApplied = false;
            bool equivalent;
            AssemblyComparisonResult result;
            IntPtr asmConfigCookie = policy == null ? IntPtr.Zero : policy.ConfigCookie;
            int hr = DefaultModelCompareAssemblyIdentity(fullName1, ignoreVersion, fullName2, ignoreVersion, out equivalent, out result, asmConfigCookie);
            if (hr != 0 || !equivalent)
            {
                return AssemblyIdentityComparer.ComparisonResult.NotEquivalent;
            }

            switch (result)
            {
                case AssemblyComparisonResult.EquivalentFullMatch:
                    // all properties match
                    return AssemblyIdentityComparer.ComparisonResult.Equivalent;

                case AssemblyComparisonResult.EquivalentWeakNamed:
                    // both names are weak (have no public key token) and their simple names match:
                    return AssemblyIdentityComparer.ComparisonResult.Equivalent;

                case AssemblyComparisonResult.EquivalentFxUnified:
                    // Framework assembly with unified version.
                    unificationApplied = true;
                    return AssemblyIdentityComparer.ComparisonResult.Equivalent;

                case AssemblyComparisonResult.EquivalentUnified:
                    // Strong named, all properties but version match.
                    Debug.Assert(ignoreVersion);
                    return AssemblyIdentityComparer.ComparisonResult.EquivalentIgnoringVersion;

                default:
                    // Partial name was specified:
                    return equivalent ? AssemblyIdentityComparer.ComparisonResult.Equivalent : AssemblyIdentityComparer.ComparisonResult.NotEquivalent;
            }
        }

        internal static int DefaultModelCompareAssemblyIdentity(
            string identity1,
            bool isUnified1,
            string identity2,
            bool isUnified2,
            out bool areEquivalent,
            out AssemblyComparisonResult result,
            IntPtr asmConfigCookie)
        {
            lock (s_assemblyIdentityGate)
            {
                return CompareAssemblyIdentityWithConfig(
                    identity1,
                    isUnified1,
                    identity2,
                    isUnified2,
                    asmConfigCookie,
                    out areEquivalent,
                    out result);
            }
        }

        // internal for testing
        [DllImport("clr", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = true)]
        private static extern int CompareAssemblyIdentityWithConfig(
            [MarshalAs(UnmanagedType.LPWStr)] string identity1,
            bool isUnified1,
            [MarshalAs(UnmanagedType.LPWStr)] string identity2,
            bool isUnified2,
            IntPtr asmConfigCookie,
            out bool areEquivalent,
            out AssemblyComparisonResult result);
    }
}
