pub use tick::Tick;
mod tick{
	#[derive(Clone,Copy,Default,Hash,PartialEq,Eq,PartialOrd,Ord)]
	pub struct Tick(u64);
	impl std::ops::Add<u64> for Tick{
		type Output=Self;
		fn add(self,rhs:u64)->Self::Output{
			Self(self.0+rhs)
		}
	}
	impl std::ops::Sub<u64> for Tick{
		type Output=Self;
		fn sub(self,rhs:u64)->Self::Output{
			Self(self.0-rhs)
		}
	}
	impl std::ops::AddAssign<u64> for Tick{
		fn add_assign(&mut self,rhs:u64){
			self.0+=rhs;
		}
	}
	impl std::ops::SubAssign<u64> for Tick{
		fn sub_assign(&mut self,rhs:u64){
			self.0-=rhs;
		}
	}
}
#[derive(Default)]
pub struct Scheduler{
	tick:Tick,
	schedule:std::collections::HashMap<Tick,Vec<mlua::Thread>>,
}

impl Scheduler{
	pub fn has_scheduled_threads(&self)->bool{
		!self.schedule.is_empty()
	}
	pub fn schedule_thread(&mut self,delay:u64,thread:mlua::Thread){
		self.schedule.entry(self.tick+delay.max(1))
		.or_insert(Vec::new())
		.push(thread);
	}
	pub fn tick_threads(&mut self)->Option<Vec<mlua::Thread>>{
		self.tick+=1;
		self.schedule.remove(&self.tick)
	}
}

pub fn scheduler_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut crate::scheduler::Scheduler)->mlua::Result<T>)->mlua::Result<T>{
	let mut scheduler=lua.app_data_mut::<crate::scheduler::Scheduler>().ok_or_else(||mlua::Error::runtime("Scheduler missing"))?;
	f(&mut *scheduler)
}

fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
	let delay=match dt{
		mlua::Value::Integer(i)=>i.max(0) as u64*60,
		mlua::Value::Number(f)=>{
			let delay=f.max(0.0)*60.0;
			match delay.classify(){
				std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?,
				// cases where the number is too large to schedule
				std::num::FpCategory::Infinite=>return Ok(()),
				std::num::FpCategory::Normal=>if (u64::MAX as f64)<delay{
					return Ok(());
				},
				_=>(),
			}
			delay as u64
		},
		mlua::Value::Nil=>0,
		_=>Err(mlua::Error::runtime("Expected float"))?,
	};
	scheduler_mut(lua,|scheduler|{
		scheduler.schedule_thread(delay.max(2),lua.current_thread());
		Ok(())
	})
}

// This is used to avoid calling coroutine.yield from the rust side.
const LUA_WAIT:&str=
"local coroutine_yield=coroutine.yield
local schedule_thread=schedule_thread
return function(dt)
	schedule_thread(dt)
	return coroutine_yield()
end";

pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
	let coroutine_table=globals.get::<mlua::Table>("coroutine")?;
	let schedule_thread=lua.create_function(schedule_thread)?;

	//create wait function environment
	let wait_env=lua.create_table()?;
	wait_env.raw_set("coroutine",coroutine_table)?;
	wait_env.raw_set("schedule_thread",schedule_thread)?;

	//construct wait function from Lua code
	let wait=lua.load(LUA_WAIT)
	.set_name("wait")
	.set_environment(wait_env)
	.call::<mlua::Function>(())?;

	globals.raw_set("wait",wait)?;

	Ok(())
}
