#![no_std]
#![no_main]

use core::{cell::UnsafeCell, convert::Infallible, panic::PanicInfo};

use embedded_graphics::{
    pixelcolor::{Gray8, GrayColor},
    prelude::*,
    primitives::Rectangle,
    Pixel,
};

use kinda::{
    App, AppCommand, AppUpdate, InputEvent, WifiAccessPoint, WifiCredentials, WifiEvent,
    WifiNetworkList, WifiSecurity, WifiSsid,
};

const WIDTH: u32 = 800;
const HEIGHT: u32 = 480;
const PIXEL_COUNT: usize = (WIDTH * HEIGHT) as usize;

const EVENT_UP: u32 = 0;
const EVENT_DOWN: u32 = 1;
const EVENT_SELECT: u32 = 2;
const EVENT_BACK: u32 = 3;
const EVENT_SELECT_LONG: u32 = 4;
const EVENT_BACK_LONG: u32 = 5;

struct Shared<T> {
    value: UnsafeCell<T>,
}

unsafe impl<T> Sync for Shared<T> {}

impl<T> Shared<T> {
    const fn new(value: T) -> Self {
        Self {
            value: UnsafeCell::new(value),
        }
    }

    fn get(&self) -> &mut T {
        unsafe { &mut *self.value.get() }
    }
}

struct WebSimulator {
    app: Option<App>,
}

impl WebSimulator {
    const fn new() -> Self {
        Self { app: None }
    }

    fn reset(&mut self) {
        self.app = Some(App::new());
        self.render();
    }

    fn input(&mut self, event: InputEvent) {
        let update = self.app().update(event);
        self.apply_update(update);
    }

    fn app(&mut self) -> &mut App {
        self.app.get_or_insert_with(App::new)
    }

    fn apply_update(&mut self, update: AppUpdate) {
        let mut dirty = update.dirty;
        let mut command = update.command;

        while let Some(next_command) = command.take() {
            let wifi_event = self.handle_command(next_command);
            let next_update = self.app().handle_wifi_event(wifi_event);
            dirty |= next_update.dirty;
            command = next_update.command;
        }

        if dirty {
            self.render();
        }
    }

    fn handle_command(&self, command: AppCommand) -> WifiEvent {
        match command {
            AppCommand::ScanWifi => WifiEvent::ScanFinished(Ok(self.mock_networks())),
            AppCommand::JoinWifi {
                access_point,
                password: _,
            } => WifiEvent::JoinFinished(Ok(access_point.ssid)),
            AppCommand::JoinSavedWifi { credentials } => self.join_saved(credentials),
        }
    }

    fn join_saved(&self, credentials: WifiCredentials) -> WifiEvent {
        WifiEvent::JoinFinished(Ok(credentials.ssid))
    }

    fn mock_networks(&self) -> WifiNetworkList {
        let mut networks = WifiNetworkList::new();
        self.push_mock_network(
            &mut networks,
            "kinda-lab",
            [0x02, 0x00, 0x00, 0x00, 0x00, 0x01],
            6,
            -42,
            WifiSecurity::Wpa2Personal,
        );
        self.push_mock_network(
            &mut networks,
            "open-books",
            [0x02, 0x00, 0x00, 0x00, 0x00, 0x02],
            11,
            -64,
            WifiSecurity::Open,
        );
        self.push_mock_network(
            &mut networks,
            "reader-test",
            [0x02, 0x00, 0x00, 0x00, 0x00, 0x03],
            1,
            -76,
            WifiSecurity::Wpa2Wpa3Personal,
        );
        networks
    }

    fn push_mock_network(
        &self,
        networks: &mut WifiNetworkList,
        name: &str,
        bssid: [u8; 6],
        channel: u8,
        signal_strength: i8,
        security: WifiSecurity,
    ) {
        let mut ssid = WifiSsid::new();
        if ssid.push_str(name).is_err() {
            return;
        }

        let _ = networks.push(WifiAccessPoint {
            ssid,
            bssid,
            channel,
            signal_strength,
            security,
        });
    }

    fn render(&mut self) {
        let app = self.app();
        let mut display = PixelDisplay::new(PIXELS.get());
        match app.render(&mut display) {
            Ok(()) => {}
            Err(error) => match error {},
        }
    }
}

struct PixelDisplay<'a> {
    pixels: &'a mut [u8; PIXEL_COUNT],
}

impl<'a> PixelDisplay<'a> {
    fn new(pixels: &'a mut [u8; PIXEL_COUNT]) -> Self {
        Self { pixels }
    }

    fn set_pixel(&mut self, point: Point, color: Gray8) {
        if point.x < 0 || point.y < 0 {
            return;
        }

        let x = point.x as u32;
        let y = point.y as u32;
        if x >= WIDTH || y >= HEIGHT {
            return;
        }

        self.pixels[(y * WIDTH + x) as usize] = color.luma();
    }
}

impl DrawTarget for PixelDisplay<'_> {
    type Color = Gray8;
    type Error = Infallible;

    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Pixel<Self::Color>>,
    {
        for Pixel(point, color) in pixels {
            self.set_pixel(point, color);
        }

        Ok(())
    }

    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
        let bounds = Rectangle::new(Point::new(0, 0), self.size());
        let area = area.intersection(&bounds);
        if area.size.width == 0 || area.size.height == 0 {
            return Ok(());
        }

        let value = color.luma();
        let x_start = area.top_left.x as u32;
        let y_start = area.top_left.y as u32;
        let x_end = x_start + area.size.width;
        let y_end = y_start + area.size.height;

        for y in y_start..y_end {
            let row_start = (y * WIDTH) as usize;
            for x in x_start..x_end {
                self.pixels[row_start + x as usize] = value;
            }
        }

        Ok(())
    }

    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
        self.pixels.fill(color.luma());
        Ok(())
    }
}

impl OriginDimensions for PixelDisplay<'_> {
    fn size(&self) -> Size {
        Size::new(WIDTH, HEIGHT)
    }
}

static SIMULATOR: Shared<WebSimulator> = Shared::new(WebSimulator::new());
static PIXELS: Shared<[u8; PIXEL_COUNT]> = Shared::new([0; PIXEL_COUNT]);

#[no_mangle]
pub extern "C" fn kinda_sim_init() {
    SIMULATOR.get().reset();
}

#[no_mangle]
pub extern "C" fn kinda_sim_input(event: u32) {
    let Some(event) = input_event(event) else {
        return;
    };

    SIMULATOR.get().input(event);
}

#[no_mangle]
pub extern "C" fn kinda_sim_width() -> u32 {
    WIDTH
}

#[no_mangle]
pub extern "C" fn kinda_sim_height() -> u32 {
    HEIGHT
}

#[no_mangle]
pub extern "C" fn kinda_sim_pixels() -> *const u8 {
    PIXELS.get().as_ptr()
}

#[no_mangle]
pub extern "C" fn kinda_sim_pixel_len() -> usize {
    PIXEL_COUNT
}

fn input_event(event: u32) -> Option<InputEvent> {
    match event {
        EVENT_UP => Some(InputEvent::Up),
        EVENT_DOWN => Some(InputEvent::Down),
        EVENT_SELECT => Some(InputEvent::Select),
        EVENT_BACK => Some(InputEvent::Back),
        EVENT_SELECT_LONG => Some(InputEvent::SelectLong),
        EVENT_BACK_LONG => Some(InputEvent::BackLong),
        _ => None,
    }
}

#[panic_handler]
fn panic(_info: &PanicInfo<'_>) -> ! {
    loop {}
}
