#include <uacpi/platform/atomic.h>
#include <uacpi/internal/mutex.h>
#include <uacpi/internal/log.h>
#include <uacpi/internal/registers.h>
#include <uacpi/internal/context.h>
#include <uacpi/kernel_api.h>
#include <uacpi/internal/namespace.h>

#ifndef UACPI_BAREBONES_MODE

#ifndef UACPI_REDUCED_HARDWARE

#define GLOBAL_LOCK_PENDING (1 << 0)

#define GLOBAL_LOCK_OWNED_BIT 1
#define GLOBAL_LOCK_OWNED (1 << GLOBAL_LOCK_OWNED_BIT)

#define GLOBAL_LOCK_MASK 3u

static uacpi_bool try_acquire_global_lock_from_firmware(uacpi_u32 *lock)
{
    uacpi_u32 value, new_value;
    uacpi_bool was_owned;

    value = *(volatile uacpi_u32*)lock;
    do {
        was_owned = (value & GLOBAL_LOCK_OWNED) >> GLOBAL_LOCK_OWNED_BIT;

        // Clear both owned & pending bits.
        new_value = value & ~GLOBAL_LOCK_MASK;

        // Set owned unconditionally
        new_value |= GLOBAL_LOCK_OWNED;

        // Set pending iff the lock was owned at the time of reading
        if (was_owned)
            new_value |= GLOBAL_LOCK_PENDING;
    } while (!uacpi_atomic_cmpxchg32(lock, &value, new_value));

    return !was_owned;
}

static uacpi_bool do_release_global_lock_to_firmware(uacpi_u32 *lock)
{
    uacpi_u32 value, new_value;

    value = *(volatile uacpi_u32*)lock;
    do {
        new_value = value & ~GLOBAL_LOCK_MASK;
    } while (!uacpi_atomic_cmpxchg32(lock, &value, new_value));

    return value & GLOBAL_LOCK_PENDING;
}

static uacpi_status uacpi_acquire_global_lock_from_firmware(void)
{
    uacpi_cpu_flags flags;
    uacpi_u16 spins = 0;
    uacpi_bool success;

    if (!g_uacpi_rt_ctx.has_global_lock)
        return UACPI_STATUS_OK;

    flags = uacpi_kernel_lock_spinlock(g_uacpi_rt_ctx.global_lock_spinlock);
    for (;;) {
        spins++;
        uacpi_trace(
            "trying to acquire the global lock from firmware... (attempt %u)\n",
            spins
        );

        success = try_acquire_global_lock_from_firmware(
            &g_uacpi_rt_ctx.facs->global_lock
        );
        if (success)
            break;

        if (uacpi_unlikely(spins == 0xFFFF))
            break;

        g_uacpi_rt_ctx.global_lock_pending = UACPI_TRUE;
        uacpi_trace(
            "global lock is owned by firmware, waiting for a release "
            "notification...\n"
        );
        uacpi_kernel_unlock_spinlock(g_uacpi_rt_ctx.global_lock_spinlock, flags);

        uacpi_kernel_wait_for_event(g_uacpi_rt_ctx.global_lock_event, 0xFFFF);
        flags = uacpi_kernel_lock_spinlock(g_uacpi_rt_ctx.global_lock_spinlock);
    }

    g_uacpi_rt_ctx.global_lock_pending = UACPI_FALSE;
    uacpi_kernel_unlock_spinlock(g_uacpi_rt_ctx.global_lock_spinlock, flags);

    if (uacpi_unlikely(!success)) {
        uacpi_error("unable to acquire global lock after %u attempts\n", spins);
        return UACPI_STATUS_HARDWARE_TIMEOUT;
    }

    uacpi_trace("global lock successfully acquired after %u attempt%s\n",
                spins, spins > 1 ? "s" : "");
    return UACPI_STATUS_OK;
}

static void uacpi_release_global_lock_to_firmware(void)
{
    if (!g_uacpi_rt_ctx.has_global_lock)
        return;

    uacpi_trace("releasing the global lock to firmware...\n");
    if (do_release_global_lock_to_firmware(&g_uacpi_rt_ctx.facs->global_lock)) {
        uacpi_trace("notifying firmware of the global lock release since the "
                    "pending bit was set\n");
        uacpi_write_register_field(UACPI_REGISTER_FIELD_GBL_RLS, 1);
    }
}
#endif

UACPI_ALWAYS_OK_FOR_REDUCED_HARDWARE(
    uacpi_status uacpi_acquire_global_lock_from_firmware(void)
)
UACPI_STUB_IF_REDUCED_HARDWARE(
    void uacpi_release_global_lock_to_firmware(void)
)

uacpi_status uacpi_acquire_native_mutex_with_timeout(
    uacpi_handle mtx, uacpi_u16 timeout
)
{
    uacpi_status ret;

    if (uacpi_unlikely(mtx == UACPI_NULL))
        return UACPI_STATUS_INVALID_ARGUMENT;

    ret = uacpi_kernel_acquire_mutex(mtx, timeout);
    if (uacpi_likely_success(ret))
        return ret;

    if (uacpi_unlikely(ret != UACPI_STATUS_TIMEOUT || timeout == 0xFFFF)) {
        uacpi_error(
            "unexpected status %08X (%s) while acquiring %p (timeout=%04X)\n",
            ret, uacpi_status_to_string(ret), mtx, timeout
        );
    }

    return ret;
}

uacpi_status uacpi_acquire_global_lock(uacpi_u16 timeout, uacpi_u32 *out_seq)
{
    uacpi_status ret;

    UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED);

    if (uacpi_unlikely(out_seq == UACPI_NULL))
        return UACPI_STATUS_INVALID_ARGUMENT;

    ret = uacpi_acquire_native_mutex_with_timeout(
        g_uacpi_rt_ctx.global_lock_mutex->handle, timeout
    );
    if (ret != UACPI_STATUS_OK)
        return ret;

    ret = uacpi_acquire_global_lock_from_firmware();
    if (uacpi_unlikely_error(ret)) {
        uacpi_release_native_mutex(g_uacpi_rt_ctx.global_lock_mutex->handle);
        return ret;
    }

    if (uacpi_unlikely(g_uacpi_rt_ctx.global_lock_seq_num == 0xFFFFFFFF))
        g_uacpi_rt_ctx.global_lock_seq_num = 0;

    *out_seq = g_uacpi_rt_ctx.global_lock_seq_num++;
    g_uacpi_rt_ctx.global_lock_acquired = UACPI_TRUE;
    return UACPI_STATUS_OK;
}

uacpi_status uacpi_release_global_lock(uacpi_u32 seq)
{
    UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED);

    if (uacpi_unlikely(!g_uacpi_rt_ctx.global_lock_acquired ||
                       seq != g_uacpi_rt_ctx.global_lock_seq_num))
        return UACPI_STATUS_INVALID_ARGUMENT;

    g_uacpi_rt_ctx.global_lock_acquired = UACPI_FALSE;
    uacpi_release_global_lock_to_firmware();
    uacpi_release_native_mutex(g_uacpi_rt_ctx.global_lock_mutex->handle);

    return UACPI_STATUS_OK;
}

uacpi_bool uacpi_this_thread_owns_aml_mutex(uacpi_mutex *mutex)
{
    uacpi_thread_id id;

    id = UACPI_ATOMIC_LOAD_THREAD_ID(&mutex->owner);
    return id == uacpi_kernel_get_thread_id();
}

uacpi_status uacpi_acquire_aml_mutex(uacpi_mutex *mutex, uacpi_u16 timeout)
{
    uacpi_thread_id this_id;
    uacpi_status ret = UACPI_STATUS_OK;

    this_id = uacpi_kernel_get_thread_id();
    if (UACPI_ATOMIC_LOAD_THREAD_ID(&mutex->owner) == this_id) {
        if (uacpi_unlikely(mutex->depth == 0xFFFF)) {
            uacpi_warn(
                "failing an attempt to acquire mutex @%p, too many recursive "
                "acquires\n", mutex
            );
            return UACPI_STATUS_DENIED;
        }

        mutex->depth++;
        return ret;
    }

    uacpi_namespace_write_unlock();
    ret = uacpi_acquire_native_mutex_with_timeout(mutex->handle, timeout);
    if (ret != UACPI_STATUS_OK)
        goto out;

    if (mutex->handle == g_uacpi_rt_ctx.global_lock_mutex->handle) {
        ret = uacpi_acquire_global_lock_from_firmware();
        if (uacpi_unlikely_error(ret)) {
            uacpi_release_native_mutex(mutex->handle);
            goto out;
        }
    }

    UACPI_ATOMIC_STORE_THREAD_ID(&mutex->owner, this_id);
    mutex->depth = 1;

out:
    uacpi_namespace_write_lock();
    return ret;
}

uacpi_status uacpi_release_aml_mutex(uacpi_mutex *mutex)
{
    if (mutex->depth-- > 1)
        return UACPI_STATUS_OK;

    if (mutex->handle == g_uacpi_rt_ctx.global_lock_mutex->handle)
        uacpi_release_global_lock_to_firmware();

    UACPI_ATOMIC_STORE_THREAD_ID(&mutex->owner, UACPI_THREAD_ID_NONE);
    uacpi_release_native_mutex(mutex->handle);

    return UACPI_STATUS_OK;
}

uacpi_status uacpi_recursive_lock_init(struct uacpi_recursive_lock *lock)
{
    lock->mutex = uacpi_kernel_create_mutex();
    if (uacpi_unlikely(lock->mutex == UACPI_NULL))
        return UACPI_STATUS_OUT_OF_MEMORY;

    lock->owner = UACPI_THREAD_ID_NONE;
    lock->depth = 0;

    return UACPI_STATUS_OK;
}

uacpi_status uacpi_recursive_lock_deinit(struct uacpi_recursive_lock *lock)
{
    if (uacpi_unlikely(lock->depth)) {
        uacpi_warn(
            "de-initializing active recursive lock %p with depth=%zu\n",
            lock, lock->depth
        );
        lock->depth = 0;
    }

    lock->owner = UACPI_THREAD_ID_NONE;

    if (lock->mutex != UACPI_NULL) {
        uacpi_kernel_free_mutex(lock->mutex);
        lock->mutex = UACPI_NULL;
    }

    return UACPI_STATUS_OK;
}

uacpi_status uacpi_recursive_lock_acquire(struct uacpi_recursive_lock *lock)
{
    uacpi_thread_id this_id;
    uacpi_status ret = UACPI_STATUS_OK;

    this_id = uacpi_kernel_get_thread_id();
    if (UACPI_ATOMIC_LOAD_THREAD_ID(&lock->owner) == this_id) {
        lock->depth++;
        return ret;
    }

    ret = uacpi_acquire_native_mutex(lock->mutex);
    if (uacpi_unlikely_error(ret))
        return ret;

    UACPI_ATOMIC_STORE_THREAD_ID(&lock->owner, this_id);
    lock->depth = 1;
    return ret;
}

uacpi_status uacpi_recursive_lock_release(struct uacpi_recursive_lock *lock)
{
    if (lock->depth-- > 1)
        return UACPI_STATUS_OK;

    UACPI_ATOMIC_STORE_THREAD_ID(&lock->owner, UACPI_THREAD_ID_NONE);
    return uacpi_release_native_mutex(lock->mutex);
}

uacpi_status uacpi_rw_lock_init(struct uacpi_rw_lock *lock)
{
    lock->read_mutex = uacpi_kernel_create_mutex();
    if (uacpi_unlikely(lock->read_mutex == UACPI_NULL))
        return UACPI_STATUS_OUT_OF_MEMORY;

    lock->write_mutex = uacpi_kernel_create_mutex();
    if (uacpi_unlikely(lock->write_mutex == UACPI_NULL)) {
        uacpi_kernel_free_mutex(lock->read_mutex);
        lock->read_mutex = UACPI_NULL;
        return UACPI_STATUS_OUT_OF_MEMORY;
    }

    lock->num_readers = 0;
    return UACPI_STATUS_OK;
}

uacpi_status uacpi_rw_lock_deinit(struct uacpi_rw_lock *lock)
{
    if (uacpi_unlikely(lock->num_readers)) {
        uacpi_warn("de-initializing rw_lock %p with %zu active readers\n",
                   lock, lock->num_readers);
        lock->num_readers = 0;
    }

    if (lock->read_mutex != UACPI_NULL) {
        uacpi_kernel_free_mutex(lock->read_mutex);
        lock->read_mutex = UACPI_NULL;
    }
    if (lock->write_mutex != UACPI_NULL) {
        uacpi_kernel_free_mutex(lock->write_mutex);
        lock->write_mutex = UACPI_NULL;
    }

    return UACPI_STATUS_OK;
}

uacpi_status uacpi_rw_lock_read(struct uacpi_rw_lock *lock)
{
    uacpi_status ret;

    ret = uacpi_acquire_native_mutex(lock->read_mutex);
    if (uacpi_unlikely_error(ret))
        return ret;

    if (lock->num_readers++ == 0) {
        ret = uacpi_acquire_native_mutex(lock->write_mutex);
        if (uacpi_unlikely_error(ret))
            lock->num_readers = 0;
    }

    uacpi_kernel_release_mutex(lock->read_mutex);
    return ret;
}

uacpi_status uacpi_rw_unlock_read(struct uacpi_rw_lock *lock)
{
    uacpi_status ret;

    ret = uacpi_acquire_native_mutex(lock->read_mutex);
    if (uacpi_unlikely_error(ret))
        return ret;

    if (lock->num_readers-- == 1)
        uacpi_release_native_mutex(lock->write_mutex);

    uacpi_kernel_release_mutex(lock->read_mutex);
    return ret;
}

uacpi_status uacpi_rw_lock_write(struct uacpi_rw_lock *lock)
{
    return uacpi_acquire_native_mutex(lock->write_mutex);
}

uacpi_status uacpi_rw_unlock_write(struct uacpi_rw_lock *lock)
{
    return uacpi_release_native_mutex(lock->write_mutex);
}

#endif // !UACPI_BAREBONES_MODE
