#![no_main]
#![no_std]

extern crate alloc;

use aligned_vec::{AVec, ConstAlign};
use alloc::vec::Vec;
use uefi::proto::device_path::build::media::HardDrive;
use uefi::proto::device_path::build::DevicePathBuilder;
use uefi::proto::device_path::media::{PartitionFormat, PartitionSignature};

use uefi::{prelude::*, println};

use uefi::proto::console::gop::GraphicsOutput;
use uefi::proto::device_path::{DeviceType, LoadedImageDevicePath};
use uefi::proto::media::block::{BlockIO, Lba};
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() + 511) / 512) * 512;
//const VOLUME_LABEL: &CStr16 = cstr16!("EFIGIFEDEMO");

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

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

    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)
    //     .expect("loaded image");
    let loaded_image_dev_path = boot_services
        .open_protocol_exclusive::<LoadedImageDevicePath>(img_handle)
        .expect("loaded image dev path");

    /*
        {
            let fat_dev = loaded_image.device().expect("loaded image device");
            let fat_block = unsafe {
                boot_services.open_protocol::<BlockIO>(
                    OpenProtocolParams {
                        handle: fat_dev,
                        agent: img_handle,
                        controller: None,
                    },
                    OpenProtocolAttributes::GetProtocol,
                )
            }
            .expect("fat block io");
            let fat_start_lba = fat_block.media().lowest_aligned_lba();
            assert_eq!(fat_start_lba, GIF_SIZE);
        }
    */
    let mut dev_path_buf = Vec::new();
    let mut dev_path_builder = DevicePathBuilder::with_vec(&mut dev_path_buf);
    for dev_path_node in loaded_image_dev_path.node_iter() {
        if dev_path_node.device_type() == DeviceType::MEDIA {
            break;
        } else {
            dev_path_builder = dev_path_builder
                .push(&dev_path_node)
                .expect("push existing node");
        }
    }
    let harder_drive = HardDrive {
        partition_number: 0,
        partition_start: 0,
        partition_size: GIF_SIZE as u64 / 512,
        partition_signature: PartitionSignature::None,
        partition_format: PartitionFormat::MBR,
    };
    dev_path_builder = dev_path_builder
        .push(&harder_drive)
        .expect("push hard drive");
    let mut dev_path = dev_path_builder.finalize().expect("finalize dev path");
    let device_handle = boot_services
        .locate_device_path::<BlockIO>(&mut dev_path)
        .expect("locate dev path");

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

    let mut gif_buf: AVec<u8, ConstAlign<512>> = AVec::with_capacity(512, GIF_SIZE);
    for _ in 0..GIF_SIZE {
        gif_buf.push(0u8);
    }

    block_io
        .read_blocks(block_io.media().media_id(), Lba::from(0u64), &mut gif_buf)
        .expect("read blocks");

    let gif = tinygif::Gif::<Rgb888>::from_slice(&gif_buf).expect("gif from slice");

    // Get gop
    let gop_handle = boot_services
        .get_handle_for_protocol::<GraphicsOutput>()
        .expect("graphics output handle");
    let mut gop = boot_services
        .open_protocol_exclusive::<GraphicsOutput>(gop_handle)
        .expect("graphics output open");

    // Create UefiDisplay
    for mode in gop.modes(&boot_services).collect::<Vec<_>>() {
        println!("{:?}", mode.info());
        let (w, h) = mode.info().resolution();
        if w >= gif.width() as usize && h >= gif.height() as usize {
            match mode.info().pixel_format() {
                uefi::proto::console::gop::PixelFormat::Rgb
                | uefi::proto::console::gop::PixelFormat::Bgr => {
                    if gop.set_mode(&mode).is_ok() {
                        break;
                    }
                }
                uefi::proto::console::gop::PixelFormat::Bitmask
                | uefi::proto::console::gop::PixelFormat::BltOnly => {}
            }
        }
    }

    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).expect("fill black");

    let translation = Point::new(
        (res_w as i32 - gif.width() as i32) / 2,
        (res_h as i32 - gif.height() as i32) / 2,
    );
    /*
    let timer_handle = boot_services
        .get_handle_for_protocol::<Timestamp>()
        .expect("timestamp handle");
    let timer = boot_services
        .open_protocol_exclusive::<Timestamp>(timer_handle)
        .expect("timestamp open");

    let timer_hz = timer.get_properties().expect("timer props").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;
    */
    loop {
        for frame in gif.frames() {
            frame
                .draw(&mut display.translated(translation))
                .expect("frame draw");
            display.flush();
            boot_services.stall(frame.delay_centis as usize * 10);
            /*
            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();
                }
            }
            */
        }
        if gif.frames().count() == 1 {
            boot_services.stall(1_000_000_000);
        }
    }

    // Status::SUCCESS
}

/*
// uefi-rs book example for reference:

#![no_main]
#![no_std]

extern crate alloc;

use alloc::vec;
use alloc::vec::Vec;
use uefi::prelude::*;
use uefi::proto::console::gop::{BltOp, BltPixel, BltRegion, GraphicsOutput};
use uefi::proto::loaded_image::LoadedImage;
use uefi::proto::media::block::{BlockIO, Lba};
use uefi::table::boot::{OpenProtocolAttributes, OpenProtocolParams};
use uefi::Result;

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

#[derive(Clone, Copy)]
struct Point {
    x: f32,
    y: f32,
}

impl Point {
    fn new(x: f32, y: f32) -> Self {
        Self { x, y }
    }
}

struct Buffer {
    width: usize,
    height: usize,
    pixels: Vec<BltPixel>,
}

impl Buffer {
    /// Create a new `Buffer`.
    fn new(width: usize, height: usize) -> Self {
        Buffer {
            width,
            height,
            pixels: vec![BltPixel::new(0, 0, 0); width * height],
        }
    }

    /// Get a single pixel.
    fn pixel(&mut self, x: usize, y: usize) -> Option<&mut BltPixel> {
        self.pixels.get_mut(y * self.width + x)
    }

    /// Blit the buffer to the framebuffer.
    fn blit(&self, gop: &mut GraphicsOutput) -> Result {
        gop.blt(BltOp::BufferToVideo {
            buffer: &self.pixels,
            src: BltRegion::Full,
            dest: (0, 0),
            dims: (self.width, self.height),
        })
    }
}

fn draw_sierpinski(bt: &BootServices) -> Result {
    // Open graphics output protocol.
    let gop_handle = bt
        .get_handle_for_protocol::<GraphicsOutput>()
        .expect("gop handle");
    let mut gop = bt
        .open_protocol_exclusive::<GraphicsOutput>(gop_handle)
        .expect("gop proto");

    // Create a buffer to draw into.
    let (width, height) = gop.current_mode_info().resolution();
    let mut buffer = Buffer::new(width, height);

    // Initialize the buffer with a simple gradient background.
    for y in 0..height {
        let r = ((y as f32) / ((height - 1) as f32)) * 255.0;
        for x in 0..width {
            let g = ((x as f32) / ((width - 1) as f32)) * 255.0;
            let pixel = buffer.pixel(x, y).expect("gradient pixel");
            pixel.red = r as u8;
            pixel.green = g as u8;
            pixel.blue = 255;
        }
    }

    let size = Point::new(width as f32, height as f32);

    // Define the vertices of a big triangle.
    let border = 20.0;
    let triangle = [
        Point::new(size.x / 2.0, border),
        Point::new(border, size.y - border),
        Point::new(size.x - border, size.y - border),
    ];

    // `p` is the point to draw. Start at the center of the triangle.
    let mut p = Point::new(size.x / 2.0, size.y / 2.0);

    // Loop forever, drawing the frame after each new point is changed.
    loop {
        // Choose one of the triangle's vertices at random.
        let v = triangle[1]; // chosen by fair dice roll.

        // Move `p` halfway to the chosen vertex.
        p.x = (p.x + v.x) * 0.5;
        p.y = (p.y + v.y) * 0.5;

        // Set `p` to black.
        let pixel = buffer
            .pixel(p.x as usize, p.y as usize)
            .expect("black pixel");
        pixel.red = 0;
        pixel.green = 100;
        pixel.blue = 0;

        // Draw the buffer to the screen.
        buffer.blit(&mut gop).expect("blit");
    }
}

#[entry]
fn main(_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
    uefi::helpers::init(&mut system_table).expect("table init");
    let boot_services = system_table.boot_services();

    // Disable the watchdog timer
    boot_services
        .set_watchdog_timer(0, 0x10000, None)
        .expect("disable watchdog timer");

    let img_handle = boot_services.image_handle();
    let loaded_image = boot_services
        .open_protocol_exclusive::<LoadedImage>(img_handle)
        .expect("loaded image");

    let device_handle = loaded_image.device().expect("device handle");

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

    let mut gif_buf = Vec::new();
    gif_buf.resize(GIF_SIZE, 0u8);
    block_io
        .read_blocks(block_io.media().media_id(), Lba::from(0u64), &mut gif_buf)
        .expect("read blocks");

    draw_sierpinski(boot_services).expect("sierpinski");
    Status::SUCCESS
}
*/
