#![feature(likely_unlikely)]
#![deny(clippy::all)]

use std::env::args;
use std::process::exit;
use std::sync::atomic::AtomicU32;
use std::sync::{Arc, Mutex};
use std::thread::{scope, sleep};
use std::time::Duration;

use pixels::{Error, Pixels, SurfaceTexture};
use winit::dpi::LogicalSize;
use winit::event::{Event, WindowEvent};
use winit::event_loop::EventLoop;
use winit::keyboard::KeyCode;
use winit::platform::scancode::PhysicalKeyExtScancode;
use winit::window::Window;
use winit_input_helper::WinitInputHelper;

use crate::cpu::Computer;

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

mod cpu;

fn main() -> Result<(), Error> {
    let event_loop = EventLoop::new().unwrap();
    let mut input = WinitInputHelper::new();
    let window = {
        let size = LogicalSize::new((WIDTH * 3) as f64, (HEIGHT * 3) as f64);
        #[allow(deprecated)]
        Arc::new(
            event_loop
                .create_window(
                    Window::default_attributes()
                        .with_title("bisare screen")
                        .with_inner_size(size)
                        .with_min_inner_size(size),
                )
                .unwrap(),
        )
    };

    let pixels = Mutex::new({
        let window_size = window.inner_size();
        let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
        Pixels::new(WIDTH, HEIGHT, surface_texture)?
    });

    let keyboard = AtomicU32::new(0);
    let program = args()
        .nth(1)
        .expect("you must supply the exec name as the first argument");
    let kbref = &keyboard;
    let pixelref = &pixels;
    scope(|sc| {
        sc.spawn(|| {
            let mut simulation = Computer::new(program, pixelref, kbref);
            loop {
                simulation.step();
            }
        });

        #[allow(deprecated)]
        let res = event_loop.run(|event, elwt| {
            match event {
                Event::Resumed => {}
                Event::NewEvents(_) => input.step(),
                Event::AboutToWait => input.end_step(),
                Event::DeviceEvent { event, .. } => {
                    input.process_device_event(&event);
                }
                Event::WindowEvent { event, .. } => {
                    // Draw the current frame
                    if event == WindowEvent::RedrawRequested {
                        if let Err(_) = pixels.lock().unwrap().render() {
                            elwt.exit();
                            return;
                        }
                        sleep(Duration::from_millis(30));
                    }

                    if let WindowEvent::KeyboardInput {
                        device_id: _,
                        ref event,
                        is_synthetic: _,
                    } = event
                    {
                        match event.state {
                            winit::event::ElementState::Pressed => {
                                if let Some(val) = event.physical_key.to_scancode() {
                                    println!("key with code {val} was pressed");
                                    kbref.store(val, std::sync::atomic::Ordering::Relaxed);
                                }
                            }
                            winit::event::ElementState::Released => {
                                println!("key released");
                                kbref.store(0, std::sync::atomic::Ordering::Relaxed)
                            }
                        }
                    }

                    // Handle input events
                    if input.process_window_event(&event) {
                        // Close events
                        if input.close_requested() {
                            elwt.exit();
                            return;
                        }

                        // Resize the window
                        if let Some(size) = input.window_resized() {
                            if let Err(_) = pixels
                                .lock()
                                .unwrap()
                                .resize_surface(size.width, size.height)
                            {
                                elwt.exit();
                                return;
                            }
                        }

                        // Update internal state and request a redraw
                        window.request_redraw();
                    }
                }
                _ => {}
            }
        });
        match res {
            Ok(_) => exit(0),
            Err(e) => {
                println!("{e}");
                exit(1);
            }
        }
    })
}
