/**********************************************************************************
* 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.
***********************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Rendering;

namespace BlueprintReality.Interprocess.Textures
{
    public class WatchedUnitySharedTexture : IDisposable
    {
        private static IntPtr acquireSyncFunc = IntPtr.Zero, releaseSyncFunc = IntPtr.Zero;

        private int index;

        public IntPtr SrcTexHandle { get; protected set; }
        public Texture2D Texture { get; protected set; }

        public WatchedUnitySharedTexture(IntPtr srcProcHandle, IntPtr remoteTexHandle)
        {
            Initialize(srcProcHandle, remoteTexHandle, true);
        }
        public WatchedUnitySharedTexture(IntPtr remoteTexHandle)
        {
            Initialize(IntPtr.Zero, remoteTexHandle, false);
        }
        void Initialize(IntPtr srcProcHandle, IntPtr remoteTexHandle, bool ntHandleMode)
        {
            if (acquireSyncFunc == IntPtr.Zero)
                acquireSyncFunc = GetWatchedSharedTex_AcquireSyncFunc();
            if (releaseSyncFunc == IntPtr.Zero)
                releaseSyncFunc = GetWatchedSharedTex_ReleaseSyncFunc();

            SrcTexHandle = remoteTexHandle;

            IntPtr texPtr = IntPtr.Zero;
            uint width = 0, height = 0, fmt = 0;
            switch (SystemInfo.graphicsDeviceType)
            {
                case GraphicsDeviceType.Direct3D11:
                    if (ntHandleMode)
                        index = CreateWatchedSharedTex_D3D11_NT(srcProcHandle, remoteTexHandle, out texPtr, out width, out height, out fmt);
                    else
                        index = CreateWatchedSharedTex_D3D11(remoteTexHandle, out texPtr, out width, out height, out fmt);
                    break;
                case GraphicsDeviceType.Direct3D12:
                    index = CreateWatchedSharedTex_D3D12(srcProcHandle, remoteTexHandle, out texPtr, out width, out height, out fmt);
                    break;
            }
            if (index == -1)
                return;

            Texture = Texture2D.CreateExternalTexture((int)width, (int)height, GetUnityFormatFromDirectXFormat(fmt), false, true, texPtr);
        }

        public void Dispose()
        {
            if (Texture != null)
            {
                MonoBehaviour.Destroy(Texture);
                ReleaseWatchedSharedTex(index);
            }
        }

        //Affects render thread
        public void AcquireSync()
        {
            if (Texture != null)
                GL.IssuePluginEvent(acquireSyncFunc, index);
        }
        public void ReleaseSync()
        {
            if (Texture != null)
                GL.IssuePluginEvent(releaseSyncFunc, index);
        }

        public static TextureFormat GetUnityFormatFromDirectXFormat(uint format)
        {
            switch (format)
            {
                case 2:
                    return TextureFormat.RGBAFloat;
                case 10:
                    return TextureFormat.RGBAHalf;
                case 24:
                case 27:
                case 28:
                    return TextureFormat.RGBA32;
                case 41:
                    return TextureFormat.RFloat;
                case 56:
                    return TextureFormat.R16;
                case 65:
                    return TextureFormat.Alpha8;
                case 87:
                    return TextureFormat.BGRA32;

                default:
                    // Add more pixel format support
                    Debug.LogError("Unsupported Pixel Format:" + format + "!!!");
                    return TextureFormat.RGBA32;
            }
        }

        //D3D11 source and dest
        [DllImport(MixCastInteropPlugin.DllName)]
        private static extern int CreateWatchedSharedTex_D3D11(IntPtr texHandle,
            out IntPtr sharedTex, out uint width, out uint height, out uint format);

        //D3D12 source and D3D11 dest
        [DllImport(MixCastInteropPlugin.DllName)]
        private static extern int CreateWatchedSharedTex_D3D11_NT(IntPtr srcProcHandle, IntPtr texHandle,
            out IntPtr sharedTexPtr, out uint width, out uint height, out uint format);

        //D3D12 source and dest
        [DllImport(MixCastInteropPlugin.DllName)]
        private static extern int CreateWatchedSharedTex_D3D12(IntPtr srcProcHandle, IntPtr remoteTexHandle,
            out IntPtr sharedTexPtr, out uint width, out uint height, out uint format);

        [DllImport(MixCastInteropPlugin.DllName)]
        private static extern void ReleaseWatchedSharedTex(int index);

        [DllImport(MixCastInteropPlugin.DllName)]
        private static extern IntPtr GetWatchedSharedTex_AcquireSyncFunc();
        [DllImport(MixCastInteropPlugin.DllName)]
        private static extern IntPtr GetWatchedSharedTex_ReleaseSyncFunc();
    }
}
