namespace Fluent.Internal
{
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Media;

    /// <summary>
    /// Class with helper functions for UI related stuff
    /// </summary>
    // ReSharper disable once InconsistentNaming
    internal static class UIHelper
    {
        /// <summary>
        /// Gets the first visual child of <paramref name="parent"/>.
        /// If there are no visual children <c>null</c> is returned.
        /// </summary>
        /// <returns>The first visual child of <paramref name="parent"/> or <c>null</c> if there are no children.</returns>
        public static DependencyObject GetFirstVisualChild(DependencyObject parent)
        {
            var childrenCount = VisualTreeHelper.GetChildrenCount(parent);

            return childrenCount == 0
                       ? null
                       : VisualTreeHelper.GetChild(parent, 0);
        }

        /// <summary>
        /// Tries to find immediate visual child of type <typeparamref name="T"/> which matches <paramref name="predicate"/>
        /// </summary>
        /// <returns>
        /// The visual child of type <typeparamref name="T"/> that matches <paramref name="predicate"/>.
        /// Returns <c>null</c> if no child matches.
        /// </returns>
        public static T FindImmediateVisualChild<T>(DependencyObject parent, Predicate<T> predicate)
            where T : DependencyObject
        {
            foreach (var child in GetVisualChildren(parent))
            {
                if (child is T obj
                    && predicate(obj))
                {
                    return obj;
                }
            }

            return null;
        }

        /// <summary>
        /// Gets the first visual child of type TChildItem by walking down the visual tree.
        /// </summary>
        /// <typeparam name="TChildItem">The type of visual child to find.</typeparam>
        /// <param name="parent">The parent element whose visual tree shall be walked down.</param>
        /// <returns>The first element of type TChildItem found in the visual tree is returned. If none is found, null is returned.</returns>
        public static TChildItem FindVisualChild<TChildItem>(DependencyObject parent)
            where TChildItem : DependencyObject
        {
            foreach (var child in GetVisualChildren(parent))
            {
                if (child is TChildItem item)
                {
                    return item;
                }

                var childOfChild = FindVisualChild<TChildItem>(child);
                if (childOfChild != null)
                {
                    return childOfChild;
                }
            }

            return null;
        }

        /// <summary>
        /// Gets all visual children of <paramref name="parent"/>.
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<DependencyObject> GetVisualChildren(DependencyObject parent)
        {
            var visualChildrenCount = VisualTreeHelper.GetChildrenCount(parent);

            for (var i = 0; i < visualChildrenCount; i++)
            {
                var child = VisualTreeHelper.GetChild(parent, i);

                yield return child;
            }
        }

        /// <summary>
        /// Finds the parent control of type <typeparamref name="T"/>.
        /// First looks at the visual tree and then at the logical tree to find the parent.
        /// </summary>
        /// <returns>The found visual/logical parent or null.</returns>
        /// <remarks>This method searches further up the parent chain instead of just using the immediate parent.</remarks>
        public static T GetParent<T>(DependencyObject element, Predicate<T> filter = null)
            where T : DependencyObject
        {
            if (element is null)
            {
                return null;
            }

            {
                var item = GetVisualParent(element);

                while (item != null)
                {
                    if (item is T variable
                        && (filter?.Invoke(variable) ?? true))
                    {
                        return variable;
                    }

                    item = GetVisualParent(item) ?? LogicalTreeHelper.GetParent(item);
                }
            }

            {
                var item = LogicalTreeHelper.GetParent(element);

                while (item != null)
                {
                    if (item is T variable
                        && (filter?.Invoke(variable) ?? true))
                    {
                        return variable;
                    }

                    item = LogicalTreeHelper.GetParent(item);
                }
            }

            return null;
        }

        /// <summary>
        /// Returns either the visual or logical parent of <paramref name="element"/>.
        /// This also works for <see cref="ContentElement"/> and <see cref="FrameworkContentElement"/>.
        /// </summary>
        public static DependencyObject GetVisualOrLogicalParent(DependencyObject element)
        {
            return GetVisualParent(element) ?? LogicalTreeHelper.GetParent(element);
        }

        /// <summary>
        /// Returns the visual parent of <paramref name="element"/>.
        /// This also works for <see cref="ContentElement"/> and <see cref="FrameworkContentElement"/>.
        /// </summary>
        public static DependencyObject GetVisualParent(DependencyObject element)
        {
            if (element is null)
            {
                return null;
            }

            if (element is ContentElement contentElement)
            {
                var parent = ContentOperations.GetParent(contentElement);

                if (parent != null)
                {
                    return parent;
                }

                var frameworkContentElement = contentElement as FrameworkContentElement;
                return frameworkContentElement?.Parent;
            }

            return VisualTreeHelper.GetParent(element);
        }

        /// <summary>
        /// First checks if <paramref name="visual"/> is either a <see cref="AdornerDecorator"/> or <see cref="ScrollContentPresenter"/> and if it is returns it's <see cref="AdornerLayer"/>.
        /// If those checks yield no result <see cref="AdornerLayer.GetAdornerLayer"/> is called.
        /// </summary>
        /// <param name="visual">The visual element for which to find an adorner layer.</param>
        /// <returns>An adorner layer for the specified visual, or null if no adorner layer can be found.</returns>
        /// <exception cref="T:System.ArgumentNullException">Raised when visual is null.</exception>
        public static AdornerLayer GetAdornerLayer(Visual visual)
        {
            if (visual is null)
            {
                throw new ArgumentNullException(nameof(visual));
            }

            if (visual is AdornerDecorator decorator)
            {
                return decorator.AdornerLayer;
            }

            if (visual is ScrollContentPresenter scrollContentPresenter)
            {
                return scrollContentPresenter.AdornerLayer;
            }

            return AdornerLayer.GetAdornerLayer(visual);
        }

        /// <summary>
        /// Gets all containers from the <see cref="ItemContainerGenerator"/> of <paramref name="itemsControl"/>.
        /// </summary>
        /// <typeparam name="T">The desired container type.</typeparam>
        public static IEnumerable<T> GetAllItemContainers<T>(ItemsControl itemsControl)
            where T : class
        {
            return GetAllItemContainers<T>(itemsControl.ItemContainerGenerator);
        }

        /// <summary>
        /// Gets all containers from <paramref name="itemContainerGenerator"/>.
        /// </summary>
        /// <typeparam name="T">The desired container type.</typeparam>
        public static IEnumerable<T> GetAllItemContainers<T>(ItemContainerGenerator itemContainerGenerator)
            where T : class
        {
            for (var i = 0; i < itemContainerGenerator.Items.Count; i++)
            {
                if (itemContainerGenerator.ContainerFromIndex(i) is T container)
                {
                    yield return container;
                }
            }
        }
    }
}