/**********************************************************************************
* Blueprint Reality Inc. CONFIDENTIAL
* 2019 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.MixCast.Thrift;
using BlueprintReality.Thrift.SharedTextures;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;

namespace BlueprintReality.SharedTextures
{
    public class SharedTextureSending : MonoBehaviour
    {
        static bool exists = false;
        private static void EnsureExists()
        {
            if (!exists)
            {
                new GameObject().AddComponent<SharedTextureSending>();
                exists = true;
            }
        }

        private class RegisteredLocalTexture
        {
            public Texture source;
            public Texture destination;
            public string texId;
        }

        private void Awake()
        {
            name = GetType().Name;
            gameObject.hideFlags = HideFlags.HideAndDontSave;
            DontDestroyOnLoad(gameObject);
            StartCoroutine("CallCopyLocalTextures");
        }

        private static Dictionary<string, RegisteredLocalTexture> registeredLocalTextures = new Dictionary<string, RegisteredLocalTexture>();
        public static event Action<string, SharedTex> OnTextureSent;

        private IEnumerator CallCopyLocalTextures()
        {
            while (true)
            {
                CopyLocalTextures();
                yield return null;
            }
        }

        private void CopyLocalTextures()
        {
            foreach (var label in registeredLocalTextures.Values)
            {
                if (label.source != null && label.destination != null)
                {
                    Graphics.CopyTexture(label.source, label.destination);
                }
            }
        }

        private static bool CheckFormat(int format, ref TextureFormat convFmt)
        {
            if (format == 28)
            {
                convFmt = TextureFormat.RGBA32;
                return true;
            }
            else if (format == 87)
            {
                convFmt = TextureFormat.BGRA32;
                return true;
            }
            else if (format == 56)
            {
                convFmt = TextureFormat.R16;
                return true;
            }
            else if( format == 41 )
            {
                convFmt = TextureFormat.RFloat;
                return true;
            }
            else if (format == 2)
            {
                convFmt = TextureFormat.RGBAFloat;
                return true;
            }
            else if (format == 24)
            {
                //Debug.LogWarning("Unreal Back Buffer Format");
                convFmt = TextureFormat.RGBA32;
                return false;
            }
            else
            {
                // Add more pixel format support
                Debug.LogError("Unsupported Pixel Format:" + format + "!!!");
                convFmt = TextureFormat.RGBA32;
                return false;
            }
        }

        [DllImport("ExTexture")]
        private static extern IntPtr CreateSharedTexture(IntPtr texture, ref int width, ref int height, ref int format, ref ulong handle);

        private static SharedTex getSharedTex(Texture texture, out IntPtr srv)
        {
            srv = IntPtr.Zero;

            if (texture == null)
            {
                return null;
            }

            int width = 0;
            int height = 0;
            int format = 0;
            ulong handle = 0;

            try
            {
                srv = CreateSharedTexture(texture.GetNativeTexturePtr(), ref width, ref height, ref format, ref handle);
            }
            catch
            {
                Debug.LogError("Register Local Texture failed");
                return null;
            }
            if (srv == IntPtr.Zero)
            {
                Debug.LogError("Register Local Texture failed (SRV: Zero)");
                return null;
            }
            if (handle <= 0)
            {
                Debug.LogError("Register Local Texture failed (Invalid Handle)");
                return null;
            }
            return new SharedTex((long)handle, width, height, format);
        }

        public static void RegisterLocal(string texId, Texture texture)
        {
            EnsureExists();
            SharedTexturePlugin.EnsureExists();

            IntPtr srv = IntPtr.Zero;
            SharedTex tex = getSharedTex(texture, out srv);
            bool allocResult = (tex != null) && (srv != IntPtr.Zero);

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

            bool shareResult = allocResult && client.TrySharedTextureNotify(texId, tex);
            if (shareResult)
            {
                TextureFormat convFormat = TextureFormat.RGBA32;
                CheckFormat(tex.Format, ref convFormat);
                Texture newDst = Texture2D.CreateExternalTexture(tex.Width, tex.Height, convFormat, false, true, srv);
                RegisteredLocalTexture label = new RegisteredLocalTexture();
                label.source = texture;
                label.destination = newDst;
                label.texId = texId;
                if (registeredLocalTextures.ContainsKey(texId))
                    Debug.LogError("Texture with ID " + texId + " already registered");
                else
                    Debug.Log("Registered shared tex: " + texId);
                registeredLocalTextures.Add(texId, label);
                if (OnTextureSent != null)
                    OnTextureSent(texId, tex);
            }
            else
                Debug.Log("Couldn't register texture due to " + (allocResult ? "Thrift error" : "DirectX error"));
        }

        public static bool UnregisterLocal(string texId)
        {
            if (string.IsNullOrEmpty(texId))
                return false;

            EnsureExists();
            SharedTexturePlugin.EnsureExists();

            if (registeredLocalTextures.ContainsKey(texId))
            {
                var label = registeredLocalTextures[texId];
                Debug.Log("Unregistering shared tex: " + texId);
                registeredLocalTextures.Remove(texId);
                SharedTex emptyTex = new SharedTex(0, 0, 0, 0);

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

                if (client != null)
                {
                    bool res = client.TrySharedTextureNotify(label.texId, emptyTex);
                    if (OnTextureSent != null)
                        OnTextureSent(texId, emptyTex);
                    return res;
                }
                else
                    return false;
            }
            else
            {
                Debug.LogError("No Local Texture found for ID: " + texId);
                return false;
            }
        }

        public static bool HasLocalTexture(string texId)
        {
            return registeredLocalTextures.ContainsKey(texId);
        }
        public static bool TryGetLocalTexture(string texId, ref Texture texture)
        {
            if (!registeredLocalTextures.ContainsKey(texId))
                return false;
            texture = registeredLocalTextures[texId].source;
            return true;
        }
    }
}
#endif
