//! # Animations
//!
//! Animations are components that need to be added to entities
//!
//! ```
//! # use core::time::Duration;
//! # use lv_bevy_ecs::animation::Animation;
//! # use lv_bevy_ecs::functions::*;
//! # use lv_bevy_ecs::support::OpacityLevel;
//! # use lv_bevy_ecs::sys::{lv_part_t_LV_PART_MAIN, lv_anim_count_running};
//! # use lv_bevy_ecs::widgets::{Button, LvglWorld};
//! #
//! # lv_bevy_ecs::setup_test_display!();
//! #
//! let mut world = LvglWorld::default();
//! let button = Button::create_widget();
//!
//! let anim = Animation::new(
//!     Duration::from_secs(5),
//!     OpacityLevel::Transparent as i32,
//!     OpacityLevel::Cover as i32,
//!     |obj, val| {
//!         lv_obj_set_style_opa(obj, val as u8, lv_part_t_LV_PART_MAIN);
//!     },
//! );
//! let mut button_entity = world.spawn((Button, button, anim));
//! unsafe {
//!     assert_eq!(lv_anim_count_running(), 1);
//! }
//! ```

use ::alloc::boxed::Box;
use ::core::{ffi::c_void, mem::MaybeUninit, ptr::NonNull, time::Duration};

use bevy_ecs::{component::Component, lifecycle::HookContext, world::DeferredWorld};

use crate::widgets::{Wdg, Widget};
use crate::{info, warn};

#[derive(Component)]
#[component(on_insert = add_animation)]
#[component(storage = "SparseSet")]
pub struct Animation {
    raw: Option<lightvgl_sys::lv_anim_t>,
}

impl Animation {
    pub fn new<F>(duration: Duration, start: i32, end: i32, animator: F) -> Self
    where
        F: FnMut(&mut Wdg, i32),
    {
        let mut raw = unsafe {
            let mut anim = MaybeUninit::<lightvgl_sys::lv_anim_t>::uninit();
            lightvgl_sys::lv_anim_init(anim.as_mut_ptr());
            anim.assume_init()
        };
        raw.duration = duration.as_millis().try_into().unwrap_or(0);
        raw.start_value = start;
        raw.current_value = start;
        raw.end_value = end;
        raw.user_data = Box::<F>::into_raw(Box::new(animator)) as *mut _;
        raw.exec_cb = Some(animator_trampoline::<F>);

        Self { raw: Some(raw) }
    }

    #[cfg(feature = "no_ecs")]
    pub fn set_widget(&mut self, widget: &mut Wdg) {
        self.raw_mut().var = widget.raw_mut() as *mut _;
    }

    pub fn start(&mut self) {
        unsafe {
            self.raw = Some(*lightvgl_sys::lv_anim_start(&self.raw.take().unwrap()));
        }
    }

    pub fn raw(&self) -> &lightvgl_sys::lv_anim_t {
        self.raw.as_ref().unwrap()
    }

    pub fn raw_mut(&mut self) -> &mut lightvgl_sys::lv_anim_t {
        self.raw.as_mut().unwrap()
    }
}

unsafe impl Send for Animation {}
unsafe impl Sync for Animation {}

impl Drop for Animation {
    fn drop(&mut self) {
        info!("Dropping Animation");
    }
}

fn add_animation(mut world: DeferredWorld, ctx: HookContext) {
    let obj = world
        .get_mut::<Widget>(ctx.entity)
        .expect("Animation components must be added entities with a Widget component")
        .as_mut()
        .raw();
    let mut anim = world.get_mut::<Animation>(ctx.entity).unwrap();
    anim.raw.as_mut().unwrap().var = obj as *mut _;
    anim.start();
    info!("Added Animation");
}

unsafe extern "C" fn animator_trampoline<F>(obj: *mut c_void, val: i32)
where
    F: FnMut(&mut Wdg, i32),
{
    unsafe {
        let ptr = lightvgl_sys::lv_anim_get(obj, None) as *mut lightvgl_sys::lv_anim_t;
        let anim = NonNull::new(ptr).unwrap();
        let obj = obj as *mut lightvgl_sys::lv_obj_t;
        if anim.as_ref().user_data.is_null() {
            warn!("Animation user data was null, this should never happen!");
            return;
        }
        let mut obj_wdg = Wdg::from_ptr(obj);
        let callback = &mut *(anim.as_ref().user_data as *mut F);
        callback(&mut obj_wdg, val);
    }
}
