namespace Caliburn.Micro {
    using System;
    using System.Collections.Generic;
    using System.Linq;
#if WinRT
    using System.ServiceModel;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Controls.Primitives;
    using Windows.UI.Xaml.Media;
#else
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Media.Media3D;
    using Caliburn.Micro.Core;
#endif
    /// 
    /// Provides methods for searching a given scope for named elements.
    ///  
    public static class BindingScope {
        static readonly List ChildResolvers = new List();
        static readonly Dictionary NonResolvableChildTypes = new Dictionary();
        static BindingScope()
        {
            AddChildResolver(e => new[] { e.Content as DependencyObject });
            AddChildResolver(e => e.Items.OfType().ToArray() );
#if !SILVERLIGHT && !WinRT
            AddChildResolver(e => new[] { e.Header as DependencyObject });
            AddChildResolver(e => new[] { e.Header as DependencyObject });
#endif
#if WinRT
            AddChildResolver(e => new[] { e.ZoomedInView as DependencyObject, e.ZoomedOutView as DependencyObject });
            AddChildResolver(e => new[] { e.Header as DependencyObject });
#endif
#if WinRT81
            AddChildResolver(e => new[] { e.Footer as DependencyObject });
            AddChildResolver(ResolveHub);
            AddChildResolver(e => new[] { e.Header as DependencyObject });
            AddChildResolver(ResolveCommandBar);
            AddChildResolver(e => ResolveFlyoutBase(e.Flyout));
            AddChildResolver(e => ResolveFlyoutBase(FlyoutBase.GetAttachedFlyout(e)));
#endif
#if WINDOWS_UWP
            AddChildResolver(e => new[] { e.Pane as DependencyObject, e.Content as DependencyObject });
#endif
        }
        /// 
        /// Searches through the list of named elements looking for a case-insensitive match.
        ///  
        /// The named element or null if not found. 
        public static FrameworkElement FindName(this IEnumerable elementsToSearch, string name) {
#if WinRT
            return elementsToSearch.FirstOrDefault(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
#else
            return elementsToSearch.FirstOrDefault(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
#endif
        }
        /// 
        /// Adds a child resolver.
        ///  
        ///  filter, Func> resolver) {
            if (filter == null) {
                throw new ArgumentNullException("filter");
            }
            if (resolver == null) {
                throw new ArgumentNullException("resolver");
            }
            NonResolvableChildTypes.Clear();
            
            var childResolver = new ChildResolver(filter, resolver);
            ChildResolvers.Add(childResolver);
            return childResolver;
        }
        /// 
        /// Adds a child resolver.
        ///  
        /// (Func> resolver) where T : DependencyObject
        {
            if (resolver == null) {
                throw new ArgumentNullException("resolver");
            }
            NonResolvableChildTypes.Clear();
            var childResolver = new ChildResolver(resolver);
            ChildResolvers.Add(childResolver);
            return childResolver;
        }
        /// 
        /// Removes a child resolver.
        ///  
        /// true, when the resolver was (found and) removed. 
        public static bool RemoveChildResolver(ChildResolver resolver) {
            if (resolver == null) {
                throw new ArgumentNullException("resolver");
            }
            return ChildResolvers.Remove(resolver);
        }
        /// 
        /// Gets all the  
        /// Named  
        /// Pass in a  
        public static Func> GetNamedElements = elementInScope => {
            var routeHops = FindScopeNamingRoute(elementInScope);
            return FindNamedDescendants(routeHops);
        };
        /// 
        /// Gets the parent of the given object in the Visual Tree.
        ///  
        /// The parent of the given object in the Visual Tree 
        public static Func GetVisualParent = e => VisualTreeHelper.GetParent(e);
        /// 
        /// Finds a set of named  
        /// 
        /// Searches all the elements in the HeaderedContentControl.Header ,
        /// the HeaderedItemsControl.Header , if any are found.
        ///  
        public static Func> FindNamedDescendants = routeHops => {
            if (routeHops == null) {
                throw new ArgumentNullException("routeHops");
            }
            if (routeHops.Root == null) {
                throw new ArgumentException(String.Format("Root is null on the given {0}", typeof (ScopeNamingRoute)));
            }
            var descendants = new List();
            var queue = new Queue();
            queue.Enqueue(routeHops.Root);
            while (queue.Count > 0) {
                var current = queue.Dequeue();
                var currentElement = current as FrameworkElement;
                if (currentElement != null && !string.IsNullOrEmpty(currentElement.Name))
                    descendants.Add(currentElement);
                if (current is UserControl && !ReferenceEquals(current, routeHops.Root))
                    continue;
                DependencyObject hopTarget;
                if (routeHops.TryGetHop(current, out hopTarget)) {
                    queue.Enqueue(hopTarget);
                    continue;
                }
#if NET
                var childCount = (current is Visual || current is Visual3D)
                    ? VisualTreeHelper.GetChildrenCount(current) : 0;
#else
                var childCount = (current is UIElement)
                    ? VisualTreeHelper.GetChildrenCount(current) : 0;
#endif
                if (childCount > 0) {
                    for (var i = 0; i < childCount; i++) {
                        var childDo = VisualTreeHelper.GetChild(current, i);
                        queue.Enqueue(childDo);
                    }
#if WinRT
                    var page = current as Page;
                    if (page != null) {
                        if (page.BottomAppBar != null)
                            queue.Enqueue(page.BottomAppBar);
                        if (page.TopAppBar != null)
                            queue.Enqueue(page.TopAppBar);
                    }
#endif
                }
                else {
                    var currentType = current.GetType();
                    if (!NonResolvableChildTypes.ContainsKey(currentType)) {
                        var resolvers = ChildResolvers.Where(r => r.CanResolve(currentType)).ToArray();
                        if (!resolvers.Any()) {
                            NonResolvableChildTypes[currentType] = null;
                        }
                        else {
                            resolvers
                                .SelectMany(r => r.Resolve(current) ?? Enumerable.Empty())
                                .Where(c => c != null)
                                .Apply(queue.Enqueue);
                        }
                    }
                }
            }
            return descendants;
        };
#if WinRT81
        private static IEnumerable ResolveFlyoutBase(FlyoutBase flyoutBase) {
            if (flyoutBase == null)
                yield break;
            var flyout = flyoutBase as Flyout;
            if (flyout != null && flyout.Content != null)
                yield return flyout.Content;
            var menuFlyout = flyoutBase as MenuFlyout;
            if (menuFlyout != null && menuFlyout.Items != null) {
                foreach (var item in menuFlyout.Items) {
                    foreach (var subItem in ResolveMenuFlyoutItems(item)) {
                        yield return subItem;
                    }
                }
            }
        }
        private static IEnumerable ResolveMenuFlyoutItems(MenuFlyoutItemBase item) {
            yield return item;
#if WINDOWS_UWP
            var subItem = item as MenuFlyoutSubItem;
            if (subItem != null && subItem.Items != null) {
                foreach (var subSubItem in subItem.Items) {
                    yield return subSubItem;
                }
            }
#endif
        }
        private static IEnumerable ResolveCommandBar(CommandBar commandBar) {
            foreach (var child in commandBar.PrimaryCommands.OfType()) {
                yield return child;
            }
            foreach (var child in commandBar.SecondaryCommands.OfType())
            {
                yield return child;
            }
        }
        private static IEnumerable ResolveHub(Hub hub) {
            yield return hub.Header as DependencyObject;
            foreach (var section in hub.Sections)
                yield return section;
        }
#endif
        /// 
        /// Finds a path of dependency objects which traces through visual anscestry until a root which is Page  with a dependency object Page.ContentProperty  value, 
        /// a dependency object with  
        public static Func FindScopeNamingRoute = elementInScope => {
            var root = elementInScope;
            var previous = elementInScope;
            DependencyObject contentPresenter = null;
            var routeHops = new ScopeNamingRoute();
            while (true) {
                if (root == null) {
                    root = previous;
                    break;
                }
                if (root is UserControl)
                    break;
#if !SILVERLIGHT
                if (root is Page) {
                    root = ((Page) root).Content as DependencyObject ?? root;
                    break;
                }
#endif
                if ((bool) root.GetValue(View.IsScopeRootProperty))
                    break;
#if WinRT
                if (root is AppBar) {
                    var frame = Window.Current.Content as Frame;
                    var page = (frame != null) ? frame.Content as Page : null;
                    if (page != null && (root == page.TopAppBar || root == page.BottomAppBar)) {
                        root = page;
                        break;
                    }
                }
#endif
                if (root is ContentPresenter)
                    contentPresenter = root;
                else if (root is ItemsPresenter && contentPresenter != null) {
                    routeHops.AddHop(root, contentPresenter);
                    contentPresenter = null;
                }
                previous = root;
                root = GetVisualParent(previous);
            }
            routeHops.Root = root;
            return routeHops;
        };
        /// 
        /// Maintains a connection in the visual tree of dependency objects in order to record a route through it.
        ///  
        public class ScopeNamingRoute {
            readonly Dictionary path = new Dictionary();
            DependencyObject root;
            /// 
            /// Gets or sets the starting point of the route.
            ///  
            public DependencyObject Root {
                get { return root; }
                set {
                    if (path.ContainsValue(value)) {
                        throw new ArgumentException("Value is a target of some route hop; cannot be a root.");
                    }
                    root = value;
                }
            }
            /// 
            /// Adds a segment to the route.
            ///  
            /// 
            /// Tries to get a target dependency object given a source.
            ///  
            ///