use crate::macros::bit;

const fn rom_word(word_address: usize) -> *mut u16 {
    unsafe { (0x800_0000 as *mut u16).add(word_address) }
}

#[derive(Debug, Eq, PartialEq)]
pub enum EAResult {
    SUCCESS,
    FAIL,
    ABORT,
}

pub unsafe fn get_id() -> (u16, u16) {
    let p1 = rom_word(0x555);
    let p2 = rom_word(0x2AA);
    p1.write_volatile(0xAA);
    p2.write_volatile(0x55);
    p1.write_volatile(0x90);

    let dev = rom_word(0x1).read_volatile();
    let man = rom_word(0x0).read_volatile();

    p1.write(0xF0);

    (man, dev)
}

/* Wait for an embedded algorithm to complete, using the 'data polling status' system (see §5.5.2) */
pub unsafe fn wait_dps(read_addr: *mut u16, expected_data_bit: u16) -> EAResult {
    loop {
        let data_polling_status = read_addr.read_volatile();
        if bit!(data_polling_status, 7) == expected_data_bit {
            break EAResult::SUCCESS;
        };
        if bit!(data_polling_status, 5) == 1 {
            let dps_2 = read_addr.read_volatile();
            if bit!(dps_2, 7) == expected_data_bit {
                break EAResult::SUCCESS;
            }
            break EAResult::FAIL;
        }
        if bit!(data_polling_status, 1) == 1 {
            break EAResult::ABORT;
        }
    }
}

/* Write a buffer of at most 512 bytes to the given sector and write buffer line. */
pub unsafe fn write_buffer(sector_num: usize, wbl_num: usize, buf: &[u8]) -> EAResult {
    let buf_16 = core::slice::from_raw_parts(buf.as_ptr() as *const u16, buf.len() / 2);

    assert!(buf_16.len() <= 256);

    let sector_addr = rom_word(sector_num << 16);
    // the load address = the address that words will be written to
    let load_addr_base = sector_addr.add(wbl_num << 8);
    let last_load_addr = load_addr_base.add(buf_16.len() - 1);

    rom_word(0x555).write_volatile(0xAA);
    rom_word(0x2AA).write_volatile(0x55);

    sector_addr.write_volatile(0x25);
    sector_addr.write_volatile((buf_16.len() - 1) as u16);

    for (i, &word) in buf_16.iter().enumerate() {
        load_addr_base.add(i).write_volatile(word);
    }

    sector_addr.write_volatile(0x29);

    // wait
    let data = buf_16[buf_16.len() - 1];
    wait_dps(last_load_addr, bit!(data, 7))
}

/* Write a buffer of at most 131072 bytes to the given sector. */
pub unsafe fn write_sector(sector_num: usize, buf: &[u8]) {
    let n_bytes = buf.len();
    let n_wbls = n_bytes / 512;

    let (chunks, rest) = buf.as_chunks::<512>();
    for wbl_num in 0..n_wbls {
        let res = write_buffer(sector_num, wbl_num, &chunks[wbl_num]);
        if res != EAResult::SUCCESS {
            panic!("Write via buffer resulted in {:?}!", res);
        }
    }

    if !rest.is_empty() {
        let res = write_buffer(sector_num, n_wbls, rest);
        if res != EAResult::SUCCESS {
            panic!("Write via buffer resulted in {:?}!", res);
        }
    }
}

/* Erase a given sector */
pub unsafe fn erase_sector(sector_num: usize) -> EAResult {
    let p_555 = rom_word(0x555);
    let p_2aa = rom_word(0x2AA);

    let sector_addr = rom_word(sector_num << 16);

    p_555.write_volatile(0xAA);
    p_2aa.write_volatile(0x55);

    p_555.write_volatile(0x80);
    p_555.write_volatile(0xAA);
    p_2aa.write_volatile(0x55);

    sector_addr.write_volatile(0x30);

    wait_dps(sector_addr, 1)
}
