namespace Caliburn.Micro
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Linq;
    using System.Reflection;
#if !WinRT
    using System.Windows;
    using Caliburn.Micro.Core;
#else
    using Windows.UI.Xaml;
#endif
    /// 
    /// A source of assemblies that are inspectable by the framework.
    /// 
    public static class AssemblySource {
        /// 
        /// The singleton instance of the AssemblySource used by the framework.
        /// 
        public static readonly IObservableCollection Instance = new BindableCollection();
        /// 
        /// Finds a type which matches one of the elements in the sequence of names.
        /// 
        public static Func, Type> FindTypeByNames = names => {
            if (names == null) {
                return null;
            }
            var type = names
                .Join(Instance.SelectMany(a => a.GetExportedTypes()), n => n, t => t.FullName, (n, t) => t)
                .FirstOrDefault();
            return type;
        };
    }
    /// 
    /// A caching subsystem for .
    /// 
    public static class AssemblySourceCache {
        static bool isInstalled;
        static readonly IDictionary TypeNameCache = new Dictionary();
        /// 
        /// Extracts the types from the spezified assembly for storing in the cache.
        /// 
        public static Func> ExtractTypes = assembly =>
            assembly.GetExportedTypes()
                .Where(t =>
#if !CORE
                    typeof(UIElement).IsAssignableFrom(t) ||
#endif
                    typeof(INotifyPropertyChanged).IsAssignableFrom(t));
        /// 
        /// Installs the caching subsystem.
        /// 
        public static void Install() {
            if (isInstalled) return;
            isInstalled = true;
            AssemblySource.Instance.CollectionChanged += (s, e) => {
                switch (e.Action) {
                    case NotifyCollectionChangedAction.Add:
                        e.NewItems.OfType()
                            .SelectMany(a => ExtractTypes(a))
                            .Apply(t => TypeNameCache.Add(t.FullName, t));
                        break;
                    case NotifyCollectionChangedAction.Remove:
                    case NotifyCollectionChangedAction.Replace:
                    case NotifyCollectionChangedAction.Reset:
                        TypeNameCache.Clear();
                        AssemblySource.Instance
                            .SelectMany(a => ExtractTypes(a))
                            .Apply(t => TypeNameCache.Add(t.FullName, t));
                        break;
                }
            };
            AssemblySource.Instance.Refresh();
            AssemblySource.FindTypeByNames = names => {
                if (names == null) {
                    return null;
                }
                var type = names.Select(n => TypeNameCache.GetValueOrDefault(n)).FirstOrDefault(t => t != null);
                return type;
            };
        }
    }
}