/*
    * Clipboard.hpp
    * SYS_CLIPBOARD_* plain-text clipboard syscalls
    * Copyright (c) 2026 Daniel Hammer
*/

#pragma once

#include <Sched/Scheduler.hpp>
#include <Memory/Heap.hpp>
#include <CppLib/Spinlock.hpp>

#include "Syscall.hpp"

namespace Montauk {

    static constexpr int ClipboardMaxSlots = 32;

    struct ClipboardSlot {
        bool inUse;
        char user[32];
        char* text;
        uint32_t len;
        uint64_t serial;
    };

    static ClipboardSlot g_clipboardSlots[ClipboardMaxSlots];
    static kcp::Mutex g_clipboardLock;

    static int Sys_ClipboardClear();

    static const char* ClipboardCurrentUser() {
        auto* proc = Sched::GetCurrentProcessPtr();
        if (proc == nullptr) return nullptr;
        return proc->user[0] ? proc->user : "default";
    }

    static void ClipboardCopyUser(char dst[32], const char* src) {
        if (src == nullptr || src[0] == '\0') src = "default";
        int i = 0;
        for (; i < 31 && src[i]; i++)
            dst[i] = src[i];
        dst[i] = '\0';
    }

    static bool ClipboardUserEquals(const char* a, const char* b) {
        int i = 0;
        while (a[i] && b[i]) {
            if (a[i] != b[i]) return false;
            i++;
        }
        return a[i] == b[i];
    }

    static uint64_t ClipboardNextSerial(uint64_t serial) {
        serial++;
        return serial == 0 ? 1 : serial;
    }

    static ClipboardSlot* ClipboardFindSlotLocked(const char* user) {
        for (int i = 0; i < ClipboardMaxSlots; i++) {
            if (g_clipboardSlots[i].inUse && ClipboardUserEquals(g_clipboardSlots[i].user, user))
                return &g_clipboardSlots[i];
        }
        return nullptr;
    }

    static ClipboardSlot* ClipboardEnsureSlotLocked(const char* user) {
        ClipboardSlot* freeSlot = nullptr;
        for (int i = 0; i < ClipboardMaxSlots; i++) {
            ClipboardSlot* slot = &g_clipboardSlots[i];
            if (slot->inUse) {
                if (ClipboardUserEquals(slot->user, user))
                    return slot;
            } else if (freeSlot == nullptr) {
                freeSlot = slot;
            }
        }

        if (freeSlot == nullptr) return nullptr;

        freeSlot->inUse = true;
        ClipboardCopyUser(freeSlot->user, user);
        freeSlot->text = nullptr;
        freeSlot->len = 0;
        freeSlot->serial = 0;
        return freeSlot;
    }

    static int Sys_ClipboardSetText(const char* data, uint32_t len) {
        if (len == 0)
            return Sys_ClipboardClear();
        if (data == nullptr || len > CLIPBOARD_MAX_TEXT_BYTES || Memory::g_heap == nullptr)
            return -1;

        for (uint32_t i = 0; i < len; i++) {
            if (data[i] == '\0')
                return -1;
        }

        const char* currentUser = ClipboardCurrentUser();
        if (currentUser == nullptr) return -1;

        char user[32];
        ClipboardCopyUser(user, currentUser);

        char* newText = (char*)Memory::g_heap->Request(len + 1);
        if (newText == nullptr) return -1;
        for (uint32_t i = 0; i < len; i++)
            newText[i] = data[i];
        newText[len] = '\0';

        char* oldText = nullptr;

        g_clipboardLock.Acquire();
        ClipboardSlot* slot = ClipboardEnsureSlotLocked(user);
        if (slot == nullptr) {
            g_clipboardLock.Release();
            Memory::g_heap->Free(newText);
            return -1;
        }

        oldText = slot->text;
        slot->text = newText;
        slot->len = len;
        slot->serial = ClipboardNextSerial(slot->serial);
        g_clipboardLock.Release();

        if (oldText != nullptr)
            Memory::g_heap->Free(oldText);

        return 0;
    }

    static int Sys_ClipboardGetInfo(ClipboardInfo* out) {
        if (out == nullptr) return -1;

        out->textBytes = 0;
        out->_pad = 0;
        out->serial = 0;

        const char* currentUser = ClipboardCurrentUser();
        if (currentUser == nullptr) return -1;

        char user[32];
        ClipboardCopyUser(user, currentUser);

        g_clipboardLock.Acquire();
        ClipboardSlot* slot = ClipboardFindSlotLocked(user);
        if (slot != nullptr) {
            out->textBytes = slot->len;
            out->serial = slot->serial;
        }
        g_clipboardLock.Release();
        return 0;
    }

    static int Sys_ClipboardGetText(char* buffer, uint32_t bufferLen, uint32_t* outLen, uint64_t* outSerial) {
        if (outLen == nullptr) return -1;

        *outLen = 0;
        if (outSerial != nullptr) *outSerial = 0;

        const char* currentUser = ClipboardCurrentUser();
        if (currentUser == nullptr) return -1;

        char user[32];
        ClipboardCopyUser(user, currentUser);

        g_clipboardLock.Acquire();
        ClipboardSlot* slot = ClipboardFindSlotLocked(user);
        if (slot == nullptr || slot->len == 0) {
            if (slot != nullptr && outSerial != nullptr)
                *outSerial = slot->serial;
            g_clipboardLock.Release();
            return 0;
        }

        *outLen = slot->len;
        if (outSerial != nullptr) *outSerial = slot->serial;
        if (buffer == nullptr || bufferLen < slot->len) {
            g_clipboardLock.Release();
            return -1;
        }

        for (uint32_t i = 0; i < slot->len; i++)
            buffer[i] = slot->text[i];
        g_clipboardLock.Release();
        return 0;
    }

    static int Sys_ClipboardClear() {
        const char* currentUser = ClipboardCurrentUser();
        if (currentUser == nullptr) return -1;

        char user[32];
        ClipboardCopyUser(user, currentUser);

        char* oldText = nullptr;

        g_clipboardLock.Acquire();
        ClipboardSlot* slot = ClipboardEnsureSlotLocked(user);
        if (slot == nullptr) {
            g_clipboardLock.Release();
            return -1;
        }

        oldText = slot->text;
        slot->text = nullptr;
        slot->len = 0;
        slot->serial = ClipboardNextSerial(slot->serial);
        g_clipboardLock.Release();

        if (oldText != nullptr)
            Memory::g_heap->Free(oldText);

        return 0;
    }

}
