/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * Licensed under the Oculus SDK License Agreement (the "License");
 * you may not use the Oculus SDK except in compliance with the License,
 * which is provided at the time of installation or download, or which
 * otherwise accompanies this software in either electronic or hard copy form.
 *
 * You may obtain a copy of the License at
 *
 * https://developer.oculus.com/licenses/oculussdk/
 *
 * Unless required by applicable law or agreed to in writing, the Oculus SDK
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/************************************************************************************
 * Filename    :   ONSPPropagationInterface.cs
 * Content     :   Interface into the Oculus Audio propagation functions
 ***********************************************************************************/

using System;
using System.Runtime.InteropServices;
using UnityEngine;
using Oculus.Spatializer.Propagation;

namespace Oculus
{
    namespace Spatializer
    {
        namespace Propagation
        {
            /***********************************************************************************/
            // ENUMS and STRUCTS
            /***********************************************************************************/
            public enum FaceType : uint
            {
                TRIANGLES = 0,
                QUADS
            }

            public enum MaterialProperty : uint
            {
                ABSORPTION = 0,
                TRANSMISSION,
                SCATTERING
            }

            // Matches internal mesh layout
            [StructLayout(LayoutKind.Sequential, Pack = 1)]
            public struct MeshGroup
            {
                public UIntPtr indexOffset;
                public UIntPtr faceCount;
                [MarshalAs(UnmanagedType.U4)]
                public FaceType faceType;
                public IntPtr material;
            }
        }
    }
}

class ONSPPropagation
{
    static PropagationInterface CachedInterface;
    public static PropagationInterface Interface { get { if (CachedInterface == null) CachedInterface = FindInterface(); return CachedInterface; } }

    static PropagationInterface FindInterface()
    {
        IntPtr temp;
        try
        {
            WwisePluginInterface.ovrAudio_GetPluginContext(out temp, ClientType.OVRA_CLIENT_TYPE_WWISE_UNKNOWN);
            Debug.Log("Propagation initialized with Wwise Oculus Spatializer plugin");
            return new WwisePluginInterface();
        }
        catch(System.DllNotFoundException)
        {
            // this is fine
        }
        try
        {
            FMODPluginInterface.ovrAudio_GetPluginContext(out temp, ClientType.OVRA_CLIENT_TYPE_FMOD);
            Debug.Log("Propagation initialized with FMOD Oculus Spatializer plugin");
            return new FMODPluginInterface();
        }
        catch (System.DllNotFoundException)
        {
            // this is fine
        }

        Debug.Log("Propagation initialized with Unity Oculus Spatializer plugin");
        return new UnityNativeInterface();
    }

    public enum ovrAudioScalarType : uint
    {
        Int8,
        UInt8,
        Int16,
        UInt16,
        Int32,
        UInt32,
        Int64,
        UInt64,
        Float16,
        Float32,
        Float64
    }

    public class ClientType
    {
        // Copied from AudioSDK\OVRAudio\OVR_Audio_Internal.h
        public const uint OVRA_CLIENT_TYPE_NATIVE = 0;
        public const uint OVRA_CLIENT_TYPE_WWISE_2016 = 1;
        public const uint OVRA_CLIENT_TYPE_WWISE_2017_1 = 2;
        public const uint OVRA_CLIENT_TYPE_WWISE_2017_2 = 3;
        public const uint OVRA_CLIENT_TYPE_WWISE_2018_1 = 4;
        public const uint OVRA_CLIENT_TYPE_FMOD = 5;
        public const uint OVRA_CLIENT_TYPE_UNITY = 6;
        public const uint OVRA_CLIENT_TYPE_UE4 = 7;
        public const uint OVRA_CLIENT_TYPE_VST = 8;
        public const uint OVRA_CLIENT_TYPE_AAX = 9;
        public const uint OVRA_CLIENT_TYPE_TEST = 10;
        public const uint OVRA_CLIENT_TYPE_OTHER = 11;
        public const uint OVRA_CLIENT_TYPE_WWISE_UNKNOWN = 12;
        public const uint OVRA_CLIENT_TYPE_WWISE_2019_1 = 13;
        public const uint OVRA_CLIENT_TYPE_WWISE_2019_2 = 14;
        public const uint OVRA_CLIENT_TYPE_WWISE_2021_1 = 15;
    }

    public interface PropagationInterface
    {
        /***********************************************************************************/
        // Settings API
        int SetPropagationQuality(float quality);
        int SetPropagationThreadAffinity(UInt64 cpuMask);

        /***********************************************************************************/
        // Geometry API
        int CreateAudioGeometry(out IntPtr geometry);
        int DestroyAudioGeometry(IntPtr geometry);
        int AudioGeometryUploadMeshArrays(IntPtr geometry,
                                                        float[] vertices, int vertexCount,
                                                        int[] indices, int indexCount,
                                                        MeshGroup[] groups, int groupCount);
        int AudioGeometrySetTransform(IntPtr geometry, float[] matrix4x4);
        int AudioGeometryGetTransform(IntPtr geometry, out float[] matrix4x4);
        int AudioGeometryWriteMeshFile(IntPtr geometry, string filePath);
        int AudioGeometryReadMeshFile(IntPtr geometry, string filePath);
        int AudioGeometryWriteMeshFileObj(IntPtr geometry, string filePath);

        /***********************************************************************************/
        // Material API
        int AudioMaterialGetFrequency(IntPtr material, MaterialProperty property, float frequency, out float value);
        int CreateAudioMaterial(out IntPtr material);
        int DestroyAudioMaterial(IntPtr material);
        int AudioMaterialSetFrequency(IntPtr material, MaterialProperty property, float frequency, float value);
        int AudioMaterialReset(IntPtr material, MaterialProperty property);
    }

    /***********************************************************************************/
    // UNITY NATIVE
    /***********************************************************************************/
    public class UnityNativeInterface : PropagationInterface
    {
        // The name used for the plugin DLL.
        public const string strOSPS = "AudioPluginOculusSpatializer";

        /***********************************************************************************/
        // Context API: Required to create internal context if it does not exist yet
        IntPtr context_ = IntPtr.Zero;
        IntPtr context { get { if (context_ == IntPtr.Zero) { ovrAudio_GetPluginContext(out context_, ClientType.OVRA_CLIENT_TYPE_UNITY); } return context_; } }

        [DllImport(strOSPS)]
        public static extern int ovrAudio_GetPluginContext(out IntPtr context, uint clientType);

        /***********************************************************************************/
        // Settings API
        [DllImport(strOSPS)]
        private static extern int ovrAudio_SetPropagationQuality(IntPtr context, float quality);
        public int SetPropagationQuality(float quality)
        {
            return ovrAudio_SetPropagationQuality(context, quality);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_SetPropagationThreadAffinity(IntPtr context, UInt64 cpuMask);
        public int SetPropagationThreadAffinity(UInt64 cpuMask)
        {
            return ovrAudio_SetPropagationThreadAffinity(context, cpuMask);
        }

        /***********************************************************************************/
        // Geometry API
        [DllImport(strOSPS)]
        private static extern int ovrAudio_CreateAudioGeometry(IntPtr context, out IntPtr geometry);
        public int CreateAudioGeometry(out IntPtr geometry)
        {
            return ovrAudio_CreateAudioGeometry(context, out geometry);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_DestroyAudioGeometry(IntPtr geometry);
        public int DestroyAudioGeometry(IntPtr geometry)
        {
            return ovrAudio_DestroyAudioGeometry(geometry);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryUploadMeshArrays(IntPtr geometry,
                                                                        float[] vertices, UIntPtr verticesBytesOffset, UIntPtr vertexCount, UIntPtr vertexStride, ovrAudioScalarType vertexType,
                                                                        int[] indices, UIntPtr indicesByteOffset, UIntPtr indexCount, ovrAudioScalarType indexType,
                                                                        MeshGroup[] groups, UIntPtr groupCount);

        public int AudioGeometryUploadMeshArrays(IntPtr geometry,
                                                        float[] vertices, int vertexCount,
                                                        int[] indices, int indexCount,
                                                        MeshGroup[] groups, int groupCount)
        {
            return ovrAudio_AudioGeometryUploadMeshArrays(geometry,
                vertices, UIntPtr.Zero, (UIntPtr)vertexCount, UIntPtr.Zero, ovrAudioScalarType.Float32,
                indices, UIntPtr.Zero, (UIntPtr)indexCount, ovrAudioScalarType.UInt32,
                groups, (UIntPtr)groupCount);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometrySetTransform(IntPtr geometry, float[] matrix4x4);
        public int AudioGeometrySetTransform(IntPtr geometry, float[] matrix4x4)
        {
            return ovrAudio_AudioGeometrySetTransform(geometry, matrix4x4);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryGetTransform(IntPtr geometry, out float[] matrix4x4);
        public int AudioGeometryGetTransform(IntPtr geometry, out float[] matrix4x4)
        {
            return ovrAudio_AudioGeometryGetTransform(geometry, out matrix4x4);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryWriteMeshFile(IntPtr geometry, string filePath);
        public int AudioGeometryWriteMeshFile(IntPtr geometry, string filePath)
        {
            return ovrAudio_AudioGeometryWriteMeshFile(geometry, filePath);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryReadMeshFile(IntPtr geometry, string filePath);
        public int AudioGeometryReadMeshFile(IntPtr geometry, string filePath)
        {
            return ovrAudio_AudioGeometryReadMeshFile(geometry, filePath);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryWriteMeshFileObj(IntPtr geometry, string filePath);
        public int AudioGeometryWriteMeshFileObj(IntPtr geometry, string filePath)
        {
            return ovrAudio_AudioGeometryWriteMeshFileObj(geometry, filePath);
        }

        /***********************************************************************************/
        // Material API
        [DllImport(strOSPS)]
        private static extern int ovrAudio_CreateAudioMaterial(IntPtr context, out IntPtr material);
        public int CreateAudioMaterial(out IntPtr material)
        {
            return ovrAudio_CreateAudioMaterial(context, out material);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_DestroyAudioMaterial(IntPtr material);
        public int DestroyAudioMaterial(IntPtr material)
        {
            return ovrAudio_DestroyAudioMaterial(material);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioMaterialSetFrequency(IntPtr material, MaterialProperty property, float frequency, float value);
        public int AudioMaterialSetFrequency(IntPtr material, MaterialProperty property, float frequency, float value)
        {
            return ovrAudio_AudioMaterialSetFrequency(material, property, frequency, value);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioMaterialGetFrequency(IntPtr material, MaterialProperty property, float frequency, out float value);
        public int AudioMaterialGetFrequency(IntPtr material, MaterialProperty property, float frequency, out float value)
        {
            return ovrAudio_AudioMaterialGetFrequency(material, property, frequency, out value);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioMaterialReset(IntPtr material, MaterialProperty property);
        public int AudioMaterialReset(IntPtr material, MaterialProperty property)
        {
            return ovrAudio_AudioMaterialReset(material, property);
        }
    }

    /***********************************************************************************/
    // WWISE
    /***********************************************************************************/
    public class WwisePluginInterface : PropagationInterface
    {
        // The name used for the plugin DLL.
        public const string strOSPS = "OculusSpatializerWwise";

        /***********************************************************************************/
        // Context API: Required to create internal context if it does not exist yet
        IntPtr context_ = IntPtr.Zero;
        IntPtr context { get { if (context_ == IntPtr.Zero) { ovrAudio_GetPluginContext(out context_, ClientType.OVRA_CLIENT_TYPE_WWISE_UNKNOWN); } return context_; } }

        [DllImport(strOSPS)]
        public static extern int ovrAudio_GetPluginContext(out IntPtr context, uint clientType);

        /***********************************************************************************/
        // Settings API
        [DllImport(strOSPS)]
        private static extern int ovrAudio_SetPropagationQuality(IntPtr context, float quality);
        public int SetPropagationQuality(float quality)
        {
            return ovrAudio_SetPropagationQuality(context, quality);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_SetPropagationThreadAffinity(IntPtr context, UInt64 cpuMask);
        public int SetPropagationThreadAffinity(UInt64 cpuMask)
        {
            return ovrAudio_SetPropagationThreadAffinity(context, cpuMask);
        }

        /***********************************************************************************/
        // Geometry API
        [DllImport(strOSPS)]
        private static extern int ovrAudio_CreateAudioGeometry(IntPtr context, out IntPtr geometry);
        public int CreateAudioGeometry(out IntPtr geometry)
        {
            return ovrAudio_CreateAudioGeometry(context, out geometry);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_DestroyAudioGeometry(IntPtr geometry);
        public int DestroyAudioGeometry(IntPtr geometry)
        {
            return ovrAudio_DestroyAudioGeometry(geometry);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryUploadMeshArrays(IntPtr geometry,
                                                                        float[] vertices, UIntPtr verticesBytesOffset, UIntPtr vertexCount, UIntPtr vertexStride, ovrAudioScalarType vertexType,
                                                                        int[] indices, UIntPtr indicesByteOffset, UIntPtr indexCount, ovrAudioScalarType indexType,
                                                                        MeshGroup[] groups, UIntPtr groupCount);

        public int AudioGeometryUploadMeshArrays(IntPtr geometry,
                                                        float[] vertices, int vertexCount,
                                                        int[] indices, int indexCount,
                                                        MeshGroup[] groups, int groupCount)
        {
            return ovrAudio_AudioGeometryUploadMeshArrays(geometry,
                vertices, UIntPtr.Zero, (UIntPtr)vertexCount, UIntPtr.Zero, ovrAudioScalarType.Float32,
                indices, UIntPtr.Zero, (UIntPtr)indexCount, ovrAudioScalarType.UInt32,
                groups, (UIntPtr)groupCount);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometrySetTransform(IntPtr geometry, float[] matrix4x4);
        public int AudioGeometrySetTransform(IntPtr geometry, float[] matrix4x4)
        {
            return ovrAudio_AudioGeometrySetTransform(geometry, matrix4x4);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryGetTransform(IntPtr geometry, out float[] matrix4x4);
        public int AudioGeometryGetTransform(IntPtr geometry, out float[] matrix4x4)
        {
            return ovrAudio_AudioGeometryGetTransform(geometry, out matrix4x4);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryWriteMeshFile(IntPtr geometry, string filePath);
        public int AudioGeometryWriteMeshFile(IntPtr geometry, string filePath)
        {
            return ovrAudio_AudioGeometryWriteMeshFile(geometry, filePath);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryReadMeshFile(IntPtr geometry, string filePath);
        public int AudioGeometryReadMeshFile(IntPtr geometry, string filePath)
        {
            return ovrAudio_AudioGeometryReadMeshFile(geometry, filePath);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryWriteMeshFileObj(IntPtr geometry, string filePath);
        public int AudioGeometryWriteMeshFileObj(IntPtr geometry, string filePath)
        {
            return ovrAudio_AudioGeometryWriteMeshFileObj(geometry, filePath);
        }

        /***********************************************************************************/
        // Material API
        [DllImport(strOSPS)]
        private static extern int ovrAudio_CreateAudioMaterial(IntPtr context, out IntPtr material);
        public int CreateAudioMaterial(out IntPtr material)
        {
            return ovrAudio_CreateAudioMaterial(context, out material);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_DestroyAudioMaterial(IntPtr material);
        public int DestroyAudioMaterial(IntPtr material)
        {
            return ovrAudio_DestroyAudioMaterial(material);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioMaterialSetFrequency(IntPtr material, MaterialProperty property, float frequency, float value);
        public int AudioMaterialSetFrequency(IntPtr material, MaterialProperty property, float frequency, float value)
        {
            return ovrAudio_AudioMaterialSetFrequency(material, property, frequency, value);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioMaterialGetFrequency(IntPtr material, MaterialProperty property, float frequency, out float value);
        public int AudioMaterialGetFrequency(IntPtr material, MaterialProperty property, float frequency, out float value)
        {
            return ovrAudio_AudioMaterialGetFrequency(material, property, frequency, out value);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioMaterialReset(IntPtr material, MaterialProperty property);
        public int AudioMaterialReset(IntPtr material, MaterialProperty property)
        {
            return ovrAudio_AudioMaterialReset(material, property);
        }
    }

    /***********************************************************************************/
    // FMOD
    /***********************************************************************************/
    public class FMODPluginInterface : PropagationInterface
    {
        // The name used for the plugin DLL.
        public const string strOSPS = "OculusSpatializerFMOD";

        /***********************************************************************************/
        // Context API: Required to create internal context if it does not exist yet
        IntPtr context_ = IntPtr.Zero;
        IntPtr context { get { if (context_ == IntPtr.Zero) { ovrAudio_GetPluginContext(out context_, ClientType.OVRA_CLIENT_TYPE_FMOD); } return context_; } }

        [DllImport(strOSPS)]
        public static extern int ovrAudio_GetPluginContext(out IntPtr context, uint clientType);

        /***********************************************************************************/
        // Settings API
        [DllImport(strOSPS)]
        private static extern int ovrAudio_SetPropagationQuality(IntPtr context, float quality);
        public int SetPropagationQuality(float quality)
        {
            return ovrAudio_SetPropagationQuality(context, quality);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_SetPropagationThreadAffinity(IntPtr context, UInt64 cpuMask);
        public int SetPropagationThreadAffinity(UInt64 cpuMask)
        {
            return ovrAudio_SetPropagationThreadAffinity(context, cpuMask);
        }

        /***********************************************************************************/
        // Geometry API
        [DllImport(strOSPS)]
        private static extern int ovrAudio_CreateAudioGeometry(IntPtr context, out IntPtr geometry);
        public int CreateAudioGeometry(out IntPtr geometry)
        {
            return ovrAudio_CreateAudioGeometry(context, out geometry);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_DestroyAudioGeometry(IntPtr geometry);
        public int DestroyAudioGeometry(IntPtr geometry)
        {
            return ovrAudio_DestroyAudioGeometry(geometry);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryUploadMeshArrays(IntPtr geometry,
                                                                        float[] vertices, UIntPtr verticesBytesOffset, UIntPtr vertexCount, UIntPtr vertexStride, ovrAudioScalarType vertexType,
                                                                        int[] indices, UIntPtr indicesByteOffset, UIntPtr indexCount, ovrAudioScalarType indexType,
                                                                        MeshGroup[] groups, UIntPtr groupCount);

        public int AudioGeometryUploadMeshArrays(IntPtr geometry,
                                                        float[] vertices, int vertexCount,
                                                        int[] indices, int indexCount,
                                                        MeshGroup[] groups, int groupCount)
        {
            return ovrAudio_AudioGeometryUploadMeshArrays(geometry,
                vertices, UIntPtr.Zero, (UIntPtr)vertexCount, UIntPtr.Zero, ovrAudioScalarType.Float32,
                indices, UIntPtr.Zero, (UIntPtr)indexCount, ovrAudioScalarType.UInt32,
                groups, (UIntPtr)groupCount);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometrySetTransform(IntPtr geometry, float[] matrix4x4);
        public int AudioGeometrySetTransform(IntPtr geometry, float[] matrix4x4)
        {
            return ovrAudio_AudioGeometrySetTransform(geometry, matrix4x4);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryGetTransform(IntPtr geometry, out float[] matrix4x4);
        public int AudioGeometryGetTransform(IntPtr geometry, out float[] matrix4x4)
        {
            return ovrAudio_AudioGeometryGetTransform(geometry, out matrix4x4);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryWriteMeshFile(IntPtr geometry, string filePath);
        public int AudioGeometryWriteMeshFile(IntPtr geometry, string filePath)
        {
            return ovrAudio_AudioGeometryWriteMeshFile(geometry, filePath);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryReadMeshFile(IntPtr geometry, string filePath);
        public int AudioGeometryReadMeshFile(IntPtr geometry, string filePath)
        {
            return ovrAudio_AudioGeometryReadMeshFile(geometry, filePath);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioGeometryWriteMeshFileObj(IntPtr geometry, string filePath);
        public int AudioGeometryWriteMeshFileObj(IntPtr geometry, string filePath)
        {
            return ovrAudio_AudioGeometryWriteMeshFileObj(geometry, filePath);
        }

        /***********************************************************************************/
        // Material API
        [DllImport(strOSPS)]
        private static extern int ovrAudio_CreateAudioMaterial(IntPtr context, out IntPtr material);
        public int CreateAudioMaterial(out IntPtr material)
        {
            return ovrAudio_CreateAudioMaterial(context, out material);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_DestroyAudioMaterial(IntPtr material);
        public int DestroyAudioMaterial(IntPtr material)
        {
            return ovrAudio_DestroyAudioMaterial(material);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioMaterialSetFrequency(IntPtr material, MaterialProperty property, float frequency, float value);
        public int AudioMaterialSetFrequency(IntPtr material, MaterialProperty property, float frequency, float value)
        {
            return ovrAudio_AudioMaterialSetFrequency(material, property, frequency, value);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioMaterialGetFrequency(IntPtr material, MaterialProperty property, float frequency, out float value);
        public int AudioMaterialGetFrequency(IntPtr material, MaterialProperty property, float frequency, out float value)
        {
            return ovrAudio_AudioMaterialGetFrequency(material, property, frequency, out value);
        }

        [DllImport(strOSPS)]
        private static extern int ovrAudio_AudioMaterialReset(IntPtr material, MaterialProperty property);
        public int AudioMaterialReset(IntPtr material, MaterialProperty property)
        {
            return ovrAudio_AudioMaterialReset(material, property);
        }
    }
}