#![no_main]
#![no_std]

use core::time::Duration;

use uefi::prelude::*;
use uefi::proto::console::gop::GraphicsOutput;
use uefi::proto::loaded_image::LoadedImage;
use uefi::proto::media::block::{BlockIO, Lba};
use uefi::proto::misc::Timestamp;
use uefi::table::boot::{OpenProtocolAttributes, OpenProtocolParams};

use uefi_graphics2::embedded_graphics::{pixelcolor::Rgb888, prelude::*};
use uefi_graphics2::UefiDisplay;

const GIF_SIZE: usize = include_bytes!("bad-apple.gif").len();

#[entry]
fn main(_image_handle: Handle, mut boot_system_table: SystemTable<Boot>) -> Status {
    uefi::helpers::init(&mut boot_system_table).unwrap();

    // Disable the watchdog timer
    boot_system_table
        .boot_services()
        .set_watchdog_timer(0, 0x10000, None)
        .unwrap();

    let boot_services = boot_system_table.boot_services();

    let img_handle = boot_services.image_handle();
    let loaded_image = boot_services
        .open_protocol_exclusive::<LoadedImage>(img_handle)
        .unwrap();

    let device_handle = loaded_image.device().unwrap();

    let block_io = unsafe {
        boot_services.open_protocol::<BlockIO>(
            OpenProtocolParams {
                handle: device_handle,
                agent: img_handle,
                controller: None,
            },
            OpenProtocolAttributes::GetProtocol,
        )
    }
    .unwrap();

    let mut gif_buf = [0u8; GIF_SIZE];
    block_io
        .read_blocks(block_io.media().media_id(), Lba::from(0u64), &mut gif_buf)
        .unwrap();

    let gif = tinygif::Gif::<Rgb888>::from_slice(&gif_buf).unwrap();

    // Get gop
    let gop_handle = boot_services
        .get_handle_for_protocol::<GraphicsOutput>()
        .unwrap();
    let mut gop = boot_services
        .open_protocol_exclusive::<GraphicsOutput>(gop_handle)
        .unwrap();

    // Create UefiDisplay
    let mode = gop.current_mode_info();
    let (res_w, res_h) = mode.resolution();
    let mut display = UefiDisplay::new(gop.frame_buffer(), mode);

    // Tint the entire screen cyan
    display.fill_entire(Rgb888::BLACK).unwrap();

    let timer_handle = boot_services
        .get_handle_for_protocol::<Timestamp>()
        .unwrap();
    let timer = boot_services
        .open_protocol_exclusive::<Timestamp>(timer_handle)
        .unwrap();

    let timer_hz = timer.get_properties().unwrap().frequency as f64;

    let start_timestamp = timer.get_timestamp();
    let mut gif_elapsed = Duration::new(0, 0);

    let get_real_elapsed =
        || Duration::from_secs_f64((timer.get_timestamp() - start_timestamp) as f64 / timer_hz);

    let mut skipped_flushes = 0;
    let translation = Point::new(
        (res_w as i32 - gif.width() as i32) / 2,
        (res_h as i32 / gif.height() as i32) / 2,
    );
    for frame in gif.frames() {
        frame.draw(&mut display.translated(translation)).unwrap();
        gif_elapsed += Duration::from_millis(frame.delay_centis as u64 * 10);

        // skip flush to catch up
        if gif_elapsed > get_real_elapsed() {
            display.flush();
            if let Some(time_diff) = get_real_elapsed().checked_sub(gif_elapsed) {
                boot_services.stall(time_diff.as_micros() as usize);
            }
        } else {
            skipped_flushes += 1;
            if skipped_flushes == 5 {
                // don't skip more than 5 frames at a time
                // so you can actually still see anything
                skipped_flushes = 0;
                display.flush();
            }
        }
    }

    Status::SUCCESS
}
