pub mod account;

use {bytemuck::Pod, core::mem, pinocchio::program_error::ProgramError, shank::ShankType};

/// Trait for types that have a fixed, known size
pub trait SizeOf {
    /// Size of the type in bytes
    const SIZE_OF: usize;
}

/// Implement SizeOf for all Pod types
impl<T: Pod> SizeOf for T {
    const SIZE_OF: usize = mem::size_of::<T>();
}

/// Trait to represent a type that can be initialized.
pub trait Initializable {
    /// Return `true` if the object is initialized.
    fn is_initialized(&self) -> bool;
}

/// Representation of account state initialized flag.
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, ShankType)]
pub enum AccountState {
    /// Account is not yet initialized
    Uninitialized,
    /// Account has been initialized
    Initialized,
}

/// Load a reference for an initialized `T` from the given bytes
#[inline(always)]
pub fn load<'data, T: Pod + Initializable>(input: &'data [u8]) -> Result<&'data T, ProgramError> {
    if input.len() != T::SIZE_OF {
        return Err(ProgramError::InvalidAccountData);
    }

    let account = bytemuck::from_bytes::<T>(input);

    if !account.is_initialized() {
        return Err(ProgramError::UninitializedAccount);
    }

    Ok(account)
}

/// Load a mutable reference for an initialized `T` from the given bytes
#[inline(always)]
pub fn load_mut<'data, T: Pod + Initializable>(
    input: &'data mut [u8],
) -> Result<&'data mut T, ProgramError> {
    if input.len() != T::SIZE_OF {
        return Err(ProgramError::InvalidAccountData);
    }

    let account = bytemuck::from_bytes_mut::<T>(input);

    if !account.is_initialized() {
        return Err(ProgramError::UninitializedAccount);
    }

    Ok(account)
}

/// Load a reference without checking if the data is initialized
#[inline(always)]
pub fn load_unchecked<'data, T: Pod>(input: &'data [u8]) -> Result<&'data T, ProgramError> {
    if input.len() != T::SIZE_OF {
        return Err(ProgramError::InvalidAccountData);
    }

    Ok(bytemuck::from_bytes::<T>(input))
}

/// Load a mutable reference without checking if the data is initialized
#[inline(always)]
pub fn load_mut_unchecked<'data, T: Pod>(
    input: &'data mut [u8],
) -> Result<&'data mut T, ProgramError> {
    if input.len() != T::SIZE_OF {
        return Err(ProgramError::InvalidAccountData);
    }

    Ok(bytemuck::from_bytes_mut::<T>(input))
}
