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

// the bit chunks are deposited in reverse
fn read_trey_float(bits:u32)->f32{
	let s=bits&1;
	let e=(bits>>1)&((1<<8)-1);
	let m=(bits>>(1+8))&((1<<23)-1);
	f32::from_bits(m|(e<<23)|(s<<31))
}
fn read_trey_double(bits:u64)->f64{
	let s=bits&1;
	let e=(bits>>1)&((1<<11)-1);
	let m=(bits>>(1+11))&((1<<52)-1);
	f64::from_bits(m|(e<<52)|(s<<63))
}

#[binrw]
#[brw(little)]
pub struct Vector2{
	#[br(map=read_trey_float)]
	pub x:f32,
	#[br(map=read_trey_float)]
	pub y:f32,
}
#[binrw]
#[brw(little)]
pub struct Vector3{
	#[br(map=read_trey_float)]
	pub x:f32,
	#[br(map=read_trey_float)]
	pub y:f32,
	#[br(map=read_trey_float)]
	pub z:f32,
}

bitflags::bitflags!{
	pub struct GameControls:u32{
		const MoveForward=1<<0;
		const MoveLeft=1<<1;
		const MoveBack=1<<2;
		const MoveRight=1<<3;
		const MoveUp=1<<4;
		const MoveDown=1<<5;
		const LookUp=1<<6;
		const LookLeft=1<<7;
		const LookDown=1<<8;
		const LookRight=1<<9;
		const Jump=1<<10;
		const Crouch=1<<11;
		const Sprint=1<<12;
		const Zoom=1<<13;
		const Use=1<<14;
		const Action1=1<<15;
		const Action2=1<<16;
	}
}
#[derive(Debug)]
pub struct GameControlsError;
impl std::fmt::Display for GameControlsError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for GameControlsError{}
impl GameControls{
	fn try_from_bits(bits:u32)->Result<Self,GameControlsError>{
		Self::from_bits(bits).ok_or(GameControlsError)
	}
}

// input
#[binrw]
#[brw(little)]
pub struct InputEvent{
	#[br(try_map=GameControls::try_from_bits)]
	#[bw(map=GameControls::bits)]
	pub game_controls:GameControls,
	pub mouse_pos:Vector2,
}
#[binrw]
#[brw(little)]
pub struct TimedInputEvent{
	#[br(map=read_trey_double)]
	pub time:f64,
	pub event:InputEvent,
}

// output
bitflags::bitflags!{
	pub struct TickInfo:u32{
		const TickEnd=1<<0;
		const Jump=1<<1;
		const Strafe=1<<2;
		const Touching=1<<3;
	}
}
#[derive(Debug)]
pub struct TickInfoError;
impl std::fmt::Display for TickInfoError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for TickInfoError{}
impl TickInfo{
	fn try_from_bits(bits:u32)->Result<Self,TickInfoError>{
		Self::from_bits(bits).ok_or(TickInfoError)
	}
}
#[binrw]
#[brw(little)]
pub struct OutputEvent{
	#[br(try_map=TickInfo::try_from_bits)]
	#[bw(map=TickInfo::bits)]
	pub tick_info:TickInfo,
	pub angles:Vector3,
	pub position:Vector3,
	pub velocity:Vector3,
	pub acceleration:Vector3,
}
#[binrw]
#[brw(little)]
pub struct TimedOutputEvent{
	#[br(map=read_trey_double)]
	pub time:f64,
	pub event:OutputEvent,
}

// sound
#[binrw]
#[brw(little)]
pub enum SoundType{
	#[brw(magic=101u32)]
	TrackGround,
	#[brw(magic=102u32)]
	TrackLadder,
	#[brw(magic=103u32)]
	TrackWater,
	#[brw(magic=104u32)]
	TrackAir,
	#[brw(magic=201u32)]
	JumpGround,
	#[brw(magic=202u32)]
	JumpLadder,
	#[brw(magic=301u32)]
	SmashGround,
	#[brw(magic=302u32)]
	SmashWall,
}
#[binrw]
#[brw(little)]
pub struct SoundEvent{
	pub sound_type:SoundType,
	/// Roblox enum
	pub material:u32,
}
#[binrw]
#[brw(little)]
pub struct TimedSoundEvent{
	#[br(map=read_trey_double)]
	pub time:f64,
	pub event:SoundEvent,
}

// world
#[binrw]
#[brw(little)]
pub struct WorldEventReset{
	pub position:Vector3,
}
#[binrw]
#[brw(little)]
pub struct WorldEventButton{
	#[br(pad_after=8)]
	pub button_id:u32,
}
#[binrw]
#[brw(little)]
pub struct WorldEventSetTime{
	#[br(map=read_trey_double)]
	#[br(pad_after=4)]
	pub time:f64,
}
#[binrw]
#[brw(little)]
pub struct WorldEventSetPaused{
	#[br(map=|paused:u32|paused!=0)]
	#[bw(map=|paused:&bool|*paused as u32)]
	#[br(pad_after=8)]
	pub paused:bool,
}
#[binrw]
#[brw(little)]
pub enum WorldEvent{
	#[brw(magic=0u32)]
	Reset(WorldEventReset),
	#[brw(magic=1u32)]
	Button(WorldEventButton),
	#[brw(magic=2u32)]
	SetTime(WorldEventSetTime),
	#[brw(magic=3u32)]
	SetPaused(WorldEventSetPaused),
}
#[binrw]
#[brw(little)]
pub struct TimedWorldEvent{
	#[br(map=read_trey_double)]
	pub time:f64,
	pub event:WorldEvent,
}

// gravity
#[binrw]
#[brw(little)]
pub struct GravityEvent{
	pub gravity:Vector3,
}
#[binrw]
#[brw(little)]
pub struct TimedGravityEvent{
	#[br(map=read_trey_double)]
	pub time:f64,
	pub event:GravityEvent,
}

// run
#[binrw]
#[brw(little)]
pub enum RunEventType{
	#[brw(magic=0u32)]
	Prepare,
	#[brw(magic=1u32)]
	Start,
	#[brw(magic=2u32)]
	Finish,
	#[brw(magic=3u32)]
	Clear,
	#[brw(magic=4u32)]
	Flag,
	#[brw(magic=5u32)]
	LoadState,
	#[brw(magic=6u32)]
	SaveState,
}
#[binrw]
#[brw(little)]
pub enum Mode{
	#[brw(magic=0i32)]
	Main,
	#[brw(magic=1i32)]
	Bonus,
	#[brw(magic=-1i32)]
	All,
	#[brw(magic=-2i32)]
	Invalid,
	#[brw(magic=-3i32)]
	InProgress,
}
#[binrw]
#[brw(little)]
pub enum FlagReason{
	#[brw(magic=0u32)]
	Anticheat,
	#[brw(magic=1u32)]
	StyleChange,
	#[brw(magic=2u32)]
	Clock,
	#[brw(magic=3u32)]
	Pause,
	#[brw(magic=4u32)]
	Flying,
	#[brw(magic=5u32)]
	Gravity,
	#[brw(magic=6u32)]
	Timescale,
	#[brw(magic=7u32)]
	Timetravel,
	#[brw(magic=8u32)]
	Teleport,
	#[brw(magic=9u32)]
	Practice,
	// b"data"
	#[brw(magic=1635017060u32)]
	None,
}
#[binrw]
#[brw(little)]
pub struct RunEvent{
	pub run_event_type:RunEventType,
	pub mode:Mode,
	pub flag_reason:FlagReason,
}
#[binrw]
#[brw(little)]
pub struct TimedRunEvent{
	#[br(map=read_trey_double)]
	pub time:f64,
	pub event:RunEvent,
}

// camera
#[binrw]
#[brw(little)]
pub enum CameraEventType{
	#[brw(magic=0u32)]
	CameraPunch,
	#[brw(magic=1u32)]
	Transform,
}
#[binrw]
#[brw(little)]
pub struct CameraEvent{
	pub camera_event_type:CameraEventType,
	pub value:Vector3,
}
#[binrw]
#[brw(little)]
pub struct TimedCameraEvent{
	#[br(map=read_trey_double)]
	pub time:f64,
	pub event:CameraEvent,
}

// setting
#[binrw]
#[brw(little)]
pub enum SettingType{
	#[brw(magic=0u32)]
	FieldOfView,
	#[brw(magic=1u32)]
	Sensitivity,
	#[brw(magic=2u32)]
	VerticalSensitivityMultiplier,
	#[brw(magic=3u32)]
	AbsoluteSensitivity,
	#[brw(magic=4u32)]
	TurnSpeed,
}
#[binrw]
#[brw(little)]
pub struct SettingEvent{
	pub setting_type:SettingType,
	#[br(map=read_trey_double)]
	pub value:f64,
}
#[binrw]
#[brw(little)]
pub struct TimedSettingEvent{
	#[br(map=read_trey_double)]
	pub time:f64,
	pub event:SettingEvent,
}

#[derive(Default)]
pub struct Block{
	pub input_events:Vec<TimedInputEvent>,
	pub output_events:Vec<TimedOutputEvent>,
	pub sound_events:Vec<TimedSoundEvent>,
	pub world_events:Vec<TimedWorldEvent>,
	pub gravity_events:Vec<TimedGravityEvent>,
	pub run_events:Vec<TimedRunEvent>,
	pub camera_events:Vec<TimedCameraEvent>,
	pub setting_events:Vec<TimedSettingEvent>,
}

#[binrw]
#[brw(little)]
enum EventType{
	#[brw(magic=1u32)]
	Input,
	#[brw(magic=2u32)]
	Output,
	#[brw(magic=3u32)]
	Sound,
	#[brw(magic=4u32)]
	World,
	#[brw(magic=5u32)]
	Gravity,
	#[brw(magic=6u32)]
	Run,
	#[brw(magic=7u32)]
	Camera,
	#[brw(magic=8u32)]
	Setting,
}
#[binrw]
#[brw(little)]
struct EventChunkHeader{
	event_type:EventType,
	num_events:u32,
}

// first time I've managed to write BinRead generics without this stupid T::Args<'a>:Required thing blocking me
fn read_data_into_events<'a,R:BinReaderExt,T>(data:&mut R,events:&mut Vec<T>,num_events:usize)->binrw::BinResult<()>
where
	T:binrw::BinRead,
	T::Args<'a>:binrw::__private::Required,
{
	// there is only supposed to be at most one of each type of event chunk per block, so no need to amortize.
	events.reserve_exact(num_events);
	for _ in 0..num_events{
		events.push(data.read_le()?);
	}
	Ok(())
}
fn read_data_into_events_amortized<'a,R:BinReaderExt,T>(data:&mut R,events:&mut Vec<T>,num_events:usize)->binrw::BinResult<()>
where
	T:binrw::BinRead,
	T::Args<'a>:binrw::__private::Required,
{
	// this is used when reading multiple blocks into a single object, so amortize the allocation cost.
	events.reserve(num_events);
	for _ in 0..num_events{
		events.push(data.read_le()?);
	}
	Ok(())
}

impl Block{
	fn read<R:BinReaderExt>(data:R)->binrw::BinResult<Block>{
		let mut block=Block::default();
		Block::read_into(data,&mut block)?;
		Ok(block)
	}
	fn read_into<R:BinReaderExt>(mut data:R,block:&mut Block)->binrw::BinResult<()>{
		// well... this looks error prone
		while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){
			match event_chunk_header.event_type{
				EventType::Input=>read_data_into_events(&mut data,&mut block.input_events,event_chunk_header.num_events as usize)?,
				EventType::Output=>read_data_into_events(&mut data,&mut block.output_events,event_chunk_header.num_events as usize)?,
				EventType::Sound=>read_data_into_events(&mut data,&mut block.sound_events,event_chunk_header.num_events as usize)?,
				EventType::World=>read_data_into_events(&mut data,&mut block.world_events,event_chunk_header.num_events as usize)?,
				EventType::Gravity=>read_data_into_events(&mut data,&mut block.gravity_events,event_chunk_header.num_events as usize)?,
				EventType::Run=>read_data_into_events(&mut data,&mut block.run_events,event_chunk_header.num_events as usize)?,
				EventType::Camera=>read_data_into_events(&mut data,&mut block.camera_events,event_chunk_header.num_events as usize)?,
				EventType::Setting=>read_data_into_events(&mut data,&mut block.setting_events,event_chunk_header.num_events as usize)?,
			}
		}
		Ok(())
	}
	fn read_into_amortized<R:BinReaderExt>(mut data:R,block:&mut Block)->binrw::BinResult<()>{
		// sad code duplication
		while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){
			match event_chunk_header.event_type{
				EventType::Input=>read_data_into_events_amortized(&mut data,&mut block.input_events,event_chunk_header.num_events as usize)?,
				EventType::Output=>read_data_into_events_amortized(&mut data,&mut block.output_events,event_chunk_header.num_events as usize)?,
				EventType::Sound=>read_data_into_events_amortized(&mut data,&mut block.sound_events,event_chunk_header.num_events as usize)?,
				EventType::World=>read_data_into_events_amortized(&mut data,&mut block.world_events,event_chunk_header.num_events as usize)?,
				EventType::Gravity=>read_data_into_events_amortized(&mut data,&mut block.gravity_events,event_chunk_header.num_events as usize)?,
				EventType::Run=>read_data_into_events_amortized(&mut data,&mut block.run_events,event_chunk_header.num_events as usize)?,
				EventType::Camera=>read_data_into_events_amortized(&mut data,&mut block.camera_events,event_chunk_header.num_events as usize)?,
				EventType::Setting=>read_data_into_events_amortized(&mut data,&mut block.setting_events,event_chunk_header.num_events as usize)?,
			}
		}
		Ok(())
	}
}

#[derive(Debug)]
pub enum Error{
	InvalidBlockId(BlockId),
	Seek(std::io::Error),
	InvalidData(binrw::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{}

#[binrw]
#[brw(little)]
#[derive(Debug,Clone,Copy)]
pub struct BlockId(#[br(map=|i:u32|i-1)]u32);
#[binrw]
#[brw(little)]
#[derive(Debug,Clone,Copy)]
pub struct BlockPosition(#[br(map=|i:u32|i-1)]u32);

#[binrw]
#[brw(little)]
#[derive(Debug,Clone,Copy)]
pub struct TimedBlockId{
	#[br(map=read_trey_double)]
	pub time:f64,
	pub block_id:BlockId,
}
impl PartialEq for TimedBlockId{
	fn eq(&self,other:&Self)->bool{
		self.time.eq(&other.time)
	}
}
impl PartialOrd for TimedBlockId{
	fn partial_cmp(&self,other:&Self)->Option<core::cmp::Ordering>{
		self.time.partial_cmp(&other.time)
	}
}

#[binrw]
#[brw(little)]
#[derive(Debug)]
pub struct FileHeader{
	#[brw(magic=b"qbot")]
	pub file_version:u32,
	pub num_offline_events:u32,
	pub num_realtime_events:u32,
	#[br(count=num_offline_events+num_realtime_events+1)]
	pub block_positions:Vec<BlockPosition>,
	#[br(count=num_offline_events)]
	pub offline_blocks_timeline:Vec<TimedBlockId>,
	#[br(count=num_realtime_events)]
	pub realtime_blocks_timeline:Vec<TimedBlockId>,
}
pub struct BlockInfo{
	start:u32,
	length:u32,
}
impl FileHeader{
	pub fn block_info(&self,block_id:BlockId)->Result<BlockInfo,Error>{
		if self.block_positions.len() as u32<=block_id.0{
			return Err(Error::InvalidBlockId(block_id));
		}
		let start=self.block_positions[block_id.0 as usize].0;
		let end=self.block_positions[block_id.0 as usize+1].0;
		Ok(BlockInfo{start,length:end-start})
	}
}

struct MergeIter<T,It0:Iterator<Item=T>,It1:Iterator<Item=T>>{
	it0:It0,
	it1:It1,
	item0:Option<T>,
	item1:Option<T>,
}
impl<T,It0:Iterator<Item=T>,It1:Iterator<Item=T>> MergeIter<T,It0,It1>{
	fn new(mut it0:It0,mut it1:It1)->Self{
		Self{
			item0:it0.next(),
			item1:it1.next(),
			it0,
			it1,
		}
	}
}
impl<T:PartialOrd,It0:Iterator<Item=T>,It1:Iterator<Item=T>> Iterator for MergeIter<T,It0,It1>{
	type Item=T;
	fn next(&mut self)->Option<Self::Item>{
		match (&self.item0,&self.item1){
			(None,None)=>None,
			(Some(_),None)=>core::mem::replace(&mut self.item0,self.it0.next()),
			(None,Some(_))=>core::mem::replace(&mut self.item1,self.it1.next()),
			(Some(item0),Some(item1))=>match item0.partial_cmp(item1){
				Some(core::cmp::Ordering::Less)
				|Some(core::cmp::Ordering::Equal)
				|None
				=>core::mem::replace(&mut self.item0,self.it0.next()),
				Some(core::cmp::Ordering::Greater)
				=>core::mem::replace(&mut self.item1,self.it1.next()),
			},
		}
	}
}

pub struct File<R:BinReaderExt>{
	pub header:FileHeader,
	pub data:FileData<R>,
}
impl<R:BinReaderExt> File<R>{
	pub fn new(mut data:R)->Result<File<R>,binrw::Error>{
		Ok(File{
			header:data.read_le()?,
			data:FileData{data},
		})
	}
	pub fn read_all(&mut self)->Result<Block,Error>{
		let block_iter=MergeIter::new(
			self.header.offline_blocks_timeline.iter(),
			self.header.realtime_blocks_timeline.iter(),
		);
		let mut big_block=Block::default();
		for &TimedBlockId{time:_,block_id} in block_iter{
			let block_info=self.header.block_info(block_id)?;
			self.data.read_block_info_into_block(block_info,&mut big_block)?;
		}
		Ok(big_block)
	}
}

pub struct FileData<R:BinReaderExt>{
	data:R,
}
impl<R:BinReaderExt> FileData<R>{
	fn data_mut(&mut self)->&mut R{
		&mut self.data
	}
	fn block_reader(&mut self,block_info:BlockInfo)->Result<binrw::io::TakeSeek<&mut R>,Error>{
		self.data.seek(std::io::SeekFrom::Start(block_info.start as u64)).map_err(Error::Seek)?;
		Ok(self.data_mut().take_seek(block_info.length as u64))
	}
	pub fn read_block_info(&mut self,block_info:BlockInfo)->Result<Block,Error>{
		let data=self.block_reader(block_info)?;
		let block=Block::read(data).map_err(Error::InvalidData)?;
		Ok(block)
	}
	pub fn read_block_info_into_block(&mut self,block_info:BlockInfo,block:&mut Block)->Result<(),Error>{
		let data=self.block_reader(block_info)?;
		Block::read_into_amortized(data,block).map_err(Error::InvalidData)?;
		Ok(())
	}
}