#include "mock-server.h"

struct wl_display* display = NULL;

void* alloc_zeroed(size_t size) {
    void* data = malloc(size);
    memset(data, 0, size);
    return data;
}

static const char* get_display_name() {
    const char* result = getenv("WAYLAND_DISPLAY");
    if (!result) {
        FATAL("WAYLAND_DISPLAY not set");
    }
    return result;
}

typedef struct {
    const struct wl_message* message;
    RequestOverrideFunction function;
    struct wl_list link;
} RequestOverride;

struct wl_list request_overrides;

void install_request_override(
    const struct wl_interface* interface,
    const char* name,
    RequestOverrideFunction function
) {
    for (int i = 0; i < interface->method_count; i++) {
        if (strcmp(name, interface->methods[i].name) == 0) {
            RequestOverride* override = ALLOC_STRUCT(RequestOverride);
            override->message = &interface->methods[i];
            override->function = function;
            wl_list_insert(&request_overrides, &override->link);
            return;
        }
    }
    FATAL_FMT("Interface %s does not have a request named %s", interface->name, name);
}

static int default_dispatcher(
    const void* data,
    void* resource,
    uint32_t opcode,
    const struct wl_message* message,
    union wl_argument* args
) {
    // First, check if there is an override
    RequestOverride* override;
    wl_list_for_each(override, &request_overrides, link) {
        if (override->message == message) {
            override->function(resource, message, args);
            return 0;
        }
    }

    // If there are any new-id type arguments, resources need to be created for them
    // See https://wayland.freedesktop.org/docs/html/apb.html#Client-structwl__message
    int arg = 0;
    for (const char* c = message->signature; *c; c++) {
        if (*c == 'n' && args[arg].n != 0) {
            struct wl_resource* new_resource = wl_resource_create(
                wl_resource_get_client(resource),
                message->types[arg],
                wl_resource_get_version(resource),
                args[arg].n);
            wl_resource_set_dispatcher(new_resource, default_dispatcher, NULL, NULL, NULL);
        }
        if (*c >= 'a' && *c <= 'z')
            arg++;
    }
    if (strcmp(message->name, "destroy") == 0) {
        wl_resource_destroy(resource);
    }
    return 0;
}

void use_default_impl(struct wl_resource* resource) {
    wl_resource_set_dispatcher(resource, default_dispatcher, NULL, NULL, NULL);
}

static void default_global_bind(struct wl_client* client, void* data, uint32_t version, uint32_t id) {
    struct wl_interface* interface = data;
    struct wl_resource* resource = wl_resource_create(client, interface, version, id);
    use_default_impl(resource);
};

void default_global_create(struct wl_display* display, const struct wl_interface* interface, int version) {
    wl_global_create(display, interface, version, (void*)interface, default_global_bind);
}

char type_code_at_index(const struct wl_message* message, int index) {
    int i = 0;
    for (const char* c = message->signature; *c; c++) {
        if (*c >= 'a' && *c <= 'z') {
            if (i == index)
                return *c;
            else
                i++;
        }
    }
    FATAL_FMT(".%s does not have an argument %d", message->name, index);
}

static void client_disconnect(struct wl_listener *listener, void *data) {
    wl_display_terminate(display);
}

static struct wl_listener client_disconnect_listener = {
    .notify = client_disconnect,
};

static void client_connect(struct wl_listener *listener, void *data) {
    struct wl_client* client = (struct wl_client*)data;
    wl_client_add_destroy_listener(client, &client_disconnect_listener);
}

static struct wl_listener client_connect_listener = {
    .notify = client_connect,
};

int main(int argc, const char** argv) {
    wl_list_init(&request_overrides);

    display = wl_display_create();
    if (wl_display_add_socket(display, get_display_name()) != 0) {
        FATAL_FMT("server failed to connect to Wayland display %s", get_display_name());
    }

    wl_display_add_client_created_listener(display, &client_connect_listener);

    init();

    wl_display_run(display);
    wl_display_destroy(display);

    return 0;
}
