#![deny(unconditional_recursion)]
#![no_std]



use generic_array::ArrayLength;



/// Represents a disk that can be read from and written to.
///
/// The disk is divided into blocks of a fixed size.
///
/// Reads and writes are in bytes but must be aligned to the disk's write granularity.
///
/// Erases are in full block units.
///
/// The first byte of the disk is at offset `0`, which is also the start of the
/// first block with index `0`.
///
pub trait Disk: embedded_io::ErrorType {
	/// The size of an erasable block in bytes.
	const ERASE_BLOCK_SIZE: usize;

	/// The minimum size of a write operation in bytes.
	///
	/// Also defines the write alignment.
	///
	/// Currently, also used for read granularity.
	#[allow(non_camel_case_types)]
	type WRITE_GRANULARITY: ArrayLength;

	/// Reads data from the disk into the buffer.
	///
	/// The given offset is in bytes and must be aligned to `WRITE_GRANULARITY`.
	/// The buffer must be at least `WRITE_GRANULARITY` bytes long.
	fn read(&mut self, offset: usize, buf: &mut [u8]) -> Result<usize, Self::Error>;

	/// Writes data from the buffer to the disk.
	///
	/// The given offset is in bytes and must be aligned to `WRITE_GRANULARITY`.
	/// The buffer must be at least `WRITE_GRANULARITY` bytes long.
	///
	/// A write should only happen on a section of a block that has been
	/// previously erased and not yet written to.
	/// It is permissible to write independent sections of a block in any order.
	fn write(&mut self, offset: usize, buf: &[u8]) -> Result<usize, Self::Error>;

	/// Erases the block at the given block index.
	///
	/// The block index is in units of `ERASE_BLOCK_SIZE`.
	///
	/// The first block has index `0` and the last block has index
	/// `self.block_count() - 1`.
	fn erase(&mut self, block: usize) -> Result<(), Self::Error>;

	/// Returns the number of blocks on the disk.
	///
	/// The number of blocks is fixed and does not change.
	fn block_count(&self) -> usize;

	/// Reads the exact number of bytes required to fill the buffer.
	///
	/// The given offset is in bytes and must be aligned to `WRITE_GRANULARITY`.
	/// The buffer size must be a multiple of `WRITE_GRANULARITY`.
	///
	/// If the buffer cannot be completely filled, an error is returned.
	#[track_caller]
	fn read_exact(
		&mut self,
		mut offset: usize,
		mut buf: &mut [u8],
	) -> Result<(), embedded_io::ReadExactError<Self::Error>> {
		while !buf.is_empty() {
			let bytes_read = self.read(offset, buf)?;
			if bytes_read == 0 {
				return Err(embedded_io::ReadExactError::UnexpectedEof);
			}
			offset += bytes_read;
			buf = &mut buf[bytes_read..];
		}
		Ok(())
	}

	/// Writes the exact number of bytes from the buffer to the disk.
	///
	/// The given offset is in bytes and must be aligned to `WRITE_GRANULARITY`.
	/// The buffer size must be a multiple of `WRITE_GRANULARITY`.
	///
	/// If the buffer cannot be completely written, an error is returned.
	#[track_caller]
	fn write_all(&mut self, mut offset: usize, mut buf: &[u8]) -> Result<(), Self::Error> {
		while !buf.is_empty() {
			let bytes_written = self.write(offset, buf)?;
			if bytes_written == 0 {
				panic!("write() returned Ok(0)");
			}
			offset += bytes_written;
			buf = &buf[bytes_written..];
		}
		Ok(())
	}
}

impl<T: Disk> Disk for &mut T {
	type WRITE_GRANULARITY = T::WRITE_GRANULARITY;

	const ERASE_BLOCK_SIZE: usize = T::ERASE_BLOCK_SIZE;

	#[track_caller]
	fn read(&mut self, offset: usize, buf: &mut [u8]) -> Result<usize, Self::Error> {
		(**self).read(offset, buf)
	}

	#[track_caller]
	fn write(&mut self, offset: usize, buf: &[u8]) -> Result<usize, Self::Error> {
		(**self).write(offset, buf)
	}

	#[track_caller]
	fn erase(&mut self, block: usize) -> Result<(), Self::Error> {
		(**self).erase(block)
	}

	#[track_caller]
	fn block_count(&self) -> usize {
		(**self).block_count()
	}
}
