/**********************************************************************************
* Blueprint Reality Inc. CONFIDENTIAL
* 2021 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.
***********************************************************************************/

#if UNITY_STANDALONE_WIN
using BlueprintReality.Interprocess;
using BlueprintReality.Interprocess.Textures;
using BlueprintReality.MixCast.Thrift;
using BlueprintReality.Thrift.SharedTextures;
using BlueprintReality.Unity;
using System;
using System.Collections;
using UnityEngine;

namespace BlueprintReality.SharedTextures
{
    [Serializable]
    public class SharedTextureReceiver : IDisposable
    {
        public static Func<string, SharedTextureReceiver> Create { get; private set; }
        static SharedTextureReceiver()
        {
            Create = (string texId) =>
            {
                return new SharedTextureReceiver(texId);
            };
        }
        public static void OverrideCreateFunc(Func<string, SharedTextureReceiver> newFunc)
        {
            Create = newFunc;
        }

        public string TextureId { get; protected set; }

        public bool RequestSucceeded { get; protected set; }

        private Texture localTex;

        private WatchedUnitySharedTexture sharedTex;
        private RenderTexture sharedCopyTex;

        public Texture Texture
        {
            get
            {
                if (localTex != null)
                    return localTex;
                else if (sharedTex != null)
                    return sharedCopyTex;
                else
                    return null;
            }
        }


        protected TextureWrapMode wrapMode = TextureWrapMode.Clamp;
        public TextureWrapMode WrapMode
        {
            get
            {
                return wrapMode;
            }
            set
            {
                wrapMode = value;
                if (Texture != null)
                    Texture.wrapMode = wrapMode;
            }
        }

        protected FilterMode filterMode = FilterMode.Bilinear;
        public FilterMode FilterMode
        {
            get
            {
                return filterMode;
            }
            set
            {
                filterMode = value;
                if (Texture != null)
                    Texture.filterMode = filterMode;
            }
        }

        protected SharedTex textureInfo;

        public event Action OnTextureChanged;

        protected SharedTextureReceiver(string texId)
        {
            TextureId = texId;
            SharedTextureSending.OnTextureSent += HandleLocalTextureUpdate;
            RefreshTextureInfo();

            MixCastInteropPlugin.pluginUpdate += CopyOverTex;
        }
        public virtual void Dispose()
        {
            SharedTextureSending.OnTextureSent -= HandleLocalTextureUpdate;

            MixCastInteropPlugin.pluginUpdate -= CopyOverTex;

            if (localTex != null)
            {
                localTex = null;

                if (OnTextureChanged != null)
                    OnTextureChanged.Invoke();
            }
            if (sharedTex != null)
            {
                ClearWatchedSharedTexture();

                if (OnTextureChanged != null)
                    OnTextureChanged.Invoke();
            }
        }

        void CopyOverTex()
        {
            if(sharedTex != null && sharedTex.Texture != null)
            {
                sharedTex.AcquireSync();
                Graphics.Blit(sharedTex.Texture, sharedCopyTex);
                sharedTex.ReleaseSync();
            }
        }

        public virtual void RefreshTextureInfo()
        {
            Texture newTex;
            if (SharedTextureSending.TryGetLocalTexture(TextureId, out newTex))
            {
                textureInfo = null;
                RequestSucceeded = true;
                if (OnTextureChanged != null)
                    OnTextureChanged.Invoke();
                return;
            }

            SharedTextureCommunication.Client client = string.IsNullOrEmpty(SharedTexturePlugin.manualThriftAddress) ?
                UnityThriftMixCastClient.Get<SharedTextureCommunication.Client>() :
                UnityThriftMixCastClient.Get<SharedTextureCommunication.Client>(SharedTexturePlugin.manualThriftAddress);

            RequestSucceeded = client.TrySharedTextureRequest(TextureId, out textureInfo);
            if (RequestSucceeded)
                RefreshWatchedSharedTexture();
        }
        protected void RefreshWatchedSharedTexture()
        {
            if (textureInfo == null)
                return;

            if (sharedTex != null && sharedTex.SrcTexHandle.ToInt64() == textureInfo.Handle)
                return;

            ClearWatchedSharedTexture();

            if (textureInfo.Handle != 0)
            {
                if (textureInfo.ProcId != 0)
                {
                    System.Diagnostics.Process srcProc = System.Diagnostics.Process.GetProcessById((int)textureInfo.ProcId);
                    sharedTex = new WatchedUnitySharedTexture(srcProc.Handle, (IntPtr)textureInfo.Handle);
                }
                else
                    sharedTex = new WatchedUnitySharedTexture((IntPtr)textureInfo.Handle);

                if (sharedTex.Texture != null)
                {
                    sharedTex.Texture.wrapMode = wrapMode;
                    sharedTex.Texture.filterMode = filterMode;

                    sharedCopyTex = new RenderTexture(sharedTex.Texture.width, sharedTex.Texture.height, 0, sharedTex.Texture.GetUncompressedRenderTextureFormat(), 0)
                    {
                        wrapMode = wrapMode,
                        filterMode = filterMode
                    };
                    Graphics.Blit(sharedTex.Texture, sharedCopyTex);
                }
            }

            if (OnTextureChanged != null)
                OnTextureChanged.Invoke();
        }
        void ClearWatchedSharedTexture()
        {
            if (sharedTex == null)
                return;

            sharedTex.Dispose();
            sharedTex = null;

            MonoBehaviour.Destroy(sharedCopyTex);
            sharedCopyTex = null;
        }

        void HandleLocalTextureUpdate(string evTextureId, SharedTex evTextureInfo)
        {
            if (evTextureId != TextureId)
                return;

            bool changed = false;

            Texture newTex;
            SharedTextureSending.TryGetLocalTexture(TextureId, out newTex);
            if (newTex != null && sharedTex != null)    //going from remote to local
            {
                ClearWatchedSharedTexture();
                changed = true;
            }

            if (localTex != newTex)
            {
                localTex = newTex;
                changed = true;
            }
            RequestSucceeded = localTex != null;

            if (changed && OnTextureChanged != null)
                OnTextureChanged.Invoke();

            if (localTex == null)   //going from local to possibly remote
                RefreshTextureInfo();
        }

        //Useful for streamlining event driven updates
        public void HandleRemoteTextureUpdate(string evTextureId, SharedTex evTextureInfo)
        {
            if (TextureId != evTextureId || localTex != null)
                return;

            textureInfo = evTextureInfo;
            RefreshWatchedSharedTexture();
            RequestSucceeded = true;
        }
    }
}
#endif
