using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace OPlanner {

	public class CustomDragData {

		public DragListBox ListBoxSource;
		public object ItemSource;
		public int OriginalIndex;
		public int OldInsertIndex;
		public int InsertIndex;
		public CustomDropAction DropAction;

		public CustomDragData() {
			OldInsertIndex = -1;
			InsertIndex = -1;
			DropAction = CustomDropAction.None;
		}

		public CustomDragData(DragListBox listBoxSource, object itemSource, int originalIndex) {
			ListBoxSource = listBoxSource;
			ItemSource = itemSource;
			OriginalIndex = originalIndex;
			OldInsertIndex = -1;
			InsertIndex = -1;
			DropAction = CustomDropAction.None;
		}

	}

	public enum CustomDropAction {
		None, Copy, Move
	}

	public delegate void CustomDropHandler(DragListBox sender, CustomDragData dragData);

	public delegate void AllowDropCheckHandler(CustomDragData dragData, out bool allow);

	public class DragListBox : ListBox {

		public static DependencyProperty AllowReorderProperty;
		public static DependencyProperty AllowDragOutProperty;
		public static DependencyProperty CopyOutgoingProperty;

		static DragListBox() {
			AllowReorderProperty = DependencyProperty.Register("AllowReorder", typeof(bool), typeof(DragListBox));
			AllowDragOutProperty = DependencyProperty.Register("AllowDragOut", typeof(bool), typeof(DragListBox));
			CopyOutgoingProperty = DependencyProperty.Register("CopyOutgoing", typeof(bool), typeof(DragListBox));
		}

		private object dragItem;
		private Rect dragRect;
		private Panel itemsHost;
		private bool dragEntered;
		private ScrollViewer scrollViewer;
		private DispatcherTimer scrollTimer;
		private bool scrollUp;
		private bool isScrolling;
		public bool CopyOutgoing {
			get {
				return (bool)base.GetValue(CopyOutgoingProperty);
			}
			set {
				base.SetValue(CopyOutgoingProperty, value);
			}
		}
		public bool AllowReorder {
			get {
				return (bool)base.GetValue(AllowReorderProperty);
			}
			set {
				base.SetValue(AllowReorderProperty, value);
			}
		}
		public bool AllowDragOut {
			get {
				return (bool)base.GetValue(AllowDragOutProperty);
			}
			set {
				base.SetValue(AllowDragOutProperty, value);
			}
		}
		public event CustomDropHandler CustomDropPreview;
		public event CustomDropHandler CustomDrop;
		public event AllowDropCheckHandler AllowDropCheck;
		public TimeSpan ScrollDelay;
		public TimeSpan ScrollInterval;
		public double ScrollOffset;

		public DragListBox() {
			dragRect = Rect.Empty;
			dragEntered = false;
			CopyOutgoing = true;
			AllowReorder = true;
			AllowDragOut = true;
			isScrolling = false;
			Loaded += new RoutedEventHandler(LoadedHandler);
			ScrollDelay = TimeSpan.FromSeconds(0.1);
			ScrollInterval = TimeSpan.FromSeconds(0.2);
			ScrollOffset = 6;
		}

		void LoadedHandler(object sender, RoutedEventArgs e) {
			itemsHost = FindItemsHost(this);
			if (itemsHost == null) {
				return;
			}
			scrollViewer = FindScrollViewer(this);
			if (scrollViewer == null) {
				return;
			}
			PreviewMouseLeftButtonDown += new MouseButtonEventHandler(PreviewMouseLeftButtonDownHandler);
			LostMouseCapture += new MouseEventHandler(LostMouseCaptureHandler);
			PreviewMouseMove += new MouseEventHandler(PreviewMouseMoveHandler);
			PreviewMouseLeftButtonUp += new MouseButtonEventHandler(PreviewMouseLeftButtonUpHandler);
			DragEnter += new DragEventHandler(PreviewDragEnterHandler);
			DragOver += new DragEventHandler(PreviewDragOverHandler);
			DragLeave += new DragEventHandler(PreviewDragLeaveHandler);
			Drop += new DragEventHandler(PreviewDropHandler);
		}

		void PreviewMouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e) {
			if (AllowDragOut == false) {
				return;
			}
			dragItem = GetItem(e.OriginalSource);
			if (dragItem == null) {
				dragRect = Rect.Empty;
				return;
			}
			Size dragSize = new Size(SystemParameters.MinimumHorizontalDragDistance, SystemParameters.MinimumVerticalDragDistance);
			dragRect = new Rect(new Point(e.GetPosition(this).X - (dragSize.Width / 2), e.GetPosition(this).Y - (dragSize.Height / 2)), dragSize);
			Mouse.Capture(this, CaptureMode.SubTree);
		}

		void LostMouseCaptureHandler(object sender, MouseEventArgs e) {
			dragItem = null;
			dragRect = Rect.Empty;
		}

		void PreviewMouseMoveHandler(object sender, MouseEventArgs e) {
			if (e.LeftButton != MouseButtonState.Pressed || dragItem == null || dragRect == Rect.Empty || dragRect.Contains(e.GetPosition(this).X, e.GetPosition(this).Y)) {
				return;
			}
			object tempItem = GetItem(e.OriginalSource);
			if (tempItem != dragItem) {
				dragItem = null;
				dragRect = Rect.Empty;
				return;
			}
			if (IsMouseCaptureWithin) {
				Mouse.Capture(null);
			} else {
				return;
			}
			CustomDragData obj = new CustomDragData(this, tempItem, Items.IndexOf(tempItem));
			DragDropEffects dropEffect = DragDrop.DoDragDrop(this, new DataObject(typeof(CustomDragData), obj), DragDropEffects.All);
		}

		void PreviewMouseLeftButtonUpHandler(object sender, MouseButtonEventArgs e) {
			if (IsMouseCaptured) {
				Mouse.Capture(null);
			}
		}

		void PreviewDragEnterHandler(object sender, DragEventArgs e) {
			CustomDragData obj = GetDragData(e);
			if (obj == null) {
				return;
			}
			SetHitTest(false);
		}

		void PreviewDragOverHandler(object sender, DragEventArgs e) {
			CustomDragData obj = GetDragData(e);
			if (obj == null) {
				return;
			}
			if (AllowReorder) {
				int pos = -1;
				if (!dragEntered) {
					dragEntered = true;
					if (obj.ListBoxSource == this) {
						pos = Items.IndexOf(obj.ItemSource);
					}
				}
				if (pos == -1) {
					pos = GetInsertIndex(e);
				}
				obj.OldInsertIndex = obj.InsertIndex;
				obj.InsertIndex = pos;
				OnCustomDropPreview(obj);
			} else {
				if (!dragEntered) {
					dragEntered = true;
					obj.InsertIndex = Items.Count;
					OnCustomDropPreview(obj);
				}
			}
			if (ShouldScrollUp(e)) {
				if (!isScrolling) {
					StartScroll(true);
				}
			} else if (ShouldScrollDown(e)) {
				if (!isScrolling) {
					StartScroll(false);
				}
			} else {
				StopScroll();
			}
		}

		void PreviewDragLeaveHandler(object sender, DragEventArgs e) {
			CustomDragData obj = GetDragData(e);
			e.Effects = DragDropEffects.None;
			if (obj == null) {
				return;
			}
			DragLeaveCleanUp(obj);
		}

		void PreviewDropHandler(object sender, DragEventArgs e) {
			CustomDragData obj = GetDragData(e);
			e.Effects = DragDropEffects.None;
			if (obj == null) {
				return;
			}
			int tempOldInsertIndex = obj.OldInsertIndex;
			int tempInsertIndex = obj.InsertIndex;
			CustomDropAction tempAction = obj.DropAction;
			DragLeaveCleanUp(obj);
			obj.DropAction = tempAction;
			obj.OldInsertIndex = tempOldInsertIndex;
			obj.InsertIndex = tempInsertIndex;
			OnCustomDrop(obj);
		}

		private CustomDragData GetDragData(DragEventArgs e) {
			if (e.Handled) {
				return null;
			}
			e.Handled = true;
			if (scrollViewer == null) {
				e.Effects = DragDropEffects.None;
				return null;
			}
			if (!scrollViewer.IsAncestorOf(e.OriginalSource as DependencyObject)) {
				e.Effects = DragDropEffects.None;
				return null;
			}
			CustomDragData obj = (CustomDragData)e.Data.GetData(typeof(CustomDragData));
			if (obj == null) {
				e.Effects = DragDropEffects.None;
				return null;
			}
			obj.DropAction = CustomDropAction.None;
			if (AllowDropCheck != null) {
				bool allow = false;
				AllowDropCheck.Invoke(obj, out allow);
				if (allow == false) {
					e.Effects = DragDropEffects.None;
					return null;
				}
			}
			if (obj.ListBoxSource == this) {
				if (AllowReorder) {
					e.Effects = DragDropEffects.Move;
					obj.DropAction = CustomDropAction.Move;
				} else {
					e.Effects = DragDropEffects.None;
					return null;
				}
			} else {
				if (obj.ListBoxSource.CopyOutgoing) {
					e.Effects = DragDropEffects.Copy;
					obj.DropAction = CustomDropAction.Copy;
				} else {
					e.Effects = DragDropEffects.Move;
					obj.DropAction = CustomDropAction.Move;
				}
			}
			return obj;
		}

		private int GetInsertIndex(DragEventArgs e) {
			for (int i = 0; i < Items.Count; i++) {
				ListBoxItem item = ItemContainerGenerator.ContainerFromItem(Items[i]) as ListBoxItem;
				if (item == null) {
					continue;
				}
				double y = e.GetPosition(item).Y;
				if (y <= item.ActualHeight) {
					if (y > item.ActualHeight / 2) {
						return i + 1;
					} else {
						return i;
					}
				}
			}
			return Items.Count;
		}

		private void SetHitTest(bool val) {
			if (scrollViewer == null) {
				return;
			}
			for (int i = 0; i < VisualTreeHelper.GetChildrenCount(scrollViewer); i++) {
				DependencyObject child = VisualTreeHelper.GetChild(scrollViewer, i);
				UIElement element = child as UIElement;
				if (element == null) {
					continue;
				}
				element.IsHitTestVisible = val;
			}
		}

		private void DragLeaveCleanUp(CustomDragData obj) {
			StopScroll();
			obj.OldInsertIndex = -1;
			obj.InsertIndex = -1;
			obj.DropAction = CustomDropAction.None;
			OnCustomDropPreview(obj);
			SetHitTest(true);
			dragEntered = false;
		}

		protected virtual void OnCustomDrop(CustomDragData dragData) {
			if (CustomDrop != null) {
				CustomDrop.Invoke(this, dragData);
			}
		}

		protected virtual void OnCustomDropPreview(CustomDragData dragData) {
			if (CustomDropPreview != null) {
				CustomDropPreview.Invoke(this, dragData);
			}
		}

		private Panel FindItemsHost(DependencyObject obj) {
			for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) {
				DependencyObject child = VisualTreeHelper.GetChild(obj, i);
				if (child == null) {
					continue;
				}
				if (child is Panel) {
					if (((Panel)child).IsItemsHost) {
						return (Panel)child;
					}
				}
				Panel childOfChild = FindItemsHost(child);
				if (childOfChild != null) {
					return childOfChild;
				}
			}
			return null;
		}

		private ScrollViewer FindScrollViewer(DependencyObject obj) {
			if (itemsHost == null) {
				return null;
			}
			for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) {
				DependencyObject child = VisualTreeHelper.GetChild(obj, i);
				if (child == null) {
					continue;
				}
				if (child is ScrollViewer) {
					if (((ScrollViewer)child).IsAncestorOf(itemsHost)) {
						return (ScrollViewer)child;
					}
				}
				ScrollViewer childOfChild = FindScrollViewer(child);
				if (childOfChild != null) {
					return childOfChild;
				}
			}
			return null;
		}

		private object GetItem(object source) {
			DependencyObject obj = source as DependencyObject;
			if (obj == null) {
				return null;
			}
			for (int i = 0; i < Items.Count; i++) {
				ListBoxItem item = ItemContainerGenerator.ContainerFromItem(Items[i]) as ListBoxItem;
				if (item == null) {
					continue;
				}
				if (item.IsAncestorOf(obj)) {
					return Items[i];
				}
			}
			return null;
		}

		private bool ShouldScrollUp(DragEventArgs e) {
			if (scrollViewer == null) {
				return false;
			}
			if (scrollViewer.VerticalOffset <= 0) {
				return false;
			}
			double y = e.GetPosition(itemsHost).Y;
			if (y <= ScrollOffset) {
				return true;
			}
			return false;
		}

		private bool ShouldScrollDown(DragEventArgs e) {
			if (scrollViewer == null) {
				return false;
			}
			if (scrollViewer.VerticalOffset >= scrollViewer.ScrollableHeight) {
				return false;
			}
			double y = e.GetPosition(itemsHost).Y;
			if (itemsHost.ActualHeight - y <= ScrollOffset) {
				return true;
			}
			return false;
		}

		private void StopScroll() {
			if (scrollTimer == null) {
				return;
			}
			isScrolling = false;
			scrollTimer.Stop();
			scrollTimer = null;
		}

		private void StartScroll(bool up) {
			StopScroll();
			if (scrollViewer == null) {
				return;
			}
			scrollUp = up;
			scrollTimer = new DispatcherTimer();
			scrollTimer.Interval = ScrollDelay;
			scrollTimer.Tick += new EventHandler(scrollTimer_Tick);
			isScrolling = true;
			scrollTimer.Start();
		}

		void scrollTimer_Tick(object sender, EventArgs e) {
			scrollTimer.Interval = ScrollInterval;
			if (scrollUp) {
				if (scrollViewer.VerticalOffset <= 0) {
					StopScroll();
					return;
				}
				scrollViewer.LineUp();
			} else {
				if (scrollViewer.VerticalOffset >= scrollViewer.ScrollableHeight) {
					StopScroll();
					return;
				}
				scrollViewer.LineDown();
			}
		}

		protected override void OnSelectionChanged(SelectionChangedEventArgs e) {
			if (dragEntered) {
				e.Handled = true;
			}
			base.OnSelectionChanged(e);
		}

	}

}
