#include "IPC.h"
#include "Mouse.h"
#include "Screen.h"
#include <luna/Alignment.h>
#include <luna/String.h>
#include <os/File.h>
#include <os/SharedMemory.h>
#include <sys/mman.h>
#include <time.h>

#define TRY_OR_IPC_ERROR(expr)                                                                                         \
    ({                                                                                                                 \
        auto _expr_rc = (expr);                                                                                        \
        if (!_expr_rc.has_value())                                                                                     \
        {                                                                                                              \
            client.conn->send_error(_expr_rc.error());                                                                 \
            return {};                                                                                                 \
        }                                                                                                              \
        _expr_rc.release_value();                                                                                      \
    })

#define CHECK_WINDOW_ID(request)                                                                                       \
    do {                                                                                                               \
        if ((usize)request.window >= client.windows.size() || !client.windows[request.window])                         \
        {                                                                                                              \
            os::eprintln("wind: Window id is invalid!");                                                               \
            return {};                                                                                                 \
        }                                                                                                              \
    } while (0)

static Result<void> handle_create_window_message(Client& client)
{
    ui::CreateWindowRequest request;
    if (!TRY(client.conn->read_message(request))) return {};

    request.rect = request.rect.normalized();

    auto name = TRY_OR_IPC_ERROR(String::from_cstring("Window"));

    auto shm_path = TRY_OR_IPC_ERROR(String::format("/wind-shm-%d-%lu"_sv, client.conn->fd(), time(NULL)));

    auto* window = new (std::nothrow) Window(request.rect, move(name));
    if (!window)
    {
        client.conn->send_error(ENOMEM);
        return {};
    }

    auto guard = make_scope_guard([window] {
        g_windows.remove(window);
        delete window;
    });

    window->pixels = (u32*)TRY_OR_IPC_ERROR(
        os::SharedMemory::create(shm_path.view(), window->surface.height * window->surface.width * 4));

    TRY_OR_IPC_ERROR(client.windows.try_append(window));
    int id = static_cast<int>(client.windows.size() - 1);

    // No more fallible operations, this operation is guaranteed to succeed now.
    guard.deactivate();

    window->client = &client;
    window->id = id;

    ui::CreateWindowResponse response;
    response.window = id;
    SET_IPC_STRING(response.shm_path, shm_path.chars());
    window->shm_path = move(shm_path);
    client.conn->send_async(response);
    return {};
}

static Result<void> handle_remove_shm_message(Client& client)
{
    ui::RemoveSharedMemoryRequest request;
    if (!TRY(client.conn->read_message(request))) return {};

    CHECK_WINDOW_ID(request);

    shm_unlink(client.windows[request.window]->shm_path.chars());

    return {};
}

static Result<void> handle_set_window_title_message(Client& client)
{
    ui::SetWindowTitleRequest request;
    if (!TRY(client.conn->read_message(request))) return {};

    auto name = COPY_IPC_STRING(request.title);

    os::println("wind: SetWindowTitle(\"%s\") for window %d", name.chars(), request.window);

    CHECK_WINDOW_ID(request);

    client.windows[request.window]->name = move(name);

    return {};
}

static Result<void> handle_invalidate_message(Client& client)
{
    ui::InvalidateRequest request;
    if (!TRY(client.conn->read_message(request))) return {};

    CHECK_WINDOW_ID(request);

    client.windows[request.window]->dirty = true;

    return {};
}

static Result<void> handle_close_window_message(Client& client)
{
    ui::CloseWindowRequest request;
    if (!TRY(client.conn->read_message(request))) return {};

    CHECK_WINDOW_ID(request);

    auto* window = client.windows[request.window];
    client.windows[request.window] = nullptr;
    g_windows.remove(window);
    Mouse::the().window_did_close(window);
    delete window;

    return {};
}

static Result<void> handle_get_screen_rect_message(Client& client)
{
    ui::GetScreenRectRequest request;
    if (!TRY(client.conn->read_message(request))) return {}; // Kinda pointless, but required.

    ui::GetScreenRectResponse response;
    response.rect = Screen::the().canvas().rect();
    client.conn->send_async(response);

    return {};
}

static Result<void> handle_set_titlebar_height_message(Client& client)
{
    ui::SetTitlebarHeightRequest request;
    if (!TRY(client.conn->read_message(request))) return {};

    if (request.height < 0) request.height = 0;

    CHECK_WINDOW_ID(request);

    auto* window = client.windows[request.window];

    if (request.height > window->surface.height)
    {
        os::eprintln("wind: SetTitlebarHeight: titlebar height bigger than window!");
        return {};
    }

    window->titlebar = ui::Rect { 0, 0, window->surface.width, request.height };
    return {};
}

static Result<void> handle_set_special_window_attributes_message(Client& client)
{
    ui::SetSpecialWindowAttributesRequest request;
    if (!TRY(client.conn->read_message(request))) return {};

    if (!client.privileged)
    {
        os::eprintln(
            "wind: Unprivileged client trying to call privileged request (SetSpecialWindowAttributes), disconnecting!");
        client.should_be_disconnected = true;
        return {};
    }

    CHECK_WINDOW_ID(request);

    client.windows[request.window]->attributes = request.attributes;

    return {};
}

namespace wind
{
    void handle_ipc_message(os::IPC::ClientConnection&, u8 id, void* c)
    {
        Client& client = *(Client*)c;
        switch (id)
        {
        case ui::CREATE_WINDOW_ID: handle_create_window_message(client); break;
        case ui::REMOVE_SHM_ID: handle_remove_shm_message(client); break;
        case ui::SET_WINDOW_TITLE_ID: handle_set_window_title_message(client); break;
        case ui::INVALIDATE_ID: handle_invalidate_message(client); break;
        case ui::CLOSE_WINDOW_ID: handle_close_window_message(client); break;
        case ui::GET_SCREEN_RECT_ID: handle_get_screen_rect_message(client); break;
        case ui::SET_TITLEBAR_HEIGHT_ID: handle_set_titlebar_height_message(client); break;
        case ui::SET_SPECIAL_WINDOW_ATTRIBUTES_ID: handle_set_special_window_attributes_message(client); break;
        default: os::eprintln("wind: Invalid IPC message from client!"); return;
        }
    }
}