// 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.Diagnostics;
using System.IO;
using Microsoft.Build.BackEnd;

#nullable disable

namespace Microsoft.Build.Shared
{
    /// <summary>
    /// This class packages information about how to load a given assembly -- an assembly can be loaded by either its assembly
    /// name (strong or weak), or its filename/path.
    /// </summary>
    /// <remarks>
    /// Uses factory to instantiate correct private class to save space: only one field is ever used of the two.
    /// </remarks>
    internal abstract class AssemblyLoadInfo : ITranslatable, IEquatable<AssemblyLoadInfo>
    {
        /// <summary>
        /// This constructor initializes the assembly information.
        /// </summary>
        internal static AssemblyLoadInfo Create(string assemblyName, string assemblyFile)
        {
            ErrorUtilities.VerifyThrow((!string.IsNullOrEmpty(assemblyName)) || (!string.IsNullOrEmpty(assemblyFile)),
                "We must have either the assembly name or the assembly file/path.");
            ErrorUtilities.VerifyThrow((assemblyName == null) || (assemblyFile == null),
                "We must not have both the assembly name and the assembly file/path.");

            if (assemblyName != null)
            {
                return new AssemblyLoadInfoWithName(assemblyName);
            }
            else
            {
                return new AssemblyLoadInfoWithFile(assemblyFile);
            }
        }

        /// <summary>
        /// Gets the assembly's identity denoted by its strong/weak name.
        /// </summary>
        public abstract string AssemblyName
        {
            get;
        }

        /// <summary>
        /// Gets the path to the assembly file.
        /// </summary>
        public abstract string AssemblyFile
        {
            get;
        }

        /// <summary>
        /// Get the assembly location
        /// </summary>
        internal abstract string AssemblyLocation
        {
            get;
        }

        /// <summary>
        /// Computes a hashcode for this assembly info, so this object can be used as a key into
        /// a hash table.
        /// </summary>
        public override int GetHashCode()
        {
            return AssemblyLocation.GetHashCode();
        }

        public bool Equals(AssemblyLoadInfo other)
        {
            return Equals((object)other);
        }

        /// <summary>
        /// Determines if two AssemblyLoadInfos are effectively the same.
        /// </summary>
        public override bool Equals(Object obj)
        {
            if (obj == null)
            {
                return false;
            }

            AssemblyLoadInfo otherAssemblyInfo = obj as AssemblyLoadInfo;

            if (otherAssemblyInfo == null)
            {
                return false;
            }

            return (this.AssemblyName == otherAssemblyInfo.AssemblyName) && (this.AssemblyFile == otherAssemblyInfo.AssemblyFile);
        }

        public void Translate(ITranslator translator)
        {
            ErrorUtilities.VerifyThrow(translator.Mode == TranslationDirection.WriteToStream, "write only");
            string assemblyName = AssemblyName;
            string assemblyFile = AssemblyFile;
            translator.Translate(ref assemblyName);
            translator.Translate(ref assemblyFile);
        }

        public static AssemblyLoadInfo FactoryForTranslation(ITranslator translator)
        {
            string assemblyName = null;
            string assemblyFile = null;
            translator.Translate(ref assemblyName);
            translator.Translate(ref assemblyFile);

            return Create(assemblyName, assemblyFile);
        }

        /// <summary>
        /// Assembly represented by name
        /// </summary>
        [DebuggerDisplay("{AssemblyName}")]
        private sealed class AssemblyLoadInfoWithName : AssemblyLoadInfo
        {
            /// <summary>
            /// Assembly name
            /// </summary>
            private string _assemblyName;

            /// <summary>
            /// Constructor
            /// </summary>
            internal AssemblyLoadInfoWithName(string assemblyName)
            {
                _assemblyName = assemblyName;
            }

            /// <summary>
            /// Gets the assembly's identity denoted by its strong/weak name.
            /// </summary>
            public override string AssemblyName
            {
                get { return _assemblyName; }
            }

            /// <summary>
            /// Gets the path to the assembly file.
            /// </summary>
            public override string AssemblyFile
            {
                get { return null; }
            }

            /// <summary>
            /// Get the assembly location
            /// </summary>
            internal override string AssemblyLocation
            {
                get { return _assemblyName; }
            }
        }

        /// <summary>
        /// Assembly info that uses a file path
        /// </summary>
        [DebuggerDisplay("{AssemblyFile}")]
        private sealed class AssemblyLoadInfoWithFile : AssemblyLoadInfo
        {
            /// <summary>
            /// Path to assembly
            /// </summary>
            private string _assemblyFile;

            /// <summary>
            /// Constructor
            /// </summary>
            internal AssemblyLoadInfoWithFile(string assemblyFile)
            {
                ErrorUtilities.VerifyThrow(Path.IsPathRooted(assemblyFile), "Assembly file path should be rooted");

                _assemblyFile = assemblyFile;
            }

            /// <summary>
            /// Gets the assembly's identity denoted by its strong/weak name.
            /// </summary>
            public override string AssemblyName
            {
                get { return null; }
            }

            /// <summary>
            /// Gets the path to the assembly file.
            /// </summary>
            public override string AssemblyFile
            {
                get { return _assemblyFile; }
            }

            /// <summary>
            /// Get the assembly location
            /// </summary>
            internal override string AssemblyLocation
            {
                get { return _assemblyFile; }
            }
        }
    }
}
