using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Waaagh.Behaviors {
    sealed public class ItemsDraggable {
        #region Drag Drop Data Format String
        static readonly string DraggableDataFormat = "DraggableData";
        static readonly string DraggableCollectionFormat = "DraggableCollection";
        #endregion

        #region Enabled
        static public bool GetEnabled(DependencyObject obj) {
            return (bool)obj.GetValue(EnabledProperty);
        }

        static public void SetEnabled(DependencyObject obj, bool value) {
            obj.SetValue(EnabledProperty, value);
        }

        static public readonly DependencyProperty EnabledProperty =
            DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(ItemsDraggable),
                new PropertyMetadata(false, OnEnabledChanged));

        static private void OnEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) {
            if (sender is ItemsControl itemsControl == false) {
                throw new InvalidOperationException("The attached control is not an 'ItemsControl'.");
            }
            itemsControl.AllowDrop = false;
            itemsControl.MouseMove -= ItemsDraggable_MouseMove;
            itemsControl.DragEnter -= ItemsDraggable_DragEnter;
            itemsControl.DragOver -= ItemsDraggable_DragOver;
            itemsControl.DragLeave -= ItemsDraggable_DragLeave;
            itemsControl.Drop -= ItemsDraggable_Drop;
            if (GetEnabled(itemsControl) == true) {
                itemsControl.AllowDrop = true;
                itemsControl.MouseMove += ItemsDraggable_MouseMove;
                itemsControl.DragEnter += ItemsDraggable_DragEnter;
                itemsControl.DragOver += ItemsDraggable_DragOver;
                itemsControl.DragLeave += ItemsDraggable_DragLeave;
                itemsControl.Drop += ItemsDraggable_Drop;
            }
        }



        public static Func<object, Type, IList?> GetQueryTargetCollection(DependencyObject obj) {
            return (Func<object, Type, IList?>)obj.GetValue(QueryTargetCollectionProperty);
        }

        public static void SetQueryTargetCollection(DependencyObject obj, Func<object, Type, IList?> value) {
            obj.SetValue(QueryTargetCollectionProperty, value);
        }

        public static readonly DependencyProperty QueryTargetCollectionProperty =
            DependencyProperty.RegisterAttached("QueryTargetCollection", typeof(Func<object, Type, IList?>), typeof(ItemsDraggable), new PropertyMetadata(null));


        #endregion

        #region Drag Drop Event
        static private void ItemsDraggable_MouseMove(object sender, MouseEventArgs args) {
            if (args.LeftButton != MouseButtonState.Pressed) {
                return;
            }
            if (sender is ItemsControl source == false) {
                return;
            }
            if (source.ItemsSource is IList == false) {
                return;
            }
            if (GetElement(source, args.GetPosition(source)) is Control sourceControl == false) {
                return;
            }
            if (GetParent(sourceControl) is ItemsControl sourceItemsControl == false) {
                return;
            }

            DataObject data = new DataObject();
            data.SetData(DraggableDataFormat, sourceControl.DataContext);
            data.SetData(DraggableCollectionFormat, sourceItemsControl.ItemsSource);
            DragDrop.DoDragDrop(sourceItemsControl, data, DragDropEffects.Move);
        }

        static private void ItemsDraggable_DragEnter(object sender, DragEventArgs args) {

        }

        static private void ItemsDraggable_DragOver(object sender, DragEventArgs args) {

        }

        static private void ItemsDraggable_DragLeave(object sender, DragEventArgs args) {

        }

        static private void ItemsDraggable_Drop(object sender, DragEventArgs args) {
            if (args.Effects == DragDropEffects.None) {
                return;
            }
            if (sender is ItemsControl target == false) {
                return;
            }
            // 获取 放置目标
            if (GetElement(target, args.GetPosition(target)) is Control targetControl == false) {
                return;
            }
            if (GetParent(targetControl) is ItemsControl targetItemsControl == false) {
                return;
            }
            if (targetItemsControl.ItemsSource is IList targetCollection == false) {
                return;
            }
            // 获取 拖拽源
            object? sourceData = args.Data.GetData(DraggableDataFormat) as object;
            IList? sourceCollection = args.Data.GetData(DraggableCollectionFormat) as IList;
            if (sourceData == null || sourceCollection == null) {
                return;
            }
            // 判断 集合元素类型
            object? targetData = targetControl.DataContext;
            if (CheckCollectionElementType(sourceCollection, targetCollection) == false) {
                IList? collection = GetQueryTargetCollection(targetItemsControl)?.Invoke(targetControl.DataContext, sourceData.GetType());
                if (collection == null || CheckCollectionElementType(sourceCollection, collection) == false) {
                    return;
                }
                targetCollection = collection;
                targetData = null; // 置 targetData 为 null, 尝试将 sourceData 添加到 targetCollection 末尾
            }
            // 执行 拖拽影响
            DoDragDropEffects(args.Effects, sourceCollection, sourceData, targetCollection, targetData);
        }
        #endregion

        #region Drag Drop Effects
        static private void DoDragDropEffects(DragDropEffects effects, IList sourceCollection, object sourceData, IList targetCollection, object? targetData) {
            if (sourceCollection == null || sourceData == null || targetCollection == null) {
                return;
            }
            if (object.ReferenceEquals(sourceData, targetData)) {
                return;
            }
            switch (effects) {
                case DragDropEffects.Scroll: {
                    break;
                }
                case DragDropEffects.All: {
                    break;
                }
                case DragDropEffects.Copy: {
                    break;
                }
                case DragDropEffects.Move: {
                    DoMoveEffect(sourceCollection, sourceData, targetCollection, targetData);
                    break;
                }
                case DragDropEffects.Link: {
                    break;
                }
                default: {
                    break;
                }
            }
        }

        static private void DoMoveEffect(IList sourceCollection, object sourceData, IList targetCollection, object? targetData) {
            int oldIndex = sourceCollection.IndexOf(sourceData);
            int newIndex = targetCollection.IndexOf(targetData);
            if (newIndex == -1) {
                newIndex = targetCollection.Count - 1;
            }
            if (object.ReferenceEquals(sourceCollection, targetCollection)) {
                if (oldIndex == newIndex) {
                    return;
                }
                // 同个集合, 移除元素后下标可能变化
                if (oldIndex < newIndex) {
                    newIndex -= 1;
                }
            }
            sourceCollection.RemoveAt(oldIndex);
            targetCollection.Insert(newIndex + 1, sourceData);
        }

        #endregion

        #region Helper
        static private bool GetDragDropEventSource(ref ItemsControl parent, out Control child, Func<IInputElement, Point> getPosition) {
            if (parent != null) {
                ItemContainerGenerator generator = parent.ItemContainerGenerator;
                for (int i = generator.Items.Count - 1; i >= 0; --i) {
                    if (generator.ContainerFromIndex(i) is Control control == false) {
                        continue;
                    }
                    Point position = getPosition(control);
                    if (position.X < 0 || position.X > control.ActualWidth ||
                        position.Y < 0 || position.Y > control.ActualHeight) {
                        continue;
                    }
                    if (control is ContentControl) {
                        child = control;
                        return true;
                    }
                    if (control is ItemsControl itemsControl) {
                        ItemsControl prevParent = parent;
                        parent = itemsControl;
                        if (GetDragDropEventSource(ref parent, out child, getPosition) == false) {
                            child = parent;
                            parent = prevParent;
                        }
                        return true;
                    }
                }
            }
            child = default!;
            return false;
        }

        static private Control? GetElement(ItemsControl source, Point point) {
            if (GetItemContainerType(source) is Type type == false) {
                return null;
            }
            DependencyObject? dp = source.InputHitTest(point) as DependencyObject;
            while (dp != null) {
                if (dp.GetType().Equals(type)) {
                    break;
                }
                dp = VisualTreeHelper.GetParent(dp);
            }
            return dp as Control;
        }

        static private ItemsControl? GetParent(Control control) {
            for (DependencyObject dp = VisualTreeHelper.GetParent(control); dp != null; dp = VisualTreeHelper.GetParent(dp)) {
                if (dp is ItemsControl parent && parent.ItemContainerGenerator.ContainerFromItem(control.DataContext) != null) {
                    return parent;
                }
            }
            return null;
        }

        static private Type? GetItemContainerType(ItemsControl source) {
            if (source == null) {
                return null;
            }
            if (source is ListView) {
                return typeof(ListViewItem);
            }
            else if (source is ListBox) {
                return typeof(ListBoxItem);
            }
            else if (source is TreeView || source is TreeViewItem) {
                return typeof(TreeViewItem);
            }
            // else if (source is GridView) {
            //     return typeof(GridViewColumn);
            // }
            if (source.Items.Count > 0) {
                return source.ItemContainerGenerator.ContainerFromIndex(0)?.GetType();
            }
            return null;
        }

        static private bool CheckCollectionElementType(IList collection1, IList collection2) {
            return collection1.GetType().GetGenericArguments()[0] == collection2.GetType().GetGenericArguments()[0];
        }
        #endregion
    }
}