// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Internal;
using System.Globalization;

namespace System.ComponentModel.Composition.Primitives
{
    /// <summary>
    ///     Represents a contract name and metadata-based import 
    ///     required by a <see cref="ComposablePart"/> object.
    /// </summary>
    public class ContractBasedImportDefinition : ImportDefinition
    {
        // Unlike contract name, required metadata has a sensible default; set it to an empty 
        // enumerable, so that derived definitions only need to override ContractName by default.
        private readonly IEnumerable<KeyValuePair<string, Type>> _requiredMetadata = Enumerable.Empty<KeyValuePair<string, Type>>();
        private Expression<Func<ExportDefinition, bool>> _constraint;
        private readonly CreationPolicy _requiredCreationPolicy = CreationPolicy.Any;
        private readonly string _requiredTypeIdentity = null;
        private bool _isRequiredMetadataValidated = false;

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

        /// <summary>
        ///     Initializes a new instance of the <see cref="ContractBasedImportDefinition"/> class 
        ///     with the specified contract name, required metadataq, cardinality, value indicating 
        ///     if the import definition is recomposable and a value indicating if the import definition 
        ///     is a prerequisite.
        /// </summary>
        /// <param name="contractName">
        ///     A <see cref="String"/> containing the contract name of the 
        ///     <see cref="Export"/> required by the <see cref="ContractBasedImportDefinition"/>.
        /// </param>
        /// <param name="requiredTypeIdentity">
        ///     The type identity of the export type expected. Use <see cref="AttributedModelServices.GetTypeIdentity(Type)"/>
        ///     to generate a type identity for a given type. If no specific type is required pass <see langword="null"/>.
        /// </param>
        /// <param name="requiredMetadata">
        ///     An <see cref="IEnumerable{T}"/> of <see cref="String"/> objects containing
        ///     the metadata names of the <see cref="Export"/> required by the 
        ///     <see cref="ContractBasedImportDefinition"/>; or <see langword="null"/> to
        ///     set the <see cref="RequiredMetadata"/> property to an empty <see cref="IEnumerable{T}"/>.
        /// </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="ContractBasedImportDefinition"/>.
        /// </param>
        /// <param name="isRecomposable">
        ///     <see langword="true"/> if the <see cref="ContractBasedImportDefinition"/> 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="ContractBasedImportDefinition"/> is required to be 
        ///     satisfied before a <see cref="ComposablePart"/> can start producing exported 
        ///     objects; otherwise, <see langword="false"/>.
        /// </param>
        /// <param name="requiredCreationPolicy">
        ///     A value indicating that the importer requires a specific <see cref="CreationPolicy"/> for 
        ///     the exports used to satisfy this import. If no specific <see cref="CreationPolicy"/> is needed
        ///     pass the default <see cref="CreationPolicy.Any"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="contractName"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="contractName"/> is an empty string ("").
        ///     <para>
        ///         -or-
        ///     </para>
        ///     <paramref name="requiredMetadata"/> contains an element that is <see langword="null"/>.
        ///     <para>
        ///         -or-
        ///     </para>
        ///     <paramref name="cardinality"/> is not one of the <see cref="ImportCardinality"/> 
        ///     values.
        /// </exception>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        public ContractBasedImportDefinition(string contractName, string requiredTypeIdentity, IEnumerable<KeyValuePair<string, Type>> requiredMetadata, 
            ImportCardinality cardinality, bool isRecomposable, bool isPrerequisite, CreationPolicy requiredCreationPolicy)
            : base(contractName, cardinality, isRecomposable, isPrerequisite)
        {
            Requires.NotNullOrEmpty(contractName, "contractName");

            this._requiredTypeIdentity = requiredTypeIdentity;

            if (requiredMetadata != null)
            {
                this._requiredMetadata = requiredMetadata;
            }

            this._requiredCreationPolicy = requiredCreationPolicy;
        }

        /// <summary>
        ///     The type identity of the export type expected.
        /// </summary>
        /// <value>
        ///     A <see cref="string"/> that is generated by <see cref="AttributedModelServices.GetTypeIdentity(Type)"/>
        ///     on the type that this import expects. If the value is <see langword="null"/> then this import
        ///     doesn't expect a particular type.
        /// </value>
        public virtual string RequiredTypeIdentity
        {
            get { return this._requiredTypeIdentity; }
        }

        /// <summary>
        ///     Gets the metadata names of the export required by the import definition.
        /// </summary>
        /// <value>
        ///     An <see cref="IEnumerable{T}"/> of pairs of metadata keys and types of the <see cref="Export"/> required by the 
        ///     <see cref="ContractBasedImportDefinition"/>. The default is an empty 
        ///     <see cref="IEnumerable{T}"/>.
        /// </value>
        /// <remarks>
        ///     <note type="inheritinfo">
        ///         Overriders of this property should never return <see langword="null"/>
        ///         or return an <see cref="IEnumerable{T}"/> that contains an element that is
        ///         <see langword="null"/>. If the definition does not contain required metadata, 
        ///         return an empty <see cref="IEnumerable{T}"/> instead.
        ///     </note>
        /// </remarks>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        public virtual IEnumerable<KeyValuePair<string, Type>> RequiredMetadata
        {
            get
            {
                // NOTE : unlike other arguments, we validate this one as late as possible, because its validation may lead to type loading
                this.ValidateRequiredMetadata();

                return this._requiredMetadata;
            }
        }

        private void ValidateRequiredMetadata()
        {
            if (!this._isRequiredMetadataValidated)
            {
                foreach (KeyValuePair<string, Type> metadataItem in this._requiredMetadata)
                {
                    if ((metadataItem.Key == null) || (metadataItem.Value == null))
                    {
                        throw new InvalidOperationException(
                            string.Format(CultureInfo.CurrentCulture, Strings.Argument_NullElement, "requiredMetadata"));
                    }
                }
                this._isRequiredMetadataValidated = true;
            }
        }

        /// <summary>
        ///     Gets or sets a value indicating that the importer requires a specific 
        ///     <see cref="CreationPolicy"/> for the exports used to satisfy this import. T
        /// </summary>
        /// <value>
        ///     <see cref="CreationPolicy.Any"/> - default value, used if the importer doesn't 
        ///         require a specific <see cref="CreationPolicy"/>.
        /// 
        ///     <see cref="CreationPolicy.Shared"/> - Requires that all exports used should be shared
        ///         by everyone in the container.
        /// 
        ///     <see cref="CreationPolicy.NonShared"/> - Requires that all exports used should be 
        ///         non-shared in a container and thus everyone gets their own instance.
        /// </value>
        public virtual CreationPolicy RequiredCreationPolicy
        {
            get { return this._requiredCreationPolicy; }
        }

        /// <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>
        /// <remarks>
        ///     <para>
        ///         This property returns an expression that defines conditions based on the 
        ///         <see cref="ImportDefinition.ContractName"/>, <see cref="RequiredTypeIdentity"/>, 
        ///         <see cref="RequiredMetadata"/>, and <see cref="RequiredCreationPolicy"/>
        ///         properties. 
        ///     </para>
        /// </remarks>
        public override Expression<Func<ExportDefinition, bool>> Constraint
        {   
            get
            {
                if (this._constraint == null)
                {
                    this._constraint = ConstraintServices.CreateConstraint(this.ContractName, this.RequiredTypeIdentity, this.RequiredMetadata, this.RequiredCreationPolicy);
                }

                return this._constraint;
            }
        }

        /// <summary>
        ///     Executes an optimized version of the contraint given by the <see cref="Constraint"/> property
        /// </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 override bool IsConstraintSatisfiedBy(ExportDefinition exportDefinition)
        {
            Requires.NotNull(exportDefinition, "exportDefinition");

            if (!StringComparers.ContractName.Equals(this.ContractName, exportDefinition.ContractName))
            {
                return false;
            }

            return MatchRequiredMatadata(exportDefinition);
        }

        private bool MatchRequiredMatadata(ExportDefinition definition)
        {
            if (!string.IsNullOrEmpty(this.RequiredTypeIdentity))
            {
                string exportTypeIdentity = definition.Metadata.GetValue<string>(CompositionConstants.ExportTypeIdentityMetadataName);

                if (!StringComparers.ContractName.Equals(this.RequiredTypeIdentity, exportTypeIdentity))
                {
                    return false;
                }
            }

            foreach (KeyValuePair<string, Type> metadataItem in this.RequiredMetadata)
            {
                string metadataKey = metadataItem.Key;
                Type metadataValueType = metadataItem.Value;

                object metadataValue = null;
                if (!definition.Metadata.TryGetValue(metadataKey, out metadataValue))
                {
                    return false;
                }

                if (metadataValue != null)
                {
                    // the metadata value is not null, we can rely on IsInstanceOfType to do the right thing
                    if (!metadataValueType.IsInstanceOfType(metadataValue))
                    {
                        return false;
                    }
                }
                else
                {
                    // this is an unfortunate special case - typeof(object).IsInstanceofType(null) == false
                    // basically nulls are not considered valid values for anything
                    // We want them to match anything that is a reference type
                    if (metadataValueType.IsValueType)
                    {
                        // this is a pretty expensive check, but we only invoke it when metadata values are null, which is very rare
                        return false;
                    }
                }
            }

            if (this.RequiredCreationPolicy == CreationPolicy.Any)
            {
                return true;
            }

            CreationPolicy exportPolicy = definition.Metadata.GetValue<CreationPolicy>(CompositionConstants.PartCreationPolicyMetadataName);
            return exportPolicy == CreationPolicy.Any ||
                   exportPolicy == this.RequiredCreationPolicy;
        }
    }
}
