/**
 * @file    HIVEUnityPluginStandaloneCaller.cs
 * @brief   Standalone Unity 통신 브릿지
 * 
 * @ingroup hive
 * @author  cocororo
 * @date    2023 ~ Present.
 * @copyright Copyright © Com2uS Platform Corporation. All Right Reserved.
 */

using System;
using System.IO;
using System.Collections;
using System.Runtime.InteropServices;
using AOT;
using UnityEngine;

#if UNITY_EDITOR || UNITY_STANDALONE
namespace hive {

	public class HiveUnityPluginStandalonePluginInterface
	{
		public delegate void UnitySendMessageDelegate(
			[MarshalAs(UnmanagedType.LPStr)] string go,
			[MarshalAs(UnmanagedType.LPStr)] string fn,
			[MarshalAs(UnmanagedType.LPStr)] string msg);

		// C-delegate 시그니처(윈도우 동적 바인딩용,플랫폼 공통)
		public delegate IntPtr HivePlugin_callNative_uDelegate(string json);
		public delegate void HivePlugin_callNative_afterDelegate(IntPtr p);
		public delegate void SetUnitySendMessageCallbackDelegate(UnitySendMessageDelegate cb);
#if UNITY_EDITOR
		[DllImport("HiveRTTEditorPlugin")]
		public static extern IntPtr HivePlugin_callNative_u(string json);
		[DllImport("HiveRTTEditorPlugin")]
		public static extern void HivePlugin_callNative_after(IntPtr p);
		[DllImport("HiveRTTEditorPlugin")]
		public static extern void SetUnitySendMessageCallback(UnitySendMessageDelegate cb);
#elif UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX
		[DllImport("HIVE_PLUGIN")]               // libHIVE_PLUGIN.dylib / .so
		public static extern IntPtr HivePlugin_callNative_u(string json);
		[DllImport("HIVE_PLUGIN")]
		public static extern void HivePlugin_callNative_after(IntPtr p);
		[DllImport("HIVE_PLUGIN")]
		public static extern void SetUnitySendMessageCallback(UnitySendMessageDelegate cb);
#endif
	}
	
	internal class HiveUnityPluginStandaloneWindowsRuntimeLoader : IDisposable
    {

        HiveUnityPluginStandalonePluginInterface.HivePlugin_callNative_uDelegate callNative_uDelegate;
        HiveUnityPluginStandalonePluginInterface.HivePlugin_callNative_afterDelegate callNative_afterDelegate;
        HiveUnityPluginStandalonePluginInterface.SetUnitySendMessageCallbackDelegate setUnitySendMessageCallback;

#if !UNITY_EDITOR
		[DllImport("kernel32.dll", SetLastError = true)]
		static extern bool RemoveDllDirectory(IntPtr cookie);

		[DllImport("kernel32.dll", SetLastError = true)]
		static extern bool FreeLibrary(IntPtr hModule);
		
		/// <summary>
		/// HMODULE에서 lpProcName에 해당하는 함수 주소를 반환
		/// </summary>
		/// <param name="hModule">로드한 HMODULE 인스턴스</param>
		/// <param name="lpProcName">함수포인터를 가져올 함수 이름</param>
		/// <returns>함수를 찾았으면 해당 함수의 포인터 주소를, 아니면 IntPtr.Zero를 반환</returns>
		[DllImport("kernel32", SetLastError = true)]
		static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

		IntPtr dllDirectoryCookie = IntPtr.Zero;
		IntPtr libraryHandle      = IntPtr.Zero;
		
		/// <summary>
		/// DLL 파일을 동적으로 로드하여 HMODULE 인스턴스를 반환한다
		/// </summary>
		/// <param name="lpLibFileName">DLL/EXE 파일 경로</param>
		public bool LoadHivePlugin()
		{
			string libPath = "HIVE_PLUGIN.dll";
		    libraryHandle = HiveDllLoader.Load(libPath, HivePluginDirectory.HiveServiceSearchPath);

		    if (libraryHandle == IntPtr.Zero)
		    {
		        Debug.LogError($"Failed to load {libPath}. Win32Error={Marshal.GetLastWin32Error()}");
		        return false;
		    }

		    var callNativeUPtr      = GetProcAddress(libraryHandle, "HivePlugin_callNative_u");
		    var callNativeAfterPtr  = GetProcAddress(libraryHandle, "HivePlugin_callNative_after");
		    var setCallbackPtr      = GetProcAddress(libraryHandle, "SetUnitySendMessageCallback");

		    if (callNativeUPtr == IntPtr.Zero || callNativeAfterPtr == IntPtr.Zero || setCallbackPtr == IntPtr.Zero)
		    {
		        Debug.LogError("Required entry points not found in HIVE_PLUGIN.dll");
		        return false;
		    }

		    callNative_uDelegate          = Marshal.GetDelegateForFunctionPointer<HiveUnityPluginStandalonePluginInterface.HivePlugin_callNative_uDelegate>(callNativeUPtr);
		    callNative_afterDelegate      = Marshal.GetDelegateForFunctionPointer<HiveUnityPluginStandalonePluginInterface.HivePlugin_callNative_afterDelegate>(callNativeAfterPtr);
		    setUnitySendMessageCallback   = Marshal.GetDelegateForFunctionPointer<HiveUnityPluginStandalonePluginInterface.SetUnitySendMessageCallbackDelegate>(setCallbackPtr);

		    return true;
		}
		public void Dispose()
		{
			if (libraryHandle != IntPtr.Zero)
			{
				FreeLibrary(libraryHandle);
				libraryHandle = IntPtr.Zero;
			}
			if (dllDirectoryCookie != IntPtr.Zero)
			{
				RemoveDllDirectory(dllDirectoryCookie);
				dllDirectoryCookie = IntPtr.Zero;
			}
		}
#else
        public bool LoadHivePlugin()
        {
            callNative_uDelegate = HiveUnityPluginStandalonePluginInterface.HivePlugin_callNative_u;
            callNative_afterDelegate = HiveUnityPluginStandalonePluginInterface.HivePlugin_callNative_after;
            setUnitySendMessageCallback = HiveUnityPluginStandalonePluginInterface.SetUnitySendMessageCallback;
            return true;
        }
        public void Dispose() { /* 자원 없음 */}
#endif

        public IntPtr CallNative(String jsonParam) => callNative_uDelegate?.Invoke(jsonParam) ?? IntPtr.Zero;
        public void CallNative_after(IntPtr p) => callNative_afterDelegate?.Invoke(p);
        public void SetUnitySendMessageCallbackDelegate(HiveUnityPluginStandalonePluginInterface.UnitySendMessageDelegate cb) => setUnitySendMessageCallback?.Invoke(cb);
        ~HiveUnityPluginStandaloneWindowsRuntimeLoader()
        {
            Dispose();
        }

    }

	public class HIVEUnityPluginStandaloneCaller : MonoBehaviour, IHIVEUnityPluginNativeCaller
	{

		static MainThreadDispatcher mainThreadDispatcher = null;
		HiveUnityPluginStandaloneWindowsRuntimeLoader hiveUnityPluginStandaloneWindowsRuntimeLoader;

		void Awake()
		{
			hiveUnityPluginStandaloneWindowsRuntimeLoader = new HiveUnityPluginStandaloneWindowsRuntimeLoader();
			if (!hiveUnityPluginStandaloneWindowsRuntimeLoader.LoadHivePlugin())
			{
				Debug.LogError("Hive plugin load failed — native functionality disabled.");
			}
			initSendMessage();
		}

		#region 플러그인 호출을 위한 정의 및 구현
		public JSONObject callNative(JSONObject jsonParam)
		{

			String jsonParamString = jsonParam.ToString();
			IntPtr resultString = hiveUnityPluginStandaloneWindowsRuntimeLoader.CallNative(jsonParamString);
#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
			String resJsonString = Marshal.PtrToStringAuto(resultString);
#elif UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
			String resJsonString = Marshal.PtrToStringAnsi(resultString);
#endif
			if (!String.IsNullOrEmpty(resJsonString))
				hiveUnityPluginStandaloneWindowsRuntimeLoader.CallNative_after(resultString);
			return new JSONObject(resJsonString);
		}

		#endregion

		#region 네이티브 플러그인으로부터 이벤트 수신을 위한 정의 및 구현

		void initSendMessage()
		{
			mainThreadDispatcher = gameObject.AddComponent<MainThreadDispatcher>();
			s_SendMessageDelegate = UnitySendMessageWrapper;
			hiveUnityPluginStandaloneWindowsRuntimeLoader.SetUnitySendMessageCallbackDelegate(s_SendMessageDelegate);
		}

		private static HiveUnityPluginStandalonePluginInterface.UnitySendMessageDelegate s_SendMessageDelegate;

		[MonoPInvokeCallback(typeof(HiveUnityPluginStandalonePluginInterface.UnitySendMessageDelegate))]
		private static void UnitySendMessageWrapper(string gameObjectName, string methodName, string message)
		{
			if (mainThreadDispatcher != null)
				mainThreadDispatcher.Enqueue(UnitySendMessageOnTheMainThread(gameObjectName, methodName, message));
		}

		public static IEnumerator UnitySendMessageOnTheMainThread(string gameObjectName, string methodName, string message)
		{
			var gameObject = GameObject.Find(gameObjectName);
			if (gameObject != null)
			{
				gameObject.SendMessage(methodName, message);
			}
			yield return null;
		}

		#endregion
	}
}

#endif