use core::f32::consts::PI;

use alloc::format;
use embedded_graphics::image::ImageRaw;
use embedded_graphics::mono_font::ascii::*;
use embedded_graphics::mono_font::{MonoTextStyle, MonoTextStyleBuilder};
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::{DrawTarget, Size};
use embedded_graphics::primitives::{Line, PrimitiveStyle, PrimitiveStyleBuilder, StyledDrawable};
use embedded_graphics::text::{Alignment, Text};
use embedded_graphics::{image::Image, prelude::Point, Drawable};
use enum_map::EnumMap;
use figments::liber8tion::trig::Trig8;
use figments::mappings::embedded_graphics::Matrix2DSpace;
use figments::{mappings::embedded_graphics::EmbeddedGraphicsSampler};
use figments::prelude::*;
use nalgebra::Vector2;
use micromath::F32Ext;
use embedded_graphics::geometry::OriginDimensions;

use crate::events::{Personality, SensorSource, SensorState};
use crate::graphics::images;
use crate::{ego::engine::MotionState, events::Scene};

#[derive(Default, Debug)]
pub struct OledUI {
    pub scene: Scene,
    pub motion: MotionState,
    pub personality: Personality,
    pub sensor_states: EnumMap<SensorSource, SensorState>,
    pub velocity: f32,
    pub location: Vector2<f64>,
}

#[derive(Default, Debug)]
pub struct OledUniforms {
    pub ui: OledUI,
    pub frame: usize,
    pub current_screen: Screen
}

#[derive(Debug, Default, Clone, Copy)]
pub enum Screen {
    #[default]
    Blank,
    Bootsplash,
    Home,
    Sleeping,
    Waking
}

struct SensorImage {
    source: SensorSource,
    on: &'static ImageRaw<'static, BinaryColor>,
    off: &'static ImageRaw<'static, BinaryColor>,
}

struct SensorIcon<'a> {
    anchor: embedded_graphics::geometry::Point,
    image: &'a SensorImage,
    state: SensorState,
    frame: usize
}

impl<'a> SensorIcon<'a> {
    pub const fn new(image: &'a SensorImage, state: SensorState, frame: usize, anchor: embedded_graphics::geometry::Point) -> Self {
        Self {
            anchor,
            image,
            state,
            frame
        }
    }

    pub fn draw<T: DrawTarget<Color = BinaryColor>>(&self, target: &mut T) -> Result<(), T::Error> {
        match self.state {
            SensorState::AcquiringFix => {
                if (self.frame / 10) % 2 == 0 {
                    Image::new(self.image.on, self.anchor).draw(target)
                } else {
                    Ok(())
                }
            },
            SensorState::Online => {
                Image::new(self.image.on, self.anchor).draw(target)
            },
            SensorState::Offline => {
                Image::new(self.image.off, self.anchor).draw(target)
            },
            SensorState::Degraded => {
                if (self.frame / 10) % 2 == 0 {
                    Image::new(self.image.off, self.anchor).draw(target)
                } else {
                    Ok(())
                }
            }
        }
    }
}

const SENSOR_IMAGES: &[SensorImage] = &[
    SensorImage {
        source: SensorSource::GPS,
        on: &images::GPS_ON, off: &images::GPS_OFF,
    },
    SensorImage {
        source: SensorSource::IMU,
        on: &images::IMU_ON, off: &images::IMU_OFF,
    },
    SensorImage {
        source: SensorSource::MotionFrame,
        on: &images::GRAVITY_LOCATED, off: &images::GRAVITY_MISSING,
    },
    SensorImage {
        source: SensorSource::Location,
        on: &images::LOCATION_ON, off: &images::LOCATION_OFF,
    },
    SensorImage {
        source: SensorSource::Wifi,
        on: &images::ONLINE_CONNECTING, off: &images::OFFLINE
    },
    #[cfg(feature="demo")]
    SensorImage {
        source: SensorSource::Demo,
        on: &images::DEMO, off: &images::DEMO,
    },
    #[cfg(feature="simulation")]
    SensorImage {
        source: SensorSource::Simulation,
        on: &images::TAPE, off: &images::TAPE
    },
];

const DISPLAY_SIZE: Size = Size::new(128, 64);
const SENSOR_ICON_SIZE: u32 = 16;
const SENSOR_ICON_SPACING: u32 = 2;
const SENSOR_TRAY_SIZE: Size = Size::new(SENSOR_IMAGES.len() as u32 * (SENSOR_ICON_SIZE + SENSOR_ICON_SPACING), SENSOR_ICON_SIZE + SENSOR_ICON_SPACING);
const SPEEDO_TRAY_SIZE: Size = Size::new(DISPLAY_SIZE.width - SENSOR_TRAY_SIZE.width, SENSOR_TRAY_SIZE.height);

impl Screen {
    pub fn draw_screen<'a, T>(&self, sampler: &mut EmbeddedGraphicsSampler<'a, T>, state: &OledUniforms) where T: Sample<'a, Matrix2DSpace>, T::Output: AdditivePixelSink<BinaryColor> {
        match self {
            Screen::Blank => (),
            Screen::Bootsplash => {
                Image::new(&images::BOOT_LOGO, Point::zero()).draw(sampler).unwrap();
                const SPARKLE_COUNT: i32 = 8;
                for n in 0..SPARKLE_COUNT {
                    let sparkle_center = Point::new((128u8 * state.frame.wrapping_mul(n as usize).cos8()) as i32, (64u8 * state.frame.wrapping_mul(n as usize).sin8()) as i32);
                    let offset = (state.frame / 2 % 32) as i32 - 16;
                    let rotation = PI * 2.0 * state.frame.sin8();
                    let normal = Point::new((rotation.cos() * offset as f32) as i32, (rotation.sin() * offset as f32) as i32);
                    let cross_normal = Point::new((rotation.sin() * offset as f32) as i32, (rotation.cos() * offset as f32) as i32);
                    // Draw horizontal
                    Line::new(sparkle_center - normal, sparkle_center + normal)
                        .draw_styled(&SPARKLE_STYLE, sampler).unwrap();
                    // Draw vertical
                    Line::new(sparkle_center - cross_normal, sparkle_center + cross_normal)
                        .draw_styled(&SPARKLE_STYLE, sampler).unwrap();
                }
            },
            Screen::Home => {
                // Status bar
                // Sensor indicators
                for (idx, sensor) in SENSOR_IMAGES.iter().enumerate() {
                    let offset = idx * (16 + 2);
                    let position = Point::new(offset as i32, 0);
                    SensorIcon::new(sensor, state.ui.sensor_states[sensor.source], state.frame, position)
                        .draw(sampler).unwrap();
                }

                let speedo_center = SENSOR_TRAY_SIZE.width + SPEEDO_TRAY_SIZE.width / 2;

                // Speed display at the top
                Text::with_alignment(&format!("{}", state.ui.velocity), Point::new(speedo_center as i32, 12), SPEED_STYLE, Alignment::Center)
                    .draw(sampler)
                    .unwrap();

                // Separates the status bar from the UI
                Line::new(Point::new(0, SENSOR_TRAY_SIZE.height as i32), Point::new(128, SENSOR_TRAY_SIZE.height as i32)).draw_styled(&INACTIVE_STYLE, sampler).unwrap();

                // The main UI content
                Image::new(&images::BIKE, Point::new((128 / 2 - images::BIKE.size().width / 2) as i32, 24)).draw(sampler).unwrap();

                let headlight_img = if state.ui.personality == Personality::Active {
                    &images::HEADLIGHT_ON
                } else {
                    &images::HEADLIGHT_OFF
                };
                let brakelight_img = if state.ui.personality == Personality::Active {
                    &images::BRAKELIGHT_ON
                } else {
                    &images::BRAKELIGHT_OFF
                };
                Image::new(headlight_img, Point::new(((128 / 2 - images::BIKE.size().width / 2) - 18) as i32, 28)).draw(sampler).unwrap();
                Image::new(brakelight_img, Point::new(((128 / 2 + images::BIKE.size().width / 2) + 2) as i32, 28)).draw(sampler).unwrap();

                // TODO: Replace the state texts with cute animations or smth
                // Current prediction from the motion engine
                Text::with_alignment(&format!("{:?}", state.ui.motion), Point::new(128 / 2, 64 - 3), TEXT_STYLE, Alignment::Center)
                    .draw(sampler)
                    .unwrap();
                
                // Current scene in the UI
                Text::with_alignment(&format!("{:?}", state.ui.scene), Point::new(128 / 2, 64 - 13), TEXT_STYLE, Alignment::Center)
                    .draw(sampler)
                    .unwrap();
            },
            Screen::Sleeping => {
                Text::with_alignment("so eepy Zzz...", Point::new(128 / 2, 64 / 2), LOGO_STYLE, Alignment::Center)
                    .draw(sampler)
                    .unwrap();
            },
            Screen::Waking => {
                Text::with_alignment("OwO hewwo again :D", Point::new(128 / 2, 64 / 2), LOGO_STYLE, Alignment::Center)
                    .draw(sampler)
                    .unwrap();
            }
        }
    }
}

impl<HwPixel: AdditivePixelSink<BinaryColor>> RenderSource<OledUniforms, Matrix2DSpace, BinaryColor, HwPixel> for Screen {
    fn render_to<'a, Smp>(&'a self, output: &'a mut Smp, uniforms: &OledUniforms)
        where 
            Smp: Sample<'a, Matrix2DSpace, Output = HwPixel> {
        let mut sampler = EmbeddedGraphicsSampler(output, embedded_graphics::primitives::Rectangle::new(Point::new(0, 0), Size::new(128, 64)));
        self.draw_screen(&mut sampler, uniforms);
    }
}

const SPARKLE_STYLE: PrimitiveStyle<BinaryColor> = PrimitiveStyleBuilder::new()
    .stroke_color(BinaryColor::On)
    .stroke_width(2)
    .build();

const TEXT_STYLE: MonoTextStyle<BinaryColor> = MonoTextStyleBuilder::new()
    .font(&FONT_6X10)
    .text_color(BinaryColor::On)
    .build();

const LOGO_STYLE: MonoTextStyle<BinaryColor> = MonoTextStyleBuilder::new()
    .font(&FONT_9X18_BOLD)
    .text_color(BinaryColor::On)
    .build();

const SPEED_STYLE: MonoTextStyle<BinaryColor> = MonoTextStyleBuilder::new()
    .font(&FONT_10X20)
    .text_color(BinaryColor::On)
    .build();

const INACTIVE_STYLE: PrimitiveStyle<BinaryColor> = PrimitiveStyleBuilder::new()
    .fill_color(BinaryColor::Off)
    .stroke_color(BinaryColor::On)
    .stroke_width(2)
    .build();