use crate::integer::{Time,Ratio64};

#[derive(Clone,Copy,Debug)]
pub struct Paused;
#[derive(Clone,Copy,Debug)]
pub struct Unpaused;

pub trait PauseState:Copy+std::fmt::Debug{
	const IS_PAUSED:bool;
	fn new()->Self;
}
impl PauseState for Paused{
	const IS_PAUSED:bool=true;
	fn new()->Self{
		Self
	}
}
impl PauseState for Unpaused{
	const IS_PAUSED:bool=false;
	fn new()->Self{
		Self
	}
}

#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub enum Inner{}
type InnerTime=Time<Inner>;

#[derive(Clone,Copy,Debug)]
pub struct Realtime<In,Out>{
	offset:InnerTime,
	_in:core::marker::PhantomData<In>,
	_out:core::marker::PhantomData<Out>,
}
impl<In,Out> Realtime<In,Out>{
	pub const fn new(offset:InnerTime)->Self{
		Self{
			offset,
			_in:core::marker::PhantomData,
			_out:core::marker::PhantomData,
		}
	}
}

#[derive(Clone,Copy,Debug)]
pub struct Scaled<In,Out>{
	scale:Ratio64,
	offset:InnerTime,
	_in:core::marker::PhantomData<In>,
	_out:core::marker::PhantomData<Out>,
}
impl<In,Out> Scaled<In,Out>
	where Time<In>:Copy,
{
	pub const fn new(scale:Ratio64,offset:InnerTime)->Self{
		Self{
			scale,
			offset,
			_in:core::marker::PhantomData,
			_out:core::marker::PhantomData,
		}
	}
	const fn with_scale(scale:Ratio64)->Self{
		Self::new(scale,InnerTime::ZERO)
	}
	const fn scale(&self,time:Time<In>)->InnerTime{
		InnerTime::raw(self.scale.mul_int(time.get()))
	}
	const fn get_scale(&self)->Ratio64{
		self.scale
	}
	fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
		let new_time=self.get_time(time);
		self.scale=new_scale;
		self.set_time(time,new_time);
	}
}

pub trait TimerState{
	type In;
	type Out;
	fn identity()->Self;
	fn get_time(&self,time:Time<Self::In>)->Time<Self::Out>;
	fn set_time(&mut self,time:Time<Self::In>,new_time:Time<Self::Out>);
	fn get_offset(&self)->InnerTime;
	fn set_offset(&mut self,offset:InnerTime);
}
impl<In,Out> TimerState for Realtime<In,Out>{
	type In=In;
	type Out=Out;
	fn identity()->Self{
		Self::new(InnerTime::ZERO)
	}
	fn get_time(&self,time:Time<In>)->Time<Out>{
		time.coerce()+self.offset.coerce()
	}
	fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
		self.offset=new_time.coerce()-time.coerce();
	}
	fn get_offset(&self)->InnerTime{
		self.offset
	}
	fn set_offset(&mut self,offset:InnerTime){
		self.offset=offset;
	}
}
impl<In,Out> TimerState for Scaled<In,Out>
	where Time<In>:Copy,
{
	type In=In;
	type Out=Out;
	fn identity()->Self{
		Self::new(Ratio64::ONE,InnerTime::ZERO)
	}
	fn get_time(&self,time:Time<In>)->Time<Out>{
		(self.scale(time)+self.offset).coerce()
	}
	fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
		self.offset=new_time.coerce()-self.scale(time);
	}
	fn get_offset(&self)->InnerTime{
		self.offset
	}
	fn set_offset(&mut self,offset:InnerTime){
		self.offset=offset;
	}
}

#[derive(Clone,Copy,Debug)]
pub struct TimerFixed<T:TimerState,P:PauseState>{
	state:T,
	_paused:P,
}

//scaled timer methods are generic across PauseState
impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
	where Time<In>:Copy,
{
	pub fn scaled(time:Time<In>,new_time:Time<Out>,scale:Ratio64)->Self{
		let mut timer=Self{
			state:Scaled::with_scale(scale),
			_paused:P::new(),
		};
		timer.set_time(time,new_time);
		timer
	}
	pub const fn get_scale(&self)->Ratio64{
		self.state.get_scale()
	}
	pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
		self.state.set_scale(time,new_scale)
	}
}

//pause and unpause is generic across TimerState
impl<T:TimerState> TimerFixed<T,Paused>
	where Time<T::In>:Copy,
{
	pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
		let new_time=self.time();
		let mut timer=TimerFixed{
			state:self.state,
			_paused:Unpaused,
		};
		timer.set_time(time,new_time);
		timer
	}
	pub fn time(&self)->Time<T::Out>{
		self.state.get_offset().coerce()
	}
}
impl<T:TimerState> TimerFixed<T,Unpaused>
	where Time<T::In>:Copy,
{
	pub fn into_paused(self,time:Time<T::In>)->TimerFixed<T,Paused>{
		let new_time=self.time(time);
		let mut timer=TimerFixed{
			state:self.state,
			_paused:Paused,
		};
		timer.set_time(time,new_time);
		timer
	}
	pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
		self.state.get_time(time)
	}
}

//the new constructor and time queries are generic across both
impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
	pub fn new(time:Time<T::In>,new_time:Time<T::Out>)->Self{
		let mut timer=Self{
			state:T::identity(),
			_paused:P::new(),
		};
		timer.set_time(time,new_time);
		timer
	}
	pub fn from_state(state:T)->Self{
		Self{
			state,
			_paused:P::new(),
		}
	}
	pub fn into_state(self)->T{
		self.state
	}
	pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
		match P::IS_PAUSED{
			true=>self.state.set_offset(new_time.coerce()),
			false=>self.state.set_time(time,new_time),
		}
	}
}

#[derive(Debug)]
pub enum Error{
	AlreadyPaused,
	AlreadyUnpaused,
}
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{}

//wrapper type which holds type state internally
#[derive(Clone,Debug)]
pub enum Timer<T:TimerState>{
	Paused(TimerFixed<T,Paused>),
	Unpaused(TimerFixed<T,Unpaused>),
}
impl<T:TimerState> Timer<T>
	where
		T:Copy,
		Time<T::In>:Copy,
{
	pub fn from_state(state:T,paused:bool)->Self{
		match paused{
			true=>Self::Paused(TimerFixed::from_state(state)),
			false=>Self::Unpaused(TimerFixed::from_state(state)),
		}
	}
	pub fn into_state(self)->(T,bool){
		match self{
			Self::Paused(timer)=>(timer.into_state(),true),
			Self::Unpaused(timer)=>(timer.into_state(),false),
		}
	}
	pub fn paused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
		Self::Paused(TimerFixed::new(time,new_time))
	}
	pub fn unpaused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
		Self::Unpaused(TimerFixed::new(time,new_time))
	}
	pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
		match self{
			Self::Paused(timer)=>timer.time(),
			Self::Unpaused(timer)=>timer.time(time),
		}
	}
	pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
		match self{
			Self::Paused(timer)=>timer.set_time(time,new_time),
			Self::Unpaused(timer)=>timer.set_time(time,new_time),
		}
	}
	pub fn pause(&mut self,time:Time<T::In>)->Result<(),Error>{
		*self=match *self{
			Self::Paused(_)=>return Err(Error::AlreadyPaused),
			Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)),
		};
		Ok(())
	}
	pub fn unpause(&mut self,time:Time<T::In>)->Result<(),Error>{
		*self=match *self{
			Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)),
			Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused),
		};
		Ok(())
	}
	pub fn is_paused(&self)->bool{
		match self{
			Self::Paused(_)=>true,
			Self::Unpaused(_)=>false,
		}
	}
	pub fn set_paused(&mut self,time:Time<T::In>,paused:bool)->Result<(),Error>{
		match paused{
			true=>self.pause(time),
			false=>self.unpause(time),
		}
	}
}
//scaled timer methods are generic across PauseState
impl<In,Out> Timer<Scaled<In,Out>>
	where Time<In>:Copy,
{
	pub const fn get_scale(&self)->Ratio64{
		match self{
			Self::Paused(timer)=>timer.get_scale(),
			Self::Unpaused(timer)=>timer.get_scale(),
		}
	}
	pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
		match self{
			Self::Paused(timer)=>timer.set_scale(time,new_scale),
			Self::Unpaused(timer)=>timer.set_scale(time,new_scale),
		}
	}
}

#[cfg(test)]
mod test{
	use super::*;
	macro_rules! sec {
		($s: expr) => {
			Time::from_secs($s)
		};
	}

	#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
	enum Parent{}
	#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
	enum Calculated{}
	#[test]
	fn test_timerfixed_scaled(){
		//create a paused timer that reads 0s
		let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0)));
		//the paused timer at 1 second should read 0s
		assert_eq!(timer.time(),sec!(0));

		//unpause it after one second
		let timer=timer.into_unpaused(sec!(1));
		//the timer at 6 seconds should read 2.5s
		assert_eq!(timer.time(sec!(6)),Time::from_millis(2500));

		//pause the timer after 11 seconds
		let timer=timer.into_paused(sec!(11));
		//the paused timer at 20 seconds should read 5s
		assert_eq!(timer.time(),sec!(5));
	}
	#[test]
	fn test_timer()->Result<(),Error>{
		//create a paused timer that reads 0s
		let mut timer=Timer::<Realtime<Parent,Calculated>>::paused(sec!(0),sec!(0));
		//the paused timer at 1 second should read 0s
		assert_eq!(timer.time(sec!(1)),sec!(0));

		//unpause it after one second
		timer.unpause(sec!(1))?;
		//the timer at 6 seconds should read 5s
		assert_eq!(timer.time(sec!(6)),sec!(5));

		//pause the timer after 11 seconds
		timer.pause(sec!(11))?;
		//the paused timer at 20 seconds should read 10s
		assert_eq!(timer.time(sec!(20)),sec!(10));

		Ok(())
	}
}
