using System;
using System.Collections.Generic;
using System.Linq;

using UnityEngine;
using UnityEditor;

using Unity.PlasticSCM.Editor.UI;

namespace Unity.PlasticSCM.Editor.CloudDrive.Workspaces.DirectoryContent
{
    internal static class ProcessItemsGridViewEvent
    {
        internal const int VERTICAL_SCROLL_STEP = 20;

        internal static bool IfNeeded(
            Event e,
            DirectoryContentPanelMenu menu,
            IItemsGridView itemsGridView,
            IDragAndDrop dragAndDrop,
            EditorWindow parentWindow,
            Action doubleClickAction,
            Action navigateBackAction,
            List<int> selectedItemsIndexes,
            Rect rect,
            Vector2 itemSize,
            float scrollPosition,
            int columnsCount,
            int itemsCount,
            bool hasFocus,
            bool isScrollbarVisible,
            bool isDragAvailable)
        {
            if (isScrollbarVisible &&
                ProcessScrollWheelIfNeeded(e, itemsGridView, scrollPosition))
                return true;

            if (ProcessMouseClickIfNeeded(
                    e, menu, itemsGridView, doubleClickAction, selectedItemsIndexes, rect, itemSize,
                    scrollPosition, columnsCount, itemsCount, isScrollbarVisible))
                return true;

            if (hasFocus &&
                ProcessKeyDownIfNeeded(
                    e, menu, itemsGridView, navigateBackAction,
                    selectedItemsIndexes, columnsCount, itemsCount))
                return true;

            if (ProcessMouseDragIfNeeded(
                    e, itemsGridView, dragAndDrop, parentWindow, rect, itemSize, scrollPosition,
                    columnsCount, itemsCount, isScrollbarVisible, isDragAvailable))
                return true;

            return false;
        }

        static bool ProcessScrollWheelIfNeeded(
            Event e,
            IItemsGridView itemsGridView,
            float scrollPosition)
        {
            if (e.type != EventType.ScrollWheel)
                return false;

            itemsGridView.UpdateScrollPosition(
                scrollPosition + e.delta.y * VERTICAL_SCROLL_STEP);

            return true;
        }

        static bool ProcessMouseClickIfNeeded(
            Event e,
            DirectoryContentPanelMenu menu,
            IItemsGridView itemsGridView,
            Action doubleClickAction,
            List<int> selectedItemsIndexes,
            Rect rect,
            Vector2 itemSize,
            float scrollPosition,
            int columnsCount,
            int itemsCount,
            bool isScrollbarVisible)
        {
            if (e.type != EventType.MouseDown &&
                e.type != EventType.MouseUp)
                return false;

            Vector2 mousePosition = e.mousePosition - rect.position;

            if (!ItemsPosition.IsMousePositionInsideGrid(
                    mousePosition, rect, isScrollbarVisible))
            {
                return false;
            }

            int itemIndexToSelect = ItemsPosition.GetItemIndexFromMousePosition(
                mousePosition, itemSize, scrollPosition, columnsCount, itemsCount);

            bool shouldUpdateSelection = ShouldUpdateSelection(
                selectedItemsIndexes.Contains(itemIndexToSelect),
                Keyboard.HasControlOrCommandModifier(e),
                Mouse.IsRightMouseButtonPressed(e),
                e.type == EventType.MouseDown);

            if (ProcessMouseDownIfNeeded(
                    e,
                    itemsGridView,
                    doubleClickAction,
                    selectedItemsIndexes,
                    itemIndexToSelect,
                    shouldUpdateSelection))
                return true;

            if (ProcessMouseUpIfNeeded(
                    e,
                    menu,
                    itemsGridView,
                    selectedItemsIndexes,
                    itemIndexToSelect,
                    shouldUpdateSelection))
                return true;

            return false;
        }

        static bool ProcessMouseDownIfNeeded(
            Event e,
            IItemsGridView itemsGridView,
            Action doubleClickAction,
            List<int> selectedItemsIndexes,
            int itemIndexToSelect,
            bool shouldUpdateSelection)
        {
            if (e.type != EventType.MouseDown)
                return false;

            if (Mouse.IsLeftMouseButtonDoubleClicked(e))
            {
                if (itemIndexToSelect == -1)
                    return false;

                doubleClickAction();
                return true;
            }

            if (!shouldUpdateSelection)
                return true;

            HandleMouseSelection(
                itemsGridView, selectedItemsIndexes, itemIndexToSelect,
                Keyboard.HasControlOrCommandModifier(e),
                Keyboard.HasShiftModifier(e));

            return true;
        }

        static bool ProcessMouseUpIfNeeded(
            Event e,
            DirectoryContentPanelMenu menu,
            IItemsGridView itemsGridView,
            List<int> selectedItemsIndexes,
            int itemIndexToSelect,
            bool shouldUpdateSelection)
        {
            if (e.type != EventType.MouseUp)
                return false;

            if (Mouse.IsLeftMouseButtonDoubleClicked(e))
                return false;

            if (Mouse.IsRightMouseButtonPressed(e))
            {
                menu.Popup();
                return true;
            }

            if (!shouldUpdateSelection)
                return true;

            HandleMouseSelection(
                itemsGridView, selectedItemsIndexes, itemIndexToSelect,
                Keyboard.HasControlOrCommandModifier(e),
                Keyboard.HasShiftModifier(e));

            return true;
        }

        static bool ProcessKeyDownIfNeeded(
            Event e,
            DirectoryContentPanelMenu menu,
            IItemsGridView itemsGridView,
            Action navigateBackAction,
            List<int> selectedItemsIndexes,
            int columnsCount,
            int itemsCount)
        {
            if (e.type != EventType.KeyDown)
                return false;

            if (menu.ProcessKeyActionIfNeeded(e))
                return true;

            bool shouldExtendSelection =
                Keyboard.HasControlOrCommandModifier(e) ||
                Keyboard.HasShiftModifier(e);

            switch (e.keyCode)
            {
                case KeyCode.Backspace:
                    navigateBackAction();
                    break;

                case KeyCode.UpArrow:
                    HandleKeyboardSelection(
                        itemsGridView,
                        selectedItemsIndexes,
                        GetItemIndexToSelectForOffset(selectedItemsIndexes, -columnsCount),
                        itemsCount,
                        shouldExtendSelection);
                    break;

                case KeyCode.DownArrow:
                    HandleKeyboardSelection(
                        itemsGridView,
                        selectedItemsIndexes,
                        GetItemIndexToSelectForOffset(selectedItemsIndexes, columnsCount),
                        itemsCount,
                        shouldExtendSelection);
                    break;

                case KeyCode.LeftArrow:
                    HandleKeyboardSelection(
                        itemsGridView,
                        selectedItemsIndexes,
                        GetItemIndexToSelectForOffset(selectedItemsIndexes, -1),
                        itemsCount,
                        shouldExtendSelection);
                    break;

                case KeyCode.RightArrow:
                    HandleKeyboardSelection(
                        itemsGridView,
                        selectedItemsIndexes,
                        GetItemIndexToSelectForOffset(selectedItemsIndexes, 1),
                        itemsCount,
                        shouldExtendSelection);
                    break;
            }

            return true;
        }

        static bool ProcessMouseDragIfNeeded(
            Event e,
            IItemsGridView itemsGridView,
            IDragAndDrop dragAndDrop,
            EditorWindow parentWindow,
            Rect rect,
            Vector2 itemSize,
            float scrollPosition,
            int columnsCount,
            int itemsCount,
            bool isScrollbarVisible,
            bool isDragAvailable)
        {
            if (!isDragAvailable)
                return false;

            if (e.type == EventType.MouseDrag)
            {
                ProcessDragEvent.ProcessMouseDrag(dragAndDrop, parentWindow);
                return true;
            }

            if (e.type == EventType.DragExited)
            {
                ProcessDragEvent.ProcessDragExited(itemsGridView, dragAndDrop);
                return true;
            }

            if (e.type == EventType.DragUpdated)
            {
                ProcessDragEvent.ProcessDragUpdated(
                    itemsGridView,
                    dragAndDrop,
                    parentWindow,
                    e.mousePosition,
                    rect,
                    itemSize,
                    scrollPosition,
                    columnsCount,
                    itemsCount,
                    isScrollbarVisible);
                return true;
            }

            if (e.type == EventType.DragPerform)
            {
                ProcessDragEvent.ProcessDragPerform(
                    itemsGridView,
                    dragAndDrop,
                    parentWindow,
                    e.mousePosition,
                    rect,
                    itemSize,
                    scrollPosition,
                    columnsCount,
                    itemsCount,
                    isScrollbarVisible);
                return true;
            }

            return false;
        }

        static void HandleMouseSelection(
            IItemsGridView itemsGridView,
            List<int> selectedItemsIndexes,
            int itemIndexToSelect,
            bool shouldToggleSelection,
            bool shouldExtendSelection)
        {
            if (itemIndexToSelect == -1)
            {
                itemsGridView.ClearSelectedItems();
                return;
            }

            List<int> indexesToSelect = GetItemsIndexesToSelect(
                selectedItemsIndexes, itemIndexToSelect,
                shouldToggleSelection, shouldExtendSelection);

            if (indexesToSelect.Count == 0)
            {
                itemsGridView.ClearSelectedItems();
                return;
            }

            itemsGridView.SetSelection(indexesToSelect);
        }

        static void HandleKeyboardSelection(
            IItemsGridView itemsGridView,
            List<int> selectedItemsIndexes,
            int itemIndexToSelect,
            int itemsCount,
            bool shouldExtendSelection)
        {
            if (itemIndexToSelect < 0 || itemIndexToSelect >= itemsCount)
                return;

            List<int> indexesToSelect = GetItemsIndexesToSelect(
                selectedItemsIndexes, itemIndexToSelect,
                false, shouldExtendSelection);

            if (indexesToSelect.Count == 0)
            {
                itemsGridView.ClearSelectedItems();
                return;
            }

            itemsGridView.SetSelection(indexesToSelect);
        }

        static bool ShouldUpdateSelection(
            bool clickedOnSelection,
            bool hasControlPressed,
            bool isRightClick,
            bool isMouseDown)
        {
            if (isRightClick)
                return !clickedOnSelection && !hasControlPressed && isMouseDown;

            if (clickedOnSelection && !hasControlPressed)
                return !isMouseDown;

            return isMouseDown;
        }

        static int GetItemIndexToSelectForOffset(
            List<int> selectedItemsIndexes,
            int offset)
        {
            if (selectedItemsIndexes.Count == 0)
                return 0;

            return selectedItemsIndexes.Last() + offset;
        }

        static List<int> GetItemsIndexesToSelect(
            List<int> selectedItemsIndexes,
            int indexToSelect,
            bool shouldToggleSelection,
            bool shouldExtendSelection)
        {
            if (shouldExtendSelection)
            {
                return GetItemsIndexesToSelectByExtending(
                    selectedItemsIndexes, indexToSelect);
            }

            if (shouldToggleSelection)
            {
                return GetItemsIndexesToSelectByToggling(
                    selectedItemsIndexes, indexToSelect);
            }

            return new List<int> { indexToSelect };
        }

        static List<int> GetItemsIndexesToSelectByExtending(
            IList<int> selectedItemsIndexes,
            int target)
        {
            int anchor = selectedItemsIndexes.FirstOrDefault();
            int direction = anchor < target ? 1 : -1;
            int count = Math.Abs(target - anchor);

            List<int> result = new List<int>();

            for (int i = 0; i <= count; i++)
                result.Add(anchor + i * direction);

            return result;
        }

        static List<int> GetItemsIndexesToSelectByToggling(
            IList<int> selectedItemsIndexes,
            int target)
        {
            List<int> result = new List<int>(selectedItemsIndexes);

            if (result.Contains(target))
            {
                result.Remove(target);
                return result;
            }

            result.Add(target);
            return result;
        }
    }
}