using System.Runtime.InteropServices;

namespace TeamsISO.Engine.NdiInterop;

/// <summary>
/// P/Invoke declarations for the NewTek/Vizrt NDI SDK 6 native library.
/// On Windows the import target is <c>Processing.NDI.Lib.x64.dll</c>. Resolution of
/// the DLL is handled by <see cref="NdiNativeLibraryResolver"/>, which loads it from
/// the NDI Runtime installation directory exposed by the
/// <c>NDI_RUNTIME_DIR_V&lt;n&gt;</c> environment variables — the NDI installer sets
/// these but does not always add the runtime directory to <c>PATH</c>, so a default
/// loader-based resolution would fail with <c>0x8007007E</c> on otherwise correctly
/// installed machines.
/// </summary>
internal static class NdiNative
{
    private const string LibName = "Processing.NDI.Lib.x64";

    /// <summary>
    /// Registers <see cref="NdiNativeLibraryResolver"/> with the runtime before any
    /// P/Invoke against <see cref="LibName"/> fires. The static constructor is
    /// guaranteed to run before the first access to any member of this type, which
    /// includes the very first <c>[DllImport]</c> call site, so the resolver is
    /// always in place when the loader needs it.
    /// </summary>
    static NdiNative()
    {
        NdiNativeLibraryResolver.Register();
    }

    // ---- Lifecycle ----
    [DllImport(LibName, EntryPoint = "NDIlib_initialize", CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.U1)]
    public static extern bool Initialize();

    [DllImport(LibName, EntryPoint = "NDIlib_destroy", CallingConvention = CallingConvention.Cdecl)]
    public static extern void Destroy();

    [DllImport(LibName, EntryPoint = "NDIlib_version", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr Version();

    // ---- Find ----
    [DllImport(LibName, EntryPoint = "NDIlib_find_create_v2", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr FindCreateV2(IntPtr p_create_settings);

    [DllImport(LibName, EntryPoint = "NDIlib_find_destroy", CallingConvention = CallingConvention.Cdecl)]
    public static extern void FindDestroy(IntPtr p_instance);

    [DllImport(LibName, EntryPoint = "NDIlib_find_get_current_sources", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr FindGetCurrentSources(IntPtr p_instance, out uint p_no_sources);

    // ---- Receive ----
    [DllImport(LibName, EntryPoint = "NDIlib_recv_create_v3", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr RecvCreateV3(ref RecvCreateV3Settings p_create_settings);

    [DllImport(LibName, EntryPoint = "NDIlib_recv_destroy", CallingConvention = CallingConvention.Cdecl)]
    public static extern void RecvDestroy(IntPtr p_instance);

    [DllImport(LibName, EntryPoint = "NDIlib_recv_capture_v3", CallingConvention = CallingConvention.Cdecl)]
    public static extern FrameType RecvCaptureV3(
        IntPtr p_instance,
        out VideoFrameV2 p_video_data,
        IntPtr p_audio_data,
        IntPtr p_metadata,
        uint timeout_in_ms);

    [DllImport(LibName, EntryPoint = "NDIlib_recv_free_video_v2", CallingConvention = CallingConvention.Cdecl)]
    public static extern void RecvFreeVideoV2(IntPtr p_instance, ref VideoFrameV2 p_video_data);

    /// <summary>
    /// Audio-only entrypoint into the same NDIlib_recv_capture_v3 the video
    /// path uses. We bind it as a separate import so callers that only need
    /// audio can pass IntPtr.Zero for the video / metadata slots without
    /// having to allocate a video frame struct they don't intend to fill.
    /// </summary>
    [DllImport(LibName, EntryPoint = "NDIlib_recv_capture_v3", CallingConvention = CallingConvention.Cdecl)]
    public static extern FrameType RecvCaptureV3Audio(
        IntPtr p_instance,
        IntPtr p_video_data,
        out AudioFrameV3 p_audio_data,
        IntPtr p_metadata,
        uint timeout_in_ms);

    [DllImport(LibName, EntryPoint = "NDIlib_recv_free_audio_v3", CallingConvention = CallingConvention.Cdecl)]
    public static extern void RecvFreeAudioV3(IntPtr p_instance, ref AudioFrameV3 p_audio_data);

    // ---- Send ----
    [DllImport(LibName, EntryPoint = "NDIlib_send_create", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr SendCreate(ref SendCreateSettings p_create_settings);

    [DllImport(LibName, EntryPoint = "NDIlib_send_destroy", CallingConvention = CallingConvention.Cdecl)]
    public static extern void SendDestroy(IntPtr p_instance);

    [DllImport(LibName, EntryPoint = "NDIlib_send_send_video_v2", CallingConvention = CallingConvention.Cdecl)]
    public static extern void SendSendVideoV2(IntPtr p_instance, ref VideoFrameV2 p_video_data);

    // ---- Enums ----

    public enum FrameType
    {
        None = 0,
        Video = 1,
        Audio = 2,
        Metadata = 3,
        Error = 4,
        StatusChange = 100
    }

    public enum RecvBandwidth
    {
        MetadataOnly = -10,
        AudioOnly = 10,
        Lowest = 0,
        Highest = 100
    }

    public enum RecvColorFormat
    {
        BgrxBgra = 0,
        UyvyBgra = 1,
        Rgbx_Rgba = 2,
        UyvyRgba = 3,
        Fastest = 100,
        Best = 101
    }

    public enum FrameFormatType
    {
        Progressive = 1,
        Interleaved = 0,
        FieldZero = 2,
        FieldOne = 3,
        Max = 0x7FFFFFFF
    }

    public static class FourCC
    {
        public const uint UYVY = 0x59565955; // 'U','Y','V','Y'
        public const uint BGRA = 0x41524742; // 'B','G','R','A'
        public const uint BGRX = 0x58524742; // 'B','G','R','X'
        public const uint RGBA = 0x41424752; // 'R','G','B','A'
    }

    // ---- Structs ----

    [StructLayout(LayoutKind.Sequential)]
    public struct Source
    {
        public IntPtr p_ndi_name;     // const char*
        public IntPtr p_url_address;  // const char* (union, treat as URL)
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RecvCreateV3Settings
    {
        public Source source_to_connect_to;
        public RecvColorFormat color_format;
        public RecvBandwidth bandwidth;
        [MarshalAs(UnmanagedType.U1)] public bool allow_video_fields;
        public IntPtr p_ndi_recv_name; // const char*
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SendCreateSettings
    {
        public IntPtr p_ndi_name;     // const char*
        public IntPtr p_groups;       // const char*  (UTF-8, comma-separated; NULL = "Public")
        [MarshalAs(UnmanagedType.U1)] public bool clock_video;
        [MarshalAs(UnmanagedType.U1)] public bool clock_audio;
    }

    /// <summary>
    /// Mirrors <c>NDIlib_find_create_t</c>. Used to constrain which NDI groups a
    /// finder will discover sources from. Passing NULL/<c>IntPtr.Zero</c> for the
    /// settings pointer (or a struct with all fields zero) yields default behavior:
    /// <c>show_local_sources=true</c>, groups=<c>"Public"</c>, no extra IPs.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct FindCreateSettings
    {
        [MarshalAs(UnmanagedType.U1)] public bool show_local_sources;
        public IntPtr p_groups;       // const char*  (UTF-8, comma-separated; NULL = "Public")
        public IntPtr p_extra_ips;    // const char*  (UTF-8, comma-separated; NULL = none)
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct VideoFrameV2
    {
        public int xres;
        public int yres;
        public uint FourCC;
        public int frame_rate_N;
        public int frame_rate_D;
        public float picture_aspect_ratio;
        public FrameFormatType frame_format_type;
        public long timecode;
        public IntPtr p_data;
        public int line_stride_in_bytes; // (union with data_size_in_bytes)
        public IntPtr p_metadata;
        public long timestamp;
    }

    /// <summary>
    /// Mirrors <c>NDIlib_audio_frame_v3_t</c>. The <c>FourCC</c> field
    /// distinguishes the sample format; for NDI 6 the only common value is
    /// <c>FLTP</c> (32-bit float, planar, one channel-plane after another).
    /// <c>channel_stride_in_bytes</c> is the byte distance between the start
    /// of channel N and channel N+1 — for FLTP that's <c>no_samples * 4</c>.
    /// Total buffer size = <c>no_channels * channel_stride_in_bytes</c>.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct AudioFrameV3
    {
        public int sample_rate;
        public int no_channels;
        public int no_samples;
        public long timecode;
        public uint FourCC;
        public IntPtr p_data;
        public int channel_stride_in_bytes; // union with data_size_in_bytes
        public IntPtr p_metadata;
        public long timestamp;
    }
}
