//! x86-64 boot and entry functions

use core::{arch::global_asm, sync::atomic::Ordering};

use kernel_arch_x86::registers::MSR_IA32_KERNEL_GS_BASE;
use kernel_arch_x86_64::CPU_COUNT;
use libk_mm::address::{PhysicalAddress, Virtualize};
use tock_registers::interfaces::Writeable;
use yboot_proto::{
    v1::{FramebufferOption, MemoryMap},
    LoadProtocolHeader, LoadProtocolV1, KERNEL_MAGIC, LOADER_MAGIC, PROTOCOL_VERSION_1,
};

use crate::{arch::PLATFORM, kernel_main, kernel_secondary_main, mem::KERNEL_VIRT_OFFSET};

// use core::{arch::global_asm, sync::atomic::Ordering};
//
// use kernel_arch_x86::registers::MSR_IA32_KERNEL_GS_BASE;
// use kernel_arch_x86_64::CPU_COUNT;
// use libk_mm::address::{PhysicalAddress, Virtualize};
// use tock_registers::interfaces::Writeable;
// use yboot_proto::{
//     v1::{FramebufferOption, MemoryMap},
//     LoadProtocolHeader, LoadProtocolV1, KERNEL_MAGIC, LOADER_MAGIC, PROTOCOL_VERSION_1,
// };
//
// use crate::{kernel_main, kernel_secondary_main, mem::KERNEL_VIRT_OFFSET};
//
// use super::PLATFORM;
//
pub enum BootData {
    YBoot(&'static LoadProtocolV1),
}

impl BootData {
    pub fn cmdline(&self) -> &str {
        match self {
            &Self::YBoot(data) => {
                if data.cmdline != 0 && data.cmdline_len != 0 {
                    let address = PhysicalAddress::from_u64(data.cmdline).virtualize();
                    let len = data.cmdline_len as usize;
                    let raw = unsafe {
                        core::slice::from_raw_parts(
                            core::ptr::with_exposed_provenance(address),
                            len,
                        )
                    };

                    core::str::from_utf8(raw).unwrap_or("")
                } else {
                    ""
                }
            }
        }
    }
}

const BOOT_STACK_SIZE: usize = 1024 * 1024;

#[repr(C, align(0x20))]
struct BootStack {
    data: [u8; BOOT_STACK_SIZE],
}

static mut BSP_STACK: BootStack = BootStack {
    data: [0; BOOT_STACK_SIZE],
};

#[used]
#[link_section = ".data.yboot"]
static YBOOT_DATA: LoadProtocolV1 = LoadProtocolV1 {
    header: LoadProtocolHeader {
        kernel_magic: KERNEL_MAGIC,
        version: PROTOCOL_VERSION_1,
    },
    kernel_virt_offset: KERNEL_VIRT_OFFSET as _,

    memory_map: MemoryMap { address: 0, len: 0 },

    cmdline: 0,
    cmdline_len: 0,

    rsdp_address: 0,
    initrd_address: 0,
    initrd_size: 0,

    opt_framebuffer: FramebufferOption {
        req_width: 640,
        req_height: 480,

        res_width: 0,
        res_height: 0,
        res_stride: 0,
        res_address: 0,
        res_size: 0,
        res_format: 0,
        _0: 0,
    },
};

unsafe fn init_dummy_cpu() {
    static UNINIT_CPU_INNER: usize = 0;
    static UNINIT_CPU_PTR: &usize = &UNINIT_CPU_INNER;

    // Point %gs to a dummy structure so that Cpu::get_local() works properly even before the CPU
    // data structure is initialized
    MSR_IA32_KERNEL_GS_BASE.set(&UNINIT_CPU_PTR as *const _ as u64);
    core::arch::asm!("swapgs");
    MSR_IA32_KERNEL_GS_BASE.set(&UNINIT_CPU_PTR as *const _ as u64);
    core::arch::asm!("swapgs");
}

/// Application processor entry point
pub extern "C" fn ap_entry() -> ! {
    let cpu_id = CPU_COUNT.load(Ordering::Acquire);

    unsafe {
        init_dummy_cpu();
    }

    unsafe {
        PLATFORM
            .init_platform(cpu_id)
            .expect("Could not initialize the platform (AP)");
    }

    CPU_COUNT.fetch_add(1, Ordering::Release);

    kernel_secondary_main()
}

unsafe extern "C" fn bsp_entry() -> ! {
    // Safety: ok, CPU hasn't been initialized yet and it's the early kernel entry
    unsafe {
        init_dummy_cpu();
    }

    PLATFORM.set_boot_data(BootData::YBoot(&YBOOT_DATA));

    unsafe {
        PLATFORM
            .init_platform(0)
            .expect("Could not initialize the platform");
    }

    kernel_main()
}

global_asm!(
    include_str!("entry.S"),
    yboot_loader_magic = const LOADER_MAGIC,

    stack_bottom = sym BSP_STACK,
    stack_size = const BOOT_STACK_SIZE,

    entry = sym bsp_entry,
    options(att_syntax)
);

// global_asm!(
//     r#"
// // {boot_data}
// .global __x86_64_entry
//
// .section .text.entry
// __x86_64_entry:
//     cli
//     mov ${yboot_loader_magic}, %edi
//     cmp %edi, %eax
//     je 2f
//
//     // (Currently) unsupported bootloader
// 1:
//     cli
//     hlt
//     jmp 1b
//
// 2:
//     // yboot entry method
//     movabsq ${stack_bottom} + {stack_size}, %rax
//     movabsq ${entry}, %rcx
//     mov %rax, %rsp
//     callq *%rcx
//
// .section .text
// "#,
//     yboot_loader_magic = const LOADER_MAGIC,
//     stack_size = const BOOT_STACK_SIZE,
//     stack_bottom = sym BSP_STACK,
//     boot_data = sym YBOOT_DATA,
//     entry = sym __x86_64_upper_entry,
//     options(att_syntax)
// );
