use super::{
    AlignmentValue, BufferMut, BufferRef, CreateFrom, ReadFrom, Reader, Result, ShaderType,
    WriteInto, Writer,
};

/// Storage buffer wrapper facilitating RW operations
pub struct StorageBuffer<B> {
    inner: B,
}

impl<B> StorageBuffer<B> {
    pub const fn new(buffer: B) -> Self {
        Self { inner: buffer }
    }

    pub fn into_inner(self) -> B {
        self.inner
    }

    pub fn content_of<T, U>(item: &T) -> Result<U>
    where
        T: ShaderType + WriteInto,
        U: BufferMut + Default,
    {
        let mut buffer = StorageBuffer::new(U::default());
        buffer.write(item)?;
        Ok(buffer.into_inner())
    }
}

impl<B> From<B> for StorageBuffer<B> {
    fn from(buffer: B) -> Self {
        Self::new(buffer)
    }
}

impl<B> AsRef<B> for StorageBuffer<B> {
    fn as_ref(&self) -> &B {
        &self.inner
    }
}

impl<B> AsMut<B> for StorageBuffer<B> {
    fn as_mut(&mut self) -> &mut B {
        &mut self.inner
    }
}

impl<B: BufferMut> StorageBuffer<B> {
    pub fn write<T>(&mut self, value: &T) -> Result<()>
    where
        T: ?Sized + ShaderType + WriteInto,
    {
        let mut writer = Writer::new(value, &mut self.inner, 0)?;
        value.write_into(&mut writer);
        Ok(())
    }
}

impl<B: BufferRef> StorageBuffer<B> {
    pub fn read<T>(&self, value: &mut T) -> Result<()>
    where
        T: ?Sized + ShaderType + ReadFrom,
    {
        let mut writer = Reader::new::<T>(&self.inner, 0)?;
        value.read_from(&mut writer);
        Ok(())
    }

    pub fn create<T>(&self) -> Result<T>
    where
        T: ShaderType + CreateFrom,
    {
        let mut writer = Reader::new::<T>(&self.inner, 0)?;
        Ok(T::create_from(&mut writer))
    }
}

/// Uniform buffer wrapper facilitating RW operations
pub struct UniformBuffer<B> {
    inner: StorageBuffer<B>,
}

impl<B> UniformBuffer<B> {
    pub const fn new(buffer: B) -> Self {
        Self {
            inner: StorageBuffer::new(buffer),
        }
    }

    pub fn into_inner(self) -> B {
        self.inner.inner
    }

    pub fn content_of<T, U>(item: &T) -> Result<U>
    where
        T: ShaderType + WriteInto,
        U: BufferMut + Default,
    {
        let mut buffer = UniformBuffer::new(U::default());
        buffer.write(item)?;
        Ok(buffer.into_inner())
    }
}

impl<B> From<B> for UniformBuffer<B> {
    fn from(buffer: B) -> Self {
        Self::new(buffer)
    }
}

impl<B> AsRef<B> for UniformBuffer<B> {
    fn as_ref(&self) -> &B {
        &self.inner.inner
    }
}

impl<B> AsMut<B> for UniformBuffer<B> {
    fn as_mut(&mut self) -> &mut B {
        &mut self.inner.inner
    }
}

impl<B: BufferMut> UniformBuffer<B> {
    pub fn write<T>(&mut self, value: &T) -> Result<()>
    where
        T: ?Sized + ShaderType + WriteInto,
    {
        T::assert_uniform_compat();
        self.inner.write(value)
    }
}

impl<B: BufferRef> UniformBuffer<B> {
    pub fn read<T>(&self, value: &mut T) -> Result<()>
    where
        T: ?Sized + ShaderType + ReadFrom,
    {
        T::assert_uniform_compat();
        self.inner.read(value)
    }

    pub fn create<T>(&self) -> Result<T>
    where
        T: ShaderType + CreateFrom,
    {
        T::assert_uniform_compat();
        self.inner.create()
    }
}

/// Dynamic storage buffer wrapper facilitating RW operations
pub struct DynamicStorageBuffer<B> {
    inner: B,
    alignment: AlignmentValue,
    offset: usize,
}

impl<B> DynamicStorageBuffer<B> {
    /// Creates a new dynamic storage buffer wrapper with an alignment of 256
    /// (default alignment in the WebGPU spec).
    pub const fn new(buffer: B) -> Self {
        Self::new_with_alignment(buffer, 256)
    }

    /// Creates a new dynamic storage buffer wrapper with a given alignment.
    /// # Panics
    ///
    /// - if `alignment` is not a power of two.
    /// - if `alignment` is less than 32 (min alignment imposed by the WebGPU spec).
    pub const fn new_with_alignment(buffer: B, alignment: u64) -> Self {
        if alignment < 32 {
            panic!("Alignment must be at least 32!");
        }
        Self {
            inner: buffer,
            alignment: AlignmentValue::new(alignment),
            offset: 0,
        }
    }

    pub fn set_offset(&mut self, offset: u64) {
        if !self.alignment.is_aligned(offset) {
            panic!(
                "offset of {} bytes is not aligned to alignment of {} bytes",
                offset,
                self.alignment.get()
            );
        }

        self.offset = offset as usize;
    }

    pub fn into_inner(self) -> B {
        self.inner
    }
}

impl<B> From<B> for DynamicStorageBuffer<B> {
    fn from(buffer: B) -> Self {
        Self::new(buffer)
    }
}

impl<B> AsRef<B> for DynamicStorageBuffer<B> {
    fn as_ref(&self) -> &B {
        &self.inner
    }
}

impl<B> AsMut<B> for DynamicStorageBuffer<B> {
    fn as_mut(&mut self) -> &mut B {
        &mut self.inner
    }
}

impl<B: BufferMut> DynamicStorageBuffer<B> {
    pub fn write<T>(&mut self, value: &T) -> Result<u64>
    where
        T: ?Sized + ShaderType + WriteInto,
    {
        let offset = self.offset;

        let mut writer = Writer::new(value, &mut self.inner, offset)?;
        value.write_into(&mut writer);

        self.offset += self.alignment.round_up(value.size().get()) as usize;

        Ok(offset as u64)
    }
}

impl<B: BufferRef> DynamicStorageBuffer<B> {
    pub fn read<T>(&mut self, value: &mut T) -> Result<()>
    where
        T: ?Sized + ShaderType + ReadFrom,
    {
        let mut writer = Reader::new::<T>(&self.inner, self.offset)?;
        value.read_from(&mut writer);

        self.offset += self.alignment.round_up(value.size().get()) as usize;

        Ok(())
    }

    pub fn create<T>(&mut self) -> Result<T>
    where
        T: ShaderType + CreateFrom,
    {
        let mut writer = Reader::new::<T>(&self.inner, self.offset)?;
        let value = T::create_from(&mut writer);

        self.offset += self.alignment.round_up(value.size().get()) as usize;

        Ok(value)
    }
}

/// Dynamic uniform buffer wrapper facilitating RW operations
pub struct DynamicUniformBuffer<B> {
    inner: DynamicStorageBuffer<B>,
}

impl<B> DynamicUniformBuffer<B> {
    /// Creates a new dynamic uniform buffer wrapper with an alignment of 256
    /// (default alignment in the WebGPU spec).
    pub const fn new(buffer: B) -> Self {
        Self {
            inner: DynamicStorageBuffer::new(buffer),
        }
    }

    /// Creates a new dynamic uniform buffer wrapper with a given alignment.
    /// # Panics
    ///
    /// - if `alignment` is not a power of two.
    /// - if `alignment` is less than 32 (min alignment imposed by the WebGPU spec).
    pub const fn new_with_alignment(buffer: B, alignment: u64) -> Self {
        Self {
            inner: DynamicStorageBuffer::new_with_alignment(buffer, alignment),
        }
    }

    pub fn set_offset(&mut self, offset: u64) {
        self.inner.set_offset(offset);
    }

    pub fn into_inner(self) -> B {
        self.inner.inner
    }
}

impl<B> From<B> for DynamicUniformBuffer<B> {
    fn from(buffer: B) -> Self {
        Self::new(buffer)
    }
}

impl<B> AsRef<B> for DynamicUniformBuffer<B> {
    fn as_ref(&self) -> &B {
        &self.inner.inner
    }
}

impl<B> AsMut<B> for DynamicUniformBuffer<B> {
    fn as_mut(&mut self) -> &mut B {
        &mut self.inner.inner
    }
}

impl<B: BufferMut> DynamicUniformBuffer<B> {
    pub fn write<T>(&mut self, value: &T) -> Result<u64>
    where
        T: ?Sized + ShaderType + WriteInto,
    {
        T::assert_uniform_compat();
        self.inner.write(value)
    }
}

impl<B: BufferRef> DynamicUniformBuffer<B> {
    pub fn read<T>(&mut self, value: &mut T) -> Result<()>
    where
        T: ?Sized + ShaderType + ReadFrom,
    {
        T::assert_uniform_compat();
        self.inner.read(value)
    }

    pub fn create<T>(&mut self) -> Result<T>
    where
        T: ShaderType + CreateFrom,
    {
        T::assert_uniform_compat();
        self.inner.create()
    }
}
