/*
    * AcpiDevices.cpp
    * ACPI device enumeration via AML namespace
    * Copyright (c) 2026 Daniel Hammer
*/

#include "AcpiDevices.hpp"
#include <ACPI/AML/AmlInterpreter.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Libraries/String.hpp>
#include <Libraries/Memory.hpp>

using namespace Kt;

namespace Hal {
    namespace AcpiDevices {

        // ============================================================================

        // EISAID decoding

        // ============================================================================
        // ACPI encodes PNP IDs as compressed 32-bit EISAIDs.
        static void DecodeEisaId(uint32_t id, char* out) {
            // EISA ID encoding:
            // Bits 31-16: 3 compressed letters (5 bits each, '@' based)
            // Bits 15-0:  4 hex digits (product number)
            out[0] = (char)(((id >> 26) & 0x1F) + '@');
            out[1] = (char)(((id >> 21) & 0x1F) + '@');
            out[2] = (char)(((id >> 16) & 0x1F) + '@');

            // Product ID as 4 hex digits
            static const char hex[] = "0123456789ABCDEF";
            out[3] = hex[(id >> 12) & 0xF];
            out[4] = hex[(id >>  8) & 0xF];
            out[5] = hex[(id >>  4) & 0xF];
            out[6] = hex[(id >>  0) & 0xF];
            out[7] = '\0';
        }

        // ============================================================================

        // String comparison

        // ============================================================================
        static bool StrEqual(const char* a, const char* b) {
            while (*a && *b) {
                if (*a != *b) return false;
                a++; b++;
            }
            return *a == *b;
        }

        static void StrCopy(char* dst, const char* src, int maxLen) {
            int i = 0;
            while (src[i] && i < maxLen - 1) {
                dst[i] = src[i];
                i++;
            }
            dst[i] = '\0';
        }

        // ============================================================================

        // DeviceList methods

        // ============================================================================
        const DeviceInfo* DeviceList::FindByHid(const char* hid) const {
            for (int i = 0; i < Count; i++) {
                if (StrEqual(Devices[i].HardwareId, hid))
                    return &Devices[i];
            }
            return nullptr;
        }

        const DeviceInfo* DeviceList::FindByPath(const char* path) const {
            for (int i = 0; i < Count; i++) {
                if (StrEqual(Devices[i].Path, path))
                    return &Devices[i];
            }
            return nullptr;
        }

        // ============================================================================

        // EvaluateSta

        // ============================================================================
        uint32_t EvaluateSta(int32_t deviceNodeIndex) {
            auto& interp = AML::GetInterpreter();
            auto& ns = interp.GetNamespace();

            // Look for _STA child
            int32_t staNode = -1;
            ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None,
                [&](int32_t idx, const AML::NamespaceNode* node) -> bool {
                    if (node->Name[0] == '_' && node->Name[1] == 'S' &&
                        node->Name[2] == 'T' && node->Name[3] == 'A') {
                        staNode = idx;
                        return false; // stop
                    }
                    return true;
                });

            if (staNode < 0) return STA_DEFAULT;

            auto* staObj = ns.GetNode(staNode);
            if (!staObj) return STA_DEFAULT;

            if (staObj->Obj.Type == AML::ObjectType::Method) {
                AML::Object result{};
                char path[256];
                ns.GetNodePath(staNode, path, 256);
                if (interp.EvaluateObject(path, result))
                    return (uint32_t)result.Integer;
                return STA_DEFAULT;
            }

            if (staObj->Obj.Type == AML::ObjectType::Integer)
                return (uint32_t)staObj->Obj.Integer;

            return STA_DEFAULT;
        }

        // ============================================================================

        // EvaluateAdr

        // ============================================================================
        uint64_t EvaluateAdr(int32_t deviceNodeIndex) {
            auto& interp = AML::GetInterpreter();
            auto& ns = interp.GetNamespace();

            int32_t adrNode = -1;
            ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None,
                [&](int32_t idx, const AML::NamespaceNode* node) -> bool {
                    if (node->Name[0] == '_' && node->Name[1] == 'A' &&
                        node->Name[2] == 'D' && node->Name[3] == 'R') {
                        adrNode = idx;
                        return false;
                    }
                    return true;
                });

            if (adrNode < 0) return 0;

            auto* adrObj = ns.GetNode(adrNode);
            if (!adrObj) return 0;

            if (adrObj->Obj.Type == AML::ObjectType::Method) {
                AML::Object result{};
                char path[256];
                ns.GetNodePath(adrNode, path, 256);
                if (interp.EvaluateObject(path, result))
                    return result.Integer;
                return 0;
            }

            if (adrObj->Obj.Type == AML::ObjectType::Integer)
                return adrObj->Obj.Integer;

            return 0;
        }

        // ============================================================================

        // EvaluateHid

        // ============================================================================
        bool EvaluateHid(int32_t deviceNodeIndex, char* outHid, int maxLen) {
            auto& interp = AML::GetInterpreter();
            auto& ns = interp.GetNamespace();

            int32_t hidNode = -1;
            ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None,
                [&](int32_t idx, const AML::NamespaceNode* node) -> bool {
                    if (node->Name[0] == '_' && node->Name[1] == 'H' &&
                        node->Name[2] == 'I' && node->Name[3] == 'D') {
                        hidNode = idx;
                        return false;
                    }
                    return true;
                });

            if (hidNode < 0) return false;

            AML::Object result{};
            char path[256];
            ns.GetNodePath(hidNode, path, 256);

            if (!interp.EvaluateObject(path, result))
                return false;

            if (result.Type == AML::ObjectType::Integer) {
                // EISA ID
                DecodeEisaId((uint32_t)result.Integer, outHid);
                return true;
            }

            if (result.Type == AML::ObjectType::String) {
                StrCopy(outHid, result.String.Data, maxLen);
                return true;
            }

            return false;
        }

        // ============================================================================

        // EvaluateUid

        // ============================================================================
        bool EvaluateUid(int32_t deviceNodeIndex, char* outUid, int maxLen) {
            auto& interp = AML::GetInterpreter();
            auto& ns = interp.GetNamespace();

            int32_t uidNode = -1;
            ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None,
                [&](int32_t idx, const AML::NamespaceNode* node) -> bool {
                    if (node->Name[0] == '_' && node->Name[1] == 'U' &&
                        node->Name[2] == 'I' && node->Name[3] == 'D') {
                        uidNode = idx;
                        return false;
                    }
                    return true;
                });

            if (uidNode < 0) return false;

            AML::Object result{};
            char path[256];
            ns.GetNodePath(uidNode, path, 256);

            if (!interp.EvaluateObject(path, result))
                return false;

            if (result.Type == AML::ObjectType::Integer) {
                // Convert integer to string
                uint64_t val = result.Integer;
                char buf[21];
                int i = 0;
                if (val == 0) {
                    buf[i++] = '0';
                } else {
                    char tmp[21];
                    int t = 0;
                    while (val > 0) {
                        tmp[t++] = '0' + (val % 10);
                        val /= 10;
                    }
                    for (int j = t - 1; j >= 0; j--)
                        buf[i++] = tmp[j];
                }
                buf[i] = '\0';
                StrCopy(outUid, buf, maxLen);
                return true;
            }

            if (result.Type == AML::ObjectType::String) {
                StrCopy(outUid, result.String.Data, maxLen);
                return true;
            }

            return false;
        }

        // ============================================================================

        // EvaluateCrs

        // ============================================================================
        bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result) {
            auto& interp = AML::GetInterpreter();
            auto& ns = interp.GetNamespace();

            int32_t crsNode = -1;
            ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None,
                [&](int32_t idx, const AML::NamespaceNode* node) -> bool {
                    if (node->Name[0] == '_' && node->Name[1] == 'C' &&
                        node->Name[2] == 'R' && node->Name[3] == 'S') {
                        crsNode = idx;
                        return false;
                    }
                    return true;
                });

            if (crsNode < 0) return false;

            AML::Object crsResult{};
            char path[256];
            ns.GetNodePath(crsNode, path, 256);

            if (!interp.EvaluateObject(path, crsResult))
                return false;

            if (crsResult.Type != AML::ObjectType::Buffer)
                return false;

            return AML::ParseResourceTemplate(crsResult.Buffer.Data, crsResult.Buffer.Length, result);
        }

        // ============================================================================

        // EnumerateAll

        // ============================================================================
        void EnumerateAll(DeviceList& result) {
            auto& interp = AML::GetInterpreter();
            if (!interp.IsInitialized()) return;

            auto& ns = interp.GetNamespace();
            result.Count = 0;

            // Walk all namespace nodes looking for Device objects
            ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device,
                [&](int32_t idx, [[maybe_unused]] const AML::NamespaceNode* node) {
                    if (result.Count >= MaxDevices) return;

                    auto& dev = result.Devices[result.Count];
                    dev.NodeIndex = idx;

                    // Get the path
                    ns.GetNodePath(idx, dev.Path, 128);

                    // Evaluate _STA
                    dev.Status = EvaluateSta(idx);
                    dev.IsPresent = (dev.Status & STA_PRESENT) && (dev.Status & STA_FUNCTIONAL);

                    // Evaluate _HID
                    if (!EvaluateHid(idx, dev.HardwareId, 16))
                        dev.HardwareId[0] = '\0';

                    // Evaluate _UID
                    if (!EvaluateUid(idx, dev.UniqueId, 16))
                        dev.UniqueId[0] = '\0';

                    // Evaluate _ADR
                    dev.Address = EvaluateAdr(idx);

                    result.Count++;
                });

            KernelLogStream(OK, "ACPI") << "Enumerated " << base::dec
                << (uint64_t)result.Count << " ACPI devices";

            // Log discovered devices
            for (int i = 0; i < result.Count; i++) {
                auto& dev = result.Devices[i];
                if (dev.HardwareId[0]) {
                    KernelLogStream(DEBUG, "ACPI") << "  " << dev.Path
                        << " HID=" << dev.HardwareId
                        << (dev.IsPresent ? " [present]" : " [not present]");
                } else if (dev.Address != 0) {
                    KernelLogStream(DEBUG, "ACPI") << "  " << dev.Path
                        << " ADR=" << base::hex << dev.Address
                        << (dev.IsPresent ? " [present]" : " [not present]");
                }
            }
        }

        // ============================================================================

        // GetSleepState

        // ============================================================================
        SleepState GetSleepState(int state) {
            SleepState result{};
            result.Valid = false;

            if (state < 0 || state > 5) return result;

            char path[8] = "\\_Sx_";
            path[2] = 'S';
            path[3] = '0' + (char)state;
            path[4] = '_';
            path[5] = '\0';

            auto& interp = AML::GetInterpreter();
            AML::Object obj{};

            if (!interp.EvaluateObject(path, obj))
                return result;

            // The sleep state object should be a Package with at least 2 integers.
            // During loading, we stored Package data as a raw buffer.
            // For the common case, the \_S5_ was already extracted by the old parser.
            // Try to get it from namespace directly.
            auto& ns = interp.GetNamespace();
            int32_t node = ns.FindNode(path);
            if (node < 0) return result;

            auto* nsNode = ns.GetNode(node);
            if (!nsNode) return result;

            if (nsNode->Obj.Type == AML::ObjectType::Package) {
                // Parse the raw package buffer to extract SLP_TYP values
                const uint8_t* data = nsNode->Obj.Buffer.Data;
                uint32_t len = nsNode->Obj.Buffer.Length;
                if (len < 2) return result;

                // NumElements
                uint8_t numElements = data[0];
                uint32_t pos = 1;

                if (numElements >= 1 && pos < len) {
                    // Decode first element
                    uint8_t elem0 = data[pos];
                    if (elem0 == AML::ZeroOp) { result.SLP_TYPa = 0; pos++; }
                    else if (elem0 == AML::OneOp) { result.SLP_TYPa = 1; pos++; }
                    else if (elem0 == AML::BytePrefix && pos + 1 < len) { result.SLP_TYPa = data[pos + 1]; pos += 2; }
                    else { pos++; }
                }

                if (numElements >= 2 && pos < len) {
                    uint8_t elem1 = data[pos];
                    if (elem1 == AML::ZeroOp) { result.SLP_TYPb = 0; pos++; }
                    else if (elem1 == AML::OneOp) { result.SLP_TYPb = 1; pos++; }
                    else if (elem1 == AML::BytePrefix && pos + 1 < len) { result.SLP_TYPb = data[pos + 1]; pos += 2; }
                    else { pos++; }
                }

                result.Valid = true;
            } else if (nsNode->Obj.Type == AML::ObjectType::Integer) {
                result.SLP_TYPa = (uint16_t)nsNode->Obj.Integer;
                result.SLP_TYPb = 0;
                result.Valid = true;
            }

            return result;
        }

    };
};
