use embassy_time::{Duration, Instant, Timer};
use esp_rtos::CurrentThreadHandle;
use figments::{liber8tion::interpolate::Fract8, surface::Surface};
use figments_render::output::Brightness;
use core::{fmt::Debug, mem::MaybeUninit, ops::{Deref, DerefMut}};
use log::*;

use crate::graphics::display::DisplayControls;

#[derive(Default, Debug, Clone, Copy)]
pub struct Animation<T> {
    from: Option<T>,
    to: Option<T>,
    duration: Duration
}

pub trait AnimationActor<T> {
    fn get_value(&self) -> T;
    fn set_value(&mut self, value: T);
}

impl<S: Surface> AnimationActor<Fract8> for S {
    fn get_value(&self) -> Fract8 {
        unimplemented!()
    }

    fn set_value(&mut self, opacity: Fract8) {
        self.set_opacity(opacity);
    }
}

#[derive(Debug)]
pub struct AnimDisplay<'a>(pub &'a mut DisplayControls);

impl<'a> AnimationActor<Fract8> for AnimDisplay<'a> {
    fn get_value(&self) -> Fract8 {
        self.0.brightness()
    }

    fn set_value(&mut self, opacity: Fract8) {
        self.0.set_brightness(opacity);
    }
}

impl<S: Surface> AnimationActor<Fract8> for AnimatedSurface<S> {
    fn get_value(&self) -> Fract8 {
        self.opacity
    }

    fn set_value(&mut self, opacity: Fract8) {
        self.surface.set_opacity(opacity);
        self.opacity = opacity;
    }
}

struct Slot<'a, T> {
    from: T,
    to: T,
    cur_step: T,
    step_time: Duration,
    next_update: Instant,
    target: &'a mut dyn AnimationActor<T>
}

impl<'a, T: Debug> core::fmt::Debug for Slot<'a, T> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Slot")
            .field("from", &self.from)
            .field("to", &self.to)
            .field("cur_step", &self.cur_step)
            .field("step_time", &self.step_time)
            .field("next_update", &self.next_update).finish()
    }
}

impl Animation<Fract8> {
    pub const fn new() -> Self {
        Self {
            from: None,
            to: None,
            duration: Duration::from_ticks(0)
        }
    }

    pub const fn from(self, from: Fract8) -> Self {
        Self {
            from: Some(from),
            ..self
        }
    }

    pub const fn to(self, to: Fract8) -> Self {
        Self {
            to: Some(to),
            ..self
        }
    }

    pub const fn duration(self, duration: Duration) -> Self {
        Self {
            duration,
            ..self
        }
    }
}


const MIN_ANIMATION_RATE: Duration =  Duration::from_millis(5);

impl Animation<Fract8> {
    pub async fn apply<const ACTOR_COUNT: usize>(&self, actors: [&mut dyn AnimationActor<Fract8>; ACTOR_COUNT]) {

        let mut now = Instant::now();
        trace!("start now={now:?} ACTOR_COUNT={ACTOR_COUNT}");

        let mut actors = actors.into_iter();
        let mut animators: [Slot<Fract8>; ACTOR_COUNT] = core::array::from_fn(|_| {
            let target = actors.next().unwrap();
            let from = if let Some(val) = self.from {
                val
            } else {
                target.get_value()
            };
            let to = if let Some(val) = self.to {
                val
            } else {
                target.get_value()
            };
            let steps = to.abs_diff(from);

            let step_time = if steps == Fract8::MIN {
                // Zero ticks is an 'invalid' animator that shouldn't get processed because start == end already
                Duration::from_ticks(0)
            } else {
                // FIXME: if the resulting duration is less than the animation rate, we also need to re-scale the number added to animator.cur_step further down below. Otherwise a 0-255 animation with a 100ms duration actually ends up running for 255ms
                (self.duration / steps.to_raw().into()).max(MIN_ANIMATION_RATE)
            };
            Slot {
                from,
                to,
                cur_step: from,
                step_time,
                next_update: now,
                target
            }
        });

        trace!("animators={animators:?}");

        loop {
            // Find the next shortest delay
            let mut next_keyframe_time = animators[0].next_update;
            let mut finished = false;
            for animator in &mut animators {
                if animator.step_time.as_ticks() == 0 {
                    continue;
                }
                if animator.next_update <= now {
                    animator.next_update += animator.step_time;
                    animator.cur_step = if animator.to > animator.from {
                        animator.cur_step + Fract8::from_raw(1)
                    } else {
                        animator.cur_step - Fract8::from_raw(1)
                    };

                    if (animator.to > animator.from && animator.cur_step >= animator.to) ||
                        (animator.to <= animator.from && animator.cur_step <= animator.to) {
                        finished = true;
                    }

                    animator.target.set_value(animator.cur_step);
                }

                if next_keyframe_time <= now || animator.next_update < next_keyframe_time {
                    next_keyframe_time = animator.next_update;
                }
            }

            if finished {
                break;
            }

            let keyframe_delay = next_keyframe_time - now;
            trace!("delay {:?}", keyframe_delay.as_millis());
            Timer::after(keyframe_delay).await;
            now += keyframe_delay;
        }

        trace!("finished animators={animators:?}");
    }
}

#[derive(Debug)]
pub struct AnimatedSurface<S: Surface> {
    surface: S,
    is_on: bool,
    opacity: Fract8
}

impl<S: Surface> Deref for AnimatedSurface<S> {
    type Target = S;

    fn deref(&self) -> &Self::Target {
        &self.surface
    }
}

impl<S: Surface> DerefMut for AnimatedSurface<S> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.surface
    }
}

impl<S: Surface + Debug> From<S> for AnimatedSurface<S> {
    fn from(surface: S) -> Self {
        trace!("create anim surface={surface:?}");
        AnimatedSurface { 
            surface,
            is_on: false,
            opacity: Fract8::MAX
        }
    }
}

impl<S: Surface + Debug> AnimatedSurface<S> {
    pub async fn set_on(&mut self, is_on: bool) {
        if self.is_on != is_on {
            let anim = if is_on {
                self.surface.set_visible(true);
                Animation::default().duration(Duration::from_secs(1)).from(Fract8::MIN).to(Fract8::MAX)
            } else {
                Animation::default().duration(Duration::from_secs(1)).from(Fract8::MAX).to(Fract8::MIN)
            };
            anim.apply([&mut self.surface]).await;
            self.is_on = true;
            if !is_on {
                self.surface.set_visible(false);
            }
        }
    }
}