using UnityEngine;
using UnityEngine.Events;
using UPG.Extensions;

#region CLASS EVENTS
namespace UnityEngine.Events
{
    [System.Serializable] public class DebugMessageEvent : UnityEvent<System.DateTime, string> { };
    [System.Serializable] public class CriticalErrorEvent : UnityEvent<int, string> { };
}
#endregion

namespace UPG.Debugging
{
    /// <summary>
    /// Provides a more flexible and formatable method of displaying console messages.
    /// Channel instances can be used to toggle groups of message types.
    /// </summary>
    [CreateAssetMenu(fileName = "New Debug Channel", menuName = "Scriptables/Debug Channel")]
    [System.Serializable]
    public class DebugChannel : ScriptableObject
    {
        #region EVENTS
        public static DebugMessageEvent OnMessageReceived = new DebugMessageEvent();
        public static CriticalErrorEvent OnCriticalErrorReceived = new CriticalErrorEvent();
        #endregion

        #region ENUMS
        public enum Severity
        {
            Log,
            Warning,
            Error
        }
        #endregion

        #region INSPECTOR FIELDS
        [Header("Console Messages")]
        [SerializeField] public string LimitTo;
        [SerializeField] public bool EnableDebugMessages;
        [SerializeField] public bool EnableErrorBreakPoints;

        [Header("Visuals")]
        [SerializeField] public bool EnableDebugGizmos;
        [SerializeField] public Color ChannelDisplayColor = Color.black;
        #endregion

        #region CONSTANTS
        Mesh m_standardQuadMesh;
        Mesh m_sizedQuadMesh;
        float m_lastQuadSize = 1;
        #endregion

        #region DEFAULT METHODS
        void OnValidate()
        {
            if (!m_standardQuadMesh)
                m_standardQuadMesh = ConstructQuadMesh(1);
        }
        #endregion

        #region MESH METHODS
        Mesh ConstructQuadMesh(float _width)
        {
            Vector3[] newVertices = new Vector3[4];
            float halfHeight = _width * 0.5f;
            float halfWidth = _width * 0.5f;
            newVertices[0] = new Vector3(-halfWidth, -halfHeight, 0);
            newVertices[1] = new Vector3(-halfWidth, halfHeight, 0);
            newVertices[2] = new Vector3(halfWidth, -halfHeight, 0);
            newVertices[3] = new Vector3(halfWidth, halfHeight, 0);

            Vector2[] newUVs = new Vector2[newVertices.Length];
            newUVs[0] = new Vector2(0, 0);
            newUVs[1] = new Vector2(0, 1);
            newUVs[2] = new Vector2(1, 0);
            newUVs[3] = new Vector2(1, 1);

            int[] newTriangles = new int[] { 0, 1, 2, 3, 2, 1 };

            // Setup normals
            Vector3[] newNormals = new Vector3[newVertices.Length];
            for (int i = 0; i < newNormals.Length; i++)
            {
                newNormals[i] = Vector3.forward;
            }


            return new Mesh() { 
                vertices = newVertices,
                uv = newUVs,
                triangles = newTriangles,
                normals = newNormals
            };
        }
        #endregion

        #region DEPLOY DEBUG MESSAGE METHODS
        static void DeployDebugMessage(string _message, Severity _severity = Severity.Log)
        {
            switch (_severity)
            {
                case (Severity.Log):
                    Debug.Log(_message + "\n");
                    break;
                case (Severity.Warning):
                    Debug.LogWarning(_message + "\n");
                    break;
                case (Severity.Error):
                    Debug.LogError(_message + "\n");
                    break;
            }

            OnMessageReceived?.Invoke(System.DateTime.Now, _message.Replace("<b>", "").Replace("</b>", ""));
        }
        #endregion

        #region CHANNEL INSTANCE MESSAGES
        public void Raise(string _message, Severity _severity = Severity.Log)
        {
            if (!Application.isEditor)
                return;

            if (!EnableDebugMessages && _severity != Severity.Error)
                    return;

                if (LimitTo != "" && !_message.Contains(LimitTo))
                    return;

            DeployDebugMessage(_message, _severity);
        }
        public void Raise(string[] _messages, Severity _severity = Severity.Log)
        {
            if (!Application.isEditor)
                return;

            if (!EnableDebugMessages && _severity != Severity.Error)
                return;

            if (_messages.Length == 0)
                return;

            string _finalMessage = _messages[0] + (_messages.Length > 1 ? "\n" : "");

            for (int i = 1; i < _messages.Length; i++)
                _finalMessage += "<color=grey>" + _messages[i] + "</color>" + (i + 1 == _messages.Length ? "" : "\n");

            if (LimitTo != "" && !_finalMessage.Contains(LimitTo))
                return;

            DeployDebugMessage(_finalMessage, _severity);
        }
        public void Raise<T>(T _object, string _message, Severity _severity = Severity.Log)
        {
            if (!Application.isEditor)
                return;

            if (!EnableDebugMessages && _severity != Severity.Error)
                return;

            string _finalMessage =
                "<b>" + ToDebugTag(_object, ChannelDisplayColor) + "</b> " +
                _message;

            if (LimitTo != "" && !_finalMessage.Contains(LimitTo))
                return;

            DeployDebugMessage(_finalMessage, _severity);
        }
        public void Raise<T>(T _sourceType, string[] _messages, Severity _severity = Severity.Log)
        {
            if (!Application.isEditor)
                return;

            if (!EnableDebugMessages && _severity != Severity.Error)
                return;

            if (_messages.Length == 0)
                return;

            string _finalMessage =
                "<b>" + ToDebugTag(_sourceType, ChannelDisplayColor) + "</b> " +
                _messages[0] + (_messages.Length > 1 ? "\n" : "");

            for (int i = 1; i < _messages.Length; i++)
                _finalMessage += "<color=grey>" + _messages[i] + "</color>" + (i + 1 == _messages.Length ? "" : "\n");

            if (LimitTo != "" && !_finalMessage.Contains(LimitTo))
                return;

            DeployDebugMessage(_finalMessage, _severity);
        }
        #endregion

        #region CHANNEL OVERRIDE MESSAGES
        public static void Raise<T>(string _preferredChannel, T _sourceType, string _message, Severity _severity = Severity.Log)
        {
            DebugChannel _channel = Resources.LoadAll<DebugChannel>("Scriptables/DebugChannels").ToList().Find(x => x.name == _preferredChannel);

            if (!_channel)
                Debug.Log("Didn't find " + _preferredChannel);

            _channel?.Raise(_sourceType, _message, _severity);
        }
        public static void Raise<T>(string _preferredChannel, T _sourceType, string[] _messages, Severity _severity = Severity.Log)
        {
            DebugChannel _channel = Resources.LoadAll<DebugChannel>("Scriptables/DebugChannels").ToList().Find(x => x.name == _preferredChannel);

            if (!_channel)
                Debug.Log("Didn't find " + _preferredChannel);

            _channel?.Raise(_sourceType, _messages, _severity);
        }
        #endregion

        #region CRITICAL ERROR MESSAGES
        public static void ReportCriticalError(int _code, string _description = "Unknown")
        {
            Raise("Default", default(DebugChannel), new string[] { "Critical Error: " + _code, _description }, Severity.Error);

            OnCriticalErrorReceived?.Invoke(_code, _description);
        }
        #endregion

        #region GIZMO METHODS
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_normal"></param>
        /// <param name="_length"></param>
        public void DrawRay(Vector3 _origin, Vector3 _normal, float _length = 1)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = ChannelDisplayColor;

            Gizmos.DrawLine(_origin, _origin + _normal.normalized * _length);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_normal"></param>
        /// <param name="_color"></param>
        /// <param name="_length"></param>
        public void DrawRay(Vector3 _origin, Vector3 _normal, Color _color, float _length = 1)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = _color;

            Gizmos.DrawLine(_origin, _origin + _normal.normalized * _length);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_end"></param>
        public void DrawLine(Vector3 _origin, Vector3 _end)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = ChannelDisplayColor;

            Gizmos.DrawLine(_origin, _end);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_end"></param>
        /// <param name="_color"></param>
        public void DrawLine(Vector3 _origin, Vector3 _end, Color _color)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = _color;

            Gizmos.DrawLine(_origin, _end);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_size"></param>
        public void DrawCube(Vector3 _origin, Vector3 _size)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = ChannelDisplayColor;

            Gizmos.DrawCube(_origin, _size);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_size"></param>
        public void DrawCube(Vector3 _origin, Vector3 _size, Color _color)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = _color;

            Gizmos.DrawCube(_origin, _size);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_size"></param>
        public void DrawWireCube(Vector3 _origin, Vector3 _size)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = ChannelDisplayColor;

            Gizmos.DrawWireCube(_origin, _size);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_size"></param>
        public void DrawWireCube(Vector3 _origin, Vector3 _size, Color _color)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = _color;

            Gizmos.DrawWireCube(_origin, _size);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_radius"></param>
        public void DrawSphere(Vector3 _origin, float _radius = 1)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = ChannelDisplayColor;

            Gizmos.DrawSphere(_origin, _radius);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_radius"></param>
        public void DrawWireSphere(Vector3 _origin, float _radius = 1)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = ChannelDisplayColor;

            Gizmos.DrawWireSphere(_origin, _radius);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_color"></param>
        /// <param name="_radius"></param>
        public void DrawSphere(Vector3 _origin, Color _color, float _radius = 1)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = _color;

            Gizmos.DrawSphere(_origin, _radius);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_color"></param>
        /// <param name="_radius"></param>
        public void DrawWireSphere(Vector3 _origin, Color _color, float _radius = 1)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = _color;

            Gizmos.DrawWireSphere(_origin, _radius);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_color"></param>
        /// <param name="_radius"></param>
        public void DrawQuad(Vector3 _origin, Color _color)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = _color;

            Gizmos.DrawMesh(m_standardQuadMesh, _origin);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_color"></param>
        /// <param name="_radius"></param>
        public void DrawQuad(Vector3 _origin, Quaternion _rotation, Color _color)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = _color;

            Gizmos.DrawMesh(m_standardQuadMesh, _origin, _rotation);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_color"></param>
        /// <param name="_radius"></param>
        public void DrawQuad(Vector3 _origin, Quaternion _rotation, float _size, Color _color)
        {
            if (!EnableDebugGizmos)
                return;

            if (m_lastQuadSize != _size)
            {
                m_sizedQuadMesh = ConstructQuadMesh(_size);
                m_lastQuadSize = _size;
            }

            Gizmos.color = _color;

            Gizmos.DrawMesh(m_sizedQuadMesh, _origin, _rotation);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_size"></param>
        public void DrawMesh(Mesh _mesh, Vector3 _origin, Quaternion _rotation, Vector3 _size, Color _color)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = _color;

            Gizmos.DrawMesh(_mesh, _origin, _rotation, _size);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_name"></param>
        public void DrawIcon(Vector3 _origin, string _name)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = ChannelDisplayColor;

            Gizmos.DrawIcon(_origin, _name, false);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_origin"></param>
        /// <param name="_up"></param>
        /// <param name="_width"></param>
        public void DrawPrism(Vector3 _origin, Vector3 _up, float _width = 1)
        {
            if (!EnableDebugGizmos)
                return;

            Gizmos.color = ChannelDisplayColor;

            Quaternion _directionRotation = Quaternion.FromToRotation(Vector3.up, _up) * Quaternion.Euler(0, 90, 0);

            Vector3 _point = _origin + _up * _width;

            float _radius = _width / 2;

            Vector3 _forwardOffset = Vector3.forward * _radius;
            Vector3 _rightOffset = Vector3.right * _radius;

            Vector3[] _corners = new Vector3[] 
            {
                _forwardOffset + _rightOffset,
                _forwardOffset - _rightOffset,
                -_forwardOffset - _rightOffset,
                -_forwardOffset + _rightOffset
            };

            Gizmos.DrawLine(_origin + _directionRotation * _corners[0], _origin + _directionRotation * _corners[1]);
            Gizmos.DrawLine(_origin + _directionRotation * _corners[0], _point);

            Gizmos.DrawLine(_origin + _directionRotation * _corners[1], _origin + _directionRotation * _corners[2]);
            Gizmos.DrawLine(_origin + _directionRotation * _corners[1], _point);

            Gizmos.DrawLine(_origin + _directionRotation * _corners[2], _origin + _directionRotation * _corners[3]);
            Gizmos.DrawLine(_origin + _directionRotation * _corners[2], _point);

            Gizmos.DrawLine(_origin + _directionRotation * _corners[3], _origin + _directionRotation * _corners[0]);
            Gizmos.DrawLine(_origin + _directionRotation * _corners[3], _point);
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_message"></param>
        /// <param name="_position"></param>
        /// <param name="_offset"></param>
        public void DrawText(string _message, Vector3 _position, Vector2 _offset = default(Vector2))
        {
            if (!EnableDebugGizmos)
                return;

#if UNITY_EDITOR
            GUIStyle _style = new GUIStyle();
            _style.normal.textColor = ChannelDisplayColor;

            UnityEditor.Handles.Label(
                _position +
                (Vector3.up * _offset.y + Camera.current.transform.right * _offset.x) *
                (Camera.current.transform.position.DistanceTo(_position) * 0.05f).Clamp(0.3f, Mathf.Infinity),
                _message, _style);
#endif
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_message"></param>
        /// <param name="_position"></param>
        /// <param name="_color"></param>
        /// <param name="_offset"></param>
        public void DrawText(string _message, Vector3 _position, Color _color, Vector2 _offset = default(Vector2))
        {
            if (!EnableDebugGizmos)
                return;

#if UNITY_EDITOR
            GUIStyle _style = new GUIStyle();
            _style.normal.textColor = _color;

            UnityEditor.Handles.Label(
                _position +
                (Vector3.up * _offset.y + Camera.current.transform.right * _offset.x) *
                (Camera.current.transform.position.DistanceTo(_position) * 0.05f).Clamp(0.3f, Mathf.Infinity),
                _message, _style);
#endif
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_messages"></param>
        /// <param name="_position"></param>
        /// <param name="_offset"></param>
        public void DrawText(string[] _messages, Vector3 _position, Vector2 _offset = default(Vector2))
        {
            if (!EnableDebugGizmos)
                return;

            string _finalMessage = "";

            foreach (string _message in _messages)
                _finalMessage += _message + "\n";

#if UNITY_EDITOR
            GUIStyle _style = new GUIStyle();
            _style.normal.textColor = ChannelDisplayColor;

            UnityEditor.Handles.Label(_position +
                (Vector3.up * _offset.y + Camera.current.transform.right * _offset.x) *
                (Camera.current.transform.position.DistanceTo(_position) * 0.05f).Clamp(0.3f, Mathf.Infinity),
                _finalMessage, _style);
#endif
        }
        /// <summary>
        /// Only call in OnDrawGizmos or OnDrawGizmosSelected
        /// </summary>
        /// <param name="_messages"></param>
        /// <param name="_position"></param>
        /// <param name="_color"></param>
        /// <param name="_offset"></param>
        public void DrawText(string[] _messages, Vector3 _position, Color _color, Vector2 _offset = default(Vector2))
        {
            if (!EnableDebugGizmos)
                return;

            string _finalMessage = "";

            foreach (string _message in _messages)
                _finalMessage += _message + "\n";

#if UNITY_EDITOR
            GUIStyle _style = new GUIStyle();
            _style.normal.textColor = _color;

            UnityEditor.Handles.Label(_position +
                (Vector3.up * _offset.y + Camera.current.transform.right * _offset.x) *
                (Camera.current.transform.position.DistanceTo(_position) * 0.05f).Clamp(0.3f, Mathf.Infinity),
                _finalMessage, _style);
#endif
        }
        #endregion

        #region CONVERSION METHODS 
        public static string ToDebugTag<T>(T _source) =>
            "[<color=#" + ColorUtility.ToHtmlStringRGBA(Color.black) + ">" +
                (typeof(T) == typeof(string) ? _source as string : _source.GetType().ToString()).Split('.').GetLastObject() + "</color>]";
        public static string ToDebugTag<T>(T _source, Color _color) =>
            "[<color=#" + ColorUtility.ToHtmlStringRGBA(_color) + ">" +
                (typeof(T) == typeof(string) ? _source as string : _source.GetType().ToString()).Split('.').GetLastObject() + "</color>]";
        #endregion
    }
}