/**********************************************************************************
* Blueprint Reality Inc. CONFIDENTIAL
* 2020 Blueprint Reality Inc.
* All Rights Reserved.
*
* NOTICE:  All information contained herein is, and remains, the property of
* Blueprint Reality Inc. and its suppliers, if any.  The intellectual and
* technical concepts contained herein are proprietary to Blueprint Reality Inc.
* and its suppliers and may be covered by Patents, pending patents, and are
* protected by trade secret or copyright law.
*
* Dissemination of this information or reproduction of this material is strictly
* forbidden unless prior written permission is obtained from Blueprint Reality Inc.
***********************************************************************************/

using System;
using System.Collections;
using UnityEngine;
using BlueprintReality.MixCast.Viewfinders;
using BlueprintReality.MixCast.Cameras;
using System.IO;
using Thrift.Configuration;
#if MIXCAST_LWRP 
using UnityEngine.Rendering;
using UnityEngine.Rendering.LWRP;
#elif MIXCAST_URP
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
#endif
#if UNITY_STANDALONE_WIN
using Thrift.Unity;
using BlueprintReality.MixCast.Data;
using BlueprintReality.MixCast.Experience;
using BlueprintReality.MixCast.Thrift;
using BlueprintReality.MixCast.Windows;
using System.Runtime.InteropServices;
#endif

namespace BlueprintReality.MixCast
{
    public class MixCastSdkBehaviour : MonoBehaviour
    {
        public bool reparentToSceneRootOnStart = true;

#if UNITY_STANDALONE_WIN
        public static MixCastSdkBehaviour Instance { get; protected set; }

        public SdkCustomTrackedObjectMediator VirtualTrackedObjectManager { get; protected set; }

        public SDK_Service.Client ClientConnection { get; protected set; }

        public bool Initialized { get; protected set; }

        private ConnectionLookupTable thriftConfigs;

        private void Awake()
        {
            SdkSharedTextureReceiver.ApplyOverrideToCreateFunc();
        }
        private void OnEnable()
        {
            Initialized = false;
            StartCoroutine(Initialize());
        }

        private IEnumerator Initialize()
        {
            bool cantActivate = false;

            if (Instance != null)
                cantActivate = true;

#if UNITY_EDITOR
            if (!MixCastSdkData.ProjectSettings.enableMixCastInEditor)
                cantActivate = true;
#else
            if( MixCastSdkData.ProjectSettings.requireCommandLineArg && System.Array.IndexOf<string>(System.Environment.GetCommandLineArgs(), "-mixcast") == -1 )
                cantActivate = true;
#endif

            if (IntPtr.Size == 4)
            {
                cantActivate = true;
                Debug.LogWarning("MixCast is only compatible with 64 bit applications");
            }

            if (cantActivate)
            {
                enabled = false;
                yield break;
            }

            if (reparentToSceneRootOnStart)
                transform.parent = null;

            if (transform.parent == null)
                DontDestroyOnLoad(gameObject);

            Instance = this;

            SetExperienceInfo();
            
            BlueprintReality.Utility.UnityMainThreadRunner.EnsureExists();
            gameObject.AddComponent<BlueprintReality.SharedTextures.SharedTexturePlugin>();

            GameObject thriftRoot = new GameObject("Thrift") { hideFlags = HideFlags.HideInHierarchy };
            thriftRoot.transform.SetParent(transform);
            UnityThriftBase.GroupTransform = thriftRoot.transform;

            thriftConfigs = new ConnectionLookupTable(SpecialFiles.Thrift.ConnectionTableFilepath);
            thriftConfigs.TableRebuilt += HandleThriftConnectionsUpdated;

            while (!ThriftConfigured())
            {
                yield return null;
            }

            RefreshThriftConnections();

#if MIXCAST_LWRP
            if (GraphicsSettings.renderPipelineAsset is LightweightRenderPipelineAsset lwrpPipeline)
                MixCastRendererFeature.RegisterRenderer(MixCastSdkData.ProjectSettings.customRenderer);
#elif MIXCAST_URP
            if (GraphicsSettings.currentRenderPipeline is UniversalRenderPipelineAsset urpPipeline)
                MixCastRendererFeature.RegisterRenderer(MixCastSdkData.ProjectSettings.customRenderer);
#endif

            VirtualTrackedObjectManager = new SdkCustomTrackedObjectMediator();

            InvokeProtector.InvokeRepeating(VirtualTrackedObjectManager.Update, 0f, 1f / 90f);
            InvokeProtector.InvokeRepeating(VerifyServiceConnection, 0f, 1f / 10f);

            UnityEngine.SceneManagement.SceneManager.sceneLoaded += HandleSceneLoaded;

            if (FindObjectOfType<ExpCameraSpawner>() == null)
            {
                GameObject camerasObj = new GameObject("Cameras");
                camerasObj.transform.parent = transform;
                camerasObj.transform.localPosition = Vector3.zero;
                camerasObj.transform.localRotation = Quaternion.identity;
                camerasObj.transform.localScale = Vector3.one;
                camerasObj.AddComponent<ExpCameraSpawner>();
            }
            if (FindObjectOfType<ExpVideoInputSpawner>() == null)
            {
                GameObject videoInputObj = new GameObject("VideoInputs");
                videoInputObj.transform.parent = transform;
                videoInputObj.transform.localPosition = Vector3.zero;
                videoInputObj.transform.localRotation = Quaternion.identity;
                videoInputObj.transform.localScale = Vector3.one;
                videoInputObj.AddComponent<ExpVideoInputSpawner>();
            }
            if (FindObjectOfType<ExpViewfinderSpawner>() == null)
            {
                GameObject camerasObj = new GameObject("Viewfinders");
                camerasObj.transform.parent = transform;
                camerasObj.transform.localPosition = Vector3.zero;
                camerasObj.transform.localRotation = Quaternion.identity;
                camerasObj.transform.localScale = Vector3.one;
                camerasObj.AddComponent<ExpViewfinderSpawner>();
            }

            Initialized = true;

            yield return null;

            ClientConnection.TryNotifySdkStarted(MixCastSdkData.ExperienceInfo);
        }

        private void SetExperienceInfo()
        {
            MixCastSdkData.ExperienceInfo.MixcastVersion = MixCastSdk.VERSION_STRING;

            MixCastSdkData.ExperienceInfo.ExperienceExePath = UnityInfo.GetExePath();
            MixCastSdkData.ExperienceInfo.MainWindowHandle = (long)UnityInfo.GetMainWindowHandle();
            MixCastSdkData.ExperienceInfo.MainProcessId = UnityInfo.GetProcessId();

            MixCastSdkData.ExperienceInfo.ProjectId = MixCastSdkData.ProjectSettings.ProjectID;
            MixCastSdkData.ExperienceInfo.ExperienceTitle = Application.productName;
            MixCastSdkData.ExperienceInfo.OrganizationName = Application.companyName;

            MixCastSdkData.ExperienceInfo.EngineVersion = Application.unityVersion;
            MixCastSdkData.ExperienceInfo.AlphaIsPremultiplied = MixCastSdkData.ProjectSettings.usingPMA;
            MixCastSdkData.ExperienceInfo.ColorSpaceIsLinear = QualitySettings.desiredColorSpace == ColorSpace.Linear;

            MixCastSdkData.ExperienceInfo.CanRenderOpaqueBg = MixCastSdkData.ProjectSettings.canRenderOpaqueBg;
            MixCastSdkData.ExperienceInfo.CanRenderTransparentBg = MixCastSdkData.ProjectSettings.canRenderTransparentBg;
        }

        private void OnDisable()
        {
            if (!Initialized)
                return;

            MixCastSdk.Active = false;

            UnityEngine.SceneManagement.SceneManager.sceneLoaded -= HandleSceneLoaded;

            InvokeProtector.CancelInvokeRepeating(VirtualTrackedObjectManager.Update);
            InvokeProtector.CancelInvokeRepeating(VerifyServiceConnection);

            VirtualTrackedObjectManager = null;

#if MIXCAST_LWRP
            if (GraphicsSettings.renderPipelineAsset is LightweightRenderPipelineAsset lwrpPipeline)
                MixCastRendererFeature.UnregisterRenderer(MixCastSdkData.ProjectSettings.customRenderer);
#elif MIXCAST_URP
            if (GraphicsSettings.currentRenderPipeline is UniversalRenderPipelineAsset urpPipeline)
                MixCastRendererFeature.UnregisterRenderer(MixCastSdkData.ProjectSettings.customRenderer);
#endif

            if (ClientConnection != null)
                ClientConnection.TryNotifySdkStopped();

            if (thriftConfigs != null)
            {
                thriftConfigs.TableRebuilt -= HandleThriftConnectionsUpdated;
                thriftConfigs.Dispose();
                thriftConfigs = null;
            }

            Instance = null;
        }

        private void VerifyServiceConnection()
        {
            if (MixCastSdk.Active)
            {
                ConnectionConfig pingConnection = thriftConfigs.GetConnection(ThriftIds.Connections.SdkToClient);
                if (pingConnection == null || !UnityThriftMixCastClient.Validate<SDK_Service.Client>(pingConnection.Address, true))
                {
                    Debug.LogWarning("MixCast Service quit abruptly (ping failed)");
                    MixCastSdk.Active = false;
                    if (pingConnection != null)
                        ClientConnection.TryNotifySdkStarted(MixCastSdkData.ExperienceInfo);
                }
            }
        }

        private void HandleSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode)
        {
            MixCastSdk.SendCustomEvent("sceneLoaded(" + scene.name + ")");
        }

        private void HandleThriftConnectionsUpdated()
        {
            BlueprintReality.Utility.UnityMainThreadRunner.RunOnMainThread(RefreshThriftConnections);
        }
        private void RefreshThriftConnections()
        {
            if (ThriftConfigured())
            {
                ClientConnection = UnityThriftMixCastClient.Get<SDK_Service.Client>(thriftConfigs.GetConnection(ThriftIds.Connections.SdkToClient));
                Service_SDK_Handler handler = UnityThriftMixCastServer.Get<Service_SDK_Handler>(thriftConfigs.GetConnection(ThriftIds.Connections.ClientToSdk));
                handler.MarkOnewayMessageAsImportant("UpdateCameraMetadata");
                handler.MarkOnewayMessageAsImportant("UpdateVideoInputMetadata"); 
                handler.MarkOnewayMessageAsImportant("UpdateViewfinderMetadata");
                handler.MaxOnewayMsgQueueAllowed = 18000;

                ConnectionConfig sharedTexConfig = thriftConfigs.GetConnection(ThriftIds.Connections.SdkToSharedTextures);
                BlueprintReality.SharedTextures.SharedTexturePlugin.manualThriftAddress = sharedTexConfig.Address;
                UnityThriftMixCastClient.Get<BlueprintReality.Thrift.SharedTextures.SharedTextureCommunication.Client>(sharedTexConfig);
            }
            else
            {
                Debug.LogWarning("MixCast Service quit abruptly (files lost)");
                MixCastSdk.Active = false;
            }
        }
        private bool ThriftConfigured()
        {
            return thriftConfigs.HasConnection(ThriftIds.Connections.ClientToSdk) &&
                thriftConfigs.HasConnection(ThriftIds.Connections.SdkToClient) &&
                thriftConfigs.HasConnection(ThriftIds.Connections.SdkToSharedTextures);
        }
#endif
    }
}
