use core::fmt::Write;

use embedded_graphics::{
    geometry::{Point, Size},
    pixelcolor::Gray8,
    prelude::{DrawTarget, OriginDimensions},
};
use heapless::String;

use crate::input::InputEvent;
use crate::ui::{app::AppCommand, frame::Frame, RefreshHint};

use super::ScreenUpdate;

const BUTTON_COUNT: usize = 6;
const BUTTONS: [ButtonKind; BUTTON_COUNT] = [
    ButtonKind::Up,
    ButtonKind::Down,
    ButtonKind::Select,
    ButtonKind::Back,
    ButtonKind::SelectLong,
    ButtonKind::BackLong,
];
const LIGHT_BUTTON_LEFT: u16 = 264;
const LIGHT_BUTTON_TOP: u16 = 146;
const LIGHT_BUTTON_RIGHT: u16 = 432;
const LIGHT_BUTTON_BOTTOM: u16 = 242;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct DemoScreen {
    button_counts: [u16; BUTTON_COUNT],
    touch_count: u16,
    last_touch: Option<TouchPoint>,
    last_button: Option<ButtonKind>,
    last_event: LastEvent,
    light_on: bool,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct TouchPoint {
    x: u16,
    y: u16,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum LastEvent {
    Startup,
    Button(ButtonKind),
    Touch(TouchPoint),
    LightToggle { on: bool },
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ButtonKind {
    Up,
    Down,
    Select,
    Back,
    SelectLong,
    BackLong,
}

impl DemoScreen {
    pub const fn new() -> Self {
        Self {
            button_counts: [0; BUTTON_COUNT],
            touch_count: 0,
            last_touch: None,
            last_button: None,
            last_event: LastEvent::Startup,
            light_on: true,
        }
    }

    pub fn update(&mut self, event: InputEvent) -> ScreenUpdate {
        match event {
            InputEvent::Up => self.record_button(ButtonKind::Up),
            InputEvent::Down => self.record_button(ButtonKind::Down),
            InputEvent::Select => self.record_button(ButtonKind::Select),
            InputEvent::Back => self.record_button(ButtonKind::Back),
            InputEvent::SelectLong => self.record_button(ButtonKind::SelectLong),
            InputEvent::BackLong => self.record_button(ButtonKind::BackLong),
            InputEvent::Touch { x, y } => {
                let touch = TouchPoint { x, y };
                self.record_touch(touch);
                if Self::light_button_contains(touch) {
                    self.toggle_light();
                    return ScreenUpdate::with_command(
                        true,
                        RefreshHint::Fast,
                        AppCommand::SetFrontLight { on: self.light_on },
                    );
                }
            }
        }

        ScreenUpdate::new(true, RefreshHint::Fast)
    }

    pub fn render<D>(&self, frame: &mut Frame<'_, D>) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        frame.title("Kinda UI demo")?;
        frame.small(64, "Button and touch input demo.")?;

        self.render_buttons(frame)?;
        self.render_touch_panel(frame)?;
        self.render_light_button(frame)?;
        self.render_last_event(frame)?;
        self.render_event_meter(frame)?;

        frame.footer("Press buttons or tap the display")
    }

    fn record_button(&mut self, button: ButtonKind) {
        let index = button.index();
        self.button_counts[index] = self.button_counts[index].saturating_add(1);
        self.last_button = Some(button);
        self.last_event = LastEvent::Button(button);
    }

    fn record_touch(&mut self, touch: TouchPoint) {
        self.touch_count = self.touch_count.saturating_add(1);
        self.last_touch = Some(touch);
        self.last_button = None;
        self.last_event = LastEvent::Touch(touch);
    }

    fn toggle_light(&mut self) {
        self.light_on = !self.light_on;
        self.last_event = LastEvent::LightToggle { on: self.light_on };
    }

    fn light_button_contains(touch: TouchPoint) -> bool {
        touch.x >= LIGHT_BUTTON_LEFT
            && touch.x <= LIGHT_BUTTON_RIGHT
            && touch.y >= LIGHT_BUTTON_TOP
            && touch.y <= LIGHT_BUTTON_BOTTOM
    }

    fn render_buttons<D>(&self, frame: &mut Frame<'_, D>) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        frame.body(106, "Buttons")?;

        let mut y = 146;
        for button in BUTTONS {
            self.render_button_row(frame, y, button)?;
            y += 34;
        }

        Ok(())
    }

    fn render_button_row<D>(
        &self,
        frame: &mut Frame<'_, D>,
        y: i32,
        button: ButtonKind,
    ) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let marker = if self.last_button == Some(button) {
            ">"
        } else {
            " "
        };
        let mut row = String::<48>::new();
        let _ = write!(
            row,
            "{} {:<12} {:>3}",
            marker,
            button.label(),
            self.button_counts[button.index()]
        );
        frame.small(y, row.as_str())
    }

    fn render_light_button<D>(&self, frame: &mut Frame<'_, D>) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let black = Gray8::new(0);
        let white = Gray8::new(255);
        frame.filled_rect(
            Point::new(i32::from(LIGHT_BUTTON_LEFT), i32::from(LIGHT_BUTTON_TOP)),
            Size::new(
                u32::from(LIGHT_BUTTON_RIGHT - LIGHT_BUTTON_LEFT),
                u32::from(LIGHT_BUTTON_BOTTOM - LIGHT_BUTTON_TOP),
            ),
            white,
        )?;
        frame.outline_rect(
            Point::new(i32::from(LIGHT_BUTTON_LEFT), i32::from(LIGHT_BUTTON_TOP)),
            Size::new(
                u32::from(LIGHT_BUTTON_RIGHT - LIGHT_BUTTON_LEFT),
                u32::from(LIGHT_BUTTON_BOTTOM - LIGHT_BUTTON_TOP),
            ),
            black,
            2,
        )?;
        frame.small_at(Point::new(284, 164), "Front light")?;
        let label = if self.light_on { "On" } else { "Off" };
        frame.body_at(Point::new(284, 198), label)
    }

    fn render_touch_panel<D>(&self, frame: &mut Frame<'_, D>) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let black = Gray8::new(0);
        frame.body(378, "Touch")?;
        frame.outline_rect(Point::new(48, 420), Size::new(384, 228), black, 2)?;
        frame.line(Point::new(48, 534), Point::new(432, 534), black)?;
        frame.line(Point::new(240, 420), Point::new(240, 648), black)?;
        frame.small_at(Point::new(70, 456), "Tap/click")?;
        frame.small_at(Point::new(70, 486), "anywhere")?;
        frame.small_at(Point::new(262, 456), "Last touch")?;

        let mut touch_line = String::<64>::new();
        if let Some(touch) = self.last_touch {
            let _ = write!(touch_line, "{}, {}", touch.x, touch.y);
            self.render_crosshair(frame, touch)?;
        } else {
            let _ = touch_line.push_str("none yet");
        }
        frame.small_at(Point::new(262, 490), touch_line.as_str())?;

        let mut count_line = String::<48>::new();
        let _ = write!(count_line, "Touches: {}", self.touch_count);
        frame.small_at(Point::new(70, 576), count_line.as_str())
    }

    fn render_crosshair<D>(
        &self,
        frame: &mut Frame<'_, D>,
        touch: TouchPoint,
    ) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let black = Gray8::new(0);
        let x = i32::from(touch.x);
        let y = i32::from(touch.y);

        frame.line(Point::new(x - 18, y), Point::new(x - 6, y), black)?;
        frame.line(Point::new(x + 6, y), Point::new(x + 18, y), black)?;
        frame.line(Point::new(x, y - 18), Point::new(x, y - 6), black)?;
        frame.line(Point::new(x, y + 6), Point::new(x, y + 18), black)?;
        frame.circle(Point::new(x - 5, y - 5), 10, black)
    }

    fn render_last_event<D>(&self, frame: &mut Frame<'_, D>) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let mut line = String::<96>::new();
        match self.last_event {
            LastEvent::Startup => {
                let _ = line.push_str("Last event: waiting for input");
            }
            LastEvent::Button(button) => {
                let _ = write!(
                    line,
                    "Last event: {} button, count {}",
                    button.label(),
                    self.button_counts[button.index()]
                );
            }
            LastEvent::Touch(touch) => {
                let _ = write!(line, "Last event: touch at {}, {}", touch.x, touch.y);
            }
            LastEvent::LightToggle { on } => {
                let state = if on { "on" } else { "off" };
                let _ = write!(line, "Last event: front light {}", state);
            }
        }

        frame.small(684, line.as_str())
    }

    fn render_event_meter<D>(&self, frame: &mut Frame<'_, D>) -> Result<(), D::Error>
    where
        D: DrawTarget<Color = Gray8> + OriginDimensions,
    {
        let black = Gray8::new(0);
        let origin = Point::new(48, 724);
        let size = Size::new(384, 18);
        frame.outline_rect(origin, size, black, 2)?;

        let filled = ((self.total_events() % 41) * 380) / 40;
        if filled > 0 {
            frame.filled_rect(Point::new(50, 726), Size::new(filled, 14), black)?;
        }

        Ok(())
    }

    fn total_events(&self) -> u32 {
        let mut total = u32::from(self.touch_count);
        for count in self.button_counts {
            total = total.saturating_add(u32::from(count));
        }
        total
    }
}

impl ButtonKind {
    const fn index(self) -> usize {
        match self {
            Self::Up => 0,
            Self::Down => 1,
            Self::Select => 2,
            Self::Back => 3,
            Self::SelectLong => 4,
            Self::BackLong => 5,
        }
    }

    const fn label(self) -> &'static str {
        match self {
            Self::Up => "Up",
            Self::Down => "Down",
            Self::Select => "Select",
            Self::Back => "Back",
            Self::SelectLong => "Hold select",
            Self::BackLong => "Hold back",
        }
    }
}
