use armv4t_emu::Memory;

pub enum AccessKind {
    Read,
    Write,
}

pub struct Access {
    pub kind: AccessKind,
    pub addr: u32,
    pub val: u32,
    pub len: usize,
}

/// Wraps a `Memory` object, logging any accesses with the provided callback.
#[derive(Debug)]
pub struct MemSniffer<'a, M, F: FnMut(Access)> {
    mem: &'a mut M,
    addrs: &'a [u32],
    on_access: F,
}

impl<'a, M: Memory, F: FnMut(Access)> MemSniffer<'a, M, F> {
    pub fn new(mem: &'a mut M, addrs: &'a [u32], on_access: F) -> MemSniffer<'a, M, F> {
        MemSniffer {
            mem,
            addrs,
            on_access,
        }
    }
}

macro_rules! impl_memsniff_r {
    ($fn:ident, $ret:ty) => {
        fn $fn(&mut self, addr: u32) -> $ret {
            let ret = self.mem.$fn(addr);
            if self.addrs.contains(&addr) {
                (self.on_access)(Access {
                    kind: AccessKind::Read,
                    addr,
                    val: ret as u32,
                    len: ret.to_le_bytes().len(),
                });
            }
            ret
        }
    };
}

macro_rules! impl_memsniff_w {
    ($fn:ident, $val:ty) => {
        fn $fn(&mut self, addr: u32, val: $val) {
            self.mem.$fn(addr, val);
            if self.addrs.contains(&addr) {
                (self.on_access)(Access {
                    kind: AccessKind::Write,
                    addr,
                    val: val as u32,
                    len: val.to_le_bytes().len(),
                });
            }
        }
    };
}

impl<'a, M: Memory, F: FnMut(Access)> Memory for MemSniffer<'a, M, F> {
    impl_memsniff_r!(r8, u8);
    impl_memsniff_r!(r16, u16);
    impl_memsniff_r!(r32, u32);
    impl_memsniff_w!(w8, u8);
    impl_memsniff_w!(w16, u16);
    impl_memsniff_w!(w32, u32);
}
