#![feature(allocator_api, never_type)]
#![no_std]

use acpi::AcpiTables;
use acpi_system::{AcpiInterruptMethod, AcpiSleepState, AcpiSystem};
use alloc::boxed::Box;
use libk::error::Error;
use libk_util::{sync::IrqSafeSpinlock, OneTimeInit};

extern crate alloc;

pub mod mem;
pub use mem::AcpiAllocator;
pub mod handler;
pub use handler::AcpiHandlerImpl;
pub mod aml_handler;

pub use acpi_system::{
    EventAction, FixedEvent, InterruptPolarity, InterruptTrigger, IrqDescriptor, PciPin,
};

static ACPI_SYSTEM: OneTimeInit<IrqSafeSpinlock<AcpiSystem<AcpiHandlerImpl>>> = OneTimeInit::new();

pub fn add_event_handler<F: Fn(&AcpiSystem<AcpiHandlerImpl>) -> EventAction + 'static>(
    event: &FixedEvent,
    handler: F,
) -> Result<(), Error> {
    ACPI_SYSTEM
        .get()
        .lock()
        .enable_fixed_event(event, Box::new(handler))
        .map_err(|_| Error::InvalidArgument)
}

pub fn get_pci_route(
    aml_path: &str,
    device: u16,
    function: u16,
    pin: PciPin,
) -> Option<IrqDescriptor> {
    ACPI_SYSTEM
        .get()
        .lock()
        .pci_route(aml_path, device, function, pin)
        .ok()
}

pub fn power_off() -> Result<!, Error> {
    let system = ACPI_SYSTEM.get();
    unsafe {
        system.lock().enter_sleep_state(AcpiSleepState::S5).ok();

        loop {
            core::arch::asm!("cli; hlt");
        }
    }
}

/// Initializes ACPI management
pub fn switch_to_acpi(tables: &'static AcpiTables<AcpiHandlerImpl>) -> Result<(), Error> {
    // NOTE mostly broken for real HW
    let mut system = AcpiSystem::new(tables, Box::new(AcpiHandlerImpl)).unwrap();

    system.initialize(AcpiInterruptMethod::Apic).unwrap();

    // system
    //     .enable_fixed_event(
    //         &FixedEvent::POWER_BUTTON,
    //         Box::new(|_| {
    //             log::info!("Power button was pressed");

    //             // TODO the correct way would be to
    //             // 1. Nicely ask all the processes to quit
    //             // 2. Wait for some time
    //             // 3. Kill the remaining ones
    //             // 4. Halt other cores
    //             // 5. Sync filesystem
    //             // 6. Do something with the devices
    //             // 7. Actually enter the S5 state

    //             SHUTDOWN_FENCE.signal();
    //             SHUTDOWN_FENCE.wait_all(CPU_COUNT.load(Ordering::Acquire));

    //             log::info!("CPUs are parked, can shutdown now");

    //             EventAction::EnterSleepState(AcpiSleepState::S5)
    //         }),
    //     )
    //     .unwrap();

    ACPI_SYSTEM.init(IrqSafeSpinlock::new(system));

    Ok(())
}
