// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Internal;
using System.Threading;

namespace System.ComponentModel.Composition.Primitives
{
    /// <summary>
    ///     Represents an export. That is, a type that is made up of a delay-created exported value 
    ///     and metadata that describes that object.
    /// </summary>
    public class Export
    {
        private readonly ExportDefinition _definition;
        private readonly Func<object> _exportedValueGetter;
        private static readonly object _EmptyValue = new object();
        private volatile object _exportedValue = Export._EmptyValue;
        
        /// <summary>
        ///     Initializes a new instance of the <see cref="Export"/> class.
        /// </summary>
        /// <remarks>
        ///     <note type="inheritinfo">
        ///         Derived types calling this constructor must override <see cref="Definition"/>
        ///         and <see cref="GetExportedValueCore"/>.
        ///     </note>
        /// </remarks>
        protected Export()
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="Export"/> class 
        ///     with the specified contract name and exported value getter.
        /// </summary>
        /// <param name="contractName">
        ///     A <see cref="String"/> containing the contract name of the 
        ///     <see cref="Export"/>.
        /// </param>
        /// <param name="exportedValueGetter">
        ///     A <see cref="Func{T}"/> that is called to create the exported value of the 
        ///     <see cref="Export"/>. This allows the creation of the object to be delayed
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="contractName"/> is <see langword="null"/>.
        ///     <para>
        ///         -or-
        ///     </para>
        ///     <paramref name="exportedValueGetter"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="contractName"/> is an empty string ("").
        /// </exception>
        public Export(string contractName, Func<object> exportedValueGetter)
            : this(new ExportDefinition(contractName, (IDictionary<string, object>)null), exportedValueGetter)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="Export"/> class 
        ///     with the specified contract name, metadata and exported value getter.
        /// </summary>
        /// <param name="contractName">
        ///     A <see cref="String"/> containing the contract name of the 
        ///     <see cref="Export"/>.
        /// </param>
        /// <param name="metadata">
        ///     An <see cref="IDictionary{TKey, TValue}"/> containing the metadata of the 
        ///     <see cref="Export"/>; or <see langword="null"/> to set the 
        ///     <see cref="Metadata"/> property to an empty, read-only 
        ///     <see cref="IDictionary{TKey, TValue}"/>.
        /// </param>
        /// <param name="exportedValueGetter">
        ///     A <see cref="Func{T}"/> that is called to create the exported value of the 
        ///     <see cref="Export"/>. This allows the creation of the object to be delayed.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="contractName"/> is <see langword="null"/>.
        ///     <para>
        ///         -or-
        ///     </para>
        ///     <paramref name="exportedValueGetter"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="contractName"/> is an empty string ("").
        /// </exception>
        public Export(string contractName, IDictionary<string, object> metadata, Func<object> exportedValueGetter) 
            : this(new ExportDefinition(contractName, metadata), exportedValueGetter)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="Export"/> class 
        ///     with the specified export definition and exported value getter.
        /// </summary>
        /// <param name="definition">
        ///     An <see cref="ExportDefinition"/> that describes the contract that the 
        ///     <see cref="Export"/> satisfies.
        /// </param>
        /// <param name="exportedValueGetter">
        ///     A <see cref="Func{T}"/> that is called to create the exported value of the 
        ///     <see cref="Export"/>. This allows the creation of the object to be delayed. 
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="definition"/> is <see langword="null"/>.
        ///     <para>
        ///         -or-
        ///     </para>
        ///     <paramref name="exportedValueGetter"/> is <see langword="null"/>.
        /// </exception>
        public Export(ExportDefinition definition, Func<object> exportedValueGetter)
        {
            Requires.NotNull(definition, "definition");
            Requires.NotNull(exportedValueGetter, "exportedValueGetter");

            this._definition = definition;
            this._exportedValueGetter = exportedValueGetter;
        }

        /// <summary>
        ///     Gets the definition that describes the contract that the export satisfies.
        /// </summary>
        /// <value>
        ///     An <see cref="ExportDefinition"/> that describes the contract that 
        ///     the <see cref="Export"/> satisfies.
        /// </value>
        /// <exception cref="NotImplementedException">
        ///     This 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 ExportDefinition Definition
        {
            get 
            {
                if (_definition != null)
                {
                    return _definition;
                }

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

        /// <summary>
        ///     Gets the metadata of the export.
        /// </summary>
        /// <value>
        ///     An <see cref="IDictionary{TKey, TValue}"/> containing the metadata of the 
        ///     <see cref="Export"/>.
        /// </value>
        /// <exception cref="NotImplementedException">
        ///     The <see cref="Definition"/> property was not overridden by a derived class.
        /// </exception>
        /// <remarks>
        ///     <para>
        ///         This property returns the value of <see cref="ExportDefinition.Metadata"/>
        ///         of the <see cref="Definition"/> property.
        ///     </para>
        /// </remarks>
        public IDictionary<string, object> Metadata
        {
            get { return Definition.Metadata; }
        }

        /// <summary>
        ///     Returns the exported value of the export.
        /// </summary>
        /// <returns>
        ///     The exported <see cref="Object"/> of the <see cref="Export"/>.
        /// </returns>
        /// <exception cref="CompositionException">
        ///     An error occurred during composition. <see cref="CompositionException.Errors"/> will 
        ///     contain a collection of errors that occurred.
        /// </exception>
        /// <exception cref="CompositionContractMismatchException">
        ///     The current instance is an instance of <see cref="Lazy{T}"/> and the underlying 
        ///     exported value cannot be cast to <c>T</c>.
        /// </exception>
        /// <exception cref="NotImplementedException">
        ///     The <see cref="GetExportedValueCore"/> method was not overridden by a derived class.
        /// </exception>
        public object Value
        {
            get
            {
                // NOTE : the logic below guarantees that the value will be set exactly once. It DOES NOT, however, guarantee that GetExportedValueCore() will be executed
                // more than once, as locking would be required for that. The said locking is problematic, as we can't reliable call 3rd party code under a lock.
                if (this._exportedValue == Export._EmptyValue)
                {
                    object exportedValue = this.GetExportedValueCore();

                    // NOTE : According to http://msdn.microsoft.com/en-us/library/4bw5ewxy.aspx, the warning is bogus when used with Interlocked API.
#pragma warning disable 420
                    Interlocked.CompareExchange(ref this._exportedValue, exportedValue, Export._EmptyValue);
#pragma warning restore 420
                }

                return this._exportedValue;
            }
        }

        /// <summary>
        ///     Returns the exported value of the export.
        /// </summary>
        /// <returns>
        ///     The exported <see cref="Object"/> of the <see cref="Export"/>.
        /// </returns>
        /// <exception cref="CompositionException">
        ///     An error occurred during composition. <see cref="CompositionException.Errors"/> will 
        ///     contain a collection of errors that occurred.
        /// </exception>
        /// <exception cref="CompositionContractMismatchException">
        ///     The current instance is an instance of <see cref="Lazy{T}"/> and the underlying 
        ///     exported value cannot be cast to <c>T</c>.
        /// </exception>
        /// <exception cref="NotImplementedException">
        ///     The method was not overridden by a derived class.
        /// </exception>
        /// <remarks>
        ///     <note type="inheritinfo">
        ///         Overriders of this method should never return
        ///         <see langword="null"/>.
        ///     </note>
        /// </remarks>
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        protected virtual object GetExportedValueCore()
        {
            if (this._exportedValueGetter != null)
            {
                return this._exportedValueGetter.Invoke();
            }

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