#define ENABLE_LOG
/*
 * Copyright (c) 2014 - 2022 t_saki@serenegiant.com 
 */

using AOT;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using UnityEngine;

#if UNITY_ANDROID && UNITY_2018_3_OR_NEWER
using UnityEngine.Android;
#endif

namespace Serenegiant.UVC
{
    [RequireComponent(typeof(AndroidUtils))]
	public class UVCManager : MonoBehaviour
	{
		private const string TAG = "UVCManager#";
		private const string FQCN_DETECTOR = "com.serenegiant.usb.DeviceDetectorFragment";
        private const int FRAME_TYPE_MJPEG = 0x000007;
        private const int FRAME_TYPE_H264 = 0x000014;
        private const int FRAME_TYPE_H264_FRAME = 0x030011;

		/**
		 * 未设置IUVCSElector时
		 * 或IUVCSElector在选择分辨率时为空
		 * 返回时的默认分辨率（宽度）
		*/
		public Int32 DefaultWidth = 1280;
		/**
		 * 未设置IUVCSElector时
		 * 或IUVCSElector在选择分辨率时为空
		 * 返回时的默认分辨率（高度）
		 */
		public Int32 DefaultHeight = 720;
		/**
		 * *与UVC设备协商时
		 * H.264是否优先协商
		 * 仅安卓实机有效
		 * true:	H.264 > MJPEG > YUV
		 * false:	MJPEG > H.264 > YUV
		 */
		public bool PreferH264 = false;
		/**
         * 是否在场景渲染之前请求对UVC设备视频纹理进行渲染
         */
		public bool RenderBeforeSceneRendering = false;

		/**
		 * UVC关系的处理程序
		 */
		[SerializeField, ComponentRestriction(typeof(IUVCDrawer))]
		public Component[] UVCDrawers;

		/**
		 * 保持正在使用的相机信息的保持架类
		 */
		public class CameraInfo
		{
			internal readonly UVCDevice device;
			internal Texture previewTexture;
            internal int frameType;
			internal Int32 activeId;
			private Int32 currentWidth;
			private Int32 currentHeight;
            private bool isRenderBeforeSceneRendering;
            private bool isRendering;

			//PC测试用
			internal CameraInfo(Texture texture)
			{
				this.device = null;
				activeId = 1;
				previewTexture = texture;
				SetSize(texture.width, texture.height);
			}
			internal CameraInfo(UVCDevice device)
			{
				this.device = device;
			}

			/**
			 * 機器idを取得
			 */
			public Int32 Id{
				get { return device.id;  }
			}
	
			/**
			 * 機器名を取得
			 */
			public string DeviceName
			{
				get { return device.name; }
			}

			/**
			 * 获取供应商标识
			 */
			public int Vid
			{
				get { return device.vid; }
			}

			/**
			 * 获取产品标识
			 */
			public int Pid
			{
				get { return device.pid; }
			}

			/**
			 * 映像取得中かどうか
			 */
			public bool IsPreviewing
			{
				get { return (activeId != 0) && (previewTexture != null); }
			}

			/**
			 * 現在の解像度(幅)
			 * プレビュー中でなければ0
			 */
			public Int32 CurrentWidth
			{
				get { return currentWidth; }
			}

			/**
			 * 現在の解像度(高さ)
			 * プレビュー中でなければ0
			 */
			public Int32 CurrentHeight
			{
				get { return currentHeight; }
			}

			/**
			 * 2024/01/19 返回一个size
			 */
			public Vector2 Size => new Vector2(currentWidth, currentHeight);


			/**
			 * 現在の解像度を変更
			 * @param width
			 * @param height
			 */
			internal void SetSize(Int32 width, Int32 height)
			{
				currentWidth = width;
				currentHeight = height;
			}


			public Vector2Int IndexToCoord(int i)
			{
				var y = i / currentWidth;
				var x = i % currentWidth;
				return new Vector2Int(x, y);
			}

			public int CoordToIndex(int x, int y)
			{
				return y * currentWidth + x;
			}

			public override string ToString()
			{
				return $"{base.ToString()}({currentWidth}x{currentHeight},id={Id},activeId={activeId},IsPreviewing={IsPreviewing})";
			}

			/**
             * 开始从UVC设备渲染视频
             * @param manager
             */
			internal Coroutine StartRender(UVCManager manager, bool renderBeforeSceneRendering)
            {
                StopRender(manager);
                isRenderBeforeSceneRendering = renderBeforeSceneRendering;
                isRendering = true;
                if (renderBeforeSceneRendering)
                {
                    return manager.StartCoroutine(OnRenderBeforeSceneRendering());
                } else
                {
                    return manager.StartCoroutine(OnRender());
                }
            }

			/**
             * 完成UVC设备的视频渲染
             * @param manager
             */
			internal void StopRender(UVCManager manager)
            {
                if (isRendering)
                {
                    isRendering = false;
                    if (isRenderBeforeSceneRendering)
                    {
                        manager.StopCoroutine(OnRenderBeforeSceneRendering());
                    }
                    else
                    {
                        manager.StopCoroutine(OnRender());
                    }
                }
            }

			/**
			 * 用于渲染事件处理
			 * 作为代码例程执行
			 * 在场景渲染之前，请求将UVC设备的视频渲染到纹理
			 */
			private IEnumerator OnRenderBeforeSceneRendering()
			{
				var renderEventFunc = GetRenderEventFunc();
				for (; activeId != 0;)
				{
					yield return null;
					GL.IssuePluginEvent(renderEventFunc, activeId);
				}
				yield break;
			}

			/**
             * 用于渲染事件处理
			 * 作为代码例程执行
			 * 渲染后请求将UVC设备的视频渲染到纹理
             */
			private IEnumerator OnRender()
            {
                var renderEventFunc = GetRenderEventFunc();
                for (; activeId != 0;)
                {
                    yield return new WaitForEndOfFrame();
                    GL.IssuePluginEvent(renderEventFunc, activeId);
                }
                yield break;
            }

        }

		/**
		 * 用于在主线程上运行的同步上下文实例
		 */
		private SynchronizationContext mainContext;
		/**
		 * 当连接到终端的UVC设备的状态发生变化时，接受事件回调的分配器
		 */
		private OnDeviceChangedCallbackManager.OnDeviceChangedFunc callback;
		/**
		 * 连接到终端的UVC设备列表
		 */
		private List<UVCDevice> attachedDevices = new List<UVCDevice>();
		/**
		 * 正在获取视频的UVC设备的地图
		 * 保留设备标识的id-CameraInfo对
		 */
		private Dictionary<Int32, CameraInfo> cameraInfos = new Dictionary<int, CameraInfo>();

		//--------------------------------------------------------------------------------
		// 从UnityEngine调用
		//--------------------------------------------------------------------------------
		// Start is called before the first frame update
		IEnumerator Start()
		{
#if (!NDEBUG && DEBUG && ENABLE_LOG)
			Console.WriteLine($"{TAG}Start:");
#endif
			mainContext = SynchronizationContext.Current;
            callback = OnDeviceChangedCallbackManager.Add(this);
	
			yield return Initialize();
		}

#if (!NDEBUG && DEBUG && ENABLE_LOG)
		void OnApplicationFocus()
		{
			Console.WriteLine($"{TAG}OnApplicationFocus:");
		}
#endif

#if (!NDEBUG && DEBUG && ENABLE_LOG)
		void OnApplicationPause(bool pauseStatus)
		{
			Console.WriteLine($"{TAG}OnApplicationPause:{pauseStatus}");
		}
#endif

#if (!NDEBUG && DEBUG && ENABLE_LOG)
		void OnApplicationQuits()
		{
			Console.WriteLine($"{TAG}OnApplicationQuits:");
		}
#endif

		void OnDestroy()
		{
#if (!NDEBUG && DEBUG && ENABLE_LOG)
			Console.WriteLine($"{TAG}OnDestroy:");
#endif
			StopAll();
            OnDeviceChangedCallbackManager.Remove(this);
		}

		//--------------------------------------------------------------------------------
		// UVC设备连接状态变化时插件的回调函数
		//--------------------------------------------------------------------------------
		public void OnDeviceChanged(IntPtr devicePtr, bool attached)
        {
            var id = UVCDevice.GetId(devicePtr);
#if (!NDEBUG && DEBUG && ENABLE_LOG)
            Console.WriteLine($"{TAG}OnDeviceChangedInternal:id={id},attached={attached}");
#endif
            if (attached)
            {
                UVCDevice device = new UVCDevice(devicePtr);
#if (!NDEBUG && DEBUG && ENABLE_LOG)
                Console.WriteLine($"{TAG}OnDeviceChangedInternal:device={device.ToString()}");
#endif
                if (HandleOnAttachEvent(device))
                {
                    attachedDevices.Add(device);
                    StartPreview(device);
                }
            }
            else
            {
                var found = attachedDevices.Find(item =>
                {
                    return item != null && item.id == id;
                });
                if (found != null)
                {
                    HandleOnDetachEvent(found);
                    StopPreview(found);
                    attachedDevices.Remove(found);
                }
            }
        }
   
        //================================================================================
        /**
		 * 接続中のUVC機器一覧を取得
		 * @return 接続中のUVC機器一覧List
		 */
        public List<CameraInfo> GetAttachedDevices()
		{
			var result = new List<CameraInfo>(cameraInfos.Count);

			foreach (var info in cameraInfos.Values)
			{
				result.Add(info);
			}

			return result;
		}

//		/**
//		 * 対応解像度を取得
//		 * @param camera 対応解像度を取得するUVC機器を指定
//		 * @return 対応解像度 既にカメラが取り外されている/closeしているのであればnull
//		 */
//		public SupportedFormats GetSupportedVideoSize(CameraInfo camera)
//		{
//			var info = (camera != null) ? Get(camera.DeviceName) : null;
//			if ((info != null) && info.IsOpen)
//			{
//				return GetSupportedVideoSize(info.DeviceName);
//			}
//			else
//			{
//				return null;
//			}
//		}

//		/**
//		 * 解像度を変更
//		 * @param 解像度を変更するUVC機器を指定
//		 * @param 変更する解像度を指定, nullならデフォルトに戻す
//		 * @param 解像度が変更されたかどうか
//		 */
//		public bool SetVideoSize(CameraInfo camera, SupportedFormats.Size size)
//		{
//			var info = (camera != null) ? Get(camera.DeviceName) : null;
//			var width = size != null ? size.Width : DefaultWidth;
//			var height = size != null ? size.Height : DefaultHeight;
//			if ((info != null) && info.IsPreviewing)
//			{
//				if ((width != info.CurrentWidth) || (height != info.CurrentHeight))
//				{   // 解像度が変更になるとき
//					StopPreview(info.DeviceName);
//					StartPreview(info.DeviceName, width, height);
//					return true;
//				}
//			}
//			return false;
//		}

		private void StartPreview(UVCDevice device)
		{
			var info = CreateIfNotExist(device);
			if ((info != null) && !info.IsPreviewing) {

				int width = DefaultWidth;
				int height = DefaultHeight;

//				var supportedVideoSize = GetSupportedVideoSize(deviceName);
//				if (supportedVideoSize == null)
//				{
//					throw new ArgumentException("fauled to get supported video size");
//				}

//				// 解像度の選択処理
//				if ((UVCDrawers != null) && (UVCDrawers.Length > 0))
//				{
//					foreach (var drawer in UVCDrawers)
//					{
//						if ((drawer is IUVCDrawer) && ((drawer as IUVCDrawer).CanDraw(this, info.device)))
//						{
//							var size = (drawer as IUVCDrawer).OnUVCSelectSize(this, info.device, supportedVideoSize);
//#if (!NDEBUG && DEBUG && ENABLE_LOG)
//							Console.WriteLine($"{TAG}StartPreview:selected={size}");
//#endif
//							if (size != null)
//							{   // 一番最初に見つかった描画可能なIUVCDrawersがnull以外を返せばそれを使う
//								width = size.Width;
//								height = size.Height;
//								break;
//							}
//						}
//					}
//				}

				// FIXME 対応解像度の確認処理
#if (!NDEBUG && DEBUG && ENABLE_LOG)
				Console.WriteLine($"{TAG}StartPreview:({width}x{height}),id={device.id}");
#endif
                int[] frameTypes = {
                    PreferH264 ? FRAME_TYPE_H264 : FRAME_TYPE_MJPEG,
                    PreferH264 ? FRAME_TYPE_MJPEG : FRAME_TYPE_H264,
                };
                foreach (var frameType in frameTypes)
                {
                    if (Resize(device.id, frameType, width, height) == 0)
                    {
                        info.frameType = frameType;
                        break;
                    }
                }
                    
				info.SetSize(width, height);
				info.activeId = device.id;
				mainContext.Post(__ =>
				{   // 必须在主线程上生成纹理
#if (!NDEBUG && DEBUG && ENABLE_LOG)
					Console.WriteLine($"{TAG}影像接收用纹理生成:({width}x{height})");
#endif
					Texture2D tex = new Texture2D(
							width, height,
							TextureFormat.ARGB32,
							false, /* mipmap */
							true /* linear */);
					tex.filterMode = FilterMode.Point;
					tex.Apply();
					info.previewTexture = tex;
					var nativeTexPtr = info.previewTexture.GetNativeTexturePtr();
					Start(device.id, nativeTexPtr.ToInt32());
					HandleOnStartPreviewEvent(info);
					info.StartRender(this, RenderBeforeSceneRendering);
				}, null);
			}
		}

		private void StopPreview(UVCDevice device) {
			var info = Get(device);
			if ((info != null) && info.IsPreviewing)
			{
				mainContext.Post(__ =>
				{
					HandleOnStopPreviewEvent(info);
					Stop(device.id);
					info.StopRender(this);
					info.SetSize(0, 0);
					info.activeId = 0;
				}, null);
			}
		}


		private void StopAll() {
			List<CameraInfo> values = new List<CameraInfo>(cameraInfos.Values);
			foreach (var info in values)
			{
				StopPreview(info.device);
			}
		}

		//--------------------------------------------------------------------------------
		/**
		 * 连接UVC设备时的处理实体
		 * @param info
		 * @return true: 使用连接的UVC设备, false: 不使用连接的UVC设备 
		 */
		private bool HandleOnAttachEvent(UVCDevice device/*NonNull*/)
		{
			if ((UVCDrawers == null) || (UVCDrawers.Length == 0))
			{   // IUVCDrawerが割り当てられていないときはtrue(接続されたUVC機器を使用する)を返す
				return true;
			}
			else
			{
				bool hasDrawer = false;
				foreach (var drawer in UVCDrawers)
				{
					if (drawer is IUVCDrawer)
					{
						hasDrawer = true;
						if ((drawer as IUVCDrawer).OnUVCAttachEvent(this, device))
						{   // どれか1つのIUVCDrawerがtrueを返せばtrue(接続されたUVC機器を使用する)を返す
							return true;
						}
					}
				}
				// IUVCDrawerが割り当てられていないときはtrue(接続されたUVC機器を使用する)を返す
				return !hasDrawer;
			}
		}

		/**
		 * UVC设备被拆除时的处理实体
		 * @param info
		 */
		private void HandleOnDetachEvent(UVCDevice device/*NonNull*/)
		{
			if ((UVCDrawers != null) && (UVCDrawers.Length > 0))
			{
				foreach (var drawer in UVCDrawers)
				{
					if (drawer is IUVCDrawer)
					{
						(drawer as IUVCDrawer).OnUVCDetachEvent(this, device);
					}
				}
			}
		}

		/**
		 * 从UVC机器的影像取得结束
		 * @param args UVC機器の識別文字列
		 */
		void HandleOnStartPreviewEvent(CameraInfo info)
		{
#if (!NDEBUG && DEBUG && ENABLE_LOG)
			Console.WriteLine($"{TAG}HandleOnStartPreviewEvent:({info})");
#endif
			if ((info != null) && info.IsPreviewing && (UVCDrawers != null))
			{
				foreach (var drawer in UVCDrawers)
				{
					if ((drawer is IUVCDrawer) && (drawer as IUVCDrawer).CanDraw(this, info.device))
					{
						(drawer as IUVCDrawer).OnUVCStartEvent(this, info.device, info.previewTexture);
					}
				}
			} else {
#if (!NDEBUG && DEBUG && ENABLE_LOG)
				Console.WriteLine($"{TAG}HandleOnStartPreviewEvent:No UVCDrawers");
#endif
			}
		}

		/**
		 * UVC機器からの映像取得を終了した
		 * @param args UVC機器の識別文字列
		 */
		void HandleOnStopPreviewEvent(CameraInfo info)
		{
#if (!NDEBUG && DEBUG && ENABLE_LOG)
			Console.WriteLine($"{TAG}HandleOnStopPreviewEvent:({info})");
#endif
			if (UVCDrawers != null)
			{
				foreach (var drawer in UVCDrawers)
				{
					if ((drawer is IUVCDrawer) && (drawer as IUVCDrawer).CanDraw(this, info.device))
					{
						(drawer as IUVCDrawer).OnUVCStopEvent(this, info.device);
					}
				}
			}
		}

		//--------------------------------------------------------------------------------
		/**
		 * 获取与指定UVC标识字符串相对应的CameraInfo
		 * 如果尚未注册，则新建
		 * @param deviceName UVC機器識別文字列
		 * @param CameraInfoを返す
		 */
		/*NonNull*/
		private CameraInfo CreateIfNotExist(UVCDevice device)
		{
			if (!cameraInfos.ContainsKey(device.id))
			{
				cameraInfos[device.id] = new CameraInfo(device);
			}
			return cameraInfos[device.id];
		}

		/**
		 * 获取与指定UVC标识字符串相对应的CameraInfo
		 * @param deviceName UVC机器识别文字列
		 * @param 注册时返回CameraInfo，未注册时为空
		 */
		/*Nullable*/
		private CameraInfo Get(UVCDevice device)
		{
			return cameraInfos.ContainsKey(device.id) ? cameraInfos[device.id] : null;
		}


		//--------------------------------------------------------------------------------
		/**
		 *初始化插件
		 *如果确认并获取权限，则调用实际的插件初始化处理#InitPlugin
		 */
		private IEnumerator Initialize()
		{
#if (!NDEBUG && DEBUG && ENABLE_LOG)
			Console.WriteLine($"{TAG}Initialize:");
#endif
			if (AndroidUtils.CheckAndroidVersion(28))
			{
				yield return AndroidUtils.GrantCameraPermission((string permission, AndroidUtils.PermissionGrantResult result) =>
				{
#if (!NDEBUG && DEBUG && ENABLE_LOG)
					Console.WriteLine($"{TAG}OnPermission:{permission}={result}");
#endif
					switch (result)
					{
						case AndroidUtils.PermissionGrantResult.PERMISSION_GRANT:
							InitPlugin();
							break;
						case AndroidUtils.PermissionGrantResult.PERMISSION_DENY:
							if (AndroidUtils.ShouldShowRequestPermissionRationale(AndroidUtils.PERMISSION_CAMERA))
							{
								//无法获得权限
								//必须显示FIXME说明用的对话框等
							}
							break;
						case AndroidUtils.PermissionGrantResult.PERMISSION_DENY_AND_NEVER_ASK_AGAIN:
							break;
					}
				});
			}
			else
			{
				InitPlugin();
			}

			yield break;
		}

		/**
		 * 初始化插件
		 * 对uvc-plugin-unity的处理请求
		 */
		private void InitPlugin()
		{
#if (!NDEBUG && DEBUG && ENABLE_LOG)
			Console.WriteLine($"{TAG}InitPlugin:");
#endif
			// 检查是否分配了IUVCdrawers
			var hasDrawer = false;
			if ((UVCDrawers != null) && (UVCDrawers.Length > 0))
			{
				foreach (var drawer in UVCDrawers)
				{
					if (drawer is IUVCDrawer)
					{
						hasDrawer = true;
						break;
					}
				}
			}
			if (!hasDrawer)
			{   
				//在检查器中未设置IUVCdrawer时
				//尝试从带有此脚本的游戏对象获取
#if (!NDEBUG && DEBUG && ENABLE_LOG)
				Console.WriteLine($"{TAG}InitPlugin:has no IUVCDrawer, try to get from gameObject");
#endif
				var drawers = GetComponents(typeof(IUVCDrawer));
				if ((drawers != null) && (drawers.Length > 0))
				{
					UVCDrawers = new Component[drawers.Length];
					int i = 0;
					foreach (var drawer in drawers)
					{
						UVCDrawers[i++] = drawer;
					}
				}
			}
#if (!NDEBUG && DEBUG && ENABLE_LOG)
			Console.WriteLine($"{TAG}InitPlugin:num drawers={UVCDrawers.Length}");
#endif
			// 请求读取aandusb的DeviceDetector
			using (AndroidJavaClass clazz = new AndroidJavaClass(FQCN_DETECTOR))
			{
				clazz.CallStatic("initUVCDeviceDetector",
					AndroidUtils.GetCurrentActivity());
			}
		}

		//--------------------------------------------------------------------------------
		// 定义和声明本机插件关系
		//--------------------------------------------------------------------------------
		/**
		 * 插件中的渲染事件获取native（c/c++）函数
		 */
		[DllImport("unityuvcplugin")]
		private static extern IntPtr GetRenderEventFunc();
        /**
		 * 初期設定
		 */
        [DllImport("unityuvcplugin", EntryPoint = "Config")]
        private static extern Int32 Config(Int32 deviceId, Int32 enabled, Int32 useFirstConfig);
        /**
		 * 映像取得開始
		 */
        [DllImport("unityuvcplugin", EntryPoint ="Start")]
		private static extern Int32 Start(Int32 deviceId, Int32 tex);
		/**
		 * 映像取得終了
		 */
		[DllImport("unityuvcplugin", EntryPoint ="Stop")]
		private static extern Int32 Stop(Int32 deviceId);
		/**
		 * 影像尺寸设定
		 */
		[DllImport("unityuvcplugin")]
		private static extern Int32 Resize(Int32 deviceId, Int32 frameType, Int32 width, Int32 height);
	}   // UVCManager

	/**
	*如果是IL2Cpp的话，就不能对用于从c/c++回调的引导者进行跟踪
	*必须使用static类函数进行处理。
	*但如果是这样，则无法调用调用对象的函数，因此创建管理器类
	*首先只接受UVCmanager，所以不作为接口
     */
	public static class OnDeviceChangedCallbackManager
    {
        //コールバック関数の型を宣言
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate void OnDeviceChangedFunc(Int32 id, IntPtr devicePtr, bool attached);

        /**
		 * プラグインのnative側登録関数
		 */
        [DllImport("unityuvcplugin")]
        private static extern IntPtr Register(Int32 id, OnDeviceChangedFunc callback);
        /**
		 * プラグインのnative側登録解除関数
		 */
        [DllImport("unityuvcplugin")]
        private static extern IntPtr Unregister(Int32 id);

        private static Dictionary<Int32, UVCManager> sManagers = new Dictionary<Int32, UVCManager>();
  
        /**
         * 指定したUVCManagerを接続機器変化コールバックに追加
         */
        public static OnDeviceChangedFunc Add(UVCManager manager)
        {
            Int32 id = manager.GetHashCode();
            OnDeviceChangedFunc callback = new OnDeviceChangedFunc(OnDeviceChanged);
            sManagers.Add(id, manager);
            Register(id, callback);
            return callback;
        }

        /**
         * 指定したUVCManagerを接続機器変化コールバックから削除
         */
        public static void Remove(UVCManager manager)
        {
            Int32 id = manager.GetHashCode();
            Unregister(id);
            sManagers.Remove(id);
        }

        [MonoPInvokeCallback(typeof(OnDeviceChangedFunc))]
        public static void OnDeviceChanged(Int32 id, IntPtr devicePtr, bool attached)
        {
            var manager = sManagers.ContainsKey(id) ? sManagers[id] : null;
            if (manager != null)
            {
                manager.OnDeviceChanged(devicePtr, attached);
            }
        }
    } // OnDeviceChangedCallbackManager


}   // namespace Serenegiant.UVC
