using System;
using System.Collections.Generic;
using System.Linq;
using Beatmap.Base;
using Selector.UserInterface;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;

namespace Selector
{
    [Plugin("Selector")]
    public class Selector
    {
        private ArcGridContainer _arcGridContainer;
        private ChainGridContainer _chainsGridContainer;
        private EventGridContainer _eventGridContainer;
        private NoteGridContainer _noteGridContainer;
        private ObstacleGridContainer _obstacleGridContainer;
        private BPMChangeGridContainer _bpmGridContainer;
        private UI _ui;

        [Init]
        private void Init()
        {
            SceneManager.sceneLoaded += SceneLoaded;
            _ui = new UI(this);
        }

        private void SceneLoaded(Scene arg0, LoadSceneMode arg1)
        {
            if (arg0.buildIndex == 3)
            {
                _noteGridContainer = Object.FindObjectOfType<NoteGridContainer>();
                _eventGridContainer = Object.FindObjectOfType<EventGridContainer>();
                _obstacleGridContainer = Object.FindObjectOfType<ObstacleGridContainer>();
                _arcGridContainer = Object.FindObjectOfType<ArcGridContainer>();
                _chainsGridContainer = Object.FindObjectOfType<ChainGridContainer>();
                _bpmGridContainer = Object.FindObjectOfType<BPMChangeGridContainer>();

                var mapEditorUI = Object.FindObjectOfType<MapEditorUI>();
                _ui.AddMenu(mapEditorUI);
            }
        }

        public void Select()
        {
            var notes = new List<BaseNote>();
            var bombs = new List<BaseNote>();
            var arcs = new List<BaseArc>();
            var chains = new List<BaseChain>();
            var events = new List<BaseEvent>();
            var obstacles = new List<BaseObstacle>();
            GrabAll(ref notes, ref bombs, ref arcs, ref chains, ref events, ref obstacles);

            FilterTime(ref notes, ref bombs, ref arcs, ref chains, ref events, ref obstacles);
            FilterColor(ref notes, ref arcs, ref chains);
            FilterDirection(ref notes, ref arcs, ref chains);
            FilterX(ref notes, ref bombs, ref arcs, ref chains, ref obstacles);
            FilterY(ref notes, ref bombs, ref arcs, ref chains, ref obstacles);
            FilterType(ref events);
            FilterValue(ref events);
            FilterFloatValue(ref events);

            notes.ForEach(n => { SelectionController.Select(n, true, false, false); });
            bombs.ForEach(n => { SelectionController.Select(n, true, false, false); });
            arcs.ForEach(o => { SelectionController.Select(o, true, false, false); });
            chains.ForEach(o => { SelectionController.Select(o, true, false, false); });
            events.ForEach(e => { SelectionController.Select(e, true, false, false); });
            obstacles.ForEach(o => { SelectionController.Select(o, true, false, false); });
            SelectionController.SelectionChangedEvent?.Invoke();
        }

        public void Deselect()
        {
            var notes = new List<BaseNote>();
            var bombs = new List<BaseNote>();
            var arcs = new List<BaseArc>();
            var chains = new List<BaseChain>();
            var events = new List<BaseEvent>();
            var obstacles = new List<BaseObstacle>();
            GrabAll(ref notes, ref bombs, ref arcs, ref chains, ref events, ref obstacles);

            FilterTime(ref notes, ref bombs, ref arcs, ref chains, ref events, ref obstacles);
            FilterColor(ref notes, ref arcs, ref chains);
            FilterDirection(ref notes, ref arcs, ref chains);
            FilterX(ref notes, ref bombs, ref arcs, ref chains, ref obstacles);
            FilterY(ref notes, ref bombs, ref arcs, ref chains, ref obstacles);
            FilterType(ref events);
            FilterValue(ref events);
            FilterFloatValue(ref events);

            notes.ForEach(n => { SelectionController.Deselect(n, false); });
            bombs.ForEach(n => { SelectionController.Deselect(n, false); });
            arcs.ForEach(o => { SelectionController.Deselect(o, false); });
            chains.ForEach(o => { SelectionController.Deselect(o, false); });
            events.ForEach(e => { SelectionController.Deselect(e, false); });
            obstacles.ForEach(o => { SelectionController.Deselect(o, false); });
            SelectionController.SelectionChangedEvent?.Invoke();
        }

        public void SelectAll()
        {
            var notes = new List<BaseNote>();
            var bombs = new List<BaseNote>();
            var arcs = new List<BaseArc>();
            var chains = new List<BaseChain>();
            var events = new List<BaseEvent>();
            var obstacles = new List<BaseObstacle>();
            GrabAll(ref notes, ref bombs, ref arcs, ref chains, ref events, ref obstacles);

            notes.ForEach(n => { SelectionController.Select(n, true, false, false); });
            bombs.ForEach(n => { SelectionController.Select(n, true, false, false); });
            arcs.ForEach(o => { SelectionController.Select(o, true, false, false); });
            chains.ForEach(o => { SelectionController.Select(o, true, false, false); });
            events.ForEach(e => { SelectionController.Select(e, true, false, false); });
            obstacles.ForEach(o => { SelectionController.Select(o, true, false, false); });
            SelectionController.SelectionChangedEvent?.Invoke();
        }

        public void DeselectAll()
        {
            SelectionController.DeselectAll();
        }

        private void GrabAll(ref List<BaseNote> notes, ref List<BaseNote> bombs, ref List<BaseArc> arcs,
            ref List<BaseChain> chains, ref List<BaseEvent> events, ref List<BaseObstacle> obstacles)
        {
            notes = Options.SelectNote
                ? _noteGridContainer.MapObjects.Where(n => n.Type != 3).ToList()
                : new List<BaseNote>();
            bombs = Options.SelectBomb
                ? _noteGridContainer.MapObjects.Where(n => n.Type == 3).Cast<BaseNote>()
                    .ToList()
                : new List<BaseNote>();
            arcs = Options.SelectArc ? _arcGridContainer.MapObjects.ToList() : new List<BaseArc>();
            chains = Options.SelectChain
                ? _chainsGridContainer.MapObjects.ToList()
                : new List<BaseChain>();
            events = Options.SelectEvent
                ? _eventGridContainer.MapObjects.ToList()
                : new List<BaseEvent>();
            obstacles = Options.SelectObstacle
                ? _obstacleGridContainer.MapObjects.ToList()
                : new List<BaseObstacle>();
        }

        private float _adjustTimeStart = 0;
        private float _adjustTimeEnd = 0;
        private bool ObjectFilterTime(BaseObject obj)
        {
            return obj.JsonTime >= _adjustTimeStart && obj.JsonTime <= _adjustTimeEnd;
        }

        private void FilterTime(ref List<BaseNote> notes, ref List<BaseNote> bombs, ref List<BaseArc> arcs,
            ref List<BaseChain> chains, ref List<BaseEvent> events, ref List<BaseObstacle> obstacles)
        {
            if (!Options.TimeSelect) return;
            _adjustTimeStart = Options.TimeStart - Options.TimeTolerance;
            _adjustTimeEnd = Options.TimeEnd + Options.TimeTolerance;
            
            notes = new List<BaseNote>(notes.Where(ObjectFilterTime));
            bombs = new List<BaseNote>(bombs.Where(ObjectFilterTime));
            arcs = new List<BaseArc>(arcs.Where(ObjectFilterTime));
            chains = new List<BaseChain>(chains.Where(ObjectFilterTime));
            events = new List<BaseEvent>(events.Where(ObjectFilterTime));
            obstacles = new List<BaseObstacle>(obstacles.Where(ObjectFilterTime));
        }

        private void FilterColor(ref List<BaseNote> notes, ref List<BaseArc> arcs,
            ref List<BaseChain> chains)
        {
            if (!Options.GridColorSelect) return;
            var color = -1;
            switch (Options.GridColor)
            {
                case "Red":
                    color = 0;
                    break;
                case "Blue":
                    color = 1;
                    break;
            }
            
            notes = new List<BaseNote>(notes.Where(obj => obj.Color == color));
            arcs = new List<BaseArc>(arcs.Where(obj => obj.Color == color));
            chains = new List<BaseChain>(chains.Where(obj => obj.Color == color));
        }

        private void FilterDirection(ref List<BaseNote> notes, ref List<BaseArc> arcs,
            ref List<BaseChain> chains)
        {
            if (!Options.GridDirectionSelect) return;
            var direction = -1;
            switch (Options.GridDirection)
            {
                case "Up":
                    direction = 0;
                    break;
                case "Down":
                    direction = 1;
                    break;
                case "Left":
                    direction = 2;
                    break;
                case "Right":
                    direction = 3;
                    break;
                case "Up-Left":
                    direction = 4;
                    break;
                case "Up-Right":
                    direction = 5;
                    break;
                case "Down-Left":
                    direction = 6;
                    break;
                case "Down-Right":
                    direction = 7;
                    break;
                case "Any":
                    direction = 8;
                    break;
                case "Unknown":
                    direction = 9;
                    break;
                case "ME":
                    direction = 10;
                    break;
            }

            switch (direction)
            {
                case 9:
                    notes = new List<BaseNote>(notes.Where(obj => obj.CutDirection < 0 || obj.CutDirection > 8));
                    arcs = new List<BaseArc>(arcs.Where(obj => obj.CutDirection < 0 || obj.CutDirection > 8));
                    chains = new List<BaseChain>(chains.Where(obj => obj.CutDirection < 0 || obj.CutDirection > 8));
                    break;
                case 10:
                    notes = new List<BaseNote>(notes.Where(obj => obj.CutDirection >= 1000 && obj.CutDirection <= 1360));
                    arcs = new List<BaseArc>(arcs.Where(obj => (obj.CutDirection >= 1000 && obj.CutDirection <= 1360) || (obj.CutDirection >= 2000 && obj.CutDirection <= 2360)));
                    chains = new List<BaseChain>(chains.Where(obj =>
                        (obj.CutDirection >= 1000 && obj.CutDirection <= 1360) || (obj.CutDirection >= 2000 && obj.CutDirection <= 2360)));
                    break;
                default:
                    notes = new List<BaseNote>(notes.Where(obj => obj.CutDirection == direction));
                    arcs = new List<BaseArc>(arcs.Where(obj => obj.CutDirection == direction));
                    chains = new List<BaseChain>(chains.Where(obj => obj.CutDirection == direction));
                    break;
            }
        }

        private bool GridFilterX(BaseGrid obj)
        {
            return obj.PosX == Options.GridX;
        }

        private void FilterX(ref List<BaseNote> notes, ref List<BaseNote> bombs, ref List<BaseArc> arcs,
            ref List<BaseChain> chains, ref List<BaseObstacle> obstacles)
        {
            if (!Options.GridXSelect) return;
            notes = new List<BaseNote>(notes.Where(GridFilterX));
            bombs = new List<BaseNote>(bombs.Where(GridFilterX));
            arcs = new List<BaseArc>(arcs.Where(GridFilterX));
            chains = new List<BaseChain>(chains.Where(GridFilterX));
            obstacles = new List<BaseObstacle>(obstacles.Where(GridFilterX));
        }

        private bool GridFilterY(BaseGrid obj)
        {
            return obj.PosY == Options.GridY;
        }

        private void FilterY(ref List<BaseNote> notes, ref List<BaseNote> bombs, ref List<BaseArc> arcs,
            ref List<BaseChain> chains, ref List<BaseObstacle> obstacles)
        {
            if (!Options.GridYSelect) return;
            notes = new List<BaseNote>(notes.Where(GridFilterY));
            bombs = new List<BaseNote>(bombs.Where(GridFilterY));
            arcs = new List<BaseArc>(arcs.Where(GridFilterY));
            chains = new List<BaseChain>(chains.Where(GridFilterY));
            obstacles = new List<BaseObstacle>(obstacles.Where(GridFilterY));
        }

        private void FilterType(ref List<BaseEvent> events)
        {
            if (!Options.EventTypeSelect) return;
            var type = -1;
            switch (Options.EventType)
            {
                case "0 - Back top":
                    type = 0;
                    break;    
                case "1 - Ring":
                    type = 1;
                    break;    
                case "2 - L Laser":
                    type = 2;
                    break;    
                case "3 - R Laser":
                    type = 3;
                    break;    
                case "4 - Center":
                    type = 4;
                    break;    
                case "5 - Boost":
                    type = 5;
                    break;    
                case "6 - XL Light":
                    type = 6;
                    break;    
                case "7 - XR Light":
                    type = 7;
                    break;    
                case "8 - Ring Rot":
                    type = 8;
                    break;    
                case "9 - Ring Zoom":
                    type = 9;
                    break;    
                case "10 - XL Laser":
                    type = 10;
                    break;    
                case "11 - XR Laser":
                    type = 11;
                    break;    
                case "12 - L Rot":
                    type = 12;
                    break;    
                case "13 - R Rot":
                    type = 13;
                    break;    
                case "14 - Early Rot":
                    type = 14;
                    break;    
                case "15 - Late Rot":
                    type = 15;
                    break;    
                case "16 - Utility 0":
                    type = 16;
                    break;    
                case "17 - Utility 1":
                    type = 17;
                    break;    
                case "18 - Utility 2":
                    type = 18;
                    break;    
                case "19 - Utility 3":
                    type = 19;
                    break;    
                case "40 - Special 0":
                    type = 40;
                    break;    
                case "41 - Special 1":
                    type = 41;
                    break;    
                case "42 - Special 2":
                    type = 42;
                    break;    
                case "43 - Special 3":
                    type = 43;
                    break;    
                case "100 - BPM":
                    type = 100;
                    break;    
                case "Custom":
                    type = Options.EventTypeCustom;
                    break;    
            }
            
            events = new List<BaseEvent>(events.Where(obj => obj.Type == type));
        }

        private void FilterValue(ref List<BaseEvent> events)
        {
            if (Options.EventValueColorSelect)
            {
                switch (Options.EventValueColor)
                {
                    case "Blue":
                        events = new List<BaseEvent>(events.Where(obj => obj.IsBlue));
                        break;
                    case "Red":
                        events = new List<BaseEvent>(events.Where(obj => obj.IsRed));
                        break;
                    case "White":
                        events = new List<BaseEvent>(events.Where(obj => obj.IsWhite));
                        break;
                    case "Unknown":
                        events = new List<BaseEvent>(events.Where(obj => obj.Value < 0 || obj.Value > 12));
                        break;
                    case "Custom":
                        events = new List<BaseEvent>(events.Where(obj => obj.Value == Options.EventValueCustom));
                        break;
                }
            }
            
            if (Options.EventValueTypeSelect)
            {
                switch (Options.EventValueType)
                {
                    case "Off":
                        events = new List<BaseEvent>(events.Where(obj => obj.IsOff));
                        break;
                    case "On":
                        events = new List<BaseEvent>(events.Where(obj => obj.IsOn));
                        break;
                    case "Flash":
                        events = new List<BaseEvent>(events.Where(obj => obj.IsFlash));
                        break;
                    case "Fade":
                        events = new List<BaseEvent>(events.Where(obj => obj.IsFade));
                        break;
                    case "Transition":
                        events = new List<BaseEvent>(events.Where(obj => obj.IsTransition));
                        break;
                }
            }
        }

        private void FilterFloatValue(ref List<BaseEvent> events)
        {
            if (!Options.EventFloatValueSelect) return;
            events = new List<BaseEvent>(events.Where(obj =>
                Math.Abs(obj.FloatValue - Options.EventFloatValue) <= Options.EventFloatValueTolerance));
        }

        [Exit]
        private void Exit()
        {
        }
    }
}