using System.Collections;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using Telerik.Windows.Controls;
using Telerik.Windows.Controls.GridView;

namespace RowDragDrop
{
    public class GridViewRowDragDropManager
    {
        private readonly DragDropManager dragDropManager;
        private readonly FrameworkElement dropIndicator;
        private readonly RadGridView gridView;
        private int? currentDropIndex;
        private DropIndicationAdorner indicationAdorner;

        public GridViewRowDragDropManager(RadGridView gridView, FrameworkElement dropIndicator)
        {
            this.gridView = gridView;
            this.dropIndicator = dropIndicator;
            this.dragDropManager = new DragDropManager(gridView);

            this.gridView.RowLoaded += this.OnGridViewRowLoaded;
        }

        private void OnGridViewRowLoaded(object sender, RowLoadedEventArgs e)
        {
            GridViewRow row = e.Row as GridViewRow;
            if (row != null)
            {
                this.dragDropManager.RegisterForDragDrop(row);
                row.MouseMove += this.RowMouseMove;
                row.MouseUp += this.RowMouseUp;
            }
        }

        private void ReorderItems(GridViewRow row)
        {
            var source = this.gridView.ItemsSource as IList;
            if (source != null)
            {
                source.Remove(row.DataContext);

                if (source.Count < this.currentDropIndex.Value)
                {
                    this.currentDropIndex = source.Count;
                }

                source.Insert(this.currentDropIndex.Value, row.DataContext);

                this.gridView.Rebind();
            }
        }

        private void RowMouseMove(object sender, MouseEventArgs e)
        {
            var row = sender as GridViewRow;
            if (row != null && this.dragDropManager.ActiveDraggedElement != null)
            {
                if (this.indicationAdorner == null)
                {
                    if (this.dropIndicator != null)
                    {
                        this.dropIndicator.Width = this.gridView.ActualWidth;
                        this.indicationAdorner = new DropIndicationAdorner(this.gridView, this.dropIndicator);
                        AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.gridView);
                        adornerLayer.Add(this.indicationAdorner);
                    }
                }

                RadItemsControl itemsControl = this.gridView.ItemsControl;
                this.currentDropIndex = itemsControl.Records.IndexOf(row.Record);

                double rowHeight = row.DesiredSize.Height;
                Point point = e.GetPosition(row);
                double itemOffset = -point.Y;
                if (point.Y > rowHeight / 2)
                {
                    itemOffset = rowHeight - point.Y;
                    this.currentDropIndex++;
                }
                this.UpdateIndicationAdornerPosition(e, itemOffset);
            }
        }

        private void RowMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (this.dragDropManager.ActiveDraggedElement != null && this.indicationAdorner != null)
            {
                AdornerLayer.GetAdornerLayer(this.gridView).Remove(this.indicationAdorner);
                this.indicationAdorner = null;
                this.ReorderItems(this.dragDropManager.ActiveDraggedElement as GridViewRow);
                this.currentDropIndex = null;
            }
        }

        private void UpdateIndicationAdornerPosition(MouseEventArgs args, double itemOffset)
        {
            Point point = args.GetPosition(this.gridView);
            this.indicationAdorner.TopOffset = point.Y + itemOffset - (this.indicationAdorner.DesiredSize.Height / 2);
        }
    }
}