#define _POSIX_C_SOURCE 200112L
#include "wl.h"

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <linux/input-event-codes.h>
#include <string.h>

#include "protocols/xdg-shell-client-header.h"
#include "protocols/xdg-shell.h"
#include "protocols/wlr-layer-shell-unstable-v1-client-header.h"
#include "protocols/wlr-layer-shell-unstable-v1.h"
#include "protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h"
#include "protocols/wlr-foreign-toplevel-management-unstable-v1.h"
#include "protocols/idle-client-header.h"
#include "protocols/idle.h"
#include "pool-buffer.h"


#include "../log.h"
#include "../settings.h"
#include "../queues.h"
#include "../input.h"
#include "libgwater-wayland.h"

#define MAX_TOUCHPOINTS 10

struct window_wl {
        cairo_surface_t *c_surface;
        cairo_t * c_ctx;

        GWaterWaylandSource *esrc;
};

struct wl_ctx {
        struct wl_display *display;
        struct wl_registry *registry;
        struct wl_compositor *compositor;
        struct wl_shm *shm;
        struct zwlr_layer_shell_v1 *layer_shell;
        struct wl_seat *seat;

        struct wl_list outputs;

        struct wl_surface *surface;
        struct dunst_output *surface_output;
        struct zwlr_layer_surface_v1 *layer_surface;
        struct dunst_output *layer_surface_output;
        struct wl_callback *frame_callback;
        struct org_kde_kwin_idle *idle_handler;
        struct org_kde_kwin_idle_timeout *idle_timeout;
        struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager;
        bool configured;
        bool dirty;
        bool is_idle;
        bool has_idle_monitor;

        struct {
                struct wl_pointer *wl_pointer;
                int32_t x, y;
        } pointer;

	struct {
		struct wl_touch *wl_touch;
		struct {
			int32_t x, y;
		} pts[MAX_TOUCHPOINTS];
	} touch;

        struct dimensions cur_dim;

        int32_t width, height;
        struct pool_buffer buffers[2];
        struct pool_buffer *current_buffer;
};

struct dunst_output {
        uint32_t global_name;
        char *name;
        struct wl_output *wl_output;
        struct wl_list link;

        uint32_t scale;
        uint32_t subpixel; // TODO do something with it
        bool fullscreen;
        struct zwlr_foreign_toplevel_handle_v1 *fullscreen_toplevel; // the toplevel that is fullscreened on this output
};

struct wl_ctx ctx;

static void noop() {
        // This space intentionally left blank
}

void set_dirty();

static void output_handle_geometry(void *data, struct wl_output *wl_output,
                int32_t x, int32_t y, int32_t phy_width, int32_t phy_height,
                int32_t subpixel, const char *make, const char *model,
                int32_t transform) {
        //TODO
        struct dunst_output *output = data;
        output->subpixel = subpixel;
}

static void output_handle_scale(void *data, struct wl_output *wl_output,
                int32_t factor) {
        struct dunst_output *output = data;
        output->scale = factor;
}

static const struct wl_output_listener output_listener = {
        .geometry = output_handle_geometry,
        .mode = noop,
        .done = noop,
        .scale = output_handle_scale,
};

static void create_output( struct wl_output *wl_output, uint32_t global_name) {
        struct dunst_output *output = g_malloc0(sizeof(struct dunst_output));
        if (output == NULL) {
                LOG_E("allocation failed");
                return;
        }
        static int number = 0;
        LOG_I("New output found - id %i", number);
        output->global_name = global_name;
        output->wl_output = wl_output;
        // TODO: Fix this
        output->scale = 1;
        output->fullscreen = false;
        wl_list_insert(&ctx.outputs, &output->link);

        wl_output_set_user_data(wl_output, output);
        wl_output_add_listener(wl_output, &output_listener, output);
        number++;
}

static void destroy_output(struct dunst_output *output) {
        if (ctx.surface_output == output) {
                ctx.surface_output = NULL;
        }
        if (ctx.layer_surface_output == output) {
                ctx.layer_surface_output = NULL;
        }
        wl_list_remove(&output->link);
        wl_output_destroy(output->wl_output);
        free(output->name);
        free(output);
}

static void touch_handle_motion(void *data, struct wl_touch *wl_touch,
                uint32_t time, int32_t id,
                wl_fixed_t surface_x, wl_fixed_t surface_y) {
        if (id >= MAX_TOUCHPOINTS) {
                return;
        }
        ctx.touch.pts[id].x = wl_fixed_to_int(surface_x);
        ctx.touch.pts[id].y = wl_fixed_to_int(surface_y);
}

static void touch_handle_down(void *data, struct wl_touch *wl_touch,
                uint32_t serial, uint32_t time, struct wl_surface *sfc, int32_t id,
                wl_fixed_t surface_x, wl_fixed_t surface_y) {
        if (id >= MAX_TOUCHPOINTS) {
                return;
        }
        ctx.touch.pts[id].x = wl_fixed_to_int(surface_x);
        ctx.touch.pts[id].y = wl_fixed_to_int(surface_y);
}

static void touch_handle_up(void *data, struct wl_touch *wl_touch,
                uint32_t serial, uint32_t time, int32_t id) {
        if (id >= MAX_TOUCHPOINTS) {
                return;
        }
        input_handle_click(BTN_TOUCH, false,
                        ctx.touch.pts[id].x, ctx.touch.pts[id].y);

}

static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer,
                uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
        ctx.pointer.x = wl_fixed_to_int(surface_x);
        ctx.pointer.y = wl_fixed_to_int(surface_y);
}

static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer,
                uint32_t serial, uint32_t time, uint32_t button,
                uint32_t button_state) {
        input_handle_click(button, button_state, ctx.pointer.x, ctx.pointer.y);
}

static const struct wl_pointer_listener pointer_listener = {
        .enter = noop,
        .leave = noop,
        .motion = pointer_handle_motion,
        .button = pointer_handle_button,
        .axis = noop,
};

static const struct wl_touch_listener touch_listener = {
        .down = touch_handle_down,
        .up = touch_handle_up,
        .motion = touch_handle_motion,
        .frame = noop,
        .cancel = noop,
};

static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
                uint32_t capabilities) {

        if (ctx.pointer.wl_pointer != NULL) {
                wl_pointer_release(ctx.pointer.wl_pointer);
                ctx.pointer.wl_pointer = NULL;
        }
        if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
                ctx.pointer.wl_pointer = wl_seat_get_pointer(wl_seat);
                wl_pointer_add_listener(ctx.pointer.wl_pointer,
                        &pointer_listener, ctx.seat);
                LOG_I("Adding pointer");
        }
        if (ctx.touch.wl_touch != NULL) {
                wl_touch_release(ctx.touch.wl_touch);
                ctx.touch.wl_touch = NULL;
        }
        if (capabilities & WL_SEAT_CAPABILITY_TOUCH) {
                ctx.touch.wl_touch = wl_seat_get_touch(wl_seat);
                wl_touch_add_listener(ctx.touch.wl_touch,
                        &touch_listener, ctx.seat);
        }
}

static const struct wl_seat_listener seat_listener = {
        .capabilities = seat_handle_capabilities,
        .name = noop,
};


static void surface_handle_enter(void *data, struct wl_surface *surface,
                struct wl_output *wl_output) {
        // Don't bother keeping a list of outputs, a layer surface can only be on
        // one output a a time
        ctx.surface_output = wl_output_get_user_data(wl_output);
        set_dirty();
}

static void surface_handle_leave(void *data, struct wl_surface *surface,
                struct wl_output *wl_output) {
        ctx.surface_output = NULL;
}

static const struct wl_surface_listener surface_listener = {
        .enter = surface_handle_enter,
        .leave = surface_handle_leave,
};


static void schedule_frame_and_commit();
static void send_frame();

static void layer_surface_handle_configure(void *data,
                struct zwlr_layer_surface_v1 *surface,
                uint32_t serial, uint32_t width, uint32_t height) {
        ctx.configured = true;
        ctx.width = width;
        ctx.height = height;

        // not needed as it is set somewhere else
        /* zwlr_layer_surface_v1_set_size(surface, width, height);  */
        zwlr_layer_surface_v1_ack_configure(surface, serial);
        send_frame();
}

static void layer_surface_handle_closed(void *data,
                struct zwlr_layer_surface_v1 *surface) {
        LOG_I("Destroying layer");
        zwlr_layer_surface_v1_destroy(ctx.layer_surface);
        ctx.layer_surface = NULL;

        wl_surface_destroy(ctx.surface);
        ctx.surface = NULL;

        if (ctx.frame_callback) {
                wl_callback_destroy(ctx.frame_callback);
                ctx.frame_callback = NULL;
                ctx.dirty = true;
        }

        if (ctx.configured) {
                ctx.configured = false;
                ctx.width = ctx.height = 0;
                ctx.dirty = true;
        }

        if (ctx.dirty) {
                schedule_frame_and_commit();
        }
}

static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
        .configure = layer_surface_handle_configure,
        .closed = layer_surface_handle_closed,
};


static void idle_start (void *data, struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) {
        ctx.is_idle = true;
        LOG_D("User went idle");
}
static void idle_stop (void *data, struct org_kde_kwin_idle_timeout *org_kde_kwin_idle_timeout) {
        ctx.is_idle = false;
        LOG_D("User isn't idle anymore");
}

static const struct org_kde_kwin_idle_timeout_listener idle_timeout_listener = {
        .idle = idle_start,
        .resumed = idle_stop,
};

static void add_seat_to_idle_handler(struct wl_seat *seat) {
        if (!ctx.idle_handler){
                return;
        }
        if (settings.idle_threshold > 0) {
                uint32_t timeout_ms = settings.idle_threshold/1000;
                ctx.idle_timeout = org_kde_kwin_idle_get_idle_timeout(ctx.idle_handler, seat, timeout_ms);
                org_kde_kwin_idle_timeout_add_listener(ctx.idle_timeout, &idle_timeout_listener, 0);
                ctx.has_idle_monitor = true;
        }
}

// Warning, can return NULL
static struct dunst_output *get_configured_output() {

        switch (settings.f_mode){
                case FOLLOW_NONE: ; // this semicolon is neccesary
                        int n = 0;
                        int target_monitor = settings.monitor;

                        struct dunst_output *output;
                        wl_list_for_each(output, &ctx.outputs, link) {
                                if (n == target_monitor)
                                        return output;
                                n++;
                        }
                        LOG_W("Monitor %i doesn't exist, using focused monitor", settings.monitor);
                        return NULL;
                case FOLLOW_MOUSE:
                        // fallthrough
                case FOLLOW_KEYBOARD:
                        // fallthrough
                default:
                        return NULL;
        }
}

// does not do null checking
static void dunst_output_set_fullscreen(struct dunst_output *output,
                struct zwlr_foreign_toplevel_handle_v1 *toplevel,
                bool fullscreen) {
        output->fullscreen = fullscreen;
        if (fullscreen)
                output->fullscreen_toplevel = toplevel;
        else
                output->fullscreen_toplevel = NULL;

        LOG_D("Set output %i fullscreen state %i", output->global_name, fullscreen);
        wake_up();
}

static void toplevel_output_leave(void *data,
                struct zwlr_foreign_toplevel_handle_v1 *toplevel,
                struct wl_output *output) {
        zwlr_foreign_toplevel_handle_v1_set_user_data(toplevel, NULL);
}

static void toplevel_output_enter(void *data,
                struct zwlr_foreign_toplevel_handle_v1 *toplevel,
                struct wl_output *output) {
        // FIXME toplevel can be on multiple outputs, so a list of outputs should be kept
        zwlr_foreign_toplevel_handle_v1_set_user_data(toplevel, output);
}

static void toplevel_closed(void *data,
                struct zwlr_foreign_toplevel_handle_v1 *toplevel) {
        struct wl_output *output_wl = (struct wl_output*) data;

        if (output_wl == NULL) {
                return;
        }
        struct dunst_output *output = (struct dunst_output*) wl_output_get_user_data(output_wl);

        if (output == NULL) {
                return;
        }
        dunst_output_set_fullscreen(output, toplevel, false);
}

static void toplevel_state(void *data,
                struct zwlr_foreign_toplevel_handle_v1 *toplevel,
                struct wl_array *state) {
        struct wl_output *output_wl = (struct wl_output*) data;
        if (output_wl == NULL) {
                return;
        }
        struct dunst_output *output = (struct dunst_output*) wl_output_get_user_data(output_wl);
        if (output == NULL) {
                return;
        }

        bool fullscreen = false;
        bool activated = false;
        enum zwlr_foreign_toplevel_handle_v1_state* element;
        wl_array_for_each(element, state){
                if (*element == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) {
                        fullscreen = true;
                }
                if (*element == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) {
                        activated = true;
                }
        }
        if (fullscreen && activated) {
                dunst_output_set_fullscreen(output, toplevel, true);

        } else {
                if (output->fullscreen_toplevel == toplevel) {
                        // this toplevel was fullscreen, but isn't anymore
                        dunst_output_set_fullscreen(output, toplevel, false);
                }
        }
}

static const struct zwlr_foreign_toplevel_handle_v1_listener foreign_toplevel_handle_listener = {
        .title = noop,
        .app_id = noop,
        .output_enter = toplevel_output_enter,
        .output_leave = toplevel_output_leave,
        .state = toplevel_state,
        .done = noop,
        .closed = toplevel_closed,
};

static void toplevel_created(void *data,
                struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1,
                struct zwlr_foreign_toplevel_handle_v1 *toplevel){
        zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &foreign_toplevel_handle_listener, NULL);
}

static void toplevel_finished(void *data,
                struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1){
}

static const struct zwlr_foreign_toplevel_manager_v1_listener foreign_toplevel_manager_listener = {
        .toplevel = toplevel_created,
        .finished = toplevel_finished,
};

static void handle_global(void *data, struct wl_registry *registry,
                uint32_t name, const char *interface, uint32_t version) {
        int *count = data;
        if (*count == 0)
        {
                if (strcmp(interface, wl_compositor_interface.name) == 0) {
                        ctx.compositor = wl_registry_bind(registry, name,
                                        &wl_compositor_interface, 4);
                } else if (strcmp(interface, wl_shm_interface.name) == 0) {
                        ctx.shm = wl_registry_bind(registry, name,
                                        &wl_shm_interface, 1);
                } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
                        ctx.layer_shell = wl_registry_bind(registry, name,
                                        &zwlr_layer_shell_v1_interface, 1);
                } else if (strcmp(interface, wl_seat_interface.name) == 0) {
                        ctx.seat = wl_registry_bind(registry, name, &wl_seat_interface, 3);
                        wl_seat_add_listener(ctx.seat, &seat_listener, ctx.seat);
                        add_seat_to_idle_handler(ctx.seat);
                } else if (strcmp(interface, wl_output_interface.name) == 0) {
                        struct wl_output *output =
                                wl_registry_bind(registry, name, &wl_output_interface, 3);
                        create_output(output, name);
                } else if (strcmp(interface, org_kde_kwin_idle_interface.name) == 0 &&
                                version >= ORG_KDE_KWIN_IDLE_TIMEOUT_IDLE_SINCE_VERSION) {
                        ctx.idle_handler = wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1);
                }
        } else {
                if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0 &&
                                version >= ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN_SINCE_VERSION) {
                        // Only bind after the second pass to bind after binding to all the outputs.
                        // This is because otherwise toplevel_enter evens won't be sent.
                        ctx.toplevel_manager = wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, 2);
                        zwlr_foreign_toplevel_manager_v1_add_listener(ctx.toplevel_manager, &foreign_toplevel_manager_listener, NULL);
                }
        }
}

static void handle_global_remove(void *data, struct wl_registry *registry,
                uint32_t name) {
        struct dunst_output *output, *tmp;
        wl_list_for_each_safe(output, tmp, &ctx.outputs, link) {
                if (output->global_name == name) {
                        destroy_output(output);
                        break;
                }
        }
}

static const struct wl_registry_listener registry_listener = {
        .global = handle_global,
        .global_remove = handle_global_remove,
};

bool wl_init() {
        wl_list_init(&ctx.outputs);
        //wl_list_init(&ctx.seats); // TODO multi-seat support

        ctx.display = wl_display_connect(NULL);

        if (ctx.display == NULL) {
                LOG_W("failed to create display");
                return false;
        }

        int count = 0;
        ctx.registry = wl_display_get_registry(ctx.display);
        wl_registry_add_listener(ctx.registry, &registry_listener, &count);
        wl_display_roundtrip(ctx.display);

        count = 1;
        // we need a second pass to let for foreign_toplevel (look there for more info)
        ctx.registry = wl_display_get_registry(ctx.display);
        wl_registry_add_listener(ctx.registry, &registry_listener, &count);
        wl_display_roundtrip(ctx.display);

        if (ctx.compositor == NULL) {
                LOG_W("compositor doesn't support wl_compositor");
                return false;
        }
        if (ctx.shm == NULL) {
                LOG_W("compositor doesn't support wl_shm");
                return false;
        }
        if (ctx.layer_shell == NULL) {
                LOG_W("compositor doesn't support zwlr_layer_shell_v1");
                return false;
        }
        if (ctx.seat == NULL) {
                LOG_W("no seat was found, so dunst cannot see input");
        } else {
                if (ctx.idle_handler == NULL) {
                        LOG_I("compositor doesn't support org_kde_kwin_idle_interface");
                }
                else if (ctx.idle_timeout == NULL) {
                        // something went wrong in setting the timeout
                        LOG_W("couldn't set idle timeout");
                }
        }

        if (ctx.toplevel_manager == NULL) {
                LOG_W("compositor doesn't support zwlr_foreign_toplevel_v1. Fullscreen detection won't work");
        }

        return true;
}

void wl_deinit() {
        // We need to check if any of these are NULL, since the initialization
        // could have been aborted half way through, or the compositor doesn't
        // support some of these features.
        if (ctx.layer_surface != NULL) {
                zwlr_layer_surface_v1_destroy(ctx.layer_surface);
        }
        if (ctx.surface != NULL) {
                wl_surface_destroy(ctx.surface);
        }
        finish_buffer(&ctx.buffers[0]);
        finish_buffer(&ctx.buffers[1]);

        // The output list is initialized at the start of init, so no need to
        // check for NULL
        struct dunst_output *output, *output_tmp;
        wl_list_for_each_safe(output, output_tmp, &ctx.outputs, link) {
                destroy_output(output);
        }

        if (ctx.seat) {
                if (ctx.pointer.wl_pointer)
                        wl_pointer_release(ctx.pointer.wl_pointer);
                wl_seat_release(ctx.seat);
                ctx.seat = NULL;
        }

        if (ctx.idle_handler)
                org_kde_kwin_idle_destroy(ctx.idle_handler);

        if (ctx.idle_timeout)
                org_kde_kwin_idle_timeout_release(ctx.idle_timeout);

        if (ctx.layer_shell)
                zwlr_layer_shell_v1_destroy(ctx.layer_shell);

        if (ctx.compositor)
                wl_compositor_destroy(ctx.compositor);

        if (ctx.shm)
                wl_shm_destroy(ctx.shm);

        if (ctx.registry)
                wl_registry_destroy(ctx.registry);

        if (ctx.display)
                wl_display_disconnect(ctx.display);
}

static void schedule_frame_and_commit();

// Draw and commit a new frame.
static void send_frame() {
        int scale = 1;

        struct dunst_output *output = get_configured_output();
        int height = ctx.cur_dim.h;
        int width = ctx.cur_dim.w;

        // There are two cases where we want to tear down the surface: zero
        // notifications (height = 0) or moving between outputs.
        if (height == 0 || ctx.layer_surface_output != output) {
                if (ctx.layer_surface != NULL) {
                        zwlr_layer_surface_v1_destroy(ctx.layer_surface);
                        ctx.layer_surface = NULL;
                }
                if (ctx.surface != NULL) {
                        wl_surface_destroy(ctx.surface);
                        ctx.surface = NULL;
                }
                ctx.width = ctx.height = 0;
                ctx.surface_output = NULL;
                ctx.configured = false;
        }

        // If there are no notifications, there's no point in recreating the
        // surface right now.
        if (height == 0) {
                ctx.dirty = false;
                return;
        }

        // If we've made it here, there is something to draw. If the surface
        // doesn't exist (this is the first notification, or we moved to a
        // different output), we need to create it.
        if (ctx.layer_surface == NULL) {
                struct wl_output *wl_output = NULL;
                if (output != NULL) {
                        wl_output = output->wl_output;
                }
                ctx.layer_surface_output = output;
                ctx.surface = wl_compositor_create_surface(ctx.compositor);
                wl_surface_add_listener(ctx.surface, &surface_listener, NULL);

                ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(
                        ctx.layer_shell, ctx.surface, wl_output,
                        settings.layer, "notifications");
                zwlr_layer_surface_v1_add_listener(ctx.layer_surface,
                        &layer_surface_listener, NULL);

                // Because we're creating a new surface, we aren't going to draw
                // anything into it during this call. We don't know what size the
                // surface will be until we've asked the compositor for what we want
                // and it has responded with what it actually gave us. We also know
                // that the height we would _like_ to draw (greater than zero, or we
                // would have bailed already) is different from our ctx.height
                // (which has to be zero here), so we can fall through to the next
                // block to let it set the size for us.
        }

        assert(ctx.layer_surface);

        // We now want to resize the surface if it isn't the right size. If the
        // surface is brand new, it doesn't even have a size yet. If it already
        // exists, we might need to resize if the list of notifications has changed
        // since the last time we drew.
        if (ctx.height != height || ctx.width != width) {
                struct dimensions dim = ctx.cur_dim;
                // Set window size
                LOG_D("Window dimensions %ix%i", dim.w, dim.h);
                LOG_D("Window position %ix%i", dim.x, dim.y);
                zwlr_layer_surface_v1_set_size(ctx.layer_surface,
                                dim.w, dim.h);

                // TODO Do this only once
                uint32_t anchor = 0;
                if (settings.geometry.negative_x) {
                        anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
                } else {
                        anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
                }

                if (settings.geometry.negative_y) {
                        anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
                } else {
                        anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
                }

                // Put the window at the right position
                zwlr_layer_surface_v1_set_anchor(ctx.layer_surface,
                        anchor);
                zwlr_layer_surface_v1_set_margin(ctx.layer_surface,
                                abs(settings.geometry.y), // top
                                abs(settings.geometry.x), // right
                                abs(settings.geometry.y), // bottom
                                abs(settings.geometry.x));// left

                wl_surface_commit(ctx.surface);

                // Now we're going to bail without drawing anything. This gives the
                // compositor a chance to create the surface and tell us what size we
                // were actually granted, which may be smaller than what we asked for
                // depending on the screen size and layout of other layer surfaces.
                // This information is provided in layer_surface_handle_configure,
                // which will then call send_frame again. When that call happens, the
                // layer surface will exist and the height will hopefully match what
                // we asked for. That means we won't return here, and will actually
                // draw into the surface down below.
                // TODO: If the compositor doesn't send a configure with the size we
                // requested, we'll enter an infinite loop. We need to keep track of
                // the fact that a request was sent separately from what height we are.
                wl_display_roundtrip(ctx.display);
                return;
        }

        assert(ctx.configured);

        // Yay we can finally draw something!
        wl_surface_set_buffer_scale(ctx.surface, scale);
        wl_surface_damage_buffer(ctx.surface, 0, 0, INT32_MAX, INT32_MAX);
        wl_surface_attach(ctx.surface, ctx.current_buffer->buffer, 0, 0);
        ctx.current_buffer->busy = true;

        // Schedule a frame in case the state becomes dirty again
        schedule_frame_and_commit();

        ctx.dirty = false;
}

static void frame_handle_done(void *data, struct wl_callback *callback,
                uint32_t time) {
        wl_callback_destroy(ctx.frame_callback);
        ctx.frame_callback = NULL;

        // Only draw again if we need to
        if (ctx.dirty) {
                send_frame();
        }
}

static const struct wl_callback_listener frame_listener = {
        .done = frame_handle_done,
};

static void schedule_frame_and_commit() {
        if (ctx.frame_callback) {
                return;
        }
        if (ctx.surface == NULL) {
                // We don't yet have a surface, create it immediately
                send_frame();
                return;
        }
        ctx.frame_callback = wl_surface_frame(ctx.surface);
        wl_callback_add_listener(ctx.frame_callback, &frame_listener, NULL);
        wl_surface_commit(ctx.surface);
}

void set_dirty() {
        if (ctx.dirty) {
                return;
        }
        ctx.dirty = true;
        schedule_frame_and_commit();
}

window wl_win_create(void) {
        struct window_wl *win = g_malloc0(sizeof(struct window_wl));

        win->esrc = g_water_wayland_source_new_for_display(NULL, ctx.display);
        return win;
}

void wl_win_destroy(window winptr) {
        struct window_wl *win = (struct window_wl*)winptr;

        g_water_wayland_source_free(win->esrc);
        // FIXME: Dealloc everything
        g_free(win);
}

void wl_win_show(window win) {
        // This is here for compatibilty with the X11 output. The window is
        // already shown in wl_display_surface.
}

void wl_win_hide(window win) {
        LOG_I("Wayland: Hiding window");
        ctx.cur_dim.h = 0;
        set_dirty();
        wl_display_roundtrip(ctx.display);
}

void wl_display_surface(cairo_surface_t *srf, window winptr, const struct dimensions* dim) {
        /* struct window_wl *win = (struct window_wl*)winptr; */
        ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers, dim->w, dim->h);

        cairo_t *c = ctx.current_buffer->cairo;
        cairo_save(c);
        cairo_set_source_surface(c, srf, 0, 0);
        cairo_rectangle(c, 0, 0, dim->w, dim->h);
        cairo_fill(c);
        cairo_restore(c);

        ctx.cur_dim = *dim;

        set_dirty();
        wl_display_roundtrip(ctx.display);
}

cairo_t* wl_win_get_context(window winptr) {
        struct window_wl *win = (struct window_wl*)winptr;
        ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers, 500, 500);
        win->c_surface = ctx.current_buffer->surface;
        win->c_ctx = ctx.current_buffer->cairo;
        return win->c_ctx;
}

const struct screen_info* wl_get_active_screen(void) {
        // TODO Screen size detection
        static struct screen_info scr = {
                .w = 1920,
                .h = 1080,
                .x = 0,
                .y = 0,
                .id = 0,
                .mmh = 500
        };
        return &scr;
}

bool wl_is_idle(void) {
        LOG_I("Idle status queried: %i", ctx.is_idle);
        // When the user doesn't have a seat, or their compositor doesn't support the idle
        // protocol, we'll assume that they are not idle.
        if (settings.idle_threshold == 0 || ctx.has_idle_monitor == false) {
                return false;
        } else {
                return ctx.is_idle;
        }
}

bool wl_have_fullscreen_window(void) {
        bool have_fullscreen = false;

        struct dunst_output *current_output = get_configured_output();

        if (!current_output) {
                // Cannot detect focused output, so return true if any of the
                // outputs is fullscreen. This will work even when unfocused
                // outputs have fullscreen toplevels, since a toplevel has to
                // be fullscreen and activate to consider an output fullscreen.
                struct dunst_output *output;
                wl_list_for_each(output, &ctx.outputs, link) {
                        have_fullscreen |= output->fullscreen;
                }
        } else {
                have_fullscreen = current_output->fullscreen;
        }

        LOG_D("Fullscreen queried: %i", have_fullscreen);
        return have_fullscreen;
}
/* vim: set ft=c tabstop=8 shiftwidth=8 expandtab textwidth=0: */
