/****************************************************************************
 *
 * Copyright (c) 2012 CRI Middleware Co., Ltd.
 *
 ****************************************************************************/

#pragma warning disable 0618

using UnityEngine;
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

/**
 * \addtogroup CRIWARE_UNITY_COMPONENT
 * @{
 */
 namespace CriWare {

/**
 * <summary>CRIWARE error handling object</summary>
 * <remarks>
 * <para header='Description'>This component obtains and outputs error messages generated by the CRIWARE library.<br/></para>
 * </remarks>
 */
[AddComponentMenu("CRIWARE/Error Handler")]
public class CriWareErrorHandler : CriMonoBehaviour{
	/**
	 * <summary>Whether to enable Coroutine debug output</summary>
	 * <remarks>
	 * <para header='Note'>Whether to send the debug output to the console instead of the Unity debug window.
	 * On PC, the output will go to the debug window.</para>
	 * </remarks>
	 */
	public bool enableDebugPrintOnTerminal = false;

	/**
	 * <summary>Force crash flag (for debugging only)</summary>
	 * <remarks>
	 * <para header='Description'>When set to true, it will force a crash when an error occurs. <br/>
	 * Only valid when there is an event registered in <see cref='OnCallback'/> .</para>
	 * </remarks>
	 */
	public bool enableForceCrashOnError = false;

	/** Whether to remove the error handler at scene change */
	public bool dontDestroyOnLoad = true;

	/**
	 * \deprecated
	 * This is a deprecated API that will be removed. The value is not used.
	 */
	public static string errorMessage { get; set; }

	/**
	 * <summary>Log message prefix</summary>
	 * <remarks>
	 * <para header='Description'>Prefix indicating log messages from CRIWARE.</para>
	 * </remarks>
	 */
	public static readonly string logPrefix = "[CRIWARE]";

	/**
	 * <summary>Error callback delegate</summary>
	 * <remarks>
	 * <para header='Description'>A callback delegate that will be called when an error occurs
	 * in the CRIWARE native library. <br/>
	 * The argument string contains the message in the format "Error ID: Error details".</para>
	 * </remarks>
	 */
	public delegate void Callback(string message);

	private static event Callback _onCallback = null;
	/**
	 * <summary>Error callback event</summary>
	 * <remarks>
	 * <para header='Description'>The callback event to be called when an error occurs within the CRIWARE native library.
	 * <br/>
	 * If not set, the default log output function defined in this class will be called
	 * instead.<br/>
	 * If you need to write custom error handling based on the error message,<br/>
	 * register a delegate and handle the error inside the callback function.<br/>
	 * This event is always called from the main thread.</para>
	 * <para header='Note'>The registered callback may be called at any time while CriWareErrorHandler is active. <br/>
	 * <br/>
	 * Be aware not to release the instance of the called function before CriWareErrorHandler .<br/>
	 * <br/></para>
	 * </remarks>
	 */
	public static event Callback OnCallback
	{
		add {
			bool previous = IsEnableNativePrintMessageFunc();

			_onCallback += value;

			if (previous && !IsEnableNativePrintMessageFunc()) {
				RegisterErrorCallback();
			}
		}
		remove {
			_onCallback -= value;
			RegisterErrorCallback();
		}
	}

	/**
	 * \deprecated
	 * 削除予定の非推奨APIです。
	 * CriWareErrorHandler.OnCallback event の使用を検討してください。
	 * <summary>Error callback</summary>
	 * <remarks>
	 * <para header='Description'>A callback that will be called when an error occurs in the CRIWARE native library. <br/>
	 * If not set, the default log output function defined in this class will be called. <br/>
	 * If a user defined process based on the error message is preferred,
	 * register a delegate and perform the process inside the callback function. <br/>
	 * Set the callback to null to unregister.</para>
	 * <para header='Note'>The registered callback may be called at any time while CriWareErrorHandler is active. <br/>
	 * Therefore, be careful not to release the instance of the callback before CriWareErrorHandler .</para>
	 * </remarks>
	 */
	[Obsolete("CriWareErrorHandler.callback is deprecated. Use CriWareErrorHandler.OnCallback event", false)]
	public static Callback callback = null;

	/**
	* \deprecated
	* This is a deprecated API that will be removed. 
	* This value is not referenced since error messages can now be queued regardless of this value.
	 */
	public uint messageBufferCounts = 8;

	private ConcurrentQueue<string> unThreadSafeMessages = new ConcurrentQueue<string>();
	private static bool _enableDebugPrintOnTerminal = false;

	/* オブジェクト作成時の処理 */
	void Awake() {
		/* 初期化カウンタの更新 */
		initializationCount++;
		if (initializationCount != 1) {
			/* 多重初期化は許可しない */
			GameObject.Destroy(this);
			return;
		}

		if (!CriErrorNotifier.IsRegistered(HandleMessage)) {
			CriErrorNotifier.OnCallbackThreadUnsafe += HandleMessage;
		}

		/* シーンチェンジ後もオブジェクトを維持するかどうかの設定 */
		if (dontDestroyOnLoad) {
			DontDestroyOnLoad(transform.gameObject);
		}
	}

	/* Execution Order の設定を確実に有効にするために OnEnable をオーバーライド */
	protected override void OnEnable() {
		base.OnEnable();
		_enableDebugPrintOnTerminal = enableDebugPrintOnTerminal;
		RegisterErrorCallback();

		if (!CriErrorNotifier.IsRegistered(HandleMessage)) {
			CriErrorNotifier.OnCallbackThreadUnsafe += HandleMessage;
		}
	}

	protected override void OnDisable() {
		base.OnDisable();
		if (CriErrorNotifier.IsRegistered(HandleMessage)) {
			CriErrorNotifier.OnCallbackThreadUnsafe -= HandleMessage;
		}
	}

	public override void CriInternalUpdate() {
		DequeueErrorMessages();
	}

	public override void CriInternalLateUpdate() { }

	void OnDestroy() {
		/* 初期化カウンタの更新 */
		initializationCount--;
		if (initializationCount != 0) {
			return;
		}

		/* エラー処理の終了処理 */
		if (CriErrorNotifier.IsRegistered(HandleMessage)) {
			CriErrorNotifier.OnCallbackThreadUnsafe -= HandleMessage;
		}

		CriErrorNotifier.SetCallbackNative(IntPtr.Zero);
	}

	private static bool IsEnableNativePrintMessageFunc() {
		return !Application.isEditor
			&& _onCallback == null
			&& callback == null
			&& _enableDebugPrintOnTerminal;
	}

	private static void RegisterErrorCallback() {
		if (IsEnableNativePrintMessageFunc()) {
			CriErrorNotifier.SetCallbackNative(IntPtr.Zero);
			CriErrorNotifier.SetCallbackNative(NativeMethod.criWareUnity_GetErrorCallbackFunction());
		} else {
			CriErrorNotifier.SetCallbackNative(IntPtr.Zero);
			CriErrorNotifier.SetCallbackNative(CriErrorNotifier.GetManagedPluginFunc());
		}
	}

	/* エラーメッセージのポーリングと出力 */
	private void DequeueErrorMessages() {
		string dequeuedMessage;
		while (unThreadSafeMessages.Count != 0) {
			if (!unThreadSafeMessages.TryDequeue(out dequeuedMessage)) {
				continue;
			}
			if (_onCallback != null) {
				_onCallback(dequeuedMessage);
			}
			if (callback != null) {
				callback(dequeuedMessage);
			}
		}
	}

	private void HandleMessage(string errmsg) {
		if (errmsg == null) {
			return;
		}

		if (_onCallback == null && callback == null) {
			OutputDefaultLog(errmsg);
		} else {
			unThreadSafeMessages.Enqueue(errmsg);
		}
		if (enableForceCrashOnError) {
			UnityEngine.Diagnostics.Utils.ForceCrash(UnityEngine.Diagnostics.ForcedCrashCategory.Abort);
		}
	}

	/** Default log output */
	private static void OutputDefaultLog(string errmsg)
	{
		if (errmsg.StartsWith("E")) {
			Debug.LogError(logPrefix + " Error:" + errmsg);
		} else if (errmsg.StartsWith("W")) {
			Debug.LogWarning(logPrefix + " Warning:" + errmsg);
		} else {
			Debug.Log(logPrefix + errmsg);
		}
	}

	/** Initialization counter */
	private static int initializationCount = 0;

	private static class NativeMethod {
#if !CRIWARE_ENABLE_HEADLESS_MODE
		[DllImport(CriWare.Common.pluginName, CallingConvention = CriWare.Common.pluginCallingConvention)]
		internal static extern IntPtr criWareUnity_GetErrorCallbackFunction();
#else
		internal static IntPtr criWareUnity_GetErrorCallbackFunction() { return IntPtr.Zero; }
#endif
	}
} // end of class

	/**
	 * <summary>Gets error log for CRIWARE native libraries</summary>
	 * <remarks>
	 * <para header='Description'>This class obtains the error logs generated within the CRIWARE native library.<br/></para>
	 * </remarks>
	 */
	public static class CriErrorNotifier {
		/**
		 * <summary>Error callback delegate</summary>
		 * <remarks>
		 * <para header='Description'>A callback delegate that will be called when an error occurs
		 * in the CRIWARE native library. <br/>
		 * The argument string contains the message in the format "Error ID: Error details".</para>
		 * </remarks>
		 * <seealso cref='CriErrorNotifier::OnCallbackThreadUnsafe'/>
		 */
		public delegate void Callback(string message);
		private static event Callback _onCallbackThreadUnsafe = null;
		private static object objectLock = new System.Object();

		/**
		 * <summary>Error callback event</summary>
		 * <remarks>
		 * <para header='Description'>Callback event triggered when an error occurs in the CRIWARE native library. <br/>
		 * If it is not set, no log is output.<br/></para>
		 * <para header='Note'>This event may be called from outside the main thread.<br/>
		 * Therefore, be sure to register a thread-safe API for this event.<br/></para>
		 * </remarks>
		 * <seealso cref='CriErrorNotifier::IsRegistered'/>
		 */
		public static event Callback OnCallbackThreadUnsafe {
			add {
				lock (objectLock) {
					if (_onCallbackThreadUnsafe == null || _onCallbackThreadUnsafe.GetInvocationList().Length <= 0) {
						SetCallbackNative(null);
						SetCallbackNative(ErrorCallbackFromNative);
					}
					_onCallbackThreadUnsafe += value;
				}
			}
			remove {
				lock (objectLock) {
					_onCallbackThreadUnsafe -= value;
					if (_onCallbackThreadUnsafe == null || _onCallbackThreadUnsafe.GetInvocationList().Length <= 0) {
						SetCallbackNative(null);
					}
				}
			}
		}

		/**
		 * <summary>Check for registered error callback events</summary>
		 * <param name='target'>Method to be evaluated</param>
		 * <returns>Whether it is registered</returns>
		 * <remarks>
		 * <para header='Description'>Checks whether the specified method is registered in <see cref='CriErrorNotifier.OnCallbackThreadUnsafe'/>.<br/>
		 * Use this to avoid duplicate registrations, and to ensure that everything has been properly released.</para>
		 * </remarks>
		 * <seealso cref='CriErrorNotifier::Callback'/>
		 * <seealso cref='CriErrorNotifier::OnCallbackThreadUnsafe'/>
		 */
		public static bool IsRegistered(Callback target) {
			if (_onCallbackThreadUnsafe == null) {
				return false;
			}
			foreach (Callback item in _onCallbackThreadUnsafe.GetInvocationList()) {
				if (item == target) {
					return true;
				}
			}
			return false;
		}

		/**
		 * <summary>Plug-in internal functions</summary>
		 * <para header='Note'>This function is not designed to be called by the user.<br/></para>
		 */
		public static void CallEvent(string message) {
			// for expansion
			if (_onCallbackThreadUnsafe != null) {
				_onCallbackThreadUnsafe(message);
			}
		}

		/**
		 * <summary>Register a native error callback function (using a function pointer)</summary>
		 * <param name='errorCallback'>Error callback function pointer</param>
		 * <remarks>
		 * <para header='Description'>You can set the native function pointer to be called when an error callback occurs.<br/>
		 * Use this only when you need to register a custom native function pointer. <br/></para>
		 * <para header='Note'>Specify “IntPtr.Zero” to unregister to the native of the error callback.<br/>
		 * <br/>
		 * Once this function is called, the “event” registered in <see cref='OnCallbackThreadUnsafe'/> <br/>
		 * will no longer be called.</para>
		 * </remarks>
		 */
		public static void SetCallbackNative(IntPtr errorCallback) {
			NativeMethod.criErr_SetCallback(errorCallback);
		}

		internal static void SetCallbackNative(ErrorCallbackFunc errorCallback) {
			NativeMethod.criErr_SetCallback(errorCallback);
		}

		internal static ErrorCallbackFunc GetManagedPluginFunc() {
			return ErrorCallbackFromNative;
		}

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		internal delegate void ErrorCallbackFunc(IntPtr errmsgPtr, System.UInt32 p1, System.UInt32 p2, IntPtr parray);

		[AOT.MonoPInvokeCallback(typeof(ErrorCallbackFunc))]
		private static void ErrorCallbackFromNative(IntPtr errmsgPtr, System.UInt32 p1, System.UInt32 p2, IntPtr parray) {
			string errmsg = Marshal.PtrToStringAnsi(NativeMethod.criErr_ConvertIdToMessage(errmsgPtr, p1, p2));
			CallEvent(errmsg);
		}

		private static class NativeMethod {
#if !CRIWARE_ENABLE_HEADLESS_MODE
			[DllImport(CriWare.Common.pluginName, CallingConvention = CriWare.Common.pluginCallingConvention)]
			internal static extern void criErr_SetCallback(ErrorCallbackFunc callback);
			[DllImport(CriWare.Common.pluginName, CallingConvention = CriWare.Common.pluginCallingConvention)]
			internal static extern void criErr_SetCallback(IntPtr callback);
			[DllImport(CriWare.Common.pluginName, CallingConvention = CriWare.Common.pluginCallingConvention)]
			internal static extern IntPtr criErr_ConvertIdToMessage(IntPtr errmsgPtr, System.UInt32 p1, System.UInt32 p2);
#else
			internal static void criErr_SetCallback(ErrorCallbackFunc callback) { }
			internal static void criErr_SetCallback(IntPtr callback) { }
			internal static IntPtr criErr_ConvertIdToMessage(IntPtr errmsgPtr, System.UInt32 p1, System.UInt32 p2) { return IntPtr.Zero; }
#endif
		}
	}

} //namespace CriWare
/** @} */

/* --- end of file --- */
