use crate::device;
use crate::ui;

const SP_MEM_ADDR_REG: usize = 0;
const SP_DRAM_ADDR_REG: usize = 1;
const SP_RD_LEN_REG: usize = 2;
const SP_WR_LEN_REG: usize = 3;
pub const SP_STATUS_REG: usize = 4;
const SP_DMA_FULL_REG: usize = 5;
const SP_DMA_BUSY_REG: usize = 6;
const SP_SEMAPHORE_REG: usize = 7;
pub const SP_REGS_COUNT: usize = 8;

pub const SP_PC_REG: usize = 0;
//const SP_IBIST_REG: usize = 1;
pub const SP_REGS2_COUNT: usize = 2;

/* SP_STATUS - read */
pub const SP_STATUS_HALT: u32 = 1 << 0;
const SP_STATUS_BROKE: u32 = 1 << 1;
const SP_STATUS_DMA_BUSY: u32 = 1 << 2;
const SP_STATUS_DMA_FULL: u32 = 1 << 3;
//const SP_STATUS_IO_FULL: u32 = 1 << 4;
const SP_STATUS_SSTEP: u32 = 1 << 5;
const SP_STATUS_INTR_BREAK: u32 = 1 << 6;
const SP_STATUS_SIG0: u32 = 1 << 7;
const SP_STATUS_SIG1: u32 = 1 << 8;
const SP_STATUS_SIG2: u32 = 1 << 9;
const SP_STATUS_SIG3: u32 = 1 << 10;
const SP_STATUS_SIG4: u32 = 1 << 11;
const SP_STATUS_SIG5: u32 = 1 << 12;
const SP_STATUS_SIG6: u32 = 1 << 13;
const SP_STATUS_SIG7: u32 = 1 << 14;

/* SP_STATUS - write */
const SP_CLR_HALT: u32 = 1 << 0;
pub const SP_SET_HALT: u32 = 1 << 1;
const SP_CLR_BROKE: u32 = 1 << 2;
const SP_CLR_INTR: u32 = 1 << 3;
const SP_SET_INTR: u32 = 1 << 4;
const SP_CLR_SSTEP: u32 = 1 << 5;
const SP_SET_SSTEP: u32 = 1 << 6;
const SP_CLR_INTR_BREAK: u32 = 1 << 7;
const SP_SET_INTR_BREAK: u32 = 1 << 8;
const SP_CLR_SIG0: u32 = 1 << 9;
const SP_SET_SIG0: u32 = 1 << 10;
const SP_CLR_SIG1: u32 = 1 << 11;
const SP_SET_SIG1: u32 = 1 << 12;
const SP_CLR_SIG2: u32 = 1 << 13;
const SP_SET_SIG2: u32 = 1 << 14;
const SP_CLR_SIG3: u32 = 1 << 15;
const SP_SET_SIG3: u32 = 1 << 16;
const SP_CLR_SIG4: u32 = 1 << 17;
const SP_SET_SIG4: u32 = 1 << 18;
const SP_CLR_SIG5: u32 = 1 << 19;
const SP_SET_SIG5: u32 = 1 << 20;
const SP_CLR_SIG6: u32 = 1 << 21;
const SP_SET_SIG6: u32 = 1 << 22;
const SP_CLR_SIG7: u32 = 1 << 23;
const SP_SET_SIG7: u32 = 1 << 24;

const RSP_MEM_MASK: usize = 0x1FFF;

#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum DmaDir {
    None,
    Write,
    Read,
}

#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct RspDma {
    pub dir: DmaDir,
    pub length: u32,
    pub memaddr: u32,
    pub dramaddr: u32,
}

#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Rsp {
    pub cpu: device::rsp_cpu::Cpu,
    pub regs: [u32; SP_REGS_COUNT],
    pub regs2: [u32; SP_REGS2_COUNT],
    #[serde(with = "serde_big_array::BigArray")]
    pub mem: [u8; 0x2000],
    pub fifo: [RspDma; 2],
    pub last_status_value: u32,
    pub run_after_dma: bool,
}

pub fn read_mem_fast(
    device: &device::Device,
    address: u64,
    _access_size: device::memory::AccessSize,
) -> u32 {
    let masked_address = address as usize & RSP_MEM_MASK;
    u32::from_be_bytes(
        device.rsp.mem[masked_address..masked_address + 4]
            .try_into()
            .unwrap(),
    )
}

pub fn read_mem(
    device: &mut device::Device,
    address: u64,
    access_size: device::memory::AccessSize,
) -> u32 {
    device::cop0::add_cycles(device, access_size as u64 / 4);

    let masked_address = address as usize & RSP_MEM_MASK;
    u32::from_be_bytes(
        device.rsp.mem[masked_address..masked_address + 4]
            .try_into()
            .unwrap(),
    )
}

pub fn write_mem(device: &mut device::Device, address: u64, value: u32, _mask: u32) {
    let masked_address = address as usize & RSP_MEM_MASK;
    let mut data = u32::from_be_bytes(
        device.rsp.mem[masked_address..masked_address + 4]
            .try_into()
            .unwrap(),
    );
    device::memory::masked_write_32(&mut data, value, 0xFFFFFFFF);
    device.rsp.mem[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());

    if masked_address & 0x1000 != 0 {
        // imem being updated
        device.rsp.cpu.instructions[(masked_address & 0xFFF) / 4].func =
            device::rsp_cpu::decode_opcode(device, data);
        device.rsp.cpu.instructions[(masked_address & 0xFFF) / 4].opcode = data;
    }

    // SH/SB are broken: They overwrite the whole 32 bit, filling everything that isn't written with zeroes
}

fn do_dma(device: &mut device::Device, dma: RspDma) {
    let l = dma.length;

    let length = ((l & 0xfff) | 7) + 1;
    let count = ((l >> 12) & 0xff) + 1;
    let skip = (l >> 20) & 0xff8;

    let mut mem_addr = dma.memaddr & 0xff8;
    let mut dram_addr = dma.dramaddr & 0xfffff8;
    let offset = dma.memaddr & 0x1000;

    ui::video::check_framebuffers(dram_addr, count * (length + skip) - skip);
    if dma.dir == DmaDir::Read {
        let mut j = 0;
        while j < count {
            let mut i = 0;
            while i < length {
                let data = u32::from_be_bytes(
                    device.rsp.mem[(offset + (mem_addr & 0xFFF)) as usize
                        ..(offset + (mem_addr & 0xFFF)) as usize + 4]
                        .try_into()
                        .unwrap(),
                );
                device
                    .rdram
                    .mem
                    .get_mut(dram_addr as usize..dram_addr as usize + 4)
                    .unwrap_or(&mut [0; 4])
                    .copy_from_slice(&data.to_ne_bytes());
                mem_addr += 4;
                dram_addr += 4;
                i += 4;
            }
            dram_addr += skip;
            j += 1;
        }
    } else {
        let mut j = 0;
        while j < count {
            let mut i = 0;
            while i < length {
                let data = u32::from_ne_bytes(
                    device
                        .rdram
                        .mem
                        .get(dram_addr as usize..dram_addr as usize + 4)
                        .unwrap_or(&[0; 4])
                        .try_into()
                        .unwrap_or_default(),
                );

                if offset != 0 {
                    // imem being updated
                    device.rsp.cpu.instructions[((mem_addr & 0xFFF) / 4) as usize].func =
                        device::rsp_cpu::decode_opcode(device, data);
                    device.rsp.cpu.instructions[((mem_addr & 0xFFF) / 4) as usize].opcode = data;
                }
                device.rsp.mem[(offset + (mem_addr & 0xFFF)) as usize
                    ..(offset + (mem_addr & 0xFFF)) as usize + 4]
                    .copy_from_slice(&data.to_be_bytes());
                mem_addr += 4;
                dram_addr += 4;
                i += 4;
            }
            dram_addr += skip;
            j += 1;
        }
    }
    device.rsp.regs[SP_MEM_ADDR_REG] = (mem_addr & 0xfff) + (dma.memaddr & 0x1000);
    device.rsp.regs[SP_DRAM_ADDR_REG] = dram_addr;
    device.rsp.regs[SP_RD_LEN_REG] = 0xff8;
    device.rsp.regs[SP_WR_LEN_REG] = 0xff8;

    device::events::create_event(
        device,
        device::events::EVENT_TYPE_SPDMA,
        device::rdram::rdram_calculate_cycles((count * length) as u64) + 9,
    );
}

fn fifo_push(device: &mut device::Device, dir: DmaDir) {
    if device.rsp.regs[SP_DMA_FULL_REG] != 0 {
        panic!("RSP DMA already full")
    }

    device.rsp.cpu.sync_point = true;

    if device.rsp.regs[SP_DMA_BUSY_REG] != 0 {
        device.rsp.fifo[1].dir = dir;
        if dir == DmaDir::Read {
            device.rsp.fifo[1].length = device.rsp.regs[SP_WR_LEN_REG]
        } else {
            device.rsp.fifo[1].length = device.rsp.regs[SP_RD_LEN_REG]
        }
        device.rsp.fifo[1].memaddr = device.rsp.regs[SP_MEM_ADDR_REG];
        device.rsp.fifo[1].dramaddr = device.rsp.regs[SP_DRAM_ADDR_REG];
        device.rsp.regs[SP_DMA_FULL_REG] = 1;
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_DMA_FULL
    } else {
        device.rsp.fifo[0].dir = dir;
        if dir == DmaDir::Read {
            device.rsp.fifo[0].length = device.rsp.regs[SP_WR_LEN_REG]
        } else {
            device.rsp.fifo[0].length = device.rsp.regs[SP_RD_LEN_REG]
        }
        device.rsp.fifo[0].memaddr = device.rsp.regs[SP_MEM_ADDR_REG];
        device.rsp.fifo[0].dramaddr = device.rsp.regs[SP_DRAM_ADDR_REG];
        device.rsp.regs[SP_DMA_BUSY_REG] = 1;
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_DMA_BUSY;

        do_dma(device, device.rsp.fifo[0])
    }
}

pub fn fifo_pop(device: &mut device::Device) {
    if device.rsp.regs[SP_DMA_FULL_REG] != 0 {
        device.rsp.fifo[0].dir = device.rsp.fifo[1].dir;
        device.rsp.fifo[0].length = device.rsp.fifo[1].length;
        device.rsp.fifo[0].memaddr = device.rsp.fifo[1].memaddr;
        device.rsp.fifo[0].dramaddr = device.rsp.fifo[1].dramaddr;
        device.rsp.regs[SP_DMA_FULL_REG] = 0;
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_DMA_FULL;

        do_dma(device, device.rsp.fifo[0])
    } else {
        device.rsp.regs[SP_DMA_BUSY_REG] = 0;
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_DMA_BUSY;
        if device.rsp.run_after_dma {
            device.rsp.run_after_dma = false;
            do_task(device);
        }
    }
}

pub fn read_regs(
    device: &mut device::Device,
    address: u64,
    _access_size: device::memory::AccessSize,
) -> u32 {
    if !device.rsp.cpu.running {
        device::cop0::add_cycles(device, 20);
    }
    let reg = (address & 0xFFFF) >> 2;
    match reg as usize {
        SP_STATUS_REG => {
            let value = device.rsp.regs[reg as usize]
                & (SP_STATUS_SIG0
                    | SP_STATUS_SIG1
                    | SP_STATUS_SIG2
                    | SP_STATUS_SIG3
                    | SP_STATUS_SIG4
                    | SP_STATUS_SIG5
                    | SP_STATUS_SIG6
                    | SP_STATUS_SIG7);
            if value == device.rsp.last_status_value && value != 0 {
                device.rsp.cpu.sync_point = true;
            }

            device.rsp.last_status_value = value;
            device.rsp.regs[reg as usize]
        }
        SP_SEMAPHORE_REG => {
            let value = device.rsp.regs[reg as usize];
            if value == 1 {
                device.rsp.cpu.sync_point = true;
            }
            device.rsp.regs[reg as usize] = 1;
            value
        }
        _ => device.rsp.regs[reg as usize],
    }
}

pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
    let reg = (address & 0xFFFF) >> 2;
    match reg as usize {
        SP_STATUS_REG => update_sp_status(device, value),
        SP_RD_LEN_REG => {
            device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask);
            fifo_push(device, DmaDir::Write)
        }
        SP_WR_LEN_REG => {
            device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask);
            fifo_push(device, DmaDir::Read)
        }
        SP_SEMAPHORE_REG => {
            device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], 0, mask)
        }
        _ => device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask),
    }
}

pub fn read_regs2(
    device: &mut device::Device,
    address: u64,
    _access_size: device::memory::AccessSize,
) -> u32 {
    device::cop0::add_cycles(device, 20);
    device.rsp.regs2[((address & 0xFFFF) >> 2) as usize]
}

pub fn write_regs2(device: &mut device::Device, address: u64, value: u32, mask: u32) {
    let reg = (address & 0xFFFF) >> 2;
    match reg as usize {
        SP_PC_REG => {
            device::memory::masked_write_32(
                &mut device.rsp.regs2[reg as usize],
                value & 0xFFC,
                mask,
            );
        }
        _ => device::memory::masked_write_32(&mut device.rsp.regs2[reg as usize], value, mask),
    }
}

fn update_sp_status(device: &mut device::Device, w: u32) {
    let was_halted = device.rsp.regs[SP_STATUS_REG] & SP_STATUS_HALT != 0;

    /* clear / set halt */
    if w & SP_CLR_HALT != 0 && w & SP_SET_HALT == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_HALT
    }
    if w & SP_SET_HALT != 0 && w & SP_CLR_HALT == 0 {
        device::events::remove_event(device, device::events::EVENT_TYPE_SP);
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_HALT
    }

    /* clear broke */
    if w & SP_CLR_BROKE != 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_BROKE
    }

    /* clear SP interrupt */
    if (w & SP_CLR_INTR) != 0 && (w & SP_SET_INTR) == 0 {
        device::mi::clear_rcp_interrupt(device, device::mi::MI_INTR_SP)
    }
    /* set SP interrupt */
    if (w & SP_SET_INTR) != 0 && (w & SP_CLR_INTR) == 0 {
        device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP);
    }

    /* clear / set single step */
    if w & SP_CLR_SSTEP != 0 && w & SP_SET_SSTEP == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SSTEP
    }
    if w & SP_SET_SSTEP != 0 && w & SP_CLR_SSTEP == 0 {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SSTEP
    }

    /* clear / set interrupt on break */
    if w & SP_CLR_INTR_BREAK != 0 && w & SP_SET_INTR_BREAK == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_INTR_BREAK
    }
    if w & SP_SET_INTR_BREAK != 0 && w & SP_CLR_INTR_BREAK == 0 {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_INTR_BREAK
    }

    /* clear / set signal 0 */
    if w & SP_CLR_SIG0 != 0 && w & SP_SET_SIG0 == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG0
    }
    if w & SP_SET_SIG0 != 0 && w & SP_CLR_SIG0 == 0 {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG0
    }

    /* clear / set signal 1 */
    if w & SP_CLR_SIG1 != 0 && w & SP_SET_SIG1 == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG1
    }
    if w & SP_SET_SIG1 != 0 && w & SP_CLR_SIG1 == 0 {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG1
    }

    /* clear / set signal 2 */
    if w & SP_CLR_SIG2 != 0 && w & SP_SET_SIG2 == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG2
    }
    if w & SP_SET_SIG2 != 0 && w & SP_CLR_SIG2 == 0 {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG2
    }

    /* clear / set signal 3 */
    if w & SP_CLR_SIG3 != 0 && w & SP_SET_SIG3 == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG3
    }
    if w & SP_SET_SIG3 != 0 && w & SP_CLR_SIG3 == 0 {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG3
    }

    /* clear / set signal 4 */
    if w & SP_CLR_SIG4 != 0 && w & SP_SET_SIG4 == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG4
    }
    if w & SP_SET_SIG4 != 0 && w & SP_CLR_SIG4 == 0 {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG4
    }

    /* clear / set signal 5 */
    if w & SP_CLR_SIG5 != 0 && w & SP_SET_SIG5 == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG5
    }
    if w & SP_SET_SIG5 != 0 && w & SP_CLR_SIG5 == 0 {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG5
    }

    /* clear / set signal 6 */
    if w & SP_CLR_SIG6 != 0 && w & SP_SET_SIG6 == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG6
    }
    if w & SP_SET_SIG6 != 0 && w & SP_CLR_SIG6 == 0 {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG6
    }

    /* clear / set signal 7 */
    if w & SP_CLR_SIG7 != 0 && w & SP_SET_SIG7 == 0 {
        device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG7
    }
    if w & SP_SET_SIG7 != 0 && w & SP_CLR_SIG7 == 0 {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG7
    }

    if device.rsp.regs[SP_STATUS_REG] & SP_STATUS_HALT == 0 && was_halted {
        device.rsp.cpu.broken = false;
        device.rsp.cpu.halted = false;
        do_task(device);
    }
}

fn do_task(device: &mut device::Device) {
    device.rsp.cpu.sync_point = false;
    device.rsp.last_status_value = 0;
    device.rdp.last_status_value = 0;
    if device.rsp.regs[SP_DMA_BUSY_REG] == 1 {
        device.rsp.run_after_dma = true
    } else {
        let timer = device::rsp_cpu::run(device);

        device::events::create_event(device, device::events::EVENT_TYPE_SP, timer)
    }
}

pub fn rsp_event(device: &mut device::Device) {
    if device.rsp.cpu.broken {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_HALT | SP_STATUS_BROKE;

        if device.rsp.regs[SP_STATUS_REG] & SP_STATUS_INTR_BREAK != 0 {
            device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP)
        }
        return;
    }
    if device.rsp.cpu.halted {
        device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_HALT;
        return;
    }
    do_task(device)
}

pub fn init(device: &mut device::Device) {
    device.rsp.regs[SP_STATUS_REG] = 1;
    device::rsp_cpu::init(device);
}
