#![no_std]

extern crate alloc;
use alloc::boxed::Box;
use az::*;
use core::{
    clone::Clone,
    cmp::PartialEq,
    default::Default,
    ops::{Add, Sub},
    option::Option,
};
use fixed::types::{I48F16, I8F8, U128F0, U16F0};

mod patterns;
pub use patterns::*;

mod types;
pub use types::{BodyPattern, DashboardPattern, RGB};

fn calculate_frames(starting_time: U128F0, now: U128F0) -> U16F0 {
    let frames_128 = (now - starting_time) / U128F0::from(FPS);
    (frames_128 % U128F0::from(U16F0::MAX)).cast()
}

fn calculate_slope(start: I8F8, end: I8F8, frames: U16F0) -> I8F8 {
    let slope_i16f16 = (I48F16::from(end) - I48F16::from(start)) / I48F16::from(frames);
    slope_i16f16.saturating_as()
}

fn linear_ease(value: I8F8, frames: U16F0, slope: I8F8) -> I8F8 {
    let value_i16f16 = I48F16::from(value) + I48F16::from(frames) * I48F16::from(slope);
    value_i16f16.saturating_as()
}

#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
pub struct Instant(pub U128F0);

impl Default for Instant {
    fn default() -> Self {
        Self(U128F0::from(0_u8))
    }
}

impl Add for Instant {
    type Output = Self;

    fn add(self, r: Self) -> Self::Output {
        Self(self.0 + r.0)
    }
}

impl Sub for Instant {
    type Output = Self;

    fn sub(self, r: Self) -> Self::Output {
        Self(self.0 - r.0)
    }
}

pub const FPS: u8 = 30;

pub trait UI {
    fn check_event(&mut self, current_time: Instant) -> Option<Event>;
    fn update_lights(&self, dashboard_lights: DashboardPattern, body_lights: BodyPattern);
}

pub trait Animation {
    fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern);
}

/*
pub struct DefaultAnimation {}

impl Animation for DefaultAnimation {
    fn tick(&mut self, _: Instant) -> (DashboardPattern, BodyPattern) {
        (WATER_DASHBOARD, WATER_BODY)
    }
}
*/

pub struct Fade {
    starting_dashboard: DashboardPattern,
    starting_lights: BodyPattern,

    start_time: Instant,
    dashboard_slope: [RGB<I8F8>; 3],
    body_slope: [RGB<I8F8>; 60],
    frames: U16F0,
}

impl Fade {
    fn new(
        dashboard: DashboardPattern,
        lights: BodyPattern,
        ending_dashboard: DashboardPattern,
        ending_lights: BodyPattern,
        frames: U16F0,
        time: Instant,
    ) -> Self {
        let mut dashboard_slope = [Default::default(); 3];
        let mut body_slope = [Default::default(); 60];
        for i in 0..3 {
            let slope = RGB {
                r: calculate_slope(dashboard[i].r, ending_dashboard[i].r, frames),
                g: calculate_slope(dashboard[i].g, ending_dashboard[i].g, frames),
                b: calculate_slope(dashboard[i].b, ending_dashboard[i].b, frames),
            };
            dashboard_slope[i] = slope;
        }

        for i in 0..60 {
            let slope = RGB {
                r: calculate_slope(lights[i].r, ending_lights[i].r, frames),
                g: calculate_slope(lights[i].g, ending_lights[i].g, frames),
                b: calculate_slope(lights[i].b, ending_lights[i].b, frames),
            };
            body_slope[i] = slope;
        }

        Self {
            starting_dashboard: dashboard,
            starting_lights: lights,
            start_time: time,
            dashboard_slope,
            body_slope,
            frames,
        }
    }
}

impl Animation for Fade {
    fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
        let mut frames = calculate_frames(self.start_time.0, time.0);
        if frames > self.frames {
            frames = self.frames
        }
        let mut dashboard_pattern: DashboardPattern = OFF_DASHBOARD;
        let mut body_pattern: BodyPattern = OFF_BODY;

        for i in 0..3 {
            dashboard_pattern[i].r = linear_ease(
                self.starting_dashboard[i].r,
                frames,
                self.dashboard_slope[i].r,
            );
            dashboard_pattern[i].g = linear_ease(
                self.starting_dashboard[i].g,
                frames,
                self.dashboard_slope[i].g,
            );
            dashboard_pattern[i].b = linear_ease(
                self.starting_dashboard[i].b,
                frames,
                self.dashboard_slope[i].b,
            );
        }

        for i in 0..60 {
            body_pattern[i].r =
                linear_ease(self.starting_lights[i].r, frames, self.body_slope[i].r);
            body_pattern[i].g =
                linear_ease(self.starting_lights[i].g, frames, self.body_slope[i].g);
            body_pattern[i].b =
                linear_ease(self.starting_lights[i].b, frames, self.body_slope[i].b);
        }

        (dashboard_pattern, body_pattern)
    }
}

#[derive(Debug)]
pub enum FadeDirection {
    Transition,
    FadeIn,
    FadeOut,
}

pub enum BlinkerDirection {
    Left,
    Right,
}

pub struct Blinker {
    transition: Fade,
    fade_in: Fade,
    fade_out: Fade,
    direction: FadeDirection,

    start_time: Instant,
    frames: U16F0,
}

impl Blinker {
    fn new(
        starting_dashboard: DashboardPattern,
        starting_body: BodyPattern,
        direction: BlinkerDirection,
        time: Instant,
    ) -> Self {
        let mut ending_dashboard = OFF_DASHBOARD;

        match direction {
            BlinkerDirection::Left => {
                ending_dashboard[0].r = LEFT_BLINKER_DASHBOARD[0].r;
                ending_dashboard[0].g = LEFT_BLINKER_DASHBOARD[0].g;
                ending_dashboard[0].b = LEFT_BLINKER_DASHBOARD[0].b;
            }
            BlinkerDirection::Right => {
                ending_dashboard[2].r = RIGHT_BLINKER_DASHBOARD[2].r;
                ending_dashboard[2].g = RIGHT_BLINKER_DASHBOARD[2].g;
                ending_dashboard[2].b = RIGHT_BLINKER_DASHBOARD[2].b;
            }
        }

        let mut ending_body = OFF_BODY;
        match direction {
            BlinkerDirection::Left => {
                for i in 0..30 {
                    ending_body[i].r = LEFT_BLINKER_BODY[i].r;
                    ending_body[i].g = LEFT_BLINKER_BODY[i].g;
                    ending_body[i].b = LEFT_BLINKER_BODY[i].b;
                }
            }
            BlinkerDirection::Right => {
                for i in 30..60 {
                    ending_body[i].r = RIGHT_BLINKER_BODY[i].r;
                    ending_body[i].g = RIGHT_BLINKER_BODY[i].g;
                    ending_body[i].b = RIGHT_BLINKER_BODY[i].b;
                }
            }
        }

        Blinker {
            transition: Fade::new(
                starting_dashboard,
                starting_body,
                ending_dashboard,
                ending_body,
                BLINKER_FRAMES,
                time,
            ),
            fade_in: Fade::new(
                OFF_DASHBOARD,
                OFF_BODY,
                ending_dashboard,
                ending_body,
                BLINKER_FRAMES,
                time,
            ),
            fade_out: Fade::new(
                ending_dashboard,
                ending_body,
                OFF_DASHBOARD,
                OFF_BODY,
                BLINKER_FRAMES,
                time,
            ),
            direction: FadeDirection::Transition,
            start_time: time,
            frames: BLINKER_FRAMES,
        }
    }
}

impl Animation for Blinker {
    fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
        let frames = calculate_frames(self.start_time.0, time.0);
        if frames > self.frames {
            match self.direction {
                FadeDirection::Transition => {
                    self.direction = FadeDirection::FadeOut;
                    self.fade_out.start_time = time;
                }
                FadeDirection::FadeIn => {
                    self.direction = FadeDirection::FadeOut;
                    self.fade_out.start_time = time;
                }
                FadeDirection::FadeOut => {
                    self.direction = FadeDirection::FadeIn;
                    self.fade_in.start_time = time;
                }
            }
            self.start_time = time;
        }

        match self.direction {
            FadeDirection::Transition => self.transition.tick(time),
            FadeDirection::FadeIn => self.fade_in.tick(time),
            FadeDirection::FadeOut => self.fade_out.tick(time),
        }
    }
}

#[derive(Clone, Debug)]
pub enum Event {
    Brake,
    BrakeRelease,
    LeftBlinker,
    NextPattern,
    PreviousPattern,
    RightBlinker,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Pattern {
    Water,
    GayPride,
    TransPride,
}

impl Pattern {
    fn previous(&self) -> Pattern {
        match self {
            Pattern::Water => Pattern::TransPride,
            Pattern::GayPride => Pattern::Water,
            Pattern::TransPride => Pattern::GayPride,
        }
    }

    fn next(&self) -> Pattern {
        match self {
            Pattern::Water => Pattern::GayPride,
            Pattern::GayPride => Pattern::TransPride,
            Pattern::TransPride => Pattern::Water,
        }
    }

    fn dashboard(&self) -> DashboardPattern {
        match self {
            Pattern::Water => WATER_DASHBOARD,
            Pattern::GayPride => PRIDE_DASHBOARD,
            Pattern::TransPride => TRANS_PRIDE_DASHBOARD,
        }
    }

    fn body(&self) -> BodyPattern {
        match self {
            Pattern::Water => WATER_BODY,
            Pattern::GayPride => PRIDE_BODY,
            Pattern::TransPride => TRANS_PRIDE_BODY,
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum State {
    Pattern(Pattern),
    Brake,
    LeftBlinker,
    RightBlinker,
    BrakeLeftBlinker,
    BrakeRightBlinker,
}

pub struct App {
    ui: Box<dyn UI>,
    state: State,
    home_pattern: Pattern,
    current_animation: Box<dyn Animation>,
    dashboard_lights: DashboardPattern,
    lights: BodyPattern,
}

impl App {
    pub fn new(ui: Box<dyn UI>) -> Self {
        let pattern = Pattern::Water;
        Self {
            ui,
            state: State::Pattern(pattern),
            home_pattern: pattern,
            current_animation: Box::new(Fade::new(
                OFF_DASHBOARD,
                OFF_BODY,
                pattern.dashboard(),
                pattern.body(),
                DEFAULT_FRAMES,
                Instant(0_u32.into()),
            )),
            dashboard_lights: OFF_DASHBOARD,
            lights: OFF_BODY,
        }
    }

    fn update_animation(&mut self, time: Instant) {
        match self.state {
            State::Pattern(ref pattern) => {
                self.current_animation = Box::new(Fade::new(
                    self.dashboard_lights,
                    self.lights,
                    pattern.dashboard(),
                    pattern.body(),
                    DEFAULT_FRAMES,
                    time,
                ))
            }
            State::Brake => {
                self.current_animation = Box::new(Fade::new(
                    self.dashboard_lights,
                    self.lights,
                    BRAKES_DASHBOARD,
                    BRAKES_BODY,
                    BRAKES_FRAMES,
                    time,
                ));
            }
            State::LeftBlinker => {
                self.current_animation = Box::new(Blinker::new(
                    self.dashboard_lights,
                    self.lights,
                    BlinkerDirection::Left,
                    time,
                ));
            }
            State::RightBlinker => {
                self.current_animation = Box::new(Blinker::new(
                    self.dashboard_lights,
                    self.lights,
                    BlinkerDirection::Right,
                    time,
                ));
            }
            State::BrakeLeftBlinker => (),
            State::BrakeRightBlinker => (),
        }
    }

    fn update_state(&mut self, event: Event) {
        match event {
            Event::Brake => {
                if self.state == State::Brake {
                    self.state = State::Pattern(self.home_pattern);
                } else {
                    self.state = State::Brake;
                }
            }
            Event::BrakeRelease => self.state = State::Pattern(self.home_pattern),
            Event::LeftBlinker => match self.state {
                State::Brake => self.state = State::BrakeLeftBlinker,
                State::BrakeLeftBlinker => self.state = State::Brake,
                State::LeftBlinker => self.state = State::Pattern(self.home_pattern),
                _ => self.state = State::LeftBlinker,
            },
            Event::NextPattern => if let State::Pattern(ref pattern) = self.state {
                self.home_pattern = pattern.next();
                self.state = State::Pattern(self.home_pattern);
            },
            Event::PreviousPattern => if let State::Pattern(ref pattern) = self.state {
                self.home_pattern = pattern.previous();
                self.state = State::Pattern(self.home_pattern);
            },
            Event::RightBlinker => match self.state {
                State::Brake => self.state = State::BrakeRightBlinker,
                State::BrakeRightBlinker => self.state = State::Brake,
                State::RightBlinker => self.state = State::Pattern(self.home_pattern),
                _ => self.state = State::RightBlinker,
            },
        }
    }

    pub fn tick(&mut self, time: Instant) {
        if let Some(event) = self.ui.check_event(time) {
            self.update_state(event);
            self.update_animation(time);
        };

        let (dashboard, lights) = self.current_animation.tick(time);
        self.dashboard_lights = dashboard;
        self.lights = lights;
        self.ui.update_lights(dashboard, lights);
    }
}