use core::{ptr, slice};

use crate::maybe_with_critical_section;

const SUCCESS_CODE: i32 = 0;
const ERROR_CODE: i32 = 1;
const ERASE_BYTE: u8 = 0xff;
const WORD_SIZE: u32 = 4;
const SECTOR_SIZE: u32 = 4 << 10;
const NUM_SECTORS: u32 = 4;
const FLASH_SIZE: u32 = SECTOR_SIZE * NUM_SECTORS;

static mut FLASH_LOCK: bool = true;
static mut FLASH_DATA: [u8; FLASH_SIZE as usize] = [0u8; FLASH_SIZE as usize];

macro_rules! print_error {
    ($($tt:tt)*) => {
        #[cfg(all(test, feature = "emulation"))]
        eprintln!($($tt)*)
    };
}

fn check<const ALIGN: u32, const SIZE: u32, const MAX_LEN: u32>(
    offset: u32,
    length: u32,
    data: *const u32,
    is_write: bool,
) -> bool {
    if offset % ALIGN > 0 {
        print_error!("Not aligned offset: {offset}");
        return false;
    }
    if length % ALIGN > 0 {
        print_error!("Not aligned length: {length}");
        return false;
    }
    if offset > SIZE {
        print_error!("Offset out of range: {offset} > {SIZE}");
        return false;
    }
    if offset + length > SIZE {
        print_error!("Length out of range: {offset} + {length} > {SIZE}");
        return false;
    }
    if length > MAX_LEN {
        print_error!("Length out of range: {length} > {MAX_LEN}");
        return false;
    }
    let addr = data as u32;
    if addr % ALIGN > 0 {
        print_error!("Not aligned data: {addr:#0x}");
        return false;
    }
    if is_write && unsafe { FLASH_LOCK } {
        print_error!("Flash locked");
        return false;
    }
    true
}

pub(crate) fn spiflash_read(src_addr: u32, data: *mut u32, len: u32) -> i32 {
    if check::<WORD_SIZE, FLASH_SIZE, SECTOR_SIZE>(src_addr, len, data, false) {
        maybe_with_critical_section(|| {
            let src = unsafe { slice::from_raw_parts_mut(data as *mut u8, len as _) };
            unsafe { src.copy_from_slice(&FLASH_DATA[src_addr as usize..][..len as usize]) };
        });
        SUCCESS_CODE
    } else {
        ERROR_CODE
    }
}

pub(crate) fn spiflash_unlock() -> i32 {
    maybe_with_critical_section(|| {
        unsafe { FLASH_LOCK = false };
    });
    SUCCESS_CODE
}

pub(crate) fn spiflash_erase_sector(sector_number: u32) -> i32 {
    if check::<1, NUM_SECTORS, 1>(sector_number, 1, ptr::null(), true) {
        maybe_with_critical_section(|| {
            let dst_addr = sector_number * SECTOR_SIZE;
            let len = SECTOR_SIZE;
            unsafe { FLASH_DATA[dst_addr as usize..][..len as usize].fill(ERASE_BYTE) };
        });
        SUCCESS_CODE
    } else {
        ERROR_CODE
    }
}

pub(crate) fn spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 {
    if check::<WORD_SIZE, FLASH_SIZE, SECTOR_SIZE>(dest_addr, len, data, true) {
        maybe_with_critical_section(|| {
            let dst = unsafe { slice::from_raw_parts(data as *const u8, len as _) };
            for (d, s) in unsafe { &mut FLASH_DATA[dest_addr as usize..][..len as usize] }
                .iter_mut()
                .zip(dst)
            {
                *d &= *s;
            }
        });
        SUCCESS_CODE
    } else {
        ERROR_CODE
    }
}
