use embassy_sync::pubsub::DynSubscriber;
use embassy_time::{Duration, Timer};
use figments::{liber8tion::interpolate::Fract8, prelude::*};
use rgb::{Rgb, Rgba};
use core::fmt::Debug;
use log::*;

use crate::{animation::{AnimatedSurface, Animation}, ego::engine::MotionState, events::{Personality, Prediction, Scene, SensorSource, SensorState}, graphics::{display::{SegmentSpace, Uniforms}, shaders::*}};

pub struct Ui<S: Surface> {
    // Background layer provides an always-running background for everything to draw on
    background: AnimatedSurface<S>,

    // Tail and panels provide content
    tail: AnimatedSurface<S>,
    panels: AnimatedSurface<S>,

    motion: AnimatedSurface<S>,

    // Notification layer sits on top of the content, and is used for transient event notifications (gps lost, wifi found, etc)
    notification: AnimatedSurface<S>,
}

impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pixel = Rgba<u8>>> Ui<S> {
    pub fn new<SS: Surfaces<Surface = S>>(surfaces: &mut SS) -> Self where SS::Error: Debug {
        Self {
            background: SurfaceBuilder::build(surfaces)
                .rect(Rectangle::everything())
                .shader(Background::default())
                .visible(false)
                .finish().unwrap().into(),
            tail: SurfaceBuilder::build(surfaces)
                .rect(Rectangle::new_from_coordinates(0, 5, 255, 5))
                .shader(Tail::default())
                .visible(false)
                .finish().unwrap().into(),
            panels: SurfaceBuilder::build(surfaces)
                .rect(Rectangle::new_from_coordinates(0, 1, 255, 4))
                .shader(Panel::default())
                .visible(false)
                .finish().unwrap().into(),
            motion: SurfaceBuilder::build(surfaces)
                .rect(Rectangle::everything())
                .shader(Movement::default())
                .visible(false)
                .finish().unwrap().into(),
            notification: SurfaceBuilder::build(surfaces)
                .rect(Rectangle::everything())
                .shader(Background::default())
                .visible(false)
                .finish().unwrap().into()
        }
    }

    pub async fn flash_notification_color(&mut self, color: Rgb<u8>) {
        let fade_in = Animation::default().from(Fract8::MIN).to(Fract8::MAX).duration(Duration::from_millis(30));
        let pulse_out = Animation::default().from(Fract8::MAX).to(Fract8::from_raw(60)).duration(Duration::from_millis(100));
        let pulse_in = Animation::default().from(Fract8::MIN).to(Fract8::MAX).duration(Duration::from_millis(100));
        let fade_out = Animation::default().from(Fract8::MAX).to(Fract8::MIN).duration(Duration::from_secs(2));
        info!("Flashing notification {color}");

        self.notification.set_visible(true);

        self.notification.set_shader(Background::from_color(color));

        fade_in.apply([&mut self.notification]).await;

        // Pulse quickly 5 times
        for _ in 0..5 {
            pulse_out.apply([&mut self.notification]).await;
            pulse_in.apply([&mut self.notification]).await;
        }

        fade_out.apply([&mut self.notification]).await;
        self.notification.set_visible(false);
    }

    pub fn as_slice(&mut self) -> [&mut S; 4] {
        [
            &mut *self.tail,
            &mut *self.panels,
            &mut *self.background,
            &mut *self.motion
        ]
    }

    pub async fn show(&mut self) {
        info!("Flipping on surfaces");
        self.as_slice().set_visible(true);
    }

    pub async fn apply_scene(&mut self, next_scene: Scene) {
        info!("Activating scene {next_scene:?}");
        match next_scene {
            Scene::Ready => {
                let tail = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(96));
                let panels = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(128));
                let bg = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(32));
                let motion = Animation::default().duration(Duration::from_secs(1)).to(Fract8::MIN);
                embassy_futures::join::join4(
                    tail.apply([&mut self.tail]),
                    panels.apply([&mut self.panels]),
                    bg.apply([&mut self.background]),
                    motion.apply([&mut self.motion])
                ).await;
                self.background.set_shader(Background::default());
            },
            Scene::Idle => {
                self.background.set_shader(Thinking::default());
                let fg_fade = Animation::default().duration(Duration::from_millis(300)).to(Fract8::MIN);
                let bg_fade = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(128));
                
                embassy_futures::join::join(
                    fg_fade.apply([&mut self.tail, &mut self.panels, &mut self.motion]),
                    bg_fade.apply([&mut self.background])
                ).await;
            },
            Scene::Accelerating => {
                self.motion.set_shader(Movement::default());
                Animation::default().duration(Duration::from_secs(1)).to(Fract8::MAX).apply([&mut self.motion]).await;
            },
            Scene::Decelerating => {
                self.motion.set_shader(Movement::default().reversed());
                Animation::default().duration(Duration::from_secs(1)).to(Fract8::MAX).apply([&mut self.motion]).await;
            }
        }
    }

    pub async fn on_event(&mut self, event: Prediction) {
        match event {
            Prediction::SetPersonality(personality) => match personality {
                Personality::Active => self.apply_scene(Scene::Ready).await,
                Personality::Parked => self.apply_scene(Scene::Idle).await,
                Personality::Waking => self.show().await,
                _ => ()
            },
            Prediction::Motion { prev: _, next: MotionState::Accelerating } => self.apply_scene(Scene::Accelerating).await,
            Prediction::Motion { prev: _, next: MotionState::Decelerating } => self.apply_scene(Scene::Decelerating).await,
            Prediction::Motion { prev: _, next: MotionState::Steady } => self.apply_scene(Scene::Ready).await,
            Prediction::Motion { prev: _, next: MotionState::Stationary } => self.apply_scene(Scene::Ready).await,
            Prediction::SensorStatus(SensorSource::IMU, SensorState::Online) => self.flash_notification_color(Rgb::new(0, 255, 0)).await,
            Prediction::SensorStatus(SensorSource::Location, SensorState::Degraded) => self.flash_notification_color(Rgb::new(255, 0, 0)).await,
            Prediction::SensorStatus(SensorSource::GPS, SensorState::Online) => self.flash_notification_color(Rgb::new(0, 255, 255)).await,
            _ => ()
        }
        // Other event ideas:
        // - Bike has crashed, or is laid down
        // - Unstable physics right before crashing?
        // - Turning left/right
        // - BPM sync with phone app
        // - GPS data is being synchronized with nextcloud/whatever
        // - A periodic flash when re-initializing MPU and GPS, to indicate there might be a problem?
        // - Bluetooth/BLE connect/disconnect events
        // - Bike is waking up from stationary?
    }
}

impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pixel = Rgba<u8>>> Surface for Ui<S> {
    type Uniforms = S::Uniforms;

    type CoordinateSpace = S::CoordinateSpace;

    type Pixel = S::Pixel;

    fn set_shader<T: Shader<Self::Uniforms, Self::CoordinateSpace, Self::Pixel> + 'static>(&mut self, _shader: T) {
        unimplemented!()
    }

    fn clear_shader(&mut self) {
        self.as_slice().clear_shader();
    }

    fn set_rect(&mut self, rect: Rectangle<Self::CoordinateSpace>) {
        self.as_slice().set_rect(rect);
    }

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

    fn set_visible(&mut self, visible: bool) {
        self.as_slice().set_visible(visible);
    }

    fn set_offset(&mut self, offset: Coordinates<Self::CoordinateSpace>) {
        self.as_slice().set_offset(offset);
    }
}

#[cfg(not(feature="real-output"))]
pub type UiSurfacePool = NullBufferPool<Uniforms, SegmentSpace, Rgba<u8>>;
#[cfg(feature="real-output")]
pub type UiSurfacePool = BufferedSurfacePool<Uniforms, SegmentSpace, Rgba<u8>>;

#[embassy_executor::task]
pub async fn ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: Ui<<UiSurfacePool as Surfaces>::Surface>) {
    // FIXME: This should instead wait on some kind of flag set by the safety UI, or else we risk painting before we even have a display up and running
    Timer::after_secs(3).await;
    ui.show().await;

    // Enter the startup scene
    ui.apply_scene(Scene::Ready).await;

    // Enter the event loop
    loop {
        let evt = events.next_message_pure().await;
        ui.on_event(evt).await;
    }
}