using System;
using System.Windows;
using System.Windows.Input;

namespace HandyControl.Tools
{
    /// <summary>
    ///     输入层点击帮助类
    /// </summary>
    public static class InputClickHelper
    {
        private static readonly DependencyProperty InputInfoProperty = DependencyProperty.RegisterAttached(
            "InputInfo", typeof(InputInfo), typeof(InputClickHelper), new PropertyMetadata(default(InputInfo)));

        private static void SetInputInfo(DependencyObject element, InputInfo value) => element.SetValue(InputInfoProperty, value);

        private static InputInfo GetInputInfo(DependencyObject element) => (InputInfo) element.GetValue(InputInfoProperty);

        /// <summary>
        ///     将 MouseDown MouseMove MouseUp 封装为点击事件
        /// </summary>
        /// <param name="element">要被附加的元素</param>
        /// <param name="clickEventHandler">点击的事件</param>
        /// <param name="dragStarted">因为拖动而结束点击时触发</param>
        public static void AttachMouseDownMoveUpToClick(UIElement element, EventHandler clickEventHandler,
            EventHandler dragStarted = null)
        {
            var inputInfo = GetOrCreateInputInfo(element);
            inputInfo.ClickEventHandler += clickEventHandler;

            inputInfo.DragStarted += dragStarted;

            element.MouseDown -= Element_MouseDown;
            element.MouseDown += Element_MouseDown;
            element.MouseMove -= Element_MouseMove;
            element.MouseMove += Element_MouseMove;
            element.MouseUp -= Element_MouseUp;
            element.MouseUp += Element_MouseUp;
            element.LostMouseCapture -= Element_LostMouseCapture;
            element.LostMouseCapture += Element_LostMouseCapture;
        }

        /// <summary>
        ///     去掉对 <paramref name="element" /> 的点击时间的监听
        /// </summary>
        /// <param name="element"></param>
        /// <param name="clickEventHandler">点击的事件</param>
        /// <param name="dragStarted">因为拖动而结束点击时触发的事件</param>
        public static void DetachMouseDownMoveUpToClick(UIElement element, EventHandler clickEventHandler,
            EventHandler dragStarted = null)
        {
            var inputInfo = GetInputInfo(element);
            if (inputInfo == null) return;

            inputInfo.ClickEventHandler -= clickEventHandler;
            inputInfo.DragStarted -= dragStarted;

            if (inputInfo.IsEmpty())
            {
                element.ClearValue(InputInfoProperty);
                element.MouseDown -= Element_MouseDown;
                element.MouseMove -= Element_MouseMove;
                element.MouseUp -= Element_MouseUp;
                element.LostMouseCapture -= Element_LostMouseCapture;
            }
        }

        private static void Element_LostMouseCapture(object sender, MouseEventArgs e)
        {
            var element = (UIElement) sender;
            GetInputInfo(element)?.LostCapture();
        }

        private static void Element_MouseUp(object sender, MouseButtonEventArgs e)
        {
            var element = (UIElement) sender;
            GetInputInfo(element)?.Up(e.GetPosition(element));
        }

        private static void Element_MouseMove(object sender, MouseEventArgs e)
        {
            var element = (UIElement) sender;
            GetInputInfo(element)?.Move(e.GetPosition(element));
        }

        private static void Element_MouseDown(object sender, MouseButtonEventArgs e)
        {
            var element = (UIElement) sender;
            GetInputInfo(element)?.Down(e.GetPosition(element));
        }

        private static InputInfo GetOrCreateInputInfo(UIElement element)
        {
            var inputInfo = GetInputInfo(element);
            if (inputInfo == null)
            {
                inputInfo = new InputInfo();
                SetInputInfo(element, inputInfo);
            }

            return inputInfo;
        }

        private class InputInfo
        {
            private const double ToleranceSquared = 0.01;

            private Point _downedPosition;

            private bool _isClick;

            public event EventHandler ClickEventHandler;

            public event EventHandler DragStarted;

            public void Down(Point position)
            {
                _downedPosition = position;
                _isClick = true;
            }

            public void Move(Point position)
            {
                if (!_isClick) return;

                if ((position - _downedPosition).LengthSquared > ToleranceSquared)
                {
                    _isClick = false;
                    DragStarted?.Invoke(null, EventArgs.Empty);
                }
            }

            public void Up(Point position)
            {
                _isClick = _isClick && (position - _downedPosition).LengthSquared <= ToleranceSquared;

                if (!_isClick) return;

                ClickEventHandler?.Invoke(null, EventArgs.Empty);

                _isClick = false;
            }

            public void LostCapture() => _isClick = false;

            public bool IsEmpty() => ClickEventHandler is null && DragStarted is null;
        }
    }
}
