use core::ffi;
use core::marker::PhantomData;

use ndk::android::choreographer::*;

pub trait FrameDelegate {
	fn on_frame(&self, _frame_time_nanos: i64) {}
	fn on_vsync(&self, _frame_data: &FrameData) {}
	fn on_rate(&self, _vsync_period_nanos: i64) {}
}

#[repr(transparent)]
#[derive(Debug)]
pub struct Grapher<T: FrameDelegate> {
	instance: *const AChoreographer,
	_t: PhantomData<T>,
}
impl<T: FrameDelegate> Clone for Grapher<T> {
	fn clone(&self) -> Self {
		Self {
			instance: self.instance,
			_t: PhantomData,
		}
	}
}
impl<T: FrameDelegate> Copy for Grapher<T> {}
impl<T: FrameDelegate> Grapher<T> {
	pub fn singleton() -> Self {
		let instance = unsafe { AChoreographer_getInstance() };
		Self { instance, _t: PhantomData }
	}
	#[cfg(feature = "api29")]
	pub fn frame(&self, data: &std::rc::Rc<T>) {
		let weak = std::rc::Rc::downgrade(&data);
		let data = weak.into_raw();
		unsafe { AChoreographer_postFrameCallback64(self.instance as _, Some(on_frame::<T>), data as _) }
	}
	#[cfg(feature = "api29")]
	pub fn delay(&self, data: &std::rc::Rc<T>, delay_millis: u32) {
		let weak = std::rc::Rc::downgrade(&data);
		let data = weak.into_raw();
		unsafe { AChoreographer_postFrameCallbackDelayed64(self.instance as _, Some(on_frame::<T>), data as _, delay_millis) }
	}
	#[cfg(feature = "api33")]
	pub fn vsync(&self, data: &std::rc::Rc<T>) {
		let weak = std::rc::Rc::downgrade(&data);
		let data = weak.into_raw();
		unsafe { AChoreographer_postVsyncCallback(self.instance as _, Some(on_vsync::<T>), data as _) }
	}
	#[cfg(feature = "api30")]
	pub fn rate(&self, data: &std::rc::Rc<T>) -> RateGuard<T> {
		let weak = std::rc::Rc::downgrade(&data);
		let data = weak.into_raw();
		unsafe { AChoreographer_registerRefreshRateCallback(self.instance as _, Some(on_rate::<T>), data as _) };
		RateGuard { data }
	}
}

extern "C" fn on_vsync<T: FrameDelegate>(frame_data: *const AChoreographerFrameCallbackData, data: *mut ffi::c_void) {
	let weak = unsafe { std::rc::Weak::from_raw(data as *const T) };
	if let Some(data) = weak.upgrade() {
		data.on_vsync(FrameData::from_ndk(frame_data));
	}
}
extern "C" fn on_frame<T: FrameDelegate>(frame_time_nanos: i64, data: *mut ffi::c_void) {
	let weak = unsafe { std::rc::Weak::from_raw(data as *const T) };
	if let Some(data) = weak.upgrade() {
		data.on_frame(frame_time_nanos);
	}
}
extern "C" fn on_rate<T: FrameDelegate>(vsync_period_nanos: i64, data: *mut ffi::c_void) {
	let weak = unsafe { std::rc::Weak::from_raw(data as *const T) };
	if let Some(data) = weak.upgrade() {
		data.on_rate(vsync_period_nanos);
	}
	let _ = weak.into_raw();
}

pub struct RateGuard<T: FrameDelegate> {
	data: *const T,
}
impl<T: FrameDelegate> Drop for RateGuard<T> {
	fn drop(&mut self) {
		let instance = unsafe { AChoreographer_getInstance() };
		unsafe { AChoreographer_unregisterRefreshRateCallback(instance, Some(on_rate::<T>), self.data as _) }
		let _ = unsafe { std::rc::Weak::from_raw(self.data) };
	}
}

pub struct FrameData;
impl FrameData {
	pub fn from_ndk<'a>(frame_data: *const AChoreographerFrameCallbackData) -> &'a Self {
		unsafe { &*(frame_data as *const Self) }
	}
	fn as_ndk(&self) -> *const AChoreographerFrameCallbackData {
		self as *const Self as *const ffi::c_void as _
	}
	// #[doc = " The time in nanoseconds when the frame started being rendered."]
	pub fn frame_time_nanos(&self) -> i64 {
		unsafe { AChoreographerFrameCallbackData_getFrameTimeNanos(self.as_ndk()) }
	}
	// #[doc = " The number of possible frame timelines."]
	pub fn frame_timelines_length(&self) -> usize {
		unsafe { AChoreographerFrameCallbackData_getFrameTimelinesLength(self.as_ndk()) }
	}
	// #[doc = " Get index of the platform-preferred FrameTimeline."]
	pub fn preferred_frame_timeline_index(&self) -> usize {
		unsafe { AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(self.as_ndk()) }
	}
	// #[doc = " The vsync ID token used to map Choreographer data."]
	pub fn frame_timeline_vsync_id(&self, index: usize) -> AVsyncId {
		unsafe { AChoreographerFrameCallbackData_getFrameTimelineVsyncId(self.as_ndk(), index) }
	}
	// #[doc = " The time in nanoseconds which the frame at given index is expected to be presented."]
	pub fn frame_timeline_expected_presentation_time_nanos(&self, index: usize) -> i64 {
		unsafe { AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(self.as_ndk(), index) }
	}
	// #[doc = " The time in nanoseconds which the frame at given index needs to be ready by."]
	pub fn frame_timeline_deadline_nanos(&self, index: usize) -> i64 {
		unsafe { AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(self.as_ndk(), index) }
	}
}
