#![feature(
    likely_unlikely,
    widening_mul,
    sync_unsafe_cell,
    int_lowest_highest_one
)]
#![deny(clippy::all)]

use std::env::args;
use std::hint::unlikely;
use std::process::exit;
use std::sync::{
    Arc,
    atomic::Ordering::{Relaxed, Release},
};
use std::thread::scope;
use std::time::{Duration, Instant};

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

use crate::cpu::{Computer, MMIOInterupt};

mod cpu;
use cpu::SHARED;

fn wait_int() {
    let mut v = (&SHARED.external_interupts).load(Relaxed);
    while unlikely(v != 0) {
        println!("wating for interupt clear {v}");
        atomic_wait::wait(&SHARED.external_interupts, v);
        v = (&SHARED.external_interupts).load(std::sync::atomic::Ordering::Relaxed);
    }
}

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

struct App<'a> {
    w: Option<Arc<Window>>,
    pixels: Option<Pixels<'a>>,
}

impl<'a> ApplicationHandler for App<'a> {
    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
        let window = {
            let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
            Arc::new(
                event_loop
                    .create_window(
                        Window::default_attributes()
                            .with_title("bisare screen")
                            .with_min_inner_size(size)
                            .with_maximized(true),
                    )
                    .unwrap(),
            )
        };
        self.w = Some(window.clone());
        let size = window.inner_size();
        let surface_texture = SurfaceTexture::new(size.width, size.height, window);
        self.pixels = Some(Pixels::new(WIDTH, HEIGHT, surface_texture).unwrap());
    }

    fn window_event(
        &mut self,
        elwt: &winit::event_loop::ActiveEventLoop,
        _: winit::window::WindowId,
        event: WindowEvent,
    ) {
        // Draw the current frame
        match event {
            WindowEvent::KeyboardInput { event, .. } => {
                let enabled = (&SHARED.external_enabled_interupts).load(Relaxed)
                    & Into::<u32>::into(MMIOInterupt::Keyboard)
                    != 0;

                if enabled {
                    wait_int();
                }
                match event.state {
                    winit::event::ElementState::Pressed => {
                        if let Some(val) = event.physical_key.to_scancode() {
                            cpu::SHARED
                                .keyboard
                                .store(val + 8, std::sync::atomic::Ordering::Relaxed);
                        }
                    }
                    winit::event::ElementState::Released => SHARED
                        .keyboard
                        .store(0, std::sync::atomic::Ordering::Relaxed),
                }
                if enabled {
                    (&SHARED.external_interupts).store(MMIOInterupt::Keyboard.into(), Release);
                    atomic_wait::wake_all(&SHARED.external_interupts);
                }
            }
            WindowEvent::CursorMoved { position, .. } => {
                let enabled = (&SHARED.external_enabled_interupts).load(Relaxed)
                    & Into::<u32>::into(MMIOInterupt::MouseMove)
                    != 0;
                if enabled {wait_int();}
                match self
                    .pixels
                    .as_ref()
                    .unwrap()
                    .window_pos_to_pixel((position.x as f32, position.y as f32))
                {
                    Ok((x, y)) => {
                        (&cpu::SHARED.mouse[1])
                            .store(x as u32, Relaxed);
                        (&cpu::SHARED.mouse[2])
                            .store(y as u32, Relaxed);
                    }
                    Err(_) => {
                        (&SHARED.mouse[1]).store(u32::MAX, Relaxed);
                        (&SHARED.mouse[2]).store(u32::MAX, Relaxed);
                    }
                }
                if enabled {
                    (&SHARED.external_interupts).store(MMIOInterupt::MouseMove.into(), Release);
                    println!("wake mouse move");
                    atomic_wait::wake_all(&SHARED.external_interupts);
                }
            }
            // WindowEvent::MouseWheel {
            //     delta,
            //     ..
            // } => {}
            WindowEvent::MouseInput { state, button, .. } => {
                let enabled = (&SHARED.external_enabled_interupts).load(Relaxed)
                    & Into::<u32>::into(MMIOInterupt::MouseClick)
                    != 0;
                if enabled {wait_int();}
                let but = 1
                    << match button {
                        winit::event::MouseButton::Left => 0,
                        winit::event::MouseButton::Right => 1,
                        winit::event::MouseButton::Middle => 2,
                        winit::event::MouseButton::Back => 3,
                        winit::event::MouseButton::Forward => 4,
                        winit::event::MouseButton::Other(i) => i & 31,
                    };
                match state {
                    winit::event::ElementState::Pressed => {
                        (&cpu::SHARED.mouse[0]).fetch_or(but, std::sync::atomic::Ordering::Relaxed)
                    }
                    winit::event::ElementState::Released => (&cpu::SHARED.mouse[0])
                        .fetch_and(!but, std::sync::atomic::Ordering::Relaxed),
                };
                if enabled {
                    (&SHARED.external_interupts).store(MMIOInterupt::MouseClick.into(), Release);
                    println!("wake mouse click");
                    atomic_wait::wake_all(&SHARED.external_interupts);
                }
            }
            WindowEvent::ScaleFactorChanged { .. } => {
                self.w.as_ref().unwrap().request_redraw();
            }
            //handling redraws and other graphical events
            WindowEvent::RedrawRequested => {
                let enabled = (&SHARED.external_enabled_interupts).load(Relaxed)
                    & Into::<u32>::into(MMIOInterupt::VSync)
                    != 0;
                if enabled {
                    (&SHARED.external_interupts).store(MMIOInterupt::VSync.into(), Relaxed);
                    atomic_wait::wake_all(&SHARED.external_interupts);
                    wait_int();
                }
                let pix = self.pixels.as_mut().unwrap();

                let screen = pix.frame_mut();
                for (addr, ubgr) in cpu::SHARED.screen_buf.iter().enumerate() {
                    let ubgr = ubgr.load(std::sync::atomic::Ordering::Relaxed);
                    let rgba = [ubgr as u8, (ubgr >> 8) as u8, (ubgr >> 16) as u8, 0xff];
                    for i in 0..4 {
                        screen[addr * 4 + i] = rgba[i];
                    }
                }

                if let Err(_) = pix.render() {
                    elwt.exit();
                    return;
                }
            }
            WindowEvent::Resized(size) => {
                if self
                    .pixels
                    .as_mut()
                    .unwrap()
                    .resize_surface(size.width, size.height)
                    .is_err()
                {
                    println!("Error while resising pixels, exiting!");
                    elwt.exit();
                    return;
                }
                self.w.as_ref().unwrap().request_redraw();
            }
            WindowEvent::CloseRequested => {
                elwt.exit();
                return;
            }
            WindowEvent::Destroyed => {
                println!("Windows destroyed, exiting!");
                elwt.exit();
                return;
            }
            //do nothing for the other events
            _ => {}
        }
    }
    fn new_events(
        &mut self,
        event_loop: &winit::event_loop::ActiveEventLoop,
        cause: winit::event::StartCause,
    ) {
        match cause {
            winit::event::StartCause::ResumeTimeReached {
                requested_resume, ..
            } => {
                let mut next = requested_resume + Duration::from_secs_f64(1. / 60.);
                let now = Instant::now();
                if next < now {
                    next = now + Duration::from_secs_f64(1. / 30.);
                }
                event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(next));
                if let Some(w) = self.w.as_ref() {
                    w.request_redraw();
                }
            }
            winit::event::StartCause::WaitCancelled { .. } => {}
            winit::event::StartCause::Poll => {
                let next = Instant::now() + Duration::from_secs_f64(1. / 60.);
                event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(next));
            }
            winit::event::StartCause::Init => {
                let next = Instant::now() + Duration::from_secs_f64(1. / 60.);
                event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(next));
            }
        }
    }
}

fn main() -> Result<(), Error> {
    let event_loop = EventLoop::new().unwrap();
    // let mut input = WinitInputHelper::new();

    let mut app = App {
        w: None,
        pixels: None,
    };

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

        #[allow(deprecated)]
        let res = event_loop.run_app(&mut app);
        match res {
            Ok(_) => exit(0),
            Err(e) => {
                println!("{e}");
                exit(1);
            }
        }
    })
}
