//file format "sniff"
use std::io::{SeekFrom,Error as IoError};
use binrw::binrw;
use binrw::BinReaderExt;
use binrw::io::{TakeSeek,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(Clone,Copy,Debug,Hash,id::Id,Eq,Ord,PartialEq,PartialOrd)]
pub struct BlockId(u32);
#[binrw]
#[brw(little)]
#[derive(Debug,Clone)]
struct BlockPosition(u64);

/// The range of data for a specific Block, relative to the start of the file.
#[derive(Debug,Clone)]
pub struct BlockInfo(core::ops::Range<u64>);
impl BlockInfo{
	/// Create an adapter which seeks to the block start and reads at most the block length.
	pub fn take_seek<R:BinReaderExt>(&self,mut data:R)->Result<TakeSeek<R>,IoError>{
		data.seek(SeekFrom::Start(self.start))?;
		Ok(data.take_seek(self.end-self.start))
	}
}
impl core::ops::Deref for BlockInfo{
	type Target=core::ops::Range<u64>;
	fn deref(&self)->&Self::Target{
		&self.0
	}
}

#[binrw]
#[brw(little)]
#[derive(Debug)]
pub struct Header{
	/// Type of file
	fourcc:FourCC,
	/// File format version
	version:u32,
	/// Minimum data required to know the location of all streamable resources for this specific file
	priming:u64,
	/// uuid for this file
	resource:u128,
	//don't need try_calc: the struct will force field initialization anyways and it can be calculated there
	block_count:u32,
	#[br(count=block_count+1)]
	block_positions:Vec<BlockPosition>,
}
impl Header{
	pub const fn fourcc(&self)->FourCC{
		self.fourcc
	}
	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_positions
	}
	pub fn block_info(&self,block_id:BlockId)->Result<BlockInfo,Error>{
		let BlockId(id)=block_id;
		if self.block_positions.len() as u32<=id{
			return Err(Error::InvalidBlockId(block_id))
		}
		let BlockPosition(start)=self.block_positions[id as usize];
		let BlockPosition(end)=self.block_positions[id as usize+1];
		Ok(BlockInfo(start..end))
	}
}
