using System;
using System.Threading;
using UnityEngine;
using UnityEngine.Events;
namespace SkyHook
{
    /// 
    /// Manages SkyHook activity.
    /// A "ed" instance will be created automatically upon use.
    /// 
    public class SkyHookManager : MonoBehaviour
    {
        private static SkyHookManager _instance;
        private ManualResetEvent _mre;
        /// 
        /// Whether this process is focused.
        /// 
        public static bool IsFocused;
        /// 
        /// Whether or the event will be received only if the game window is focused.
        /// Note that only down key events will be ignored.
        /// 
        // ReSharper disable once MemberCanBePrivate.Global
        // ReSharper disable once FieldCanBeMadeReadOnly.Global
        public bool requireFocus = true;
        /// 
        /// Whether the hook is active now.
        /// 
        public bool isHookActive;
        /// 
        /// Your callback for each key updated events.
        /// Use  to register your callback.
        /// 
        // ReSharper disable once MemberCanBePrivate.Global
        public static readonly UnityEvent KeyUpdated = new();
        private SkyHookNative.Callback _callback;
        /// 
        /// The instance of .
        /// A new instance will be created if it does not exist.
        /// 
        /// This will return null if  is false.
        /// 
        // ReSharper disable once MemberCanBePrivate.Global
        public static SkyHookManager Instance
        {
            get
            {
                if (!Application.isPlaying) return null;
                if (_instance) return _instance;
                var obj = new GameObject("SkyHook Manager");
                _instance = obj.AddComponent();
                DontDestroyOnLoad(_instance);
                return _instance;
            }
        }
        private void HookCallback(SkyHookEvent ev)
        {
            if (requireFocus && !IsFocused && ev.Type == EventType.KeyPressed)
            {
                return;
            }
            KeyUpdated.Invoke(ev);
        }
        private void _StartHook()
        {
            var started = false;
            Exception exception = null;
            _mre = new(false);
            new Thread(() =>
            {
                try
                {
                    _callback = HookCallback;
                    var result = SkyHookNative.StartHook(_callback);
                    
                    if (result != null)
                    {
                        exception = new SkyHookException(result);
                    }
            
                    isHookActive = true;
                    started = true;
            
                    _mre.WaitOne();
                    
                    Debug.Log("Thread ended");
                }
                catch (Exception e)
                {
                    exception = e;
                    Debug.LogError(e);
                    throw;
                }
            }).Start();
            
            while (!started && exception == null)
            {
            }
            if (exception != null)
            {
                throw exception;
            }
        }
        private void _StopHook()
        {
            var result = SkyHookNative.StopHook();
            if (result != null)
            {
                throw new SkyHookException(result);
            }
            if (_mre != null)
            {
                _mre.Set();
            }
            isHookActive = false;
        }
        /// 
        /// Starts the native hook.
        /// 
        /// This will only work if  is true.
        /// 
        public static void StartHook()
        {
            if (!Application.isPlaying)
            {
                Debug.LogWarning("You may not call StartHook() if the application is not playing.");
                return;
            }
            Instance._StartHook();
        }
        /// 
        /// Stops the native hook.
        /// 
        /// This will only work if  is true.
        /// 
        public static void StopHook()
        {
            if (!Application.isPlaying)
            {
                Debug.LogWarning("You may not call StopHook() if the application is not playing.");
                return;
            }
            Instance._StopHook();
        }
        private void OnDestroy()
        {
            if (isHookActive)
            {
                _StopHook();
            }
        }
        private void Update()
        {
            if (requireFocus)
            {
                IsFocused = Application.isFocused;
            }
        }
    }
}