use std::{cell::RefCell, time::Duration};
use dioxus_core::{prelude::spawn, Task};
use dioxus_hooks::{use_memo, use_memo_with_dependencies, Dependency};
use dioxus_signals::{ReadOnlySignal, Readable, Signal, Writable};
use easer::functions::*;
use freya_engine::prelude::Color;
use freya_node_state::Parse;
use tokio::time::Instant;
use crate::UsePlatform;
pub fn apply_value(
    origin: f32,
    destination: f32,
    index: i32,
    time: Duration,
    ease: Ease,
    function: Function,
) -> f32 {
    let (t, b, c, d) = (
        index as f32,
        origin,
        destination - origin,
        time.as_millis() as f32,
    );
    match function {
        Function::Back => match ease {
            Ease::In => Back::ease_in(t, b, c, d),
            Ease::InOut => Back::ease_in_out(t, b, c, d),
            Ease::Out => Back::ease_out(t, b, c, d),
        },
        Function::Bounce => match ease {
            Ease::In => Bounce::ease_in(t, b, c, d),
            Ease::InOut => Bounce::ease_in_out(t, b, c, d),
            Ease::Out => Bounce::ease_out(t, b, c, d),
        },
        Function::Circ => match ease {
            Ease::In => Circ::ease_in(t, b, c, d),
            Ease::InOut => Circ::ease_in_out(t, b, c, d),
            Ease::Out => Circ::ease_out(t, b, c, d),
        },
        Function::Cubic => match ease {
            Ease::In => Cubic::ease_in(t, b, c, d),
            Ease::InOut => Cubic::ease_in_out(t, b, c, d),
            Ease::Out => Cubic::ease_out(t, b, c, d),
        },
        Function::Elastic => match ease {
            Ease::In => Elastic::ease_in(t, b, c, d),
            Ease::InOut => Elastic::ease_in_out(t, b, c, d),
            Ease::Out => Elastic::ease_out(t, b, c, d),
        },
        Function::Expo => match ease {
            Ease::In => Expo::ease_in(t, b, c, d),
            Ease::InOut => Expo::ease_in_out(t, b, c, d),
            Ease::Out => Expo::ease_out(t, b, c, d),
        },
        Function::Linear => match ease {
            Ease::In => Linear::ease_in(t, b, c, d),
            Ease::InOut => Linear::ease_in_out(t, b, c, d),
            Ease::Out => Linear::ease_out(t, b, c, d),
        },
        Function::Quad => match ease {
            Ease::In => Quad::ease_in(t, b, c, d),
            Ease::InOut => Quad::ease_in_out(t, b, c, d),
            Ease::Out => Quad::ease_out(t, b, c, d),
        },
        Function::Quart => match ease {
            Ease::In => Quart::ease_in(t, b, c, d),
            Ease::InOut => Quart::ease_in_out(t, b, c, d),
            Ease::Out => Quart::ease_out(t, b, c, d),
        },
        Function::Sine => match ease {
            Ease::In => Sine::ease_in(t, b, c, d),
            Ease::InOut => Sine::ease_in_out(t, b, c, d),
            Ease::Out => Sine::ease_out(t, b, c, d),
        },
    }
}
#[derive(Default, Clone, Copy)]
pub enum Function {
    Back,
    Bounce,
    Circ,
    Cubic,
    Elastic,
    Expo,
    #[default]
    Linear,
    Quad,
    Quart,
    Sine,
}
#[derive(Default, Clone, Copy)]
pub enum Ease {
    #[default]
    In,
    Out,
    InOut,
}
pub struct AnimColor {
    origin: Color,
    destination: Color,
    time: Duration,
    ease: Ease,
    function: Function,
    value: Color,
}
impl AnimColor {
    pub fn new(origin: &str, destination: &str) -> Self {
        Self {
            origin: Color::parse(origin).unwrap(),
            destination: Color::parse(destination).unwrap(),
            time: Duration::default(),
            ease: Ease::default(),
            function: Function::default(),
            value: Color::parse(origin).unwrap(),
        }
    }
    pub fn time(mut self, time: u64) -> Self {
        self.time = Duration::from_millis(time);
        self
    }
    pub fn duration(mut self, duration: Duration) -> Self {
        self.time = duration;
        self
    }
    pub fn ease(mut self, ease: Ease) -> Self {
        self.ease = ease;
        self
    }
    pub fn function(mut self, function: Function) -> Self {
        self.function = function;
        self
    }
}
impl AnimatedValue for AnimColor {
    fn time(&self) -> Duration {
        self.time
    }
    fn as_f32(&self) -> f32 {
        panic!("This is not a f32.")
    }
    fn as_string(&self) -> String {
        format!(
            "rgb({}, {}, {})",
            self.value.r(),
            self.value.g(),
            self.value.b()
        )
    }
    fn prepare(&mut self, direction: AnimDirection) {
        match direction {
            AnimDirection::Forward => self.value = self.origin,
            AnimDirection::Reverse => {
                self.value = self.destination;
            }
        }
    }
    fn is_finished(&self, index: i32, direction: AnimDirection) -> bool {
        match direction {
            AnimDirection::Forward => {
                index > self.time.as_millis() as i32
                    && self.value.r() >= self.destination.r()
                    && self.value.g() >= self.destination.g()
                    && self.value.b() >= self.destination.b()
            }
            AnimDirection::Reverse => {
                index > self.time.as_millis() as i32
                    && self.value.r() <= self.origin.r()
                    && self.value.g() <= self.origin.g()
                    && self.value.b() <= self.origin.b()
            }
        }
    }
    fn advance(&mut self, index: i32, direction: AnimDirection) {
        if !self.is_finished(index, direction) {
            let (origin, destination) = match direction {
                AnimDirection::Forward => (self.origin, self.destination),
                AnimDirection::Reverse => (self.destination, self.origin),
            };
            let r = apply_value(
                origin.r() as f32,
                destination.r() as f32,
                index.min(self.time.as_millis() as i32),
                self.time,
                self.ease,
                self.function,
            );
            let g = apply_value(
                origin.g() as f32,
                destination.g() as f32,
                index.min(self.time.as_millis() as i32),
                self.time,
                self.ease,
                self.function,
            );
            let b = apply_value(
                origin.b() as f32,
                destination.b() as f32,
                index.min(self.time.as_millis() as i32),
                self.time,
                self.ease,
                self.function,
            );
            self.value = Color::from_rgb(r as u8, g as u8, b as u8);
        }
    }
}
pub struct AnimNum {
    origin: f32,
    destination: f32,
    time: Duration,
    ease: Ease,
    function: Function,
    value: f32,
}
impl AnimNum {
    pub fn new(origin: f32, destination: f32) -> Self {
        Self {
            origin,
            destination,
            time: Duration::default(),
            ease: Ease::default(),
            function: Function::default(),
            value: origin,
        }
    }
    pub fn time(mut self, time: u64) -> Self {
        self.time = Duration::from_millis(time);
        self
    }
    pub fn duration(mut self, duration: Duration) -> Self {
        self.time = duration;
        self
    }
    pub fn ease(mut self, ease: Ease) -> Self {
        self.ease = ease;
        self
    }
    pub fn function(mut self, function: Function) -> Self {
        self.function = function;
        self
    }
}
impl AnimatedValue for AnimNum {
    fn time(&self) -> Duration {
        self.time
    }
    fn as_f32(&self) -> f32 {
        self.value
    }
    fn as_string(&self) -> String {
        panic!("This is not a String");
    }
    fn prepare(&mut self, direction: AnimDirection) {
        match direction {
            AnimDirection::Forward => self.value = self.origin,
            AnimDirection::Reverse => {
                self.value = self.destination;
            }
        }
    }
    fn is_finished(&self, index: i32, direction: AnimDirection) -> bool {
        match direction {
            AnimDirection::Forward => {
                index > self.time.as_millis() as i32 && self.value >= self.destination
            }
            AnimDirection::Reverse => {
                index > self.time.as_millis() as i32 && self.value <= self.origin
            }
        }
    }
    fn advance(&mut self, index: i32, direction: AnimDirection) {
        if !self.is_finished(index, direction) {
            let (origin, destination) = match direction {
                AnimDirection::Forward => (self.origin, self.destination),
                AnimDirection::Reverse => (self.destination, self.origin),
            };
            self.value = apply_value(
                origin,
                destination,
                index.min(self.time.as_millis() as i32),
                self.time,
                self.ease,
                self.function,
            )
        }
    }
}
pub trait AnimatedValue {
    fn time(&self) -> Duration;
    fn as_f32(&self) -> f32;
    fn as_string(&self) -> String;
    fn prepare(&mut self, direction: AnimDirection);
    fn is_finished(&self, index: i32, direction: AnimDirection) -> bool;
    fn advance(&mut self, index: i32, direction: AnimDirection);
}
#[derive(Default, PartialEq, Clone)]
pub struct Context {
    animated_values: Vec<Signal<Box<dyn AnimatedValue>>>,
}
impl Context {
    pub fn with(
        &mut self,
        animated_value: impl AnimatedValue + 'static,
    ) -> ReadOnlySignal<Box<dyn AnimatedValue>> {
        let val: Box<dyn AnimatedValue> = Box::new(animated_value);
        let signal = Signal::new(val);
        self.animated_values.push(signal);
        ReadOnlySignal::new(signal)
    }
}
#[derive(Clone, Copy)]
pub enum AnimDirection {
    Forward,
    Reverse,
}
#[derive(PartialEq)]
pub struct UseAnimator<Animated> {
    value: Animated,
    ctx: Context,
    platform: UsePlatform,
    task: RefCell<Option<Task>>,
    is_running: Signal<bool>,
}
impl<Animated> UseAnimator<Animated> {
    pub fn get(&self) -> &Animated {
        &self.value
    }
    pub fn is_running(&self) -> bool {
        *self.is_running.read()
    }
    pub fn reverse(&self) {
        self.run(AnimDirection::Reverse)
    }
    pub fn start(&self) {
        self.run(AnimDirection::Forward)
    }
    pub fn run(&self, direction: AnimDirection) {
        let platform = self.platform;
        let mut is_running = self.is_running;
        let mut ticker = platform.new_ticker();
        let mut values = self.ctx.animated_values.clone();
        if let Some(task) = self.task.borrow_mut().take() {
            task.cancel();
        }
        is_running.set(true);
        let task = spawn(async move {
            platform.request_animation_frame();
            let mut index = 0;
            let mut prev_frame = Instant::now();
            for value in values.iter_mut() {
                value.write().prepare(direction);
            }
            loop {
                ticker.tick().await;
                platform.request_animation_frame();
                index += prev_frame.elapsed().as_millis() as i32;
                if values
                    .iter()
                    .all(|value| value.peek().is_finished(index, direction))
                {
                    break;
                }
                for value in values.iter_mut() {
                    value.write().advance(index, direction);
                }
                prev_frame = Instant::now();
            }
            is_running.set(false);
        });
        *self.task.borrow_mut() = Some(task);
    }
}
pub fn use_animation<Animated: PartialEq + 'static>(
    run: impl Fn(&mut Context) -> Animated + 'static,
) -> ReadOnlySignal<UseAnimator<Animated>> {
    use_memo(move || {
        let mut ctx = Context::default();
        let value = run(&mut ctx);
        UseAnimator {
            value,
            ctx,
            platform: UsePlatform::new(),
            task: RefCell::new(None),
            is_running: Signal::new(false),
        }
    })
}
pub fn use_animation_with_dependencies<Animated: PartialEq + 'static, D: Dependency>(
    deps: D,
    run: impl Fn(&mut Context, D::Out) -> Animated + 'static,
) -> ReadOnlySignal<UseAnimator<Animated>>
where
    D::Out: 'static,
{
    use_memo_with_dependencies(deps, move |deps| {
        let mut ctx = Context::default();
        let value = run(&mut ctx, deps);
        UseAnimator {
            value,
            ctx,
            platform: UsePlatform::new(),
            task: RefCell::new(None),
            is_running: Signal::new(false),
        }
    })
}