use std::{ffi::c_void, ptr, slice};

use anyhow::{anyhow, Result};
use bitflags::bitflags;
use num_derive::{FromPrimitive, ToPrimitive};

#[repr(C)]
struct VB {
    _data: [u8; 0],
}

#[allow(non_camel_case_types)]
type c_int = i32;
#[allow(non_camel_case_types)]
type c_uint = u32;

#[repr(u32)]
#[derive(FromPrimitive, ToPrimitive)]
enum VBDataType {
    S8 = 0,
    U8 = 1,
    S16 = 2,
    U16 = 3,
    S32 = 4,
    F32 = 5,
}

#[repr(i32)]
#[derive(FromPrimitive, ToPrimitive)]
enum VBOption {
    PseudoHalt = 0,
}

bitflags! {
    #[repr(transparent)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    pub struct VBKey: u16 {
        const PWR = 0x0001;
        const SGN = 0x0002;
        const A   = 0x0004;
        const B   = 0x0008;
        const RT  = 0x0010;
        const LT  = 0x0020;
        const RU  = 0x0040;
        const RR  = 0x0080;
        const LR  = 0x0100;
        const LL  = 0x0200;
        const LD  = 0x0400;
        const LU  = 0x0800;
        const STA = 0x1000;
        const SEL = 0x2000;
        const RL  = 0x4000;
        const RD  = 0x8000;
    }
}

type OnFrame = extern "C" fn(sim: *mut VB) -> c_int;

#[link(name = "vb")]
extern "C" {
    #[link_name = "vbEmulate"]
    fn vb_emulate(sim: *mut VB, cycles: *mut u32) -> c_int;
    #[link_name = "vbEmulateEx"]
    fn vb_emulate_ex(sims: *mut *mut VB, count: c_uint, cycles: *mut u32) -> c_int;
    #[link_name = "vbGetCartROM"]
    fn vb_get_cart_rom(sim: *mut VB, size: *mut u32) -> *mut c_void;
    #[link_name = "vbGetPixels"]
    fn vb_get_pixels(
        sim: *mut VB,
        left: *mut c_void,
        left_stride_x: c_int,
        left_stride_y: c_int,
        right: *mut c_void,
        right_stride_x: c_int,
        right_stride_y: c_int,
    );
    #[link_name = "vbGetSamples"]
    fn vb_get_samples(
        sim: *mut VB,
        typ_: *mut VBDataType,
        capacity: *mut c_uint,
        position: *mut c_uint,
    ) -> *mut c_void;
    #[link_name = "vbGetUserData"]
    fn vb_get_user_data(sim: *mut VB) -> *mut c_void;
    #[link_name = "vbInit"]
    fn vb_init(sim: *mut VB) -> *mut VB;
    #[link_name = "vbReset"]
    fn vb_reset(sim: *mut VB);
    #[link_name = "vbSetCartROM"]
    fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int;
    #[link_name = "vbSetFrameCallback"]
    fn vb_set_frame_callback(sim: *mut VB, on_frame: OnFrame);
    #[link_name = "vbSetKeys"]
    fn vb_set_keys(sim: *mut VB, keys: u16) -> u16;
    #[link_name = "vbSetOption"]
    fn vb_set_option(sim: *mut VB, key: VBOption, value: c_int);
    #[link_name = "vbSetPeer"]
    fn vb_set_peer(sim: *mut VB, peer: *mut VB);
    #[link_name = "vbSetSamples"]
    fn vb_set_samples(
        sim: *mut VB,
        samples: *mut c_void,
        typ_: VBDataType,
        capacity: c_uint,
    ) -> c_int;
    #[link_name = "vbSetUserData"]
    fn vb_set_user_data(sim: *mut VB, tag: *mut c_void);
    #[link_name = "vbSizeOf"]
    fn vb_size_of() -> usize;
}

extern "C" fn on_frame(sim: *mut VB) -> i32 {
    // SAFETY: the *mut VB owns its userdata.
    // There is no way for the userdata to be null or otherwise invalid.
    let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
    data.frame_seen = true;
    1
}

const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4;
const AUDIO_CAPACITY_FLOATS: usize = AUDIO_CAPACITY_SAMPLES * 2;
pub const EXPECTED_FRAME_SIZE: usize = 834 * 2;

struct VBState {
    frame_seen: bool,
}

#[repr(transparent)]
pub struct Sim {
    sim: *mut VB,
}

// SAFETY: the memory pointed to by sim is valid
unsafe impl Send for Sim {}

impl Sim {
    pub fn new() -> Self {
        // init the VB instance itself
        let size = unsafe { vb_size_of() };
        // allocate a vec of u64 so that this memory is 8-byte aligned
        let memory = vec![0u64; size.div_ceil(4)];
        let sim: *mut VB = Box::into_raw(memory.into_boxed_slice()).cast();
        unsafe { vb_init(sim) };
        unsafe { vb_set_option(sim, VBOption::PseudoHalt, 1) };
        unsafe { vb_reset(sim) };

        // set up userdata
        let state = VBState { frame_seen: false };
        unsafe { vb_set_user_data(sim, Box::into_raw(Box::new(state)).cast()) };
        unsafe { vb_set_frame_callback(sim, on_frame) };

        // set up audio buffer
        let audio_buffer = vec![0.0f32; AUDIO_CAPACITY_FLOATS];
        let samples: *mut c_void = Box::into_raw(audio_buffer.into_boxed_slice()).cast();
        unsafe { vb_set_samples(sim, samples, VBDataType::F32, AUDIO_CAPACITY_SAMPLES as u32) };

        Sim { sim }
    }

    pub fn reset(&mut self) {
        unsafe { vb_reset(self.sim) };
    }

    pub fn load_rom(&mut self, rom: Vec<u8>) -> Result<()> {
        self.unload_rom();

        let size = rom.len() as u32;
        let rom = Box::into_raw(rom.into_boxed_slice()).cast();
        let status = unsafe { vb_set_cart_rom(self.sim, rom, size) };
        if status == 0 {
            Ok(())
        } else {
            let _: Vec<u8> =
                unsafe { Vec::from_raw_parts(rom.cast(), size as usize, size as usize) };
            Err(anyhow!("Invalid ROM size of {} bytes", size))
        }
    }

    pub fn clone_rom(&self) -> Option<Vec<u8>> {
        let mut size = 0;
        let rom = unsafe { vb_get_cart_rom(self.sim, &mut size) };
        if rom.is_null() {
            return None;
        }
        // SAFETY: rom definitely points to a valid array of `size` bytes
        let slice: &[u8] = unsafe { slice::from_raw_parts(rom.cast(), size as usize) };
        Some(slice.to_vec())
    }

    fn unload_rom(&mut self) -> Option<Vec<u8>> {
        let mut size = 0;
        let rom = unsafe { vb_get_cart_rom(self.sim, &mut size) };
        if rom.is_null() {
            return None;
        }
        unsafe { vb_set_cart_rom(self.sim, ptr::null_mut(), 0) };
        let vec = unsafe { Vec::from_raw_parts(rom.cast(), size as usize, size as usize) };
        Some(vec)
    }

    pub fn link(&mut self, peer: &mut Sim) {
        unsafe { vb_set_peer(self.sim, peer.sim) };
    }

    pub fn unlink(&mut self) {
        unsafe { vb_set_peer(self.sim, ptr::null_mut()) };
    }

    pub fn emulate(&mut self) {
        let mut cycles = 20_000_000;
        unsafe { vb_emulate(self.sim, &mut cycles) };
    }

    pub fn emulate_many(sims: &mut [Sim]) {
        let mut cycles = 20_000_000;
        let count = sims.len() as c_uint;
        let sims = sims.as_mut_ptr().cast();
        unsafe { vb_emulate_ex(sims, count, &mut cycles) };
    }

    pub fn read_pixels(&mut self, buffers: &mut [u8]) -> bool {
        // SAFETY: the *mut VB owns its userdata.
        // There is no way for the userdata to be null or otherwise invalid.
        let data: &mut VBState = unsafe { &mut *vb_get_user_data(self.sim).cast() };
        if !data.frame_seen {
            return false;
        }
        data.frame_seen = false;

        // the buffer must be big enough for our data
        assert!(buffers.len() >= 384 * 224 * 2);
        unsafe {
            vb_get_pixels(
                self.sim,
                buffers.as_mut_ptr().cast(),
                2,
                384 * 2,
                buffers.as_mut_ptr().offset(1).cast(),
                2,
                384 * 2,
            );
        };
        true
    }

    pub fn read_samples(&mut self, samples: &mut Vec<f32>, weight: f32) {
        let mut position = 0;
        let ptr =
            unsafe { vb_get_samples(self.sim, ptr::null_mut(), ptr::null_mut(), &mut position) };
        // SAFETY: position is an offset in a buffer of (f32, f32). so, position * 2 is an offset in a buffer of f32.
        let read_samples: &[f32] =
            unsafe { slice::from_raw_parts(ptr.cast(), position as usize * 2) };
        samples.resize(read_samples.len(), 0.0);
        for (index, sample) in read_samples.iter().enumerate() {
            samples[index] += sample * weight;
        }

        unsafe {
            vb_set_samples(
                self.sim,
                ptr,
                VBDataType::F32,
                AUDIO_CAPACITY_SAMPLES as u32,
            )
        };
    }

    pub fn set_keys(&mut self, keys: VBKey) {
        unsafe { vb_set_keys(self.sim, keys.bits()) };
    }
}

impl Drop for Sim {
    fn drop(&mut self) {
        let ptr =
            unsafe { vb_get_samples(self.sim, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()) };
        // SAFETY: the audio buffer originally came from a Vec<u32>
        let floats: Vec<f32> = unsafe {
            Vec::from_raw_parts(ptr.cast(), AUDIO_CAPACITY_FLOATS, AUDIO_CAPACITY_FLOATS)
        };
        drop(floats);

        // SAFETY: the *mut VB owns its userdata.
        // There is no way for the userdata to be null or otherwise invalid.
        let ptr: *mut VBState = unsafe { vb_get_user_data(self.sim).cast() };
        // SAFETY: we made this pointer ourselves, we can for sure free it
        unsafe { drop(Box::from_raw(ptr)) };

        // If we're linked to another sim, unlink from them.
        unsafe { vb_set_peer(self.sim, ptr::null_mut()) };

        let len = unsafe { vb_size_of() }.div_ceil(4);
        // SAFETY: the sim's memory originally came from a Vec<u64>
        let bytes: Vec<u64> = unsafe { Vec::from_raw_parts(self.sim.cast(), len, len) };
        drop(bytes);
    }
}