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

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Globalization;
using System.Linq.Expressions;
using Microsoft.Internal;

namespace System.ComponentModel.Composition.Primitives
{
    /// <summary>
    ///     Represents an import required by a <see cref="ComposablePart"/> object.
    /// </summary>
    public class ImportDefinition
    {
        internal static readonly string EmptyContractName = string.Empty;
        private readonly Expression<Func<ExportDefinition, bool>>? _constraint;
        private readonly ImportCardinality _cardinality = ImportCardinality.ExactlyOne;
        private readonly string _contractName = EmptyContractName;
        private readonly bool _isRecomposable;
        private readonly bool _isPrerequisite = true;
        private Func<ExportDefinition, bool>? _compiledConstraint;
        private readonly IDictionary<string, object?> _metadata = MetadataServices.EmptyMetadata;

        /// <summary>
        ///     Initializes a new instance of the <see cref="ImportDefinition"/> class.
        /// </summary>
        /// <remarks>
        ///     <note type="inheritinfo">
        ///         Derived types calling this constructor must override the <see cref="Constraint"/>
        ///         property, and optionally, the <see cref="Cardinality"/>, <see cref="IsPrerequisite"/>
        ///         and <see cref="IsRecomposable"/>
        ///         properties.
        ///     </note>
        /// </remarks>
        protected ImportDefinition()
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="ImportDefinition"/> class
        ///     with the specified constraint, cardinality, value indicating if the import
        ///     definition is recomposable and a value indicating if the import definition
        ///     is a prerequisite.
        /// </summary>
        /// <param name="constraint">
        ///     A <see cref="Expression{TDelegate}"/> containing a <see cref="Func{T, TResult}"/>
        ///     that defines the conditions that must be matched for the <see cref="ImportDefinition"/>
        ///     to be satisfied by an <see cref="Export"/>.
        /// </param>
        /// <param name="contractName">
        ///     The contract name of the export that this import is interested in. The contract name
        ///     property is used as guidance and not automatically enforced in the constraint. If
        ///     the contract name is a required in the constraint then it should be added to the constraint
        ///     by the caller of this constructor.
        /// </param>
        /// <param name="cardinality">
        ///     One of the <see cref="ImportCardinality"/> values indicating the
        ///     cardinality of the <see cref="Export"/> objects required by the
        ///     <see cref="ImportDefinition"/>.
        /// </param>
        /// <param name="isRecomposable">
        ///     <see langword="true"/> if the <see cref="ImportDefinition"/> can be satisfied
        ///     multiple times throughout the lifetime of a <see cref="ComposablePart"/>, otherwise,
        ///     <see langword="false"/>.
        /// </param>
        /// <param name="isPrerequisite">
        ///     <see langword="true"/> if the <see cref="ImportDefinition"/> is required to be
        ///     satisfied before a <see cref="ComposablePart"/> can start producing exported
        ///     objects; otherwise, <see langword="false"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="constraint"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="cardinality"/> is not one of the <see cref="ImportCardinality"/>
        ///     values.
        /// </exception>
        public ImportDefinition(Expression<Func<ExportDefinition, bool>> constraint, string? contractName, ImportCardinality cardinality, bool isRecomposable, bool isPrerequisite)
            : this(contractName, cardinality, isRecomposable, isPrerequisite, MetadataServices.EmptyMetadata)
        {
            Requires.NotNull(constraint, nameof(constraint));

            _constraint = constraint;
        }

        public ImportDefinition(Expression<Func<ExportDefinition, bool>> constraint, string? contractName, ImportCardinality cardinality, bool isRecomposable, bool isPrerequisite, IDictionary<string, object?>? metadata)
            : this(contractName, cardinality, isRecomposable, isPrerequisite, metadata)
        {
            Requires.NotNull(constraint, nameof(constraint));

            _constraint = constraint;
        }

        internal ImportDefinition(string? contractName, ImportCardinality cardinality, bool isRecomposable, bool isPrerequisite, IDictionary<string, object?>? metadata)
        {
            if (
                (cardinality != ImportCardinality.ExactlyOne) &&
                (cardinality != ImportCardinality.ZeroOrMore) &&
                (cardinality != ImportCardinality.ZeroOrOne)
                )
            {
                throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_InvalidEnum, nameof(cardinality), cardinality, nameof(ImportCardinality)), nameof(cardinality));
            }

            _contractName = contractName ?? EmptyContractName;
            _cardinality = cardinality;
            _isRecomposable = isRecomposable;
            _isPrerequisite = isPrerequisite;

            //Metadata on imports was added in 4.5, prior to that it was ignored.
            if (metadata != null)
            {
                _metadata = metadata;
            }
        }

        /// <summary>
        ///     Gets the contract name of the export required by the import definition.
        /// </summary>
        /// <value>
        ///     A <see cref="string"/> containing the contract name of the <see cref="Export"/>
        ///     required by the <see cref="ContractBasedImportDefinition"/>. This property should
        ///     return <see cref="string.Empty"/> for imports that do not require a specific
        ///     contract name.
        /// </value>
        public virtual string ContractName
        {
            get
            {
                Debug.Assert(_contractName != null);

                return _contractName;
            }
        }

        /// <summary>
        ///     Gets the metadata of the import definition.
        /// </summary>
        /// <value>
        ///     An <see cref="IDictionary{TKey, TValue}"/> containing the metadata of the
        ///     <see cref="ExportDefinition"/>. The default is an empty, read-only
        ///     <see cref="IDictionary{TKey, TValue}"/>.
        /// </value>
        /// <remarks>
        ///     <para>
        ///         <note type="inheritinfo">
        ///             Overriders of this property should return a read-only
        ///             <see cref="IDictionary{TKey, TValue}"/> object with a case-sensitive,
        ///             non-linguistic comparer, such as <see cref="StringComparer.Ordinal"/>,
        ///             and should never return <see langword="null"/>.
        ///             If the <see cref="ImportDefinition"/> does not contain metadata
        ///             return an empty <see cref="IDictionary{TKey, TValue}"/> instead.
        ///         </note>
        ///     </para>
        /// </remarks>
        public virtual IDictionary<string, object?> Metadata
        {
            get
            {
                Debug.Assert(_metadata != null);

                return _metadata;
            }
        }

        /// <summary>
        ///     Gets the cardinality of the exports required by the import definition.
        /// </summary>
        /// <value>
        ///     One of the <see cref="ImportCardinality"/> values indicating the
        ///     cardinality of the <see cref="Export"/> objects required by the
        ///     <see cref="ImportDefinition"/>. The default is
        ///     <see cref="ImportCardinality.ExactlyOne"/>
        /// </value>
        public virtual ImportCardinality Cardinality
        {
            get { return _cardinality; }
        }

        /// <summary>
        ///     Gets an expression that defines conditions that must be matched for the import
        ///     described by the import definition to be satisfied.
        /// </summary>
        /// <returns>
        ///     A <see cref="Expression{TDelegate}"/> containing a <see cref="Func{T, TResult}"/>
        ///     that defines the conditions that must be matched for the
        ///     <see cref="ImportDefinition"/> to be satisfied by an <see cref="Export"/>.
        /// </returns>
        /// <exception cref="NotImplementedException">
        ///     The property was not overridden by a derived class.
        /// </exception>
        /// <remarks>
        ///     <note type="inheritinfo">
        ///         Overriders of this property should never return <see langword="null"/>.
        ///     </note>
        /// </remarks>
        public virtual Expression<Func<ExportDefinition, bool>> Constraint
        {
            get
            {
                if (_constraint != null)
                {
                    return _constraint;
                }

                throw ExceptionBuilder.CreateNotOverriddenByDerived("Constraint");
            }
        }

        /// <summary>
        ///     Gets a value indicating whether the import definition is required to be
        ///     satisfied before a part can start producing exported values.
        /// </summary>
        /// <value>
        ///     <see langword="true"/> if the <see cref="ImportDefinition"/> is required to be
        ///     satisfied before a <see cref="ComposablePart"/> can start producing exported
        ///     objects; otherwise, <see langword="false"/>. The default is <see langword="true"/>.
        /// </value>
        public virtual bool IsPrerequisite
        {
            get { return _isPrerequisite; }
        }

        /// <summary>
        ///     Gets a value indicating whether the import definition can be satisfied multiple times.
        /// </summary>
        /// <value>
        ///     <see langword="true"/> if the <see cref="ImportDefinition"/> can be satisfied
        ///     multiple times throughout the lifetime of a <see cref="ComposablePart"/>, otherwise,
        ///     <see langword="false"/>. The default is <see langword="false"/>.
        /// </value>
        public virtual bool IsRecomposable
        {
            get { return _isRecomposable; }
        }

        /// <summary>
        ///     Executes of the constraint provided by the <see cref="Constraint"/> property
        ///     against a given <see cref="ExportDefinition"/> to determine if this
        ///     <see cref="ImportDefinition"/> can be satisfied by the given <see cref="Export"/>.
        /// </summary>
        /// <param name="exportDefinition">
        ///     A definition for a <see cref="Export"/> used to determine if it satisfies the
        ///     requirements for this <see cref="ImportDefinition"/>.
        /// </param>
        /// <returns>
        ///     <see langword="True"/> if the <see cref="Export"/> satisfies the requirements for
        ///     this <see cref="ImportDefinition"/>, otherwise returns <see langword="False"/>.
        /// </returns>
        /// <remarks>
        ///     <note type="inheritinfo">
        ///         Overrides of this method can provide a more optimized execution of the
        ///         <see cref="Constraint"/> property but the result should remain consistent.
        ///     </note>
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="exportDefinition"/> is <see langword="null"/>.
        /// </exception>
        public virtual bool IsConstraintSatisfiedBy(ExportDefinition exportDefinition)
        {
            Requires.NotNull(exportDefinition, nameof(exportDefinition));

            _compiledConstraint ??= Constraint.Compile();

            return _compiledConstraint.Invoke(exportDefinition);
        }

        /// <summary>
        ///     Returns a string representation of the import definition.
        /// </summary>
        /// <returns>
        ///     A <see cref="string"/> containing the value of the <see cref="Constraint"/> property.
        /// </returns>
        public override string ToString()
        {
            return Constraint.Body.ToString();
        }
    }
}
