//file format "sniff"

use binrw::{binrw,BinReaderExt,io::TakeSeekExt};

#[derive(Debug)]
pub enum Error{
	InvalidHeader(binrw::Error),
	UnexpectedEOF,
	InvalidBlockId(BlockId),
	Seek(std::io::Error),
}
impl std::fmt::Display for Error{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for Error{}

/* spec

//begin global header

//global metadata (32 bytes)
b"SNFB"
u32 format_version
u64 priming_bytes
	//how many bytes of the file must be read to guarantee all of the expected
	//format-specific metadata is available to facilitate streaming the remaining contents
	//used by the database to guarantee that it serves at least the bare minimum
u128 resource_uuid
	//identifies the file from anywhere for any other file

//global block layout (variable size)
u64 num_blocks
//the start of the first block is implicitly after the global header (32)
//num_blocks+1 used in Header.block_location is implicitly the end of the file
for block_id in 1..num_blocks{
	u64 first_byte
}

//end global header

//begin blocks

//each block is compressed with zstd or gz or something

*/
#[binrw]
#[brw(little)]
#[derive(Clone,Copy,Debug)]
pub(crate) enum FourCC{
	#[brw(magic=b"SNFM")]
	Map,
	#[brw(magic=b"SNFB")]
	Bot,
}
#[binrw]
#[brw(little)]
#[derive(Debug)]
pub struct Header{
	/// Type of file
	pub fourcc:FourCC,
	/// File format version
	pub version:u32,
	/// Minimum data required to know the location of all streamable resources for this specific file
	pub priming:u64,
	/// uuid for this file
	pub resource:u128,
	//don't need try_calc: the struct will force field initialization anyways and it can be calculated there
	pub block_count:u32,
	#[br(count=block_count+1)]
	pub block_location:Vec<u64>,
}
impl Header{
	pub const fn calculate_size(block_count:u32)->usize{
		4 // fourcc
		+4 // version
		+8 // priming
		+16 // resource
		+4 // block_count
		+(block_count as usize+1)*8 // block_location
	}
}

#[binrw]
#[brw(little)]
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,Ord,PartialEq,PartialOrd)]
pub struct BlockId(u32);

pub(crate) struct File<R:BinReaderExt>{
	header:Header,
	//reference to the data
	data:R,
}

impl<R:BinReaderExt> File<R>{
	pub(crate) fn new(mut input:R)->Result<File<R>,Error>{
		Ok(File{
			header:input.read_le().map_err(Error::InvalidHeader)?,
			data:input,
		})
	}
	pub(crate) fn data_mut(&mut self)->&mut R{
		&mut self.data
	}
	pub(crate) fn block_reader(&mut self,block_id:BlockId)->Result<binrw::io::TakeSeek<&mut R>,Error>{
		if self.header.block_location.len() as u32<=block_id.get(){
			return Err(Error::InvalidBlockId(block_id))
		}
		let block_start=self.header.block_location[block_id.get() as usize];
		let block_end=self.header.block_location[block_id.get() as usize+1];
		self.data.seek(std::io::SeekFrom::Start(block_start)).map_err(Error::Seek)?;
		Ok(self.data_mut().take_seek(block_end-block_start))
	}
	pub(crate) fn fourcc(&self)->FourCC{
		self.header.fourcc
	}
}
