using System;

using UnityEngine;
using UnityEngine.Events;

using UPG.Extensions;
using UPG.Debugging;

namespace BR
{
    public class CollisionEventUtility : MonoBehaviour
    {
        #region INSPECTOR FIELDS
        [Header("Collision Layers")]
        [SerializeField] LayerMask m_include = new LayerMask();

        [Header("Collision Events")]
        public CollisionEvent OnCollide = new CollisionEvent();
        public CollisionEvent OnOccupy = new CollisionEvent();
        public CollisionEvent OnSeparate = new CollisionEvent();

        [Header("Trigger Events")]
        public ColliderEvent OnEnter = new ColliderEvent();
        public ColliderEvent OnStay = new ColliderEvent();
        public ColliderEvent OnExit = new ColliderEvent();

        [Header("Debug")]
        [SerializeField] public DebugChannel DebugChannel = null;
        #endregion

        #region PRIVATE FIELDS
        Collider m_collider;
        #endregion

        #region DEFAULT METHODS
        private void Awake()
        {
            m_collider = GetComponent<Collider>();

            if (!m_collider)
                Destroy(this);
        }

        private void OnCollisionEnter(Collision collision)
        {
            if (m_include.ContainsLayer(collision.gameObject.layer))
                return;

            DebugChannel?.Raise(this, gameObject.name + " detected a collision with " + collision.gameObject.name + " on layer " + collision.gameObject.layer);

            OnCollide?.Invoke(collision);
        }

        private void OnCollisionStay(Collision collision)
        {
            if (m_include.ContainsLayer(collision.gameObject.layer))
                return;

            OnOccupy?.Invoke(collision);
        }

        private void OnCollisionExit(Collision collision)
        {
            if (m_include.ContainsLayer(collision.gameObject.layer))
                return;

            DebugChannel?.Raise(this, gameObject.name + " is no longer colliding with " + collision.gameObject.name + " on layer " + collision.gameObject.layer);

            OnSeparate?.Invoke(collision);
        }

        private void OnTriggerEnter(Collider other)
        {
            if (m_include.ContainsLayer(other.gameObject.layer))
                return;

            DebugChannel?.Raise(this, gameObject.name + " volume was entered by " + other.gameObject.name + " on layer " + other.gameObject.layer);

            OnEnter?.Invoke(other);
        }

        private void OnTriggerStay(Collider other)
        {
            if (m_include.ContainsLayer(other.gameObject.layer))
                return;

            OnStay?.Invoke(other);
        }

        private void OnTriggerExit(Collider other)
        {
            if (m_include.ContainsLayer(other.gameObject.layer))
                return;

            DebugChannel?.Raise(this, gameObject.name + " volume was exited by " + other.gameObject.name + " on layer " + other.gameObject.layer);

            OnExit?.Invoke(other);
        }
        #endregion

        #region INITIALIZATION METHODS
        public Tuple<BoxCollider, CollisionEventUtility> AddLoudBox()
        {
            BoxCollider _boxCol = GetComponent<BoxCollider>();

            if (!_boxCol)
                _boxCol = gameObject.AddComponent<BoxCollider>();

            return Tuple.Create(_boxCol, this);
        }
        public Tuple<SphereCollider, CollisionEventUtility> AddLoudSphere()
        {
            SphereCollider _sphereCol = GetComponent<SphereCollider>();

            if (!_sphereCol)
                _sphereCol = gameObject.AddComponent<SphereCollider>();

            return Tuple.Create(_sphereCol, this);
        }
        public Tuple<CapsuleCollider, CollisionEventUtility> AddLoudCapsule()
        {
            CapsuleCollider _capsuleCol = GetComponent<CapsuleCollider>();

            if (!_capsuleCol)
                _capsuleCol = gameObject.AddComponent<CapsuleCollider>();

            return Tuple.Create(_capsuleCol, this);
        }
        public Tuple<MeshCollider, CollisionEventUtility> AddLoudMesh(Mesh _mesh = null)
        {
            MeshCollider _meshCollider = GetComponent<MeshCollider>();

            if (!_meshCollider)
                _meshCollider = gameObject.AddComponent<MeshCollider>();

            _meshCollider.sharedMesh = _mesh;

            return Tuple.Create(_meshCollider, this);
        }
        public static Tuple<BoxCollider, CollisionEventUtility> AddLoudBox(GameObject _target)
        {
            BoxCollider _boxCol = _target.GetComponent<BoxCollider>();

            if (!_boxCol)
                _target.AddComponent<BoxCollider>();

            CollisionEventUtility _loudCollider = _target.AddComponent<CollisionEventUtility>();

            return Tuple.Create(_boxCol, _loudCollider);
        }

        public static Tuple<SphereCollider, CollisionEventUtility> AddLoudSphere(GameObject _target)
        {
            SphereCollider _sphereCol = _target.GetComponent<SphereCollider>();

            if (!_sphereCol)
                _target.AddComponent<SphereCollider>();

            CollisionEventUtility _loudCollider = _target.AddComponent<CollisionEventUtility>();

            return Tuple.Create(_sphereCol, _loudCollider);
        }

        public static Tuple<CapsuleCollider, CollisionEventUtility> AddLoudCapsule(GameObject _target)
        {
            CapsuleCollider _capsuleCollider = _target.GetComponent<CapsuleCollider>();

            if (!_capsuleCollider)
                _target.AddComponent<CapsuleCollider>();

            CollisionEventUtility _loudCollider = _target.AddComponent<CollisionEventUtility>();

            return Tuple.Create(_capsuleCollider, _loudCollider);
        }

        public static Tuple<MeshCollider, CollisionEventUtility> AddLoudMesh(GameObject _target, Mesh _mesh = null)
        {
            MeshCollider _meshCollider = _target.GetComponent<MeshCollider>();

            if (!_meshCollider)
                _target.AddComponent<MeshCollider>();

            if (_mesh != null)
                _meshCollider.sharedMesh = _mesh;

            CollisionEventUtility _loudCollider = _target.AddComponent<CollisionEventUtility>();

            return Tuple.Create(_meshCollider, _loudCollider);
        }
        #endregion
    }
}