using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace Jovian.Logger {
    /// <summary>
    /// The recommended way to log messages in Jovian Logger.
    /// Create a new instance of this struct in the class you want to log messages.
    /// </summary>
    public struct Logger {
        private string caller;
        private LogCategory logCategory;

        public Logger(Type caller,  LogCategory logCategory) : this() {
            this.logCategory = logCategory;
            this.caller = caller.Name;
            LoggerUtility.PreloadLoggerSettings();
        }

        private string Caller {
            get {
                if(string.IsNullOrEmpty(caller)) {
                    caller = "Unspecified";
                }
                return caller;
            }
        }

        private LogCategory LogCategory {
            get {
                if(logCategory == LogCategory.None) {
                    logCategory = LogCategory.General;
                }
                return logCategory;
            }
        }

        /// <summary>
        /// Logs messages that are meant to only be seen in the editor
        /// </summary>
        /// <param name="msg">Log message</param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [Conditional("UNITY_EDITOR")] [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogSpam(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogSpam($"{Caller}.{callerMethod}", msg, logCat);
        }

        /// <summary>
        /// Logs messages that are meant to only be seen in the editor with a context object
        /// </summary>
        /// <param name="msg">Log message</param>
        /// <param name="context">Object to focus on in scene/project window</param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [Conditional("UNITY_EDITOR")] [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogSpam(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogSpam($"{Caller}.{callerMethod}", msg, logCat, context);
        }

        /// <summary>
        /// Logs standard Info/Debug messages
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogInfo(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogInfo($"{Caller}.{callerMethod}", msg, logCat);
        }

        /// <summary>
        /// Logs standard Info/Debug messages with a context object
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="context">Object to focus on in scene/project window</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogInfo(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogInfo($"{Caller}.{callerMethod}", msg, logCat, context);
        }

        /// <summary>
        /// Logs standard Warning messages
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogWarning(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogWarning($"{Caller}.{callerMethod}", msg, logCat);
        }

        /// <summary>
        /// Logs standard Warning messages with a context object
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="context">Object to focus on in scene/project window</param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogWarning(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogWarning($"{Caller}.{callerMethod}", msg, logCat, context);
        }

        /// <summary>
        /// Logs standard Error messages
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogError(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogError($"{Caller}.{callerMethod}", msg, logCat);
        }

        /// <summary>
        /// Logs standard Error messages with a context object
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="context">Object to focus on in scene/project window</param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogError(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogError($"{Caller}.{callerMethod}", msg, logCat, context);
        }

        /// <summary>
        /// Logs standard Assert messages
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogAssert(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogAssert($"{Caller}.{callerMethod}", msg, logCat);
        }

        /// <summary>
        /// Logs standard Assert messages with a context object
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="context">Object to focus on in scene/project window</param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogAssert(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogAssert($"{Caller}.{callerMethod}", msg, logCat, context);
        }

        /// <summary>
        /// Logs string Exception messages
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogException(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogException($"{Caller}.{callerMethod}", msg, logCat);
        }

        /// <summary>
        /// Logs string Exception messages with a context object
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="context">Object to focus on in scene/project window</param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogException(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogException($"{Caller}.{callerMethod}", msg, logCat, context);
        }

        /// <summary>
        /// Logs standard Exception messages
        /// </summary>
        /// <param name="e"></param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogException(Exception e, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogException($"{Caller}.{callerMethod}", e.Message, logCat);
        }

        /// <summary>
        /// Logs standard Exception messages with a context object
        /// </summary>
        /// <param name="e"></param>
        /// <param name="context">Object to focus on in scene/project window</param>
        /// <param name="logCat">Optional Log Category</param>
        /// <param name="callerMethod">Implicit, do not provide</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogException(Exception e, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            InternalLogger.LogException($"{Caller}.{callerMethod}", e.Message, logCat, context);
        }

        /// <summary>
        /// Logs a watch variable that updates in place in the Custom Console instead of creating new entries.
        /// Ideal for values that change every frame (positions, FPS, state names, etc.).
        /// </summary>
        /// <param name="key">Unique identifier for this watch (e.g. "playerPos", "fps")</param>
        /// <param name="value">The current value to display</param>
        /// <param name="logCat">Optional Log Category</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Watch(string key, object value, LogCategory logCat = LogCategory.None) {
            if(logCat == LogCategory.None) {
                logCat = LogCategory;
            }

            LoggerUtility.WatchCallback?.Invoke((key, value?.ToString() ?? "null", logCat));
        }

        /// <summary>
        /// Removes a watch variable from the Custom Console.
        /// </summary>
        /// <param name="key">The watch key to remove</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Unwatch(string key) {
            LoggerUtility.UnwatchCallback?.Invoke(key);
        }
    }
}
