use std::num::NonZeroU64;

use super::{AlignmentValue, BufferMut, BufferRef, Reader, SizeValue, Writer};

const UNIFORM_MIN_ALIGNMENT: AlignmentValue = AlignmentValue::new(16);

pub struct Metadata<E> {
    pub alignment: AlignmentValue,
    pub has_uniform_min_alignment: bool,
    pub min_size: SizeValue,
    pub is_pod: bool,
    pub extra: E,
}

impl Metadata<()> {
    pub const fn from_alignment_and_size(alignment: u64, size: u64) -> Self {
        Self {
            alignment: AlignmentValue::new(alignment),
            has_uniform_min_alignment: false,
            min_size: SizeValue::new(size),
            is_pod: false,
            extra: (),
        }
    }
}

// using forget() avoids "destructors cannot be evaluated at compile-time" error
// track #![feature(const_precise_live_drops)] (https://github.com/rust-lang/rust/issues/73255)

impl<E> Metadata<E> {
    #[inline]
    pub const fn alignment(self) -> AlignmentValue {
        let value = self.alignment;
        core::mem::forget(self);
        value
    }

    #[inline]
    pub const fn uniform_min_alignment(self) -> Option<AlignmentValue> {
        let value = self.has_uniform_min_alignment;
        core::mem::forget(self);
        match value {
            true => Some(UNIFORM_MIN_ALIGNMENT),
            false => None,
        }
    }

    #[inline]
    pub const fn min_size(self) -> SizeValue {
        let value = self.min_size;
        core::mem::forget(self);
        value
    }

    #[inline]
    pub const fn is_pod(self) -> bool {
        let value = self.is_pod;
        core::mem::forget(self);
        value
    }

    #[inline]
    pub const fn pod(mut self) -> Self {
        self.is_pod = true;
        self
    }

    #[inline]
    pub const fn no_pod(mut self) -> Self {
        self.is_pod = false;
        self
    }
}

/// Base trait for all [WGSL host-shareable types](https://gpuweb.github.io/gpuweb/wgsl/#host-shareable-types)
pub trait ShaderType {
    #[doc(hidden)]
    type ExtraMetadata;
    #[doc(hidden)]
    const METADATA: Metadata<Self::ExtraMetadata>;

    /// Represents the minimum size of `Self` (equivalent to [GPUBufferBindingLayout.minBindingSize](https://gpuweb.github.io/gpuweb/#dom-gpubufferbindinglayout-minbindingsize))
    ///
    /// For [WGSL fixed-footprint types](https://gpuweb.github.io/gpuweb/wgsl/#fixed-footprint-types)
    /// it represents [WGSL Size](https://gpuweb.github.io/gpuweb/wgsl/#alignment-and-size)
    /// (equivalent to [`ShaderSize::SHADER_SIZE`])
    ///
    /// For
    /// [WGSL runtime-sized arrays](https://gpuweb.github.io/gpuweb/wgsl/#runtime-sized) and
    /// [WGSL structs containing runtime-sized arrays](https://gpuweb.github.io/gpuweb/wgsl/#struct-types)
    /// (non fixed-footprint types)
    /// this will be calculated by assuming the array has one element
    #[inline]
    fn min_size() -> NonZeroU64 {
        Self::METADATA.min_size().0
    }

    /// Returns the size of `Self` at runtime
    ///
    /// For [WGSL fixed-footprint types](https://gpuweb.github.io/gpuweb/wgsl/#fixed-footprint-types)
    /// it's equivalent to [`Self::min_size`] and [`ShaderSize::SHADER_SIZE`]
    #[inline]
    fn size(&self) -> NonZeroU64 {
        Self::METADATA.min_size().0
    }

    #[doc(hidden)]
    const UNIFORM_COMPAT_ASSERT: fn() = || {};

    /// Asserts that `Self` meets the requirements of the
    /// [uniform address space restrictions on stored values](https://gpuweb.github.io/gpuweb/wgsl/#address-spaces-uniform) and the
    /// [uniform address space layout constraints](https://gpuweb.github.io/gpuweb/wgsl/#address-space-layout-constraints)
    ///
    /// # Examples
    ///
    /// ## Array
    ///
    /// Will panic since runtime-sized arrays are not compatible with the
    /// uniform address space restrictions on stored values
    ///
    /// ```should_panic
    /// # use crate::encase::ShaderType;
    /// <Vec<test_impl::Vec4f>>::assert_uniform_compat();
    /// ```
    ///
    /// Will panic since the stride is 4 bytes
    ///
    /// ```should_panic
    /// # use crate::encase::ShaderType;
    /// <[f32; 2]>::assert_uniform_compat();
    /// ```
    ///
    /// Will not panic since the stride is 16 bytes
    ///
    /// ```
    /// # use crate::encase::ShaderType;
    /// <[test_impl::Vec4f; 2]>::assert_uniform_compat();
    /// ```
    ///
    /// ## Struct
    ///
    /// Will panic since runtime-sized arrays are not compatible with the
    /// uniform address space restrictions on stored values
    ///
    /// ```should_panic
    /// # use crate::encase::ShaderType;
    /// #[derive(ShaderType)]
    /// struct Invalid {
    ///     #[shader(size(runtime))]
    ///     vec: Vec<test_impl::Vec4f>
    /// }
    /// Invalid::assert_uniform_compat();
    /// ```
    ///
    /// Will panic since the inner struct's size must be a multiple of 16
    ///
    /// ```should_panic
    /// # use crate::encase::ShaderType;
    /// #[derive(ShaderType)]
    /// struct S {
    ///     x: f32,
    /// }
    ///
    /// #[derive(ShaderType)]
    /// struct Invalid {
    ///     a: f32,
    ///     b: S, // offset between fields 'a' and 'b' must be at least 16 (currently: 4)
    /// }
    /// Invalid::assert_uniform_compat();
    /// ```
    ///
    /// Will not panic (fixed via #[shader(align)] attribute)
    ///
    /// ```
    /// # use crate::encase::ShaderType;
    /// # #[derive(ShaderType)]
    /// # struct S {
    /// #     x: f32,
    /// # }
    /// #[derive(ShaderType)]
    /// struct Valid {
    ///     a: f32,
    ///     #[shader(align(16))]
    ///     b: S,
    /// }
    /// Valid::assert_uniform_compat();
    /// ```
    ///
    /// Will not panic (fixed via size attribute)
    ///
    /// ```
    /// # use crate::encase::ShaderType;
    /// # #[derive(ShaderType)]
    /// # struct S {
    /// #     x: f32,
    /// # }
    /// #[derive(ShaderType)]
    /// struct Valid {
    ///     #[shader(size(16))]
    ///     a: f32,
    ///     b: S,
    /// }
    /// Valid::assert_uniform_compat();
    /// ```
    #[inline]
    fn assert_uniform_compat() {
        Self::UNIFORM_COMPAT_ASSERT();
    }

    // fn assert_can_write_into()
    // where
    //     Self: WriteInto,
    // {
    // }

    // fn assert_can_read_from()
    // where
    //     Self: ReadFrom,
    // {
    // }

    // fn assert_can_create_from()
    // where
    //     Self: CreateFrom,
    // {
    // }
}

/// Trait implemented for all [WGSL fixed-footprint types](https://gpuweb.github.io/gpuweb/wgsl/#fixed-footprint-types)
pub trait ShaderSize: ShaderType {
    /// Represents [WGSL Size](https://gpuweb.github.io/gpuweb/wgsl/#alignment-and-size) (equivalent to [`ShaderType::min_size`])
    const SHADER_SIZE: NonZeroU64 = Self::METADATA.min_size().0;
}

/// Trait implemented for
/// [WGSL runtime-sized arrays](https://gpuweb.github.io/gpuweb/wgsl/#runtime-sized) and
/// [WGSL structs containing runtime-sized arrays](https://gpuweb.github.io/gpuweb/wgsl/#struct-types)
/// (non fixed-footprint types)
pub trait CalculateSizeFor {
    /// Returns the size of `Self` assuming the (contained) runtime-sized array has `nr_of_el` elements
    fn calculate_size_for(nr_of_el: u64) -> NonZeroU64;
}

#[allow(clippy::len_without_is_empty)]
pub trait RuntimeSizedArray {
    fn len(&self) -> usize;
}

pub trait WriteInto {
    fn write_into<B>(&self, writer: &mut Writer<B>)
    where
        B: BufferMut;
}

pub trait ReadFrom {
    fn read_from<B>(&mut self, reader: &mut Reader<B>)
    where
        B: BufferRef;
}

pub trait CreateFrom: Sized {
    fn create_from<B>(reader: &mut Reader<B>) -> Self
    where
        B: BufferRef;
}
