// src-tauri/src/shm.rs
// Shared memory `SonicForge_DAW_IPC` — created by the DAW, opened by the
// C++ bridge (native_bridge). Layout MUST match native_bridge/include/
// SharedMemoryIPC.h (verified by native_bridge/tests/shm_selfcheck.cpp).
#![cfg_attr(not(windows), allow(dead_code))]

use std::ffi::c_void;
use std::ptr;
use std::sync::Mutex;
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
use windows_sys::Win32::System::Memory::{
    CreateFileMappingW, MapViewOfFile, UnmapViewOfFile, FILE_MAP_ALL_ACCESS, PAGE_READWRITE,
};

pub const SHM_NAME: &str = "SonicForge_DAW_IPC";
pub const AUDIO_BLOCK_SIZE: usize = 256;
pub const MIDI_QUEUE_CAP: usize = 64;
pub const CONTROL_QUEUE_CAP: usize = 8;
pub const CONTROL_PATH_MAX: usize = 1024;

#[repr(C)]
#[derive(Clone, Copy)]
pub struct MidiEventIPC {
    pub command: u8,
    pub channel: u8,
    pub pitch: u8,
    pub velocity: u8,
    pub data2: u8, // CC value / program / PB LSB
    pub data3: u8, // PB MSB (0xE only)
    pub reserved: [u8; 2],
    pub sample_offset: u32,
}

#[repr(C)]
#[derive(Clone, Copy)]
pub struct ControlEventIPC {
    pub ctype: u32,
    pub arg0: u32,
    pub arg1: u32,
    pub channel: u32, // LOAD: MIDI channel to assign (A10)
    pub arg2: [u8; CONTROL_PATH_MAX],
}

#[repr(C)]
pub struct SharedAudioBufferIPC {
    pub client_read_index: u32,
    pub bridge_write_index: u32,
    pub master_left: [f32; AUDIO_BLOCK_SIZE],
    pub master_right: [f32; AUDIO_BLOCK_SIZE],
    pub block_timestamp: u64,
    pub midi_queue: [MidiEventIPC; MIDI_QUEUE_CAP],
    pub midi_queue_count: u32,
    pub control_queue: [ControlEventIPC; CONTROL_QUEUE_CAP],
    pub control_queue_count: u32,
}

pub struct Shm {
    handle: HANDLE,
    view: *mut SharedAudioBufferIPC,
}

unsafe impl Send for Shm {}

impl Shm {
    /// Create (or open) the mapping. Returns None on non-Windows (dev fallback).
    pub fn create() -> Option<Self> {
        if !cfg!(windows) {
            return None;
        }
        let name_wide: Vec<u16> = SHM_NAME.encode_utf16().chain(std::iter::once(0)).collect();
        let size = std::mem::size_of::<SharedAudioBufferIPC>() as u64;
        let handle = unsafe {
            CreateFileMappingW(
                HANDLE::default(),
                ptr::null(),
                PAGE_READWRITE,
                (size >> 32) as u32,
                size as u32,
                name_wide.as_ptr(),
            )
        };
        if handle == HANDLE::default() {
            return None;
        }
        let view = unsafe { MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, 0) };
        if view.Value.is_null() {
            unsafe { CloseHandle(handle) };
            return None;
        }
        Some(Shm {
            handle,
            view: view.Value as *mut SharedAudioBufferIPC,
        })
    }

    fn ipc(&self) -> &mut SharedAudioBufferIPC {
        unsafe { &mut *self.view }
    }

    pub fn push_midi(&self, cmd: u8, channel: u8, pitch: u8, velocity: u8, data2: u8, data3: u8, sample_offset: u32) -> bool {
        let ipc = self.ipc();
        if ipc.midi_queue_count as usize >= MIDI_QUEUE_CAP {
            return false;
        }
        let i = ipc.midi_queue_count as usize;
        ipc.midi_queue[i] = MidiEventIPC {
            command: cmd,
            channel,
            pitch,
            velocity,
            data2,
            data3,
            reserved: [0u8; 2],
            sample_offset,
        };
        ipc.midi_queue_count += 1;
        true
    }

    pub fn push_control(&self, ctype: u32, arg0: u32, arg1: u32, channel: u32, path: &str) -> bool {
        let ipc = self.ipc();
        if ipc.control_queue_count as usize >= CONTROL_QUEUE_CAP {
            return false;
        }
        let mut ev = ControlEventIPC {
            ctype,
            arg0,
            arg1,
            channel,
            arg2: [0u8; CONTROL_PATH_MAX],
        };
        let bytes = path.as_bytes();
        let n = bytes.len().min(CONTROL_PATH_MAX - 1);
        ev.arg2[..n].copy_from_slice(&bytes[..n]);
        // LOAD relies on C++ side strnlen() — arg1 is free for TRANSPORT playhead.
        let i = ipc.control_queue_count as usize;
        ipc.control_queue[i] = ev;
        ipc.control_queue_count += 1;
        true
    }

    pub fn read_audio(&self) -> ([f32; AUDIO_BLOCK_SIZE], [f32; AUDIO_BLOCK_SIZE]) {
        let ipc = self.ipc();
        (ipc.master_left, ipc.master_right)
    }

    pub fn write_index(&self) -> u32 {
        self.ipc().bridge_write_index
    }

    pub fn block_timestamp(&self) -> u64 {
        self.ipc().block_timestamp
    }
}

impl Drop for Shm {
    fn drop(&mut self) {
        if !self.view.is_null() {
            unsafe { UnmapViewOfFile(windows_sys::Win32::System::Memory::MEMORY_MAPPED_VIEW_ADDRESS { Value: self.view as *mut c_void }) };
        }
        unsafe { CloseHandle(self.handle) };
    }
}

/// Process-wide shared-memory state for Tauri.
pub struct ShmState(pub Mutex<Option<Shm>>);

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn layout_matches_c_header() {
        // native_bridge/tests/shm_selfcheck.cpp asserts the same sizes.
        assert_eq!(std::mem::size_of::<MidiEventIPC>(), 12);
        assert_eq!(std::mem::size_of::<ControlEventIPC>(), 1040);
        assert_eq!(std::mem::size_of::<SharedAudioBufferIPC>(), 11160);
    }
}
