// 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.Collections.Generic;
using System.ComponentModel.Composition.Primitives;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Reflection;
using Microsoft.Internal;

namespace System.ComponentModel.Composition.Hosting
{
    public partial class ApplicationCatalog : ComposablePartCatalog, ICompositionElement
    {
        private bool _isDisposed = false;
        private volatile AggregateCatalog _innerCatalog = null;
        private readonly object _thisLock = new object();
        private ICompositionElement _definitionOrigin = null;
        private ReflectionContext _reflectionContext = null;

        public ApplicationCatalog() {}

        public ApplicationCatalog(ICompositionElement definitionOrigin)
        {
            Requires.NotNull(definitionOrigin, "definitionOrigin");

            _definitionOrigin = definitionOrigin;
        }
        
        public ApplicationCatalog(ReflectionContext reflectionContext)
        {
            Requires.NotNull(reflectionContext, "reflectionContext");

            _reflectionContext = reflectionContext;
        }

        public ApplicationCatalog(ReflectionContext reflectionContext, ICompositionElement definitionOrigin)
        {
            Requires.NotNull(reflectionContext, "reflectionContext");
            Requires.NotNull(definitionOrigin, "definitionOrigin");

            _reflectionContext = reflectionContext;
            _definitionOrigin = definitionOrigin;
        }

        internal ComposablePartCatalog CreateCatalog(string location, string pattern)
        {
            if(_reflectionContext != null)
            {
                return (_definitionOrigin != null)
                    ? new DirectoryCatalog(location, pattern, _reflectionContext, _definitionOrigin)
                    : new DirectoryCatalog(location, pattern, _reflectionContext);
            }
            return (_definitionOrigin != null)
                ? new DirectoryCatalog(location, pattern, _definitionOrigin)
                : new DirectoryCatalog(location, pattern);
        }

//  Note:
//      Creating a catalog does not cause change notifications to propagate, For some reason the DeploymentCatalog did, but that is a bug.
//      InnerCatalog is delay evaluated, from data supplied at construction time and so does not propagate change notifications
        private AggregateCatalog InnerCatalog
        {
            get
            {
                if(_innerCatalog == null)
                {
                    lock(_thisLock)
                    {
                        if(_innerCatalog == null)
                        {
                            var location = AppDomain.CurrentDomain.BaseDirectory;
                            Assumes.NotNull(location);
        
                            var catalogs = new List<ComposablePartCatalog>();
                            catalogs.Add(CreateCatalog(location, "*.exe"));
                            catalogs.Add(CreateCatalog(location, "*.dll"));
        
                            string relativeSearchPath = AppDomain.CurrentDomain.RelativeSearchPath;
                            if(!string.IsNullOrEmpty(relativeSearchPath))
                            {
                                string[] probingPaths = relativeSearchPath.Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries);
                                foreach(var probingPath in probingPaths)
                                {
                                    var path = Path.Combine(location, probingPath);
                                    if(Directory.Exists(path))
                                    {
                                        catalogs.Add(CreateCatalog(path, "*.dll"));
                                    }
                                }
                            }
                            var innerCatalog = new AggregateCatalog(catalogs);
                            _innerCatalog = innerCatalog;
                        }
                    }
                }

                return _innerCatalog;
            }
        }

        protected override void Dispose(bool disposing)
        {
            try
            {
                if (!_isDisposed)
                {
                    IDisposable innerCatalog = null;
                    lock (_thisLock)
                    {
                        innerCatalog = _innerCatalog as IDisposable;
                        _innerCatalog = null;
                        _isDisposed = true;
                    }
                    if(innerCatalog != null)
                    {
                        innerCatalog.Dispose();
                    }
                }
            }
            finally
            {
                base.Dispose(disposing);
            }
        }

        public override IEnumerator<ComposablePartDefinition> GetEnumerator()
        {
            ThrowIfDisposed();

            return InnerCatalog.GetEnumerator();
        }

        /// <summary>
        ///     Returns the export definitions that match the constraint defined by the specified definition.
        /// </summary>
        /// <param name="definition">
        ///     The <see cref="ImportDefinition"/> that defines the conditions of the 
        ///     <see cref="ExportDefinition"/> objects to return.
        /// </param>
        /// <returns>
        ///     An <see cref="IEnumerable{T}"/> of <see cref="Tuple{T1, T2}"/> containing the 
        ///     <see cref="ExportDefinition"/> objects and their associated 
        ///     <see cref="ComposablePartDefinition"/> for objects that match the constraint defined 
        ///     by <paramref name="definition"/>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="definition"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="DirectoryCatalog"/> has been disposed of.
        /// </exception>
        public override IEnumerable<Tuple<ComposablePartDefinition, ExportDefinition>> GetExports(ImportDefinition definition)
        {
            ThrowIfDisposed();

            Requires.NotNull(definition, "definition");

            return InnerCatalog.GetExports(definition);
        }

        [DebuggerStepThrough]
        [ContractArgumentValidator]
        [SuppressMessage("Microsoft.Contracts", "CC1053", Justification = "Suppressing warning because this validator has no public contract")]
        private void ThrowIfDisposed()
        {
            if (_isDisposed)
            {
                throw ExceptionBuilder.CreateObjectDisposed(this);
            }
        }

        private string GetDisplayName()
        {
            return string.Format(CultureInfo.CurrentCulture,
                                "{0} (Path=\"{1}\") (PrivateProbingPath=\"{2}\")",   // NOLOC
                                GetType().Name,
                                AppDomain.CurrentDomain.BaseDirectory, 
                                AppDomain.CurrentDomain.RelativeSearchPath);
        }

        /// <summary>
        ///     Returns a string representation of the directory catalog.
        /// </summary>
        /// <returns>
        ///     A <see cref="String"/> containing the string representation of the <see cref="DirectoryCatalog"/>.
        /// </returns>
        public override string ToString()
        {
            return GetDisplayName();
        }

        /// <summary>
        ///     Gets the display name of the ApplicationCatalog.
        /// </summary>
        /// <value>
        ///     A <see cref="String"/> containing a human-readable display name of the <see cref="ApplicationCatalog"/>.
        /// </value>
        [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
        string ICompositionElement.DisplayName
        {
            get { return GetDisplayName(); }
        }

        /// <summary>
        ///     Gets the composition element from which the ApplicationCatalog originated.
        /// </summary>
        /// <value>
        ///     This property always returns <see langword="null"/>.
        /// </value>
        [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
        ICompositionElement ICompositionElement.Origin
        {
            get { return null; }
        }
    }
}
