use mlua::{FromLua, Lua, UserData, UserDataMethods};

use crate::lua::LuaEnv;

#[derive(Debug, Clone, Copy)]
pub enum EasingFunction {
    Linear,
    EaseIn,
    EaseOut,
    EaseInOut,
    Bounce,
    Elastic,
}

impl EasingFunction {
    pub fn apply(&self, t: f64) -> f64 {
        let t = t.clamp(0.0, 1.0);
        match self {
            EasingFunction::Linear => t,
            EasingFunction::EaseIn => t * t,
            EasingFunction::EaseOut => 1.0 - (1.0 - t) * (1.0 - t),
            EasingFunction::EaseInOut => {
                if t < 0.5 {
                    2.0 * t * t
                } else {
                    1.0 - 2.0 * (1.0 - t) * (1.0 - t)
                }
            }
            EasingFunction::Bounce => {
                let n1 = 7.5625;
                let d1 = 2.75;
                if t < 1.0 / d1 {
                    n1 * t * t
                } else if t < 2.0 / d1 {
                    let t = t - 1.5 / d1;
                    n1 * t * t + 0.75
                } else if t < 2.5 / d1 {
                    let t = t - 2.25 / d1;
                    n1 * t * t + 0.9375
                } else {
                    let t = t - 2.625 / d1;
                    n1 * t * t + 0.984375
                }
            }
            EasingFunction::Elastic => {
                if t == 0.0 {
                    0.0
                } else if t == 1.0 {
                    1.0
                } else {
                    let c4 = (2.0 * std::f64::consts::PI) / 3.0;
                    -(2.0_f64.powf(10.0 * t - 10.0)) * ((t * 10.0 - 10.75) * c4).sin()
                }
            }
        }
    }
}

impl FromLua for EasingFunction {
    fn from_lua(value: mlua::Value, _: &Lua) -> mlua::Result<Self> {
        match value {
            mlua::Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
            _ => unreachable!(),
        }
    }
}

impl UserData for EasingFunction {
    fn add_methods<M: UserDataMethods<Self>>(_: &mut M) {}
}

pub fn add_constructor(lua: &LuaEnv) -> crate::Result<()> {
    lua.func("Linear", |_, ()| Ok(EasingFunction::Linear))?;
    lua.func("EaseIn", |_, ()| Ok(EasingFunction::EaseIn))?;
    lua.func("EaseOut", |_, ()| Ok(EasingFunction::EaseOut))?;
    lua.func("EaseInOut", |_, ()| Ok(EasingFunction::EaseInOut))?;
    lua.func("Bounce", |_, ()| Ok(EasingFunction::Bounce))?;
    lua.func("Elastic", |_, ()| Ok(EasingFunction::Elastic))?;
    Ok(())
}
