use std::fmt::Debug;

use anyhow::{Context, Result, bail, ensure};

use crate::memory_manager::type_traits::TryToUsize;

pub trait MemoryStorable: Copy {}
pub mod type_traits;

impl MemoryStorable for f32 {}
impl MemoryStorable for f64 {}
impl MemoryStorable for i32 {}
impl MemoryStorable for i64 {}
impl MemoryStorable for u8 {}
impl MemoryStorable for i8 {}

#[derive(Debug, Clone, Copy)]
struct LoadRequest {
    pub index: usize,
    pub size: usize,
}

unsafe fn slice_from_u8<T>(slice: &[u8]) -> Result<&[T]>
where
    T: MemoryStorable,
{
    let size_t = size_of::<T>();
    let size = slice.len();
    ensure!(
        size >= size_t,
        "Size {} smaller than the data type size {}",
        size,
        size_t
    );
    ensure!(
        size.is_multiple_of(size_t),
        "Size not a multiple of selected data type size"
    );
    let (prefix, slice, suffix) = unsafe { slice.align_to::<T>() };
    ensure!(
        prefix.is_empty() && suffix.is_empty(),
        "{:?} {:?} T not aligned",
        prefix,
        suffix
    );
    Ok(slice)
}

unsafe fn slice_into_u8<T>(slice: &[T]) -> Result<&[u8]>
where
    T: MemoryStorable,
{
    ensure!(!slice.is_empty(), "Empty slice not convertable");
    let (prefix, slice, suffix) = unsafe { slice.align_to::<u8>() };
    ensure!(prefix.is_empty() && suffix.is_empty(), "T not aligned");
    Ok(slice)
}

//Not thread safe
#[derive(Debug, Clone)]
pub struct CoreMemory {
    memory: Vec<u8>,
    load_requests: Vec<LoadRequest>,
}

impl Default for CoreMemory {
    fn default() -> Self {
        Self::new()
    }
}

impl CoreMemory {
    pub fn new() -> Self {
        Self {
            memory: Vec::new(),
            load_requests: Vec::new(),
        }
    }

    pub fn reserve_load(&mut self, address: impl TryToUsize, size: impl TryToUsize) -> Result<&mut Self>
where {
        let address = address.try_into().context("address can not be negative")?;
        let size = size.try_into().context("size can not be negative")?;
        let load_request = LoadRequest {
            index: address,
            size,
        };
        if self.memory.len() < address + size {
            self.memory.resize((address + size) * 2, 0);
        }
        self.load_requests.push(load_request);
        Ok(self)
    }

    pub fn execute_load<T>(&mut self) -> Result<Vec<&[T]>>
    where
        T: MemoryStorable,
    {
        let Self {
            memory,
            load_requests,
        } = self;
        let mut res = Vec::new();
        for (load_index, load_request) in load_requests.drain(..).enumerate() {
            let LoadRequest { index, size } = load_request;
            let memory_slice = &memory[index..index + size];
            let memory_slice = unsafe { slice_from_u8(memory_slice) }
                .with_context(|| format!("Load number: {} Accessing from {} to {}", load_index, index, index + size))?;
            res.push(memory_slice);
        }
        Ok(res)
    }

    pub fn load<T>(&mut self, address: impl TryToUsize, size: impl TryToUsize) -> Result<Vec<&[T]>>
    where
        T: MemoryStorable,
    {
        let address = address.try_into().expect("address can not be negative");
        let size = size.try_into().expect("size can not be negative");
        self.reserve_load(address, size)?.execute_load()
    }

    pub fn execute_store<T>(&mut self, address: impl TryToUsize, element: &[T]) -> Result<()>
    where
        T: MemoryStorable,
    {
        let address = address.try_into().context("address can not be negative")?;
        let Self { memory, .. } = self;
        let size = std::mem::size_of_val(element);
        if memory.len() < address + size {
            memory.resize((address + size) * 2, 0);
        }
        let slice = unsafe { slice_into_u8(element) }?;
        let memory_slice = &mut memory[address..address + size];
        memory_slice.copy_from_slice(slice);
        Ok(())
    }

    pub fn memset(&mut self, address: impl TryToUsize, size: impl TryToUsize, val: u8) -> Result<()> {
        let address = address.try_into().expect("address can not be negative");
        let size = size.try_into().expect("size can not be negative");
        let Self { memory, .. } = self;
        if memory.len() < address + size {
            memory.resize((address + size) * 2, 0);
        }
        memory[address..address + size].fill(val);
        Ok(())
    }

    pub fn set_capacity(&mut self, size: impl TryToUsize) {
        let size = size.try_into().expect("size can not be negative");
        let Self { memory, .. } = self;
        if memory.len() < size {
            memory.resize(size, 0);
        }
    }

    pub fn get_len(&self) ->usize {
        self.memory.len()
    }

    pub(crate) fn clear(&mut self)  {
        self.memory.clear();
    }
}

#[cfg(test)]
mod test {

    use super::*;

    #[test]
    fn test_slice_from_u8() {
        let buff: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
        let transmuted: &[f32] = unsafe { slice_from_u8(&buff).unwrap() };
        assert!(
            transmuted[0] == 0_f32 && transmuted[1] == 0_f32,
            "Failed conversion from [00 00 00 00, 00 00 00 00] to [0_f32, 0_f32]"
        );

        let buff = [0xdb, 0x0f, 0x49, 0x40];
        let transmuted: &[f32] = unsafe { slice_from_u8(&buff).unwrap() };
        assert!(
            transmuted[0] == std::f32::consts::PI,
            "Failed conversion from [0xdb, 0x0f, 0x49, 0x40] to [3.14]"
        );

        let buff = [0x8f, 0xc2, 0xf5, 0x28, 0x5c, 0x8f, 0x18, 0x40];
        let transmuted: &[f64] = unsafe { slice_from_u8(&buff).unwrap() };
        assert!(
            transmuted[0] == 6.14_f64,
            "Failed conversion from [0x8f, 0xc2, 0xf5, 0x28, 0x5c, 0x8f, 0x18, 0x40] to [6.14]"
        );
    }

    #[test]
    fn test_slice_into_u8() {
        let buff: [f32; 2] = [0_f32, 0_f32];
        let transmuted: &[u8] = unsafe { slice_into_u8(&buff).unwrap() };
        assert!(
            transmuted[0] == 0
                && transmuted[1] == 0
                && transmuted[2] == 0
                && transmuted[3] == 0
                && transmuted[4] == 0
                && transmuted[5] == 0
                && transmuted[6] == 0
                && transmuted[7] == 0,
            "Failed conversion from [00 00 00 00, 00 00 00 00] to [0_f32, 0_f32]"
        );

        let buff = [std::f32::consts::PI];
        let transmuted: &[u8] = unsafe { slice_into_u8(&buff).unwrap() };
        println!("{:?}", transmuted);
        assert!(
            transmuted[0] == 0xdb
                && transmuted[1] == 0x0f
                && transmuted[2] == 0x49
                && transmuted[3] == 0x40,
            "Failed conversion from [0xdb, 0x0f, 0x49, 0x40] to [3.14]"
        );

        let buff = [0x8f, 0xc2, 0xf5, 0x28, 0x5c, 0x8f, 0x18, 0x40];
        let transmuted: &[f64] = unsafe { slice_from_u8(&buff).unwrap() };
        assert!(
            transmuted[0] == 6.14_f64,
            "Failed conversion from [0x8f, 0xc2, 0xf5, 0x28, 0x5c, 0x8f, 0x18, 0x40] to [6.14]"
        );
    }

    #[test]
    fn test_simple_load_store() {
        let mut core_memory = CoreMemory::new();
        core_memory.execute_store(0, &[5_f32, 6_f32, 7_f32, 15_f32]);
        core_memory.reserve_load(0, 4); // Load [5_f32]
        core_memory.reserve_load(8, 8); // Load [7_f32, 15_f32]
        let loads: Vec<&[f32]> = core_memory.execute_load().unwrap();
        let mut data = [0_f32; 2];
        data[0] = loads[0][0] + loads[1][0];
        core_memory.execute_store(4, &data[0..1]).unwrap();
        let loads: &[f32] = core_memory.reserve_load(0, 16).unwrap().execute_load().unwrap()[0];
        println!("{:?}", loads);
        assert!(loads[0] == 5_f32 && loads[1] == 12_f32 && loads[2] == 7_f32 && loads[3] == 15_f32)
    }
}
