using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Media;

// 该部分源码来自或修改于 https://github.com/OrgEleCho/EleCho.WpfSuite
// 项目: EleCho.WpfSuite
// 作者: EleCho
// 协议: MIT License

namespace PCL.Core.UI
{
    /// <summary>
    /// Used for rendering the background content of a UIElement. By adding a <see cref="BlurEffect"/> to this element, you can achieve a blurred background effect.
    /// </summary>
    public class BackgroundPresenter : FrameworkElement
    {
        private static readonly FieldInfo _DrawingContentOfUIElement = typeof(UIElement)
            .GetField("_drawingContent", BindingFlags.Instance | BindingFlags.NonPublic)!;

        private static readonly FieldInfo _ContentOfDrawingVisual = typeof(DrawingVisual)
            .GetField("_content", BindingFlags.Instance | BindingFlags.NonPublic)!;

        private static readonly FieldInfo _OffsetOfVisual = typeof(Visual)
            .GetField("_offset", BindingFlags.Instance | BindingFlags.NonPublic)!;

        private static readonly Func<UIElement, DrawingContext> _RenderOpenMethod = (Func<UIElement, DrawingContext>)typeof(UIElement)
            .GetMethod("RenderOpen", BindingFlags.Instance | BindingFlags.NonPublic)!
            .CreateDelegate(typeof(Func<UIElement, DrawingContext>));

        private static readonly Action<UIElement, DrawingContext> _OnRenderMethod = (Action<UIElement, DrawingContext>)typeof(UIElement)
            .GetMethod("OnRender", BindingFlags.Instance | BindingFlags.NonPublic)!
            .CreateDelegate(typeof(Action<UIElement, DrawingContext>));

        private static readonly GetContentBoundsDelegate _MethodGetContentBounds = (GetContentBoundsDelegate)typeof(VisualBrush)
            .GetMethod("GetContentBounds", BindingFlags.Instance | BindingFlags.NonPublic)!
            .CreateDelegate(typeof(GetContentBoundsDelegate));

        private delegate void GetContentBoundsDelegate(VisualBrush visualBrush, out Rect bounds);
        private readonly Stack<UIElement> _parentStack = new();

        public static void ForceRender(UIElement target)
        {
            using var drawingContext = _RenderOpenMethod(target);

            _OnRenderMethod.Invoke(target, drawingContext);
        }

        internal static void DrawVisual(DrawingContext drawingContext, Visual visual, Point relatedXY)
        {
            var visualBrush = new VisualBrush(visual);
            var visualOffset = (Vector)_OffsetOfVisual.GetValue(visual)!;

            _MethodGetContentBounds.Invoke(visualBrush, out var contentBounds);
            relatedXY -= visualOffset;
            if (contentBounds.IsEmpty)
            {
                return;
            }

            drawingContext.DrawRectangle(
                visualBrush, null,
                new Rect(relatedXY.X + contentBounds.X, relatedXY.Y + contentBounds.Y, contentBounds.Width, contentBounds.Height));
        }

        /// <inheritdoc/>
        protected override Geometry GetLayoutClip(Size layoutSlotSize)
        {
            return new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
        }

        /// <inheritdoc/>
        protected override void OnVisualParentChanged(DependencyObject oldParentObject)
        {
            if (oldParentObject is UIElement oldParent)
            {
                oldParent.LayoutUpdated -= ParentLayoutUpdated;
            }

            if (Parent is UIElement newParent)
            {
                newParent.LayoutUpdated += ParentLayoutUpdated;
            }
        }

        private void ParentLayoutUpdated(object? sender, EventArgs e)
        {
            // cannot use 'InvalidateVisual' here, because it will cause infinite loop

            ForceRender(this);

            // Debug.WriteLine("Parent layout updated, forcing render of BackgroundPresenter.");
        }

        internal static void DrawBackground(
            DrawingContext drawingContext, UIElement self,
            Stack<UIElement> parentStackStorage,
            int maxDepth,
            bool throwExceptionIfParentArranging)
        {
            var selfInDesignMode = DesignerProperties.GetIsInDesignMode(self);

            var parent = VisualTreeHelper.GetParent(self) as UIElement;
            while (
                parent is not null &&
                parentStackStorage.Count < maxDepth)
            {
                // parent not visible, no need to render
                if (!parent.IsVisible)
                {
                    parentStackStorage.Clear();
                    return;
                }

                if (selfInDesignMode &&
                    parent.GetType().ToString().Contains("VisualStudio"))
                {
                    // 遍历到 VS 自身的设计器元素, 中断!
                    break;
                }

                // is parent arranging
                // we cannot render it
                if (parent.RenderSize.Width == 0 ||
                    parent.RenderSize.Height == 0)
                {
                    parentStackStorage.Clear();

                    if (throwExceptionIfParentArranging)
                    {
                        throw new InvalidOperationException("Arranging");
                    }

                    // render after parent arranging finished
                    self.InvalidateArrange();
                    return;
                }

                parentStackStorage.Push(parent);
                parent = VisualTreeHelper.GetParent(parent) as UIElement;
            }

            var selfRect = new Rect(0, 0, self.RenderSize.Width, self.RenderSize.Height);
            while (parentStackStorage.Count > 0)
            {
                var currentParent = parentStackStorage.Pop();
                var breakElement = self;

                if (parentStackStorage.Count > 0)
                {
                    breakElement = parentStackStorage.Peek();
                }

                var parentRelatedXY = currentParent.TranslatePoint(default, self);

                // has render data
                if (_DrawingContentOfUIElement.GetValue(currentParent) is { } parentDrawingContent)
                {
                    var drawingVisual = new DrawingVisual();
                    _ContentOfDrawingVisual.SetValue(drawingVisual, parentDrawingContent);

                    DrawVisual(drawingContext, drawingVisual, parentRelatedXY);
                }

                var childCount = VisualTreeHelper.GetChildrenCount(currentParent);
                for (var i = 0; i < childCount; i++)
                {
                    if (VisualTreeHelper.GetChild(currentParent, i) is not UIElement child)
                    {
                        continue;
                    }

                    if (child == breakElement)
                    {
                        break;
                    }

                    var childRelatedXY = child.TranslatePoint(default, self);
                    var childRect = new Rect(childRelatedXY, child.RenderSize);

                    if (!selfRect.IntersectsWith(childRect))
                    {
                        continue; // skip if not intersecting
                    }

                    if (child.IsVisible)
                    {
                        DrawVisual(drawingContext, child, childRelatedXY);
                    }
                }
            }
        }

        /// <summary>
        /// Draw background of the specified UIElement.
        /// </summary>
        /// <param name="drawingContext"></param>
        /// <param name="self"></param>
        public static void DrawBackground(DrawingContext drawingContext, UIElement self)
        {
            var parentStack = new Stack<UIElement>();
            DrawBackground(drawingContext, self, parentStack, int.MaxValue, true);
        }

        /// <inheritdoc/>
        protected override void OnRender(DrawingContext drawingContext)
        {
            DrawBackground(drawingContext, this, _parentStack, MaxDepth, false);
        }

        /// <summary>
        /// Gets or sets the maximum depth of the visual tree to render.
        /// </summary>
        public int MaxDepth
        {
            get { return (int)GetValue(MaxDepthProperty); }
            set { SetValue(MaxDepthProperty, value); }
        }

        /// <summary>
        /// Dependency property for <see cref="MaxDepth"/>.
        /// </summary>
        public static readonly DependencyProperty MaxDepthProperty =
            DependencyProperty.Register("MaxDepth", typeof(int), typeof(BackgroundPresenter), new FrameworkPropertyMetadata(16));
    }
}
