﻿// 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.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis
{
    /// <summary>
    /// Information about a metadata reference.
    /// </summary>
    public struct MetadataReferenceProperties : IEquatable<MetadataReferenceProperties>
    {
        private readonly MetadataImageKind _kind;
        private readonly ImmutableArray<string> _aliases;
        private readonly bool _embedInteropTypes;

        /// <summary>
        /// Default properties for a module reference.
        /// </summary>
        public static MetadataReferenceProperties Module => new MetadataReferenceProperties(MetadataImageKind.Module);

        /// <summary>
        /// Default properties for an assembly reference.
        /// </summary>
        public static MetadataReferenceProperties Assembly => new MetadataReferenceProperties(MetadataImageKind.Assembly);

        /// <summary>
        /// Initializes reference properties.
        /// </summary>
        /// <param name="kind">The image kind - assembly or module.</param>
        /// <param name="aliases">Assembly aliases. Can't be set for a module.</param>
        /// <param name="embedInteropTypes">True to embed interop types from the referenced assembly to the referencing compilation. Must be false for a module.</param>
        public MetadataReferenceProperties(MetadataImageKind kind = MetadataImageKind.Assembly, ImmutableArray<string> aliases = default, bool embedInteropTypes = false)
        {
            if (!kind.IsValid())
            {
                throw new ArgumentOutOfRangeException(nameof(kind));
            }

            if (kind == MetadataImageKind.Module)
            {
                if (embedInteropTypes)
                {
                    throw new ArgumentException(CodeAnalysisResources.CannotEmbedInteropTypesFromModule, nameof(embedInteropTypes));
                }

                if (!aliases.IsDefaultOrEmpty)
                {
                    throw new ArgumentException(CodeAnalysisResources.CannotAliasModule, nameof(aliases));
                }
            }

            if (!aliases.IsDefaultOrEmpty)
            {
                foreach (var alias in aliases)
                {
                    if (!alias.IsValidClrTypeName())
                    {
                        throw new ArgumentException(CodeAnalysisResources.InvalidAlias, nameof(aliases));
                    }
                }
            }

            _kind = kind;
            _aliases = aliases;
            _embedInteropTypes = embedInteropTypes;
            HasRecursiveAliases = false;
        }

        internal MetadataReferenceProperties(MetadataImageKind kind, ImmutableArray<string> aliases, bool embedInteropTypes, bool hasRecursiveAliases)
            : this(kind, aliases, embedInteropTypes)
        {
            HasRecursiveAliases = hasRecursiveAliases;
        }

        /// <summary>
        /// Returns <see cref="MetadataReferenceProperties"/> with specified aliases.
        /// </summary>
        /// <exception cref="ArgumentException">
        /// <see cref="Kind"/> is <see cref="MetadataImageKind.Module"/>, as modules can't be aliased.
        /// </exception>
        public MetadataReferenceProperties WithAliases(IEnumerable<string> aliases)
        {
            return WithAliases(aliases.AsImmutableOrEmpty());
        }

        /// <summary>
        /// Returns <see cref="MetadataReferenceProperties"/> with specified aliases.
        /// </summary>
        /// <exception cref="ArgumentException">
        /// <see cref="Kind"/> is <see cref="MetadataImageKind.Module"/>, as modules can't be aliased.
        /// </exception>
        public MetadataReferenceProperties WithAliases(ImmutableArray<string> aliases)
        {
            return new MetadataReferenceProperties(_kind, aliases, _embedInteropTypes, HasRecursiveAliases);
        }

        /// <summary>
        /// Returns <see cref="MetadataReferenceProperties"/> with <see cref="EmbedInteropTypes"/> set to specified value.
        /// </summary>
        /// <exception cref="ArgumentException"><see cref="Kind"/> is <see cref="MetadataImageKind.Module"/>, as interop types can't be embedded from modules.</exception>
        public MetadataReferenceProperties WithEmbedInteropTypes(bool embedInteropTypes)
        {
            return new MetadataReferenceProperties(_kind, _aliases, embedInteropTypes, HasRecursiveAliases);
        }

        /// <summary>
        /// Returns <see cref="MetadataReferenceProperties"/> with <see cref="HasRecursiveAliases"/> set to specified value.
        /// </summary>
        internal MetadataReferenceProperties WithRecursiveAliases(bool value)
        {
            return new MetadataReferenceProperties(_kind, _aliases, _embedInteropTypes, value);
        }

        /// <summary>
        /// The image kind (assembly or module) the reference refers to.
        /// </summary>
        public MetadataImageKind Kind => _kind;

        /// <summary>
        /// Alias that represents a global declaration space.
        /// </summary>
        /// <remarks>
        /// Namespaces in references whose <see cref="Aliases"/> contain <see cref="GlobalAlias"/> are available in global declaration space.
        /// </remarks>
        public static string GlobalAlias => "global";

        /// <summary>
        /// Aliases for the metadata reference. Empty if the reference has no aliases.
        /// </summary>
        /// <remarks>
        /// In C# these aliases can be used in "extern alias" syntax to disambiguate type names. 
        /// </remarks>
        public ImmutableArray<string> Aliases
        {
            get
            {
                // Simplify usage - we can't avoid the _aliases field being null but we can always return empty array here:
                return _aliases.NullToEmpty();
            }
        }

        /// <summary>
        /// True if interop types defined in the referenced metadata should be embedded into the compilation referencing the metadata.
        /// </summary>
        public bool EmbedInteropTypes => _embedInteropTypes;

        /// <summary>
        /// True to apply <see cref="Aliases"/> recursively on the target assembly and on all its transitive dependencies.
        /// False to apply <see cref="Aliases"/> only on the target assembly.
        /// </summary>
        internal bool HasRecursiveAliases { get; private set; }

        public override bool Equals(object? obj)
        {
            return obj is MetadataReferenceProperties && Equals((MetadataReferenceProperties)obj);
        }

        public bool Equals(MetadataReferenceProperties other)
        {
            return Aliases.SequenceEqual(other.Aliases)
                && _embedInteropTypes == other._embedInteropTypes
                && _kind == other._kind
                && HasRecursiveAliases == other.HasRecursiveAliases;
        }

        public override int GetHashCode()
        {
            return Hash.Combine(Hash.CombineValues(Aliases), Hash.Combine(_embedInteropTypes, Hash.Combine(HasRecursiveAliases, ((int)_kind).GetHashCode())));
        }

        public static bool operator ==(MetadataReferenceProperties left, MetadataReferenceProperties right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(MetadataReferenceProperties left, MetadataReferenceProperties right)
        {
            return !left.Equals(right);
        }
    }
}
