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

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace Serenegiant.UVC
{

    public class UVCDrawer : MonoBehaviour, IUVCDrawer
    {
        /**
		 * IUVCSelectorがセットされていないとき
		 * またはIUVCSelectorが解像度選択時にnullを
		 * 返したときのデフォルトの解像度(幅)
		 */
        public int DefaultWidth = 1280;
        /**
		 * IUVCSelectorがセットされていないとき
		 * またはIUVCSelectorが解像度選択時にnullを
		 * 返したときのデフォルトの解像度(高さ)
		 */
        public int DefaultHeight = 720;


        /**
		 * 接続時及び描画時のフィルタ用
		 */
        public UVCFilter[] UVCFilters;

        /**
		 * GameObject保留UVC设备的视频目标材质
         * 如果未设置，则使用与分配此脚本相同的GameObjec。
		 */
        public List<GameObject> RenderTargets;

        [HideInInspector]
        public Action<Texture> StartPreviewAction;
        [HideInInspector]
        public Action StopPreviewAction;
        //--------------------------------------------------------------------------------
        private const string TAG = "UVCDrawer#";

        /**
		 * UVC機器からの映像の描画先Material
		 * TargetGameObjectから取得する
		 * 優先順位：
		 *	 TargetGameObjectのSkybox
		 *	 > TargetGameObjectのRenderer
		 *	 > TargetGameObjectのRawImage
		 *	 > TargetGameObjectのMaterial
		 * いずれの方法でも取得できなければStartでUnityExceptionを投げる
		 */
        private UnityEngine.Object[] TargetMaterials;
        /**
		 * オリジナルのテクスチャ
		 * UVCカメラ映像受け取り用テクスチャをセットする前に
		 * GetComponent<Renderer>().material.mainTextureに設定されていた値
		 */
        private Texture[] SavedTextures;

        private Quaternion[] quaternions;


        //================================================================================

        // Start is called before the first frame update
        void Start()
        {
#if (!NDEBUG && DEBUG && ENABLE_LOG)
            Console.WriteLine($"{TAG}Start:");
#endif
            UpdateTarget();

        }

        //		// Update is called once per frame
        //		void Update()
        //		{
        //
        //		}

        //================================================================================

        /**
		 * UVC機器が接続された
		 * IOnUVCAttachHandlerの実装
		 * @param manager 呼び出し元のUVCManager
		 * @param device 対象となるUVC機器の情報
		 * @return true: UVC機器を使用する, false: UVC機器を使用しない
		 */
        public bool OnUVCAttachEvent(UVCManager manager, UVCDevice device)
        {
#if (!NDEBUG && DEBUG && ENABLE_LOG)
            Console.WriteLine($"{TAG}OnUVCAttachEvent:{device}");
#endif
            // XXX 今の実装では基本的に全てのUVC機器を受け入れる
            // ただしTHETA SとTHETA VとTHETA Z1は映像を取得できないインターフェースがあるのでオミットする
            // CanDrawと同様にUVC機器フィルターをインスペクタで設定できるようにする
            var result = !device.IsRicoh || device.IsTHETA;

            result &= UVCFilter.Match(device, UVCFilters);

            return result;
        }

        /**
		 * UVC機器が取り外された
		 * IOnUVCDetachEventHandlerの実装
		 * @param manager 呼び出し元のUVCManager
		 * @param device 対象となるUVC機器の情報
		 */
        public void OnUVCDetachEvent(UVCManager manager, UVCDevice device)
        {
#if (!NDEBUG && DEBUG && ENABLE_LOG)
            Console.WriteLine($"{TAG}OnUVCDetachEvent:{device}");
#endif
        }

        //		/**
        //		 * 解像度選択
        //		 * IOnUVCSelectSizeHandlerの実装
        //		 * @param manager 呼び出し元のUVCManager
        //		 * @param device 対象となるUVC機器の情報
        //		 * @param formats 対応している解像度についての情報
        //		 */
        //		public SupportedFormats.Size OnUVCSelectSize(UVCManager manager, UVCDevice device, SupportedFormats formats)
        //		{
        //#if (!NDEBUG && DEBUG && ENABLE_LOG)
        //			Console.WriteLine($"{TAG}OnUVCSelectSize:{device}");
        //#endif
        //			if (device.IsTHETA_V || device.IsTHETA_Z1)
        //			{
        //#if (!NDEBUG && DEBUG && ENABLE_LOG)
        //				Console.WriteLine($"{TAG}OnUVCSelectSize:THETA V/Z1");
        //#endif
        //				return FindSize(formats, 3840, 1920);
        //			}
        //			else if (device.IsTHETA_S)
        //			{
        //#if (!NDEBUG && DEBUG && ENABLE_LOG)
        //				Console.WriteLine($"{TAG}OnUVCSelectSize:THETA S");
        //#endif
        //				return FindSize(formats, 1920, 1080);
        //			}
        //			else
        //			{
        //#if (!NDEBUG && DEBUG && ENABLE_LOG)
        //				Console.WriteLine($"{TAG}OnUVCSelectSize:other UVC device,{device}");
        //#endif
        //				return formats.Find(DefaultWidth, DefaultHeight);
        //			}
        //		}

        /**
		 * 获取IUVCdrawer是否可以绘制指定的UVC设备的视频
		 * 实现IUVCdrawer
		 * @param manager 调用方UVCmanager
		 * @param device 目标UVC设备信息
		 */
        public bool CanDraw(UVCManager manager, UVCDevice device)
        {
            return UVCFilter.Match(device, UVCFilters);
        }

        /**
	     * 已开始获取视频
         * 实现IUVCdrawer
         * @param manager 调用方UVCManager
         * @param device 对象的UVC设备的信息
         * @param tex 从UVC设备接收视频的纹理实例
		 */
        public void OnUVCStartEvent(UVCManager manager, UVCDevice device, Texture tex)
        {
#if (!NDEBUG && DEBUG && ENABLE_LOG)
            Console.WriteLine($"{TAG}OnUVCStartEvent:{device}");
#endif
            HandleOnStartPreview(tex);
            StartPreviewAction?.Invoke(tex);
        }

        /**
		 * 视频获取已完成
         * 实现IUVCdrawer
         * @param manager 调用方UVCManager
         * @param device 对象的UVC设备的信息
		 */
        public void OnUVCStopEvent(UVCManager manager, UVCDevice device)
        {
#if (!NDEBUG && DEBUG && ENABLE_LOG)
            Console.WriteLine($"{TAG}OnUVCStopEvent:{device}");
#endif
            HandleOnStopPreview();
            StopPreviewAction?.Invoke();
        }

        //================================================================================
        /**
		 * 更新目标
		 */
        private void UpdateTarget()
        {
            bool found = false;
            if ((RenderTargets != null) && (RenderTargets.Count > 0))
            {
                TargetMaterials = new UnityEngine.Object[RenderTargets.Count];
                SavedTextures = new Texture[RenderTargets.Count];
                quaternions = new Quaternion[RenderTargets.Count];
                int i = 0;
                foreach (var target in RenderTargets)
                {
                    if (target != null)
                    {
                        var material = TargetMaterials[i] = GetTargetMaterial(target);
                        if (material != null)
                        {
                            found = true;
                        }
#if (!NDEBUG && DEBUG && ENABLE_LOG)
                        Console.WriteLine($"{TAG}UpdateTarget:material={material}");
#endif
                    }
                    i++;
                }
            }
            if (!found)
            {   //找不到一个目标时，该脚本
                //尝试从AddComponent GameObject获取
                //在XXX RenderTargets中设置gameObject？
                TargetMaterials = new UnityEngine.Object[1];
                SavedTextures = new Texture[1];
                quaternions = new Quaternion[1];
                TargetMaterials[0] = GetTargetMaterial(gameObject);
                found = TargetMaterials[0] != null;
            }

            if (!found)
            {
                throw new UnityException("no target material found.");
            }
        }

        /**
		* 获取将图像绘制为纹理的材质
		* 如果指定的GameObject具有Skybox/Render/RawImage/Material，则从中获取Material
		* 如果每个都分配了多个，则返回第一个找到的可用项
		* 优先级：Skybox>Render>RawImage>Material
		* @param target
		* @return如果找不到，则返回null
		*/
        UnityEngine.Object GetTargetMaterial(GameObject target/*NonNull*/)
        {
            // Skyboxの取得を試みる
            var skyboxs = target.GetComponents<Skybox>();
            if (skyboxs != null)
            {
                foreach (var skybox in skyboxs)
                {
                    if (skybox.isActiveAndEnabled && (skybox.material != null))
                    {
                        RenderSettings.skybox = skybox.material;
                        return skybox.material;
                    }
                }
            }
            // Skyboxが取得できなければRendererの取得を試みる
            var renderers = target.GetComponents<Renderer>();
            if (renderers != null)
            {
                foreach (var renderer in renderers)
                {
                    if (renderer.enabled && (renderer.material != null))
                    {
                        return renderer.material;
                    }

                }
            }
            // SkyboxもRendererも取得できなければRawImageの取得を試みる
            var rawImages = target.GetComponents<RawImage>();
            if (rawImages != null)
            {
                foreach (var rawImage in rawImages)
                {
                    if (rawImage.enabled && (rawImage.material != null))
                    {
                        return rawImage;
                    }

                }
            }
            // SkyboxもRendererもRawImageも取得できなければMaterialの取得を試みる
            var material = target.GetComponent<Material>();
            if (material != null)
            {
                return material;
            }
            return null;
        }

        private void RestoreTexture()
        {
            for (int i = 0; i < TargetMaterials.Length; i++)
            {
                var target = TargetMaterials[i];
                try
                {
                    if (target is Material)
                    {
                        (target as Material).mainTexture = SavedTextures[i];
                    }
                    else if (target is RawImage)
                    {
                        (target as RawImage).texture = SavedTextures[i];
                    }
                }
                catch
                {
                    Console.WriteLine($"{TAG}RestoreTexture:Exception cought");
                }
                SavedTextures[i] = null;
                quaternions[i] = Quaternion.identity;
            }
        }

        private void ClearTextures()
        {
            for (int i = 0; i < SavedTextures.Length; i++)
            {
                SavedTextures[i] = null;
            }
        }

        /**
		 * 映像取得開始時の処理
		 * @param tex 映像を受け取るテクスチャ
		 */
        private void HandleOnStartPreview(Texture tex)
        {
#if (!NDEBUG && DEBUG && ENABLE_LOG)
            Console.WriteLine($"{TAG}HandleOnStartPreview:({tex})");
#endif
            int i = 0;
            foreach (var target in TargetMaterials)
            {
                if (target is Material)
                {
#if (!NDEBUG && DEBUG && ENABLE_LOG)
                    Console.WriteLine($"{TAG}HandleOnStartPreview:assign Texture to Material({target})");
#endif
                    SavedTextures[i++] = (target as Material).mainTexture;
                    (target as Material).mainTexture = tex;
                }
                else if (target is RawImage)
                {
#if (!NDEBUG && DEBUG && ENABLE_LOG)
                    Console.WriteLine($"{TAG}HandleOnStartPreview:assign Texture to RawImage({target})");
#endif
                    SavedTextures[i++] = (target as RawImage).texture;
                    (target as RawImage).texture = tex;
                }
            }
        }

        /**
		 * 映像取得が終了したときのUnity側の処理
		 */
        private void HandleOnStopPreview()
        {
#if (!NDEBUG && DEBUG && ENABLE_LOG)
            Console.WriteLine($"{TAG}HandleOnStopPreview:");
#endif
            // 描画先のテクスチャをもとに戻す
            RestoreTexture();
#if (!NDEBUG && DEBUG && ENABLE_LOG)
            Console.WriteLine($"{TAG}HandleOnStopPreview:finished");
#endif
        }

    } // class UVCDrawer

} // namespace Serenegiant.UVC
