#include <BAN/ScopeGuard.h>
#include <BAN/StringView.h>
#include <kernel/ACPI/ACPI.h>
#include <kernel/ACPI/BatterySystem.h>
#include <kernel/ACPI/AML/OpRegion.h>
#include <kernel/BootInfo.h>
#include <kernel/InterruptController.h>
#include <kernel/IO.h>
#include <kernel/Memory/PageTable.h>
#include <kernel/Process.h>
#include <kernel/Timer/Timer.h>

#define RSPD_SIZE	20
#define RSPDv2_SIZE	36

namespace Kernel::ACPI
{

	static uint32_t* s_global_lock { nullptr };

	// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#global-lock
#if ARCH(x86_64)
asm(R"(
.global acpi_acquire_global_lock
acpi_acquire_global_lock:
	movl (%rdi), %edx
	andl $(~1), %edx
	btsl $1, %edx
	adcl $0, %edx

	lock cmpxchgl %edx, (%rdi)
	jnz acpi_acquire_global_lock

	cmpb $3, %dl
	sbbq %rax, %rax
	negq %rax

	ret

.global acpi_release_global_lock
acpi_release_global_lock:
	movl (%rdi), %eax
	movl %eax, %edx

	andl $(~3), %edx

	lock cmpxchgl %edx, (%rdi)
	jnz acpi_release_global_lock

	andq $1, %rax

	ret
)");
#elif ARCH(i686)
asm(R"(
.global acpi_acquire_global_lock
acpi_acquire_global_lock:
	movl 4(%esp), %ecx
	movl (%ecx), %edx
	andl $(~1), %edx
	btsl $1, %edx
	adcl $0, %edx

	lock cmpxchgl %edx, (%ecx)
	jnz acpi_acquire_global_lock

	cmpb $3, %dl
	sbbl %eax, %eax
	negl %eax

	ret

.global acpi_release_global_lock
acpi_release_global_lock:
	movl 4(%esp), %ecx
	movl (%ecx), %eax
	movl %eax, %edx

	andl $(~3), %edx

	lock cmpxchgl %edx, (%ecx)
	jnz acpi_release_global_lock

	andl $1, %eax

	ret
)");
#endif

	// returns true if lock was acquired successfully
	extern "C" bool acpi_acquire_global_lock(uint32_t* lock);

	// returns true if lock was pending
	extern "C" bool acpi_release_global_lock(uint32_t* lock);

	void ACPI::acquire_global_lock()
	{
		if (!s_global_lock)
			return;
		ASSERT(acpi_acquire_global_lock(s_global_lock));
	}

	void ACPI::release_global_lock()
	{
		if (!s_global_lock)
			return;
		ASSERT(!acpi_release_global_lock(s_global_lock));
	}

	static BAN::ErrorOr<uint8_t> get_access_type(uint8_t access_size)
	{
		switch (access_size)
		{
			case 0: return 0;
			case 1: return 1;
			case 2: return 2;
			case 3: return 3;
			case 4: return 4;
			default:
				dwarnln("Unknown access size {}", access_size);
				return BAN::Error::from_errno(EFAULT);
		}
	}

	BAN::ErrorOr<uint64_t> GAS::read()
	{
		AML::OpRegion opregion;
		opregion.address_space = address_space_id;
		opregion.offset = address;
		opregion.length = 0xFFFFFFFF;

		AML::Node field_unit;
		field_unit.type = AML::Node::Type::FieldUnit;
		field_unit.as.field_unit.type = AML::FieldUnit::Type::Field;
		field_unit.as.field_unit.as.field.opregion = opregion;
		field_unit.as.field_unit.length = register_bit_width;
		field_unit.as.field_unit.offset = register_bit_offset;
		field_unit.as.field_unit.flags = TRY(get_access_type(access_size));

		auto result = TRY(AML::convert_from_field_unit(field_unit, AML::ConvInteger, sizeof(uint64_t)));
		return result.as.integer.value;
	}

	BAN::ErrorOr<void> GAS::write(uint64_t value)
	{
		AML::OpRegion opregion;
		opregion.address_space = address_space_id;
		opregion.offset = address;
		opregion.length = 0xFFFFFFFF;

		AML::Node field_unit;
		field_unit.type = AML::Node::Type::FieldUnit;
		field_unit.as.field_unit.type = AML::FieldUnit::Type::Field;
		field_unit.as.field_unit.as.field.opregion = opregion;
		field_unit.as.field_unit.length = register_bit_width;
		field_unit.as.field_unit.offset = register_bit_offset;
		field_unit.as.field_unit.flags = TRY(get_access_type(access_size));

		AML::Node source;
		source.type = AML::Node::Type::Integer;
		source.as.integer.value = value;

		TRY(AML::store_to_field_unit(source, field_unit));

		return {};
	}

	enum PM1Event : uint16_t
	{
		PM1_EVN_TMR = 1 << 0,
		PM1_EVN_GBL = 1 << 5,
		PM1_EVN_PWRBTN = 1 << 8,
		PM1_EVN_SLPBTN = 1 << 8,
		PM1_EVN_RTC = 1 << 10,
		PM1_EVN_PCIEXP_WAKE_DIS = 1 << 14,
	};

	enum PM1Control : uint16_t
	{
		PM1_CNT_SCI_EN = 1 << 0,
		PM1_CNT_BM_RLD = 1 << 1,
		PM1_CNT_GBL_RLS = 1 << 2,
		PM1_CNT_SLP_EN = 1 << 13,

		PM1_CNT_SLP_TYP_MASK = 0b111,
		PM1_CNT_SLP_TYP_SHIFT = 10,
	};

	struct RSDT : public SDTHeader
	{
		uint32_t entries[];
	} __attribute__((packed));

	struct XSDT : public SDTHeader
	{
		uint64_t entries[];
	} __attribute__((packed));

	static ACPI* s_instance = nullptr;

	BAN::ErrorOr<void> ACPI::initialize()
	{
		ASSERT(s_instance == nullptr);
		s_instance = new ACPI;
		if (s_instance == nullptr)
			return BAN::Error::from_errno(ENOMEM);
		TRY(s_instance->initialize_impl());

		{
			ASSERT(!s_global_lock);
			const auto* fadt = static_cast<const FADT*>(ACPI::get().get_header("FACP"_sv, 0));
			ASSERT(fadt);

			uintptr_t facs_addr = fadt->firmware_ctrl;
			if (fadt->length >= sizeof(FADT) && fadt->x_firmware_ctrl)
				facs_addr = fadt->x_firmware_ctrl;

			if (facs_addr)
			{
				size_t facs_size;
				PageTable::with_fast_page(facs_addr & PAGE_ADDR_MASK, [&] {
					facs_size = PageTable::fast_page_as<SDTHeader>(facs_addr % PAGE_SIZE).length;
				});

				size_t needed_pages = range_page_count(facs_addr, facs_size);
				vaddr_t facs_vaddr = PageTable::kernel().reserve_free_contiguous_pages(needed_pages, KERNEL_OFFSET);
				ASSERT(facs_vaddr);

				PageTable::kernel().map_range_at(
					facs_addr & PAGE_ADDR_MASK,
					facs_vaddr,
					needed_pages * PAGE_SIZE,
					PageTable::Flags::ReadWrite | PageTable::Flags::Present
				);

				auto* facs = reinterpret_cast<FACS*>(facs_vaddr + (facs_addr % PAGE_SIZE));
				s_global_lock = &facs->global_lock;
			}
		}

		return {};
	}

	ACPI& ACPI::get()
	{
		ASSERT(s_instance != nullptr);
		return *s_instance;
	}

	static bool is_rsdp(vaddr_t rsdp_addr)
	{
		const RSDP* rsdp = (const RSDP*)rsdp_addr;

		if (memcmp(rsdp->signature, "RSD PTR ", 8) != 0)
			return false;

		{
			uint8_t checksum = 0;
			for (uint32_t i = 0; i < RSPD_SIZE; i++)
				checksum += ((const uint8_t*)rsdp)[i];
			if (checksum != 0)
				return false;
		}

		if (rsdp->revision == 2)
		{
			uint8_t checksum = 0;
			for (uint32_t i = 0; i < RSPDv2_SIZE; i++)
				checksum += ((const uint8_t*)rsdp)[i];
			if (checksum != 0)
				return false;
		}

		return true;
	}

	static BAN::Optional<RSDP> locate_rsdp()
	{
		if (g_boot_info.rsdp.length)
			return g_boot_info.rsdp;

		// Look in main BIOS area below 1 MB
		for (paddr_t paddr = 0x000E0000; paddr < 0x00100000; paddr += PAGE_SIZE)
		{
			BAN::Optional<RSDP> rsdp;

			PageTable::with_fast_page(paddr, [&rsdp] {
				for (size_t offset = 0; offset + sizeof(RSDP) <= PAGE_SIZE; offset += 16)
				{
					if (is_rsdp(PageTable::fast_page() + offset))
					{
						rsdp = PageTable::fast_page_as<RSDP>(offset);
						break;
					}
				}
			});

			if (rsdp.has_value())
				return rsdp.release_value();
		}

		return {};
	}

	static bool is_valid_std_header(const SDTHeader* header)
	{
		uint8_t sum = 0;
		for (uint32_t i = 0; i < header->length; i++)
			sum += ((uint8_t*)header)[i];
		return sum == 0;
	}

	BAN::ErrorOr<void> ACPI::initialize_impl()
	{
		auto opt_rsdp = locate_rsdp();
		if (!opt_rsdp.has_value())
			return BAN::Error::from_error_code(ErrorCode::ACPI_NoRootSDT);
		const RSDP rsdp = opt_rsdp.release_value();

		uint32_t root_entry_count = 0;

		if (rsdp.revision >= 2)
		{
			TRY(PageTable::with_fast_page(rsdp.xsdt_address & PAGE_ADDR_MASK,
				[&]() -> BAN::ErrorOr<void>
				{
					auto& xsdt = PageTable::fast_page_as<const XSDT>(rsdp.xsdt_address % PAGE_SIZE);
					if (memcmp(xsdt.signature, "XSDT", 4) != 0)
						return BAN::Error::from_error_code(ErrorCode::ACPI_RootInvalid);
					if (!is_valid_std_header(&xsdt))
						return BAN::Error::from_error_code(ErrorCode::ACPI_RootInvalid);

					m_header_table_paddr = rsdp.xsdt_address + offsetof(XSDT, entries);
					m_entry_size = 8;
					root_entry_count = (xsdt.length - sizeof(SDTHeader)) / 8;
					return {};
				}
			));
		}
		else
		{
			TRY(PageTable::with_fast_page(rsdp.rsdt_address & PAGE_ADDR_MASK,
				[&]() -> BAN::ErrorOr<void>
				{
					auto& rsdt = PageTable::fast_page_as<const RSDT>(rsdp.rsdt_address % PAGE_SIZE);
					if (memcmp(rsdt.signature, "RSDT", 4) != 0)
						return BAN::Error::from_error_code(ErrorCode::ACPI_RootInvalid);
					if (!is_valid_std_header(&rsdt))
						return BAN::Error::from_error_code(ErrorCode::ACPI_RootInvalid);

					m_header_table_paddr = rsdp.rsdt_address + offsetof(RSDT, entries);
					m_entry_size = 4;
					root_entry_count = (rsdt.length - sizeof(SDTHeader)) / 4;
					return {};
				}
			));
		}

		size_t needed_pages = range_page_count(m_header_table_paddr, root_entry_count * m_entry_size);
		m_header_table_vaddr = PageTable::kernel().reserve_free_contiguous_pages(needed_pages, KERNEL_OFFSET);
		ASSERT(m_header_table_vaddr);

		m_header_table_vaddr += m_header_table_paddr % PAGE_SIZE;

		PageTable::kernel().map_range_at(
			m_header_table_paddr & PAGE_ADDR_MASK,
			m_header_table_vaddr & PAGE_ADDR_MASK,
			needed_pages * PAGE_SIZE,
			PageTable::Flags::Present
		);

		auto map_header =
			[](paddr_t header_paddr) -> vaddr_t
			{
				size_t header_length;
				PageTable::with_fast_page(header_paddr & PAGE_ADDR_MASK, [&] {
					header_length = PageTable::fast_page_as<SDTHeader>(header_paddr % PAGE_SIZE).length;
				});

				size_t needed_pages = range_page_count(header_paddr, header_length);
				vaddr_t page_vaddr = PageTable::kernel().reserve_free_contiguous_pages(needed_pages, KERNEL_OFFSET);
				ASSERT(page_vaddr);

				PageTable::kernel().map_range_at(
					header_paddr & PAGE_ADDR_MASK,
					page_vaddr,
					needed_pages * PAGE_SIZE,
					PageTable::Flags::Present
				);

				auto* header = (SDTHeader*)(page_vaddr + (header_paddr % PAGE_SIZE));
				if (!is_valid_std_header(header))
				{
					PageTable::kernel().unmap_range(page_vaddr, needed_pages * PAGE_SIZE);
					return 0;
				}

				return page_vaddr + (header_paddr % PAGE_SIZE);
			};

		for (uint32_t i = 0; i < root_entry_count; i++)
		{
			paddr_t header_paddr = (m_entry_size == 4) ?
				((uint32_t*)m_header_table_vaddr)[i] :
				((uint64_t*)m_header_table_vaddr)[i];

			vaddr_t header_vaddr = map_header(header_paddr);
			if (header_vaddr == 0)
				continue;

			MUST(m_mapped_headers.push_back({
				.paddr = header_paddr,
				.vaddr = header_vaddr
			}));
		}

		for (size_t i = 0; i < m_mapped_headers.size(); i++)
		{
			auto* header = m_mapped_headers[i].as_header();
			dprintln("found header {}", *header);

			if (memcmp(header->signature, "FACP", 4) == 0)
			{
				auto* fadt = (FADT*)header;

				paddr_t dsdt_paddr = 0;
				if (fadt->length > offsetof(FADT, x_dsdt))
					dsdt_paddr = fadt->x_dsdt;
				if (dsdt_paddr == 0 || !PageTable::is_valid_pointer(dsdt_paddr))
					dsdt_paddr = fadt->dsdt;

				vaddr_t dsdt_vaddr = map_header(dsdt_paddr);
				if (dsdt_vaddr == 0)
					continue;

				MUST(m_mapped_headers.push_back({
					.paddr = dsdt_paddr,
					.vaddr = dsdt_vaddr
				}));

				m_fadt = fadt;
				m_hardware_reduced = fadt->flags & (1 << 20);
			}
		}

		if (m_fadt == nullptr)
			Kernel::panic("No FADT found");

		return {};
	}

	const SDTHeader* ACPI::get_header(BAN::StringView signature, uint32_t index)
	{
		if (signature.size() != 4)
		{
			dprintln("Trying to get ACPI header with {} byte signature ??", signature.size());
			return nullptr;
		}
		uint32_t cnt = 0;
		for (auto& mapped_header : m_mapped_headers)
		{
			auto* header = mapped_header.as_header();
			if (memcmp(header->signature, signature.data(), 4) == 0)
				if (cnt++ == index)
					return header;
		}
		return nullptr;
	}

	BAN::ErrorOr<void> ACPI::prepare_sleep(uint8_t sleep_state)
	{
		if (!m_namespace)
			return BAN::Error::from_errno(EFAULT);

		auto [pts_path, pts_object] = TRY(m_namespace->find_named_object({}, MUST(AML::NameString::from_string("\\_PTS"))));
		if (pts_object == nullptr)
			return {};

		auto& pts_node = pts_object->node;
		if (pts_node.type != AML::Node::Type::Method)
		{
			dwarnln("Object \\_PTS is not a method");
			return BAN::Error::from_errno(EFAULT);
		}

		if (pts_node.as.method.arg_count != 1)
		{
			dwarnln("Method \\_PTS has {} arguments, expected 1", pts_node.as.method.arg_count);
			return BAN::Error::from_errno(EFAULT);
		}

		AML::Reference arg_ref;
		arg_ref.node.type = AML::Node::Type::Integer;
		arg_ref.node.as.integer.value = sleep_state;
		arg_ref.ref_count = 2;

		BAN::Array<AML::Reference*, 7> arguments(nullptr);
		arguments[0] = &arg_ref; // method call should not delete argument
		TRY(AML::method_call(pts_path, pts_node, BAN::move(arguments)));

		dprintln("Executed \\_PTS({})", sleep_state);

		return {};
	}

	BAN::ErrorOr<void> ACPI::poweroff()
	{
		if (!m_namespace)
		{
			dwarnln("ACPI namespace not initialized");
			return BAN::Error::from_errno(EFAULT);
		}

		auto [_, s5_object] = TRY(m_namespace->find_named_object({}, TRY(AML::NameString::from_string("\\_S5_"_sv))));
		if (!s5_object)
		{
			dwarnln("\\_S5 not found");
			return BAN::Error::from_errno(EFAULT);
		}

		auto& s5_node = s5_object->node;
		if (s5_node.type != AML::Node::Type::Package)
		{
			dwarnln("\\_S5 is not a package");
			return BAN::Error::from_errno(EFAULT);
		}
		if (s5_node.as.package->num_elements < 2)
		{
			dwarnln("\\_S5 package has {} elements, expected atleast 2", s5_node.as.package->num_elements);
			return BAN::Error::from_errno(EFAULT);
		}

		if (!s5_node.as.package->elements[0].resolved || !s5_node.as.package->elements[1].resolved)
		{
			dwarnln("TODO: lazy evaluate package \\_S5 elements");
			return BAN::Error::from_errno(ENOTSUP);
		}

		auto slp_typa_node = TRY(AML::convert_node(TRY(s5_node.as.package->elements[0].value.node->copy()), AML::ConvInteger, sizeof(uint64_t)));
		auto slp_typb_node = TRY(AML::convert_node(TRY(s5_node.as.package->elements[1].value.node->copy()), AML::ConvInteger, sizeof(uint64_t)));

		TRY(prepare_sleep(5));

		dprintln("Entering sleep state S5");

		const auto slp_typa_value = slp_typa_node.as.integer.value;
		const auto slp_typb_value = slp_typb_node.as.integer.value;

		uint16_t pm1a_data = IO::inw(fadt().pm1a_cnt_blk);
		pm1a_data &= ~(PM1_CNT_SLP_TYP_MASK << PM1_CNT_SLP_TYP_SHIFT);
		pm1a_data |= (slp_typa_value & PM1_CNT_SLP_TYP_MASK) << PM1_CNT_SLP_TYP_SHIFT;
		pm1a_data |= PM1_CNT_SLP_EN;
		IO::outw(fadt().pm1a_cnt_blk, pm1a_data);

		if (fadt().pm1b_cnt_blk != 0)
		{
			uint16_t pm1b_data = IO::inw(fadt().pm1b_cnt_blk);
			pm1b_data &= ~(PM1_CNT_SLP_TYP_MASK << PM1_CNT_SLP_TYP_SHIFT);
			pm1b_data |= (slp_typb_value & PM1_CNT_SLP_TYP_MASK) << PM1_CNT_SLP_TYP_SHIFT;
			pm1b_data |= PM1_CNT_SLP_EN;
			IO::outw(fadt().pm1b_cnt_blk, pm1b_data);
		}

		// system must not execute after sleep registers are written
		panic("ACPI shutdown failed. You can now safely shutdown your computer.");
	}

	BAN::ErrorOr<void> ACPI::reset()
	{
		// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/04_ACPI_Hardware_Specification/ACPI_Hardware_Specification.html#reset-register

		auto& reset_reg = fadt().reset_reg;
		switch (reset_reg.address_space_id)
		{
			case GAS::AddressSpaceID::SystemMemory:
			case GAS::AddressSpaceID::SystemIO:
			case GAS::AddressSpaceID::PCIConfig:
				break;
			default:
				dwarnln("Reset register has invalid address space ID ({})", static_cast<uint8_t>(reset_reg.address_space_id));
				return BAN::Error::from_errno(EFAULT);
		}

		if (reset_reg.register_bit_offset != 0 || reset_reg.register_bit_width != 8)
		{
			dwarnln("Reset register has invalid location ({} bits at bit offset {})", reset_reg.register_bit_width, reset_reg.register_bit_offset);
			return BAN::Error::from_errno(EFAULT);
		}

		if (!m_namespace)
			dwarnln("ACPI namespace not initialized, will not evaluate \\_S5");
		else
			TRY(prepare_sleep(5));

		dprintln("Resetting system");

		TRY(reset_reg.write(fadt().reset_value));

		// system must not execute after reset register is written
		ASSERT_NOT_REACHED();
	}

	BAN::ErrorOr<void> ACPI::load_aml_tables(BAN::StringView name, bool all)
	{
		BAN::ErrorOr<void> result {};

		for (uint32_t i = 0;; i++)
		{
			auto* header = get_header(name, i);
			if (header == nullptr)
				break;

			if (all)
				dprintln("Parsing {}{}, {} bytes", name, i + 1, header->length);
			else
				dprintln("Parsing {}, {} bytes", name, header->length);

			auto header_span = BAN::ConstByteSpan(reinterpret_cast<const uint8_t*>(header), header->length);
			if (auto parse_ret = m_namespace->parse(header_span); parse_ret.is_error())
				result = parse_ret.release_error();

			if (!all)
				break;
		}

		return result;
	}

#pragma GCC diagnostic push
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic ignored "-Wstack-usage="
#endif
	BAN::ErrorOr<void> ACPI::route_interrupt_link_device(const AML::Scope& device, uint64_t& routed_irq_mask)
	{
		ASSERT(m_namespace);

		auto prs_node = TRY(AML::convert_node(TRY(m_namespace->evaluate(device, "_PRS"_sv)), AML::ConvBuffer, -1));
		auto prs_span = BAN::ConstByteSpan(prs_node.as.str_buf->bytes, prs_node.as.str_buf->size);

		auto [srs_path, srs_node] = TRY(m_namespace->find_named_object(device, TRY(AML::NameString::from_string("_SRS"_sv))));
		if (srs_node == nullptr || srs_node->node.type != AML::Node::Type::Method)
		{
			dwarnln("interrupt link device does not have _SRS method");
			return BAN::Error::from_errno(EINVAL);
		}

		while (!prs_span.empty())
		{
			if (!(prs_span[0] & 0x80))
			{
				const uint8_t name = (prs_span[0] >> 3) & 0x0F;
				const uint8_t length = prs_span[0] & 0x07;
				if (prs_span.size() < static_cast<size_t>(1 + length))
					return BAN::Error::from_errno(EINVAL);

				if (name == 0x04)
				{
					if (length < 2)
						return BAN::Error::from_errno(EINVAL);
					const uint16_t irq_mask = prs_span[1] | (prs_span[2] << 8);

					for (uint8_t pass = 0; pass < 2; pass++)
					{
						for (uint8_t irq = 0; irq < 16; irq++)
						{
							if (!(irq_mask & (1 << irq)))
								continue;
							if (pass == 0 && (routed_irq_mask & (static_cast<uint64_t>(1) << irq)))
								continue;

							BAN::Array<uint8_t, 4> setting;
							setting[0] = 0x22 | (length > 2); // small, irq, data len
							setting[1] = (1 << irq) >> 0;     // irq low
							setting[2] = (1 << irq) >> 8;     // irq high
							if (length > 2)
								setting[3] = prs_span[3]; // flags

							auto setting_span = BAN::ConstByteSpan(setting.data(), (length > 2) ? 4 : 3);
							TRY(AML::method_call(srs_path, srs_node->node, TRY(AML::Node::create_buffer(setting_span))));

							dprintln("routed {} -> irq {}", device, irq);

							routed_irq_mask |= static_cast<uint64_t>(1) << irq;
							return {};
						}
					}
				}

				prs_span = prs_span.slice(1 + length);
			}
			else
			{
				if (prs_span.size() < 3)
					return BAN::Error::from_errno(EINVAL);
				const uint8_t name = prs_span[0] & 0x7F;
				const uint16_t length = (prs_span[2] << 8) | prs_span[1];
				if (prs_span.size() < static_cast<size_t>(3 + length))
					return BAN::Error::from_errno(EINVAL);

				// Extended Interrupt Descriptor
				if (name == 0x09)
				{
					const uint8_t irq_count = prs_span[4];
					if (irq_count == 0 || length < 2 + 4*irq_count)
						return BAN::Error::from_errno(EINVAL);

					for (uint8_t pass = 0; pass < 2; pass++)
					{
						for (uint32_t i = 0; i < irq_count; i++)
						{
							// TODO: support irq over 64 irqs?
							if (prs_span[6 + 4*i] || prs_span[7 + 4*i] || prs_span[8 + 4*i])
								continue;
							const uint8_t irq = prs_span[5 + 4*i];
							if (irq >= 64)
								continue;
							if (pass == 0 && (routed_irq_mask & (static_cast<uint64_t>(1) << irq)))
								continue;

							BAN::Array<uint8_t, 9> setting;
							setting[0] = 0x89;            // large, irq
							setting[1] = 0x06;            // data len
							setting[2] = 0x00;
							setting[3] = prs_span[3]; // flags
							setting[4] = 0x01;            // table size
							setting[5] = irq;             // irq
							setting[6] = 0x00;
							setting[7] = 0x00;
							setting[8] = 0x00;

							TRY(AML::method_call(srs_path, srs_node->node, TRY(AML::Node::create_buffer(setting.span()))));

							dprintln("routed {} -> irq {}", device, irq);

							routed_irq_mask |= static_cast<uint64_t>(1) << irq;
							return {};
						}
					}
				}

				prs_span = prs_span.slice(3 + length);
			}
		}

		dwarnln("No routable interrupt found in _PRS");
		return {};
	}
#pragma GCC diagnostic pop

	BAN::ErrorOr<void> ACPI::enter_acpi_mode(uint8_t mode)
	{
		ASSERT(!m_namespace);

		// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/16_Waking_and_Sleeping/initialization.html#placing-the-system-in-acpi-mode

		// If not hardware-reduced ACPI and SCI_EN is not set
		if (!hardware_reduced() && !(IO::inw(fadt().pm1a_cnt_blk) & PM1_CNT_SCI_EN))
		{
			// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/04_ACPI_Hardware_Specification/ACPI_Hardware_Specification.html#legacy-acpi-select-and-the-sci-interrupt
			IO::outb(fadt().smi_cmd, fadt().acpi_enable);

			// Spec says to poll until SCI_EN is set, but doesn't specify timeout
			for (size_t i = 0; i < 100; i++)
			{
				if (IO::inw(fadt().pm1a_cnt_blk) & PM1_CNT_SCI_EN)
					break;
				SystemTimer::get().sleep_ms(10);
			}

			if (!(IO::inw(fadt().pm1a_cnt_blk) & PM1_CNT_SCI_EN))
			{
				dwarnln("Failed to enable ACPI mode");
				return BAN::Error::from_errno(EINVAL);
			}

			// Enable power and sleep buttons
			IO::outw(fadt().pm1a_evt_blk + fadt().pm1_evt_len / 2, PM1_EVN_PWRBTN | PM1_EVN_SLPBTN);
			IO::outw(fadt().pm1b_evt_blk + fadt().pm1_evt_len / 2, PM1_EVN_PWRBTN | PM1_EVN_SLPBTN);
		}

		dprintln("Entered ACPI mode");

		TRY(AML::Namespace::prepare_root_namespace());
		m_namespace = &AML::Namespace::root_namespace();

		if (auto ret = load_aml_tables("DSDT"_sv, false); ret.is_error())
			dwarnln("Could not load DSDT: {}", ret.error());
		if (auto ret = load_aml_tables("SSDT"_sv, true); ret.is_error())
			dwarnln("Could not load all SSDTs: {}", ret.error());
		if (auto ret = load_aml_tables("PSDT"_sv, true); ret.is_error())
			dwarnln("Could not load all PSDTs: {}", ret.error());

		dprintln("Loaded ACPI tables");

		if (auto ret = m_namespace->post_load_initialize(); ret.is_error())
			dwarnln("Failed to initialize ACPI namespace: {}", ret.error());

		auto [pic_path, pic_obj] = TRY(m_namespace->find_named_object({}, TRY(AML::NameString::from_string("\\_PIC"_sv))));
		if (pic_obj && pic_obj->node.type == AML::Node::Type::Method)
		{
			auto& pic_node = pic_obj->node;
			if (pic_node.as.method.arg_count != 1)
			{
				dwarnln("Method \\_PIC has {} arguments, expected 1", pic_node.as.method.arg_count);
				return BAN::Error::from_errno(EINVAL);
			}

			AML::Reference arg_ref;
			arg_ref.node.type = AML::Node::Type::Integer;
			arg_ref.node.as.integer.value = mode;
			arg_ref.ref_count = 2;

			BAN::Array<AML::Reference*, 7> arguments(nullptr);
			arguments[0] = &arg_ref; // method call should not delete argument
			TRY(AML::method_call(pic_path, pic_node, BAN::move(arguments)));
		}

		dprintln("Evaluated \\_PIC({})", mode);

		uint8_t irq = fadt().sci_int;
		if (auto ret = InterruptController::get().reserve_irq(irq); ret.is_error())
			dwarnln("Could not enable ACPI interrupt: {}", ret.error());
		else
		{
			auto hex_sv_to_int =
				[](BAN::StringView sv) -> BAN::Optional<uint32_t>
				{
					uint32_t ret = 0;
					for (char c : sv)
					{
						ret <<= 4;
						if (c >= '0' && c <= '9')
							ret += c - '0';
						else if (c >= 'A' && c <= 'F')
							ret += c - 'A' + 10;
						else if (c >= 'a' && c <= 'f')
							ret += c - 'a' + 10;
						else
							return {};
					}
					return ret;
				};

			if (fadt().gpe0_blk)
			{
				auto [gpe_scope, gpe_obj] = TRY(m_namespace->find_named_object({}, TRY(AML::NameString::from_string("\\_GPE"))));
				if (gpe_obj && gpe_obj->node.is_scope())
				{
					m_gpe_scope = BAN::move(gpe_scope);

					// Enable all events in _GPE (_Lxx or _Exx)
					TRY(m_namespace->for_each_child(m_gpe_scope,
						[&](BAN::StringView name, AML::Reference* node_ref) -> BAN::Iteration
						{
							if (node_ref->node.type != AML::Node::Type::Method)
								return BAN::Iteration::Continue;

							ASSERT(name.size() == 4);
							if (!name.starts_with("_L"_sv) && !name.starts_with("_E"_sv))
								return BAN::Iteration::Continue;

							auto index = hex_sv_to_int(name.substring(2));
							if (!index.has_value())
							{
								dwarnln("invalid GPE number '{}'", name);
								return BAN::Iteration::Continue;
							}

							auto byte = index.value() / 8;
							auto bit = index.value() % 8;
							auto gpe0_en_port = fadt().gpe0_blk + (fadt().gpe0_blk_len / 2) + byte;
							IO::outb(gpe0_en_port, IO::inb(gpe0_en_port) | (1 << bit));

							m_gpe_methods[index.value()] = node_ref;
							node_ref->ref_count++;

							dprintln("Enabled GPE {}", index.value(), byte, bit);

							return BAN::Iteration::Continue;
						}
					));
				}
			}

			set_irq(irq);
			InterruptController::get().enable_irq(irq);

			Process::create_kernel([](void*) { get().acpi_event_task(); }, nullptr);
		}

		dprintln("Initialized ACPI interrupts");

		if (auto interrupt_link_devices_or_error = m_namespace->find_device_with_eisa_id("PNP0C0F"_sv); !interrupt_link_devices_or_error.is_error())
		{
			uint64_t routed_irq_mask = 0;
			auto interrupt_link_devices = interrupt_link_devices_or_error.release_value();
			for (const auto& device : interrupt_link_devices)
				if (auto ret = route_interrupt_link_device(device, routed_irq_mask); ret.is_error())
					dwarnln("failed to route interrupt link device: {}", ret.error());
			dprintln("Routed interrupt link devices");
		}

		return {};
	}

	BAN::ErrorOr<void> ACPI::initialize_acpi_devices()
	{
		ASSERT(m_namespace);
		TRY(BatterySystem::initialize(*m_namespace));
		return {};
	}

	void ACPI::acpi_event_task()
	{
		auto get_fixed_event =
			[&](uint16_t sts_port)
			{
				if (sts_port == 0)
					return 0;
				auto sts = IO::inw(sts_port);
				auto en = IO::inw(sts_port + fadt().pm1_evt_len / 2);
				if (auto pending = sts & en)
					return pending & ~(pending - 1);
				return 0;
			};

		while (true)
		{
			uint16_t sts_port;
			uint16_t pending;

			sts_port = fadt().pm1a_evt_blk;
			if (pending = get_fixed_event(sts_port); pending)
				goto handle_event;

			sts_port = fadt().pm1b_evt_blk;
			if (pending = get_fixed_event(sts_port); pending)
				goto handle_event;

			{
				bool handled_event = false;
				uint8_t gpe0_bytes = fadt().gpe0_blk_len / 2;
				for (uint8_t i = 0; i < gpe0_bytes; i++)
				{
					uint8_t sts = IO::inb(fadt().gpe0_blk + i);
					uint8_t en = IO::inb(fadt().gpe0_blk + gpe0_bytes + i);
					pending = sts & en;
					if (pending == 0)
						continue;

					auto index = i * 8 + (pending & ~(pending - 1));
					if (m_gpe_methods[index])
						if (auto ret = AML::method_call(m_gpe_scope, m_gpe_methods[index]->node, BAN::Array<AML::Reference*, 7>{}); ret.is_error())
							dwarnln("Failed to evaluate _GPE {}: ", index, ret.error());

					handled_event = true;
					IO::outb(fadt().gpe0_blk + i, 1 << index);
				}
				if (handled_event)
					continue;
			}


			// FIXME: this can cause missing of event if it happens between
			//        reading the status and blocking
			m_event_thread_blocker.block_with_timeout_ms(100, nullptr);
			continue;

handle_event:
			if (pending & PM1_EVN_PWRBTN)
			{
				dprintln("Power button pressed");
				if (auto ret = Process::clean_poweroff(POWEROFF_SHUTDOWN); ret.is_error())
					dwarnln("Failed to poweroff: {}", ret.error());
			}
			else
			{
				dwarnln("Unhandled ACPI fixed event {H}", pending);
			}

			IO::outw(sts_port, pending);
		}
	}

	void ACPI::handle_irq()
	{
		m_event_thread_blocker.unblock();
	}

}
