﻿using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using GameCreator.Runtime.Common;
using UnityEngine.Serialization;

namespace GameCreator.Runtime.Characters
{
    [Title("Point & Click")]
    [Image(typeof(IconLocationDrop), ColorTheme.Type.Green)]

    [Category("Point & Click")]
    [Description(
        "Moves the Player where the pointer's position clicks from the Main Camera's perspective"
    )]
    
    [Serializable]
    public class UnitPlayerPointClick : TUnitPlayer
    {
        private const int BUFFER_SIZE = 32;

        // RAYCAST COMPARER: ----------------------------------------------------------------------
        
        private static readonly RaycastComparer RAYCAST_COMPARER = new RaycastComparer();
        
        private class RaycastComparer : IComparer<RaycastHit>
        {
            public int Compare(RaycastHit a, RaycastHit b)
            {
                return a.distance.CompareTo(b.distance);
            }
        }
        
        // EXPOSED MEMBERS: -----------------------------------------------------------------------

        [SerializeField] 
        private InputPropertyButton m_InputMove; 
        
        [SerializeField]
        private LayerMask m_LayerMask;

        [SerializeField]
        private PropertyGetInstantiate m_Indicator;

        // MEMBERS: -------------------------------------------------------------------------------
        
        [NonSerialized] private RaycastHit[] m_HitBuffer;
        
        [NonSerialized] private bool m_Press;
        [NonSerialized] private Location m_Location;

        // INITIALIZERS: --------------------------------------------------------------------------

        public UnitPlayerPointClick()
        {
            this.m_LayerMask = -1;
            this.m_Indicator = new PropertyGetInstantiate
            {
                usePooling = true,
                size = 5,
                hasDuration = true,
                duration = 1f
            };

            this.m_InputMove = InputButtonMousePress.Create();
        }
        
        public override void OnStartup(Character character)
        {
            base.OnStartup(character);
            this.m_InputMove.OnStartup();
        }
        
        public override void OnDispose(Character character)
        {
            base.OnDispose(character);
            this.m_InputMove.OnDispose();
        }

        public override void OnEnable()
        {
            base.OnEnable();

            this.m_HitBuffer = new RaycastHit[BUFFER_SIZE];
            
            this.m_InputMove.Enable();
            this.m_InputMove.RegisterStart(this.OnStartPointClick);
            this.m_InputMove.RegisterPerform(this.OnPerformPointClick);
        }

        public override void OnDisable()
        {
            base.OnDisable();
            this.m_HitBuffer = Array.Empty<RaycastHit>();
            
            this.m_InputMove.ForgetStart(this.OnStartPointClick);
            this.m_InputMove.ForgetPerform(this.OnPerformPointClick);
            this.m_InputMove.Disable();
            
            this.Character.Motion?.MoveToDirection(Vector3.zero, Space.World, 0);
        }

        // UPDATE METHODS: ------------------------------------------------------------------------

        public override void OnUpdate()
        {
            base.OnUpdate();
            this.m_InputMove.OnUpdate();
            
            if (!this.m_Location.HasPosition) return;
            
            GameObject user = this.Character.gameObject;
            
            #if UNITY_EDITOR
            Vector3 position = this.m_Location.GetPosition(user);
            Debug.DrawLine(position, position + Vector3.up * 5f);
            #endif
            
            this.Character.Motion?.MoveToLocation(this.m_Location, 0.1f, null, 0);

            if (this.m_Press && this.m_Location.HasPosition)
            {
                this.m_Indicator.Get(
                    this.Character.gameObject,
                    this.m_Location.GetPosition(this.Character.gameObject),
                    Quaternion.identity
                );
            }
            
            this.m_Press = false;
            this.m_Location = Location.NONE;
        }
        
        // PRIVATE METHODS: -----------------------------------------------------------------------

        private void OnStartPointClick()
        {
            if (!this.Character.IsPlayer) return;
            if (!this.Character.Player.IsControllable) return;

            this.m_Press = true;
        }
        
        private void OnPerformPointClick()
        {
            if (!this.Character.IsPlayer) return;
            if (!this.m_IsControllable) return;

            Camera camera = ShortcutMainCamera.Get<Camera>();
            Ray ray = camera.ScreenPointToRay(Mouse.current.position.ReadValue());

            int hitCount = Physics.RaycastNonAlloc(
                ray, this.m_HitBuffer,
                Mathf.Infinity, this.m_LayerMask,
                QueryTriggerInteraction.Ignore
            );
            
            Array.Sort(this.m_HitBuffer, 0, hitCount, RAYCAST_COMPARER);

            for (int i = 0; i < hitCount; ++i)
            {
                int colliderLayer = this.m_HitBuffer[i].transform.gameObject.layer; 
                if ((colliderLayer & LAYER_UI) > 0) return;
                
                if (this.m_HitBuffer[i].transform.IsChildOf(this.Transform)) continue;

                Vector3 point = this.m_HitBuffer[i].point;
                this.m_Location = new Location(point);
                
                this.InputDirection = Vector3.Scale(
                    point - this.Character.transform.position, 
                    Vector3Plane.NormalUp
                );
                
                return;
            }
        }

        // STRING: --------------------------------------------------------------------------------

        public override string ToString() => "Point & Click";
    }
}