use adw::prelude::*;
use fixed::types::{I8F8, U128F0};
use glib::{Object, Sender};
use gtk::subclass::prelude::*;
use lights_core::{
    App, BodyPattern, DashboardPattern, Event, Instant, FPS, OFF_BODY, OFF_DASHBOARD, RGB, UI,
};
use std::{
    cell::RefCell,
    env,
    rc::Rc,
    sync::mpsc::{Receiver, TryRecvError},
};

const WIDTH: i32 = 640;
const HEIGHT: i32 = 480;

pub struct Update {
    dashboard: DashboardPattern,
    lights: BodyPattern,
}

pub struct DashboardLightsPrivate {
    lights: Rc<RefCell<DashboardPattern>>,
}

#[glib::object_subclass]
impl ObjectSubclass for DashboardLightsPrivate {
    const NAME: &'static str = "DashboardLights";
    type Type = DashboardLights;
    type ParentType = gtk::DrawingArea;

    fn new() -> Self {
        Self {
            lights: Rc::new(RefCell::new(OFF_DASHBOARD)),
        }
    }
}

impl ObjectImpl for DashboardLightsPrivate {}
impl WidgetImpl for DashboardLightsPrivate {}
impl DrawingAreaImpl for DashboardLightsPrivate {}

glib::wrapper! {
    pub struct DashboardLights(ObjectSubclass<DashboardLightsPrivate>) @extends gtk::DrawingArea, gtk::Widget;
}

impl DashboardLights {
    pub fn new() -> Self {
        let s: Self = Object::builder().build();

        s.set_width_request(WIDTH);
        s.set_height_request(100);

        s.set_draw_func({
            let s = s.clone();
            move |_, context, width, _| {
                let start = width as f64 / 2. - 150.;
                let lights = s.imp().lights.borrow();
                for i in 0..3 {
                    context.set_source_rgb(
                        lights[i].r.into(),
                        lights[i].g.into(),
                        lights[i].b.into(),
                    );
                    context.rectangle(start + 100. * i as f64, 10., 80., 80.);
                    let _ = context.fill();
                }
            }
        });

        s
    }

    pub fn set_lights(&self, lights: DashboardPattern) {
        *self.imp().lights.borrow_mut() = lights;
        self.queue_draw();
    }
}

pub struct BikeLightsPrivate {
    lights: Rc<RefCell<BodyPattern>>,
}

#[glib::object_subclass]
impl ObjectSubclass for BikeLightsPrivate {
    const NAME: &'static str = "BikeLights";
    type Type = BikeLights;
    type ParentType = gtk::DrawingArea;

    fn new() -> Self {
        Self {
            lights: Rc::new(RefCell::new(OFF_BODY)),
        }
    }
}

impl ObjectImpl for BikeLightsPrivate {}
impl WidgetImpl for BikeLightsPrivate {}
impl DrawingAreaImpl for BikeLightsPrivate {}

glib::wrapper! {
    pub struct BikeLights(ObjectSubclass<BikeLightsPrivate>) @extends gtk::DrawingArea, gtk::Widget;
}

impl BikeLights {
    pub fn new() -> Self {
        let s: Self = Object::builder().build();

        s.set_width_request(WIDTH);
        s.set_height_request(640);

        let center = WIDTH as f64 / 2.;

        s.set_draw_func({
            let s = s.clone();
            move |_, context, _, _| {
                let lights = s.imp().lights.borrow();
                for i in 0..30 {
                    context.set_source_rgb(
                        lights[i].r.into(),
                        lights[i].g.into(),
                        lights[i].b.into(),
                    );
                    context.rectangle(center - 45., 5. + 20. * i as f64, 15., 15.);
                    let _ = context.fill();
                }
                for i in 0..30 {
                    context.set_source_rgb(
                        lights[i + 30].r.into(),
                        lights[i + 30].g.into(),
                        lights[i + 30].b.into(),
                    );
                    context.rectangle(center + 15., 5. + 20. * (30. - (i + 1) as f64), 15., 15.);
                    let _ = context.fill();
                }
            }
        });

        s
    }

    pub fn set_lights(&self, lights: [RGB<I8F8>; 60]) {
        *self.imp().lights.borrow_mut() = lights;
        self.queue_draw();
    }
}

struct GTKUI {
    tx: Sender<Update>,
    rx: Receiver<Event>,
}

impl UI for GTKUI {
    fn check_event(&mut self, _: Instant) -> Option<Event> {
        match self.rx.try_recv() {
            Ok(event) => Some(event),
            Err(TryRecvError::Empty) => None,
            Err(TryRecvError::Disconnected) => None,
        }
    }

    fn update_lights(&self, dashboard_lights: DashboardPattern, lights: BodyPattern) {
        self.tx
            .send(Update {
                dashboard: dashboard_lights,
                lights,
            })
            .unwrap();
    }
}

fn main() {
    let adw_app = adw::Application::builder()
        .application_id("com.luminescent-dreams.bike-light-simulator")
        .build();

    adw_app.connect_activate(move |adw_app| {
        let (update_tx, update_rx) =
            gtk::glib::MainContext::channel::<Update>(gtk::glib::Priority::DEFAULT);
        let (event_tx, event_rx) = std::sync::mpsc::channel();

        std::thread::spawn(move || {
            let mut bike_app = App::new(Box::new(GTKUI {
                tx: update_tx,
                rx: event_rx,
            }));
            loop {
                bike_app.tick(Instant(U128F0::from(
                    std::time::SystemTime::now()
                        .duration_since(std::time::UNIX_EPOCH)
                        .unwrap()
                        .as_millis(),
                )));
                std::thread::sleep(std::time::Duration::from_millis(1000 / (FPS as u64)));
            }
        });

        let window = adw::ApplicationWindow::builder()
            .application(adw_app)
            .default_width(WIDTH)
            .default_height(HEIGHT)
            .build();
        let layout = gtk::Box::builder()
            .orientation(gtk::Orientation::Vertical)
            .build();
        let controls = gtk::Box::builder()
            .orientation(gtk::Orientation::Horizontal)
            .build();

        let dashboard_lights = DashboardLights::new();
        let bike_lights = BikeLights::new();

        let left_button = gtk::Button::builder().label("L").build();
        let brake_button = gtk::Button::builder().label("Brakes").build();
        let right_button = gtk::Button::builder().label("R").build();

        left_button.connect_clicked({
            let event_tx = event_tx.clone();
            move |_| {
                let _ = event_tx.send(Event::LeftBlinker);
            }
        });

        brake_button.connect_clicked({
            let event_tx = event_tx.clone();
            move |_| {
                let _ = event_tx.send(Event::Brake);
            }
        });

        right_button.connect_clicked({
            let event_tx = event_tx.clone();
            move |_| {
                let _ = event_tx.send(Event::RightBlinker);
            }
        });

        controls.append(&left_button);
        controls.append(&brake_button);
        controls.append(&right_button);
        layout.append(&controls);

        let pattern_controls = gtk::Box::builder()
            .orientation(gtk::Orientation::Horizontal)
            .build();

        let previous_pattern = gtk::Button::builder().label("Previous").build();
        let next_pattern = gtk::Button::builder().label("Next").build();

        previous_pattern.connect_clicked({
            let event_tx = event_tx.clone();
            move |_| {
                let _ = event_tx.send(Event::PreviousPattern);
            }
        });

        next_pattern.connect_clicked({
            let event_tx = event_tx.clone();
            move |_| {
                let _ = event_tx.send(Event::NextPattern);
            }
        });

        pattern_controls.append(&previous_pattern);
        pattern_controls.append(&next_pattern);
        layout.append(&pattern_controls);

        layout.append(&dashboard_lights);
        layout.append(&bike_lights);

        update_rx.attach(None, {
            let dashboard_lights = dashboard_lights.clone();
            let bike_lights = bike_lights.clone();
            move |Update { dashboard, lights }| {
                dashboard_lights.set_lights(dashboard);
                bike_lights.set_lights(lights);
                glib::ControlFlow::Continue
            }
        });

        window.set_content(Some(&layout));
        window.present();
    });

    let args: Vec<String> = env::args().collect();
    ApplicationExtManual::run_with_args(&adw_app, &args);
}
