#include <uacpi/platform/atomic.h>
#include <uacpi/internal/osi.h>
#include <uacpi/internal/helpers.h>
#include <uacpi/internal/stdlib.h>
#include <uacpi/internal/utilities.h>
#include <uacpi/kernel_api.h>

struct registered_interface {
    const uacpi_char *name;
    uacpi_u8 weight;
    uacpi_u8 kind;

    // Only applicable for predefined host interfaces
    uacpi_u8 host_type;

    // Only applicable for predefined interfaces
    uacpi_u8 disabled : 1;
    uacpi_u8 dynamic : 1;

    struct registered_interface *next;
};

static uacpi_handle interface_mutex;
static struct registered_interface *registered_interfaces;
static uacpi_interface_handler interface_handler;
static uacpi_u32 latest_queried_interface;

#define WINDOWS(string, interface)                            \
    {                                                         \
        .name = "Windows "string,                             \
        .weight = UACPI_VENDOR_INTERFACE_WINDOWS_##interface, \
        .kind = UACPI_INTERFACE_KIND_VENDOR,                  \
        .host_type = 0,                                       \
        .disabled = 0,                                        \
        .dynamic = 0,                                         \
        .next = UACPI_NULL                                    \
    }

#define HOST_FEATURE(string, type)                \
    {                                             \
        .name = string,                           \
        .weight = 0,                              \
        .kind = UACPI_INTERFACE_KIND_FEATURE,     \
        .host_type = UACPI_HOST_INTERFACE_##type, \
        .disabled = 1,                            \
        .dynamic = 0,                             \
        .next = UACPI_NULL,                       \
    }

static struct registered_interface predefined_interfaces[] = {
    // Vendor strings
    WINDOWS("2000", 2000),
    WINDOWS("2001", XP),
    WINDOWS("2001 SP1", XP_SP1),
    WINDOWS("2001.1", SERVER_2003),
    WINDOWS("2001 SP2", XP_SP2),
    WINDOWS("2001.1 SP1", SERVER_2003_SP1),
    WINDOWS("2006", VISTA),
    WINDOWS("2006.1", SERVER_2008),
    WINDOWS("2006 SP1", VISTA_SP1),
    WINDOWS("2006 SP2", VISTA_SP2),
    WINDOWS("2009", 7),
    WINDOWS("2012", 8),
    WINDOWS("2013", 8_1),
    WINDOWS("2015", 10),
    WINDOWS("2016", 10_RS1),
    WINDOWS("2017", 10_RS2),
    WINDOWS("2017.2", 10_RS3),
    WINDOWS("2018", 10_RS4),
    WINDOWS("2018.2", 10_RS5),
    WINDOWS("2019", 10_19H1),
    WINDOWS("2020", 10_20H1),
    WINDOWS("2021", 11),
    WINDOWS("2022", 11_22H2),

    // Feature strings
    HOST_FEATURE("Module Device", MODULE_DEVICE),
    HOST_FEATURE("Processor Device", PROCESSOR_DEVICE),
    HOST_FEATURE("3.0 Thermal Model", 3_0_THERMAL_MODEL),
    HOST_FEATURE("3.0 _SCP Extensions", 3_0_SCP_EXTENSIONS),
    HOST_FEATURE("Processor Aggregator Device", PROCESSOR_AGGREGATOR_DEVICE),

    // Interpreter features
    { .name = "Extended Address Space Descriptor" },
};

uacpi_status uacpi_initialize_interfaces(void)
{
    uacpi_size i;

    registered_interfaces = &predefined_interfaces[0];

    interface_mutex = uacpi_kernel_create_mutex();
    if (uacpi_unlikely(interface_mutex == UACPI_NULL))
        return UACPI_STATUS_OUT_OF_MEMORY;

    for (i = 0; i < (UACPI_ARRAY_SIZE(predefined_interfaces) - 1); ++i)
        predefined_interfaces[i].next = &predefined_interfaces[i + 1];

    return UACPI_STATUS_OK;
}

void uacpi_deinitialize_interfaces(void)
{
    struct registered_interface *iface, *next_iface = registered_interfaces;

    while (next_iface) {
        iface = next_iface;
        next_iface = iface->next;

        iface->next = UACPI_NULL;

        if (iface->dynamic) {
            uacpi_free_dynamic_string(iface->name);
            uacpi_free(iface, sizeof(*iface));
            continue;
        }

        // Only features are disabled by default
        iface->disabled = iface->kind == UACPI_INTERFACE_KIND_FEATURE ?
                UACPI_TRUE : UACPI_FALSE;
    }

    if (interface_mutex)
        uacpi_kernel_free_mutex(interface_mutex);

    interface_mutex = UACPI_NULL;
    interface_handler = UACPI_NULL;
    latest_queried_interface = 0;
    registered_interfaces = UACPI_NULL;
}

uacpi_vendor_interface uacpi_latest_queried_vendor_interface(void)
{
    return uacpi_atomic_load32(&latest_queried_interface);
}

static struct registered_interface *find_interface_unlocked(
    const uacpi_char *name
)
{
    struct registered_interface *interface = registered_interfaces;

    while (interface) {
        if (uacpi_strcmp(interface->name, name) == 0)
            return interface;

        interface = interface->next;
    }

    return UACPI_NULL;
}

static struct registered_interface *find_host_interface_unlocked(
    uacpi_host_interface type
)
{
    struct registered_interface *interface = registered_interfaces;

    while (interface) {
        if (interface->host_type == type)
            return interface;

        interface = interface->next;
    }

    return UACPI_NULL;
}

uacpi_status uacpi_install_interface(
    const uacpi_char *name, uacpi_interface_kind kind
)
{
    struct registered_interface *interface;
    uacpi_status ret = UACPI_STATUS_ALREADY_EXISTS;
    uacpi_char *name_copy;
    uacpi_size name_size;

    UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED);

    UACPI_MUTEX_ACQUIRE(interface_mutex);

    interface = find_interface_unlocked(name);
    if (interface != UACPI_NULL) {
        if (interface->disabled) {
            interface->disabled = UACPI_FALSE;
            ret = UACPI_STATUS_OK;
        }
        goto out;
    }

    interface = uacpi_kernel_alloc(sizeof(*interface));
    if (uacpi_unlikely(interface == UACPI_NULL)) {
        ret = UACPI_STATUS_OUT_OF_MEMORY;
        goto out;
    }

    name_size = uacpi_strlen(name) + 1;
    name_copy = uacpi_kernel_alloc(name_size);
    if (uacpi_unlikely(name_copy == UACPI_NULL)) {
        uacpi_free(interface, sizeof(*interface));
        ret = UACPI_STATUS_OUT_OF_MEMORY;
        goto out;
    }

    uacpi_memcpy(name_copy, name, name_size);
    interface->name = name_copy;
    interface->weight = 0;
    interface->kind = kind;
    interface->host_type = 0;
    interface->disabled = 0;
    interface->dynamic = 1;
    interface->next = registered_interfaces;
    registered_interfaces = interface;
    ret = UACPI_STATUS_OK;

out:
    UACPI_MUTEX_RELEASE(interface_mutex);
    return ret;
}

uacpi_status uacpi_uninstall_interface(const uacpi_char *name)
{
    struct registered_interface *cur, *prev;
    uacpi_status ret = UACPI_STATUS_NOT_FOUND;

    UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED);

    UACPI_MUTEX_ACQUIRE(interface_mutex);

    cur = registered_interfaces;
    prev = cur;

    while (cur) {
        if (uacpi_strcmp(cur->name, name) != 0) {
            prev = cur;
            cur = cur->next;
            continue;
        }

        if (cur->dynamic) {
            if (prev == cur) {
                registered_interfaces = cur->next;
            } else {
                prev->next = cur->next;
            }

            UACPI_MUTEX_RELEASE(interface_mutex);
            uacpi_free_dynamic_string(cur->name);
            uacpi_free(cur, sizeof(*cur));
            return UACPI_STATUS_OK;
        }

        /*
         * If this interface was already disabled, pretend we didn't actually
         * find it and keep ret as UACPI_STATUS_NOT_FOUND. The fact that it's
         * still in the registered list is an implementation detail of
         * predefined interfaces.
         */
        if (!cur->disabled) {
            cur->disabled = UACPI_TRUE;
            ret = UACPI_STATUS_OK;
        }

        break;
    }

    UACPI_MUTEX_RELEASE(interface_mutex);
    return ret;
}

static uacpi_status configure_host_interface(
    uacpi_host_interface type, uacpi_bool enabled
)
{
    struct registered_interface *interface;
    uacpi_status ret = UACPI_STATUS_NOT_FOUND;

    UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED);

    UACPI_MUTEX_ACQUIRE(interface_mutex);

    interface = find_host_interface_unlocked(type);
    if (interface == UACPI_NULL)
        goto out;

    interface->disabled = !enabled;
    ret = UACPI_STATUS_OK;

out:
    UACPI_MUTEX_RELEASE(interface_mutex);
    return ret;
}

uacpi_status uacpi_enable_host_interface(uacpi_host_interface type)
{
    return configure_host_interface(type, UACPI_TRUE);
}

uacpi_status uacpi_disable_host_interface(uacpi_host_interface type)
{
    return configure_host_interface(type, UACPI_FALSE);
}

uacpi_status uacpi_set_interface_query_handler(
    uacpi_interface_handler handler
)
{
    uacpi_status ret = UACPI_STATUS_ALREADY_EXISTS;

    UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED);

    UACPI_MUTEX_ACQUIRE(interface_mutex);

    if (interface_handler != UACPI_NULL && handler != UACPI_NULL)
        goto out;

    interface_handler = handler;
    ret = UACPI_STATUS_OK;

out:
    UACPI_MUTEX_RELEASE(interface_mutex);
    return ret;
}

uacpi_status uacpi_bulk_configure_interfaces(
    uacpi_interface_action action, uacpi_interface_kind kind
)
{
    struct registered_interface *interface;

    UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED);

    UACPI_MUTEX_ACQUIRE(interface_mutex);

    interface = registered_interfaces;
    while (interface) {
        if (kind & interface->kind)
            interface->disabled = (action == UACPI_INTERFACE_ACTION_DISABLE);

        interface = interface->next;
    }

    UACPI_MUTEX_RELEASE(interface_mutex);
    return UACPI_STATUS_OK;
}

uacpi_status uacpi_handle_osi(const uacpi_char *string, uacpi_bool *out_value)
{
    struct registered_interface *interface;
    uacpi_bool is_supported = UACPI_FALSE;

    UACPI_MUTEX_ACQUIRE(interface_mutex);

    interface = find_interface_unlocked(string);
    if (interface == UACPI_NULL)
        goto out;

    if (interface->weight > latest_queried_interface)
        uacpi_atomic_store32(&latest_queried_interface, interface->weight);

    is_supported = !interface->disabled;
    if (interface_handler)
        is_supported = interface_handler(string, is_supported);
out:
    UACPI_MUTEX_RELEASE(interface_mutex);
    *out_value = is_supported;
    return UACPI_STATUS_OK;
}
