use super::{
    bits::{rounddown, roundup},
    Error, Register, RISCV_PAGESIZE,
};
use bytes::Bytes;
use std::cmp::min;
use std::ptr;
pub mod flat;
pub mod sparse;
pub mod wxorx;
pub use ckb_vm_definitions::{
    memory::{FLAG_DIRTY, FLAG_EXECUTABLE, FLAG_FREEZED, FLAG_WRITABLE, FLAG_WXORX_BIT},
    MEMORY_FRAME_PAGE_SHIFTS, RISCV_MAX_MEMORY, RISCV_PAGE_SHIFTS,
};
#[inline(always)]
pub fn round_page_down(x: u64) -> u64 {
    rounddown(x, RISCV_PAGESIZE as u64)
}
#[inline(always)]
pub fn round_page_up(x: u64) -> u64 {
    roundup(x, RISCV_PAGESIZE as u64)
}
pub type Page = [u8; RISCV_PAGESIZE];
pub trait Memory {
    type REG: Register;
    fn new() -> Self;
    fn new_with_memory(memory_size: usize) -> Self;
    fn init_pages(
        &mut self,
        addr: u64,
        size: u64,
        flags: u8,
        source: Option<Bytes>,
        offset_from_addr: u64,
    ) -> Result<(), Error>;
    fn fetch_flag(&mut self, page: u64) -> Result<u8, Error>;
    fn set_flag(&mut self, page: u64, flag: u8) -> Result<(), Error>;
    fn clear_flag(&mut self, page: u64, flag: u8) -> Result<(), Error>;
    fn memory_size(&self) -> usize;
    fn memory_pages(&self) -> usize {
        self.memory_size() >> RISCV_PAGE_SHIFTS
    }
    fn store_byte(&mut self, addr: u64, size: u64, value: u8) -> Result<(), Error>;
    fn store_bytes(&mut self, addr: u64, value: &[u8]) -> Result<(), Error>;
    fn load_bytes(&mut self, addr: u64, size: u64) -> Result<Bytes, Error>;
    fn execute_load16(&mut self, addr: u64) -> Result<u16, Error>;
    fn execute_load32(&mut self, addr: u64) -> Result<u32, Error>;
    fn load8(&mut self, addr: &Self::REG) -> Result<Self::REG, Error>;
    fn load16(&mut self, addr: &Self::REG) -> Result<Self::REG, Error>;
    fn load32(&mut self, addr: &Self::REG) -> Result<Self::REG, Error>;
    fn load64(&mut self, addr: &Self::REG) -> Result<Self::REG, Error>;
    fn store8(&mut self, addr: &Self::REG, value: &Self::REG) -> Result<(), Error>;
    fn store16(&mut self, addr: &Self::REG, value: &Self::REG) -> Result<(), Error>;
    fn store32(&mut self, addr: &Self::REG, value: &Self::REG) -> Result<(), Error>;
    fn store64(&mut self, addr: &Self::REG, value: &Self::REG) -> Result<(), Error>;
    fn lr(&self) -> &Self::REG;
    fn set_lr(&mut self, value: &Self::REG);
}
#[inline(always)]
pub fn fill_page_data<M: Memory>(
    memory: &mut M,
    addr: u64,
    size: u64,
    source: Option<Bytes>,
    offset_from_addr: u64,
) -> Result<(), Error> {
    let mut written_size = 0;
    if offset_from_addr > 0 {
        let real_size = min(size, offset_from_addr);
        memory.store_byte(addr, real_size, 0)?;
        written_size += real_size;
    }
    if let Some(source) = source {
        let real_size = min(size - written_size, source.len() as u64);
        if real_size > 0 {
            memory.store_bytes(addr + written_size, &source[0..real_size as usize])?;
            written_size += real_size;
        }
    }
    if written_size < size {
        memory.store_byte(addr + written_size, size - written_size, 0)?;
    }
    Ok(())
}
pub fn get_page_indices(addr: u64, size: u64) -> Result<(u64, u64), Error> {
    let (addr_end, overflowed) = addr.overflowing_add(size);
    if overflowed {
        return Err(Error::MemOutOfBound);
    }
    if addr_end > RISCV_MAX_MEMORY as u64 {
        return Err(Error::MemOutOfBound);
    }
    let page = addr >> RISCV_PAGE_SHIFTS;
    let page_end = (addr_end - 1) >> RISCV_PAGE_SHIFTS;
    Ok((page, page_end))
}
pub fn check_permission<M: Memory>(
    memory: &mut M,
    page_indices: &(u64, u64),
    flag: u8,
) -> Result<(), Error> {
    for page in page_indices.0..=page_indices.1 {
        let page_flag = memory.fetch_flag(page)?;
        if (page_flag & FLAG_WXORX_BIT) != (flag & FLAG_WXORX_BIT) {
            return Err(Error::MemWriteOnExecutablePage);
        }
    }
    Ok(())
}
pub fn set_dirty<M: Memory>(memory: &mut M, page_indices: &(u64, u64)) -> Result<(), Error> {
    for page in page_indices.0..=page_indices.1 {
        memory.set_flag(page, FLAG_DIRTY)?
    }
    Ok(())
}
#[inline(always)]
pub fn memset(slice: &mut [u8], value: u8) {
    let p = slice.as_mut_ptr();
    unsafe {
        ptr::write_bytes(p, value, slice.len());
    }
}