/*
 * Copyright © 2011-2014 Intel Corporation
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of the
 * copyright holders not be used in advertising or publicity
 * pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

#include <xwayland-config.h>

#include <randrstr.h>
#include <X11/Xatom.h>

#include "xwayland-cvt.h"
#include "xwayland-output.h"
#include "xwayland-screen.h"
#include "xwayland-window.h"

#include "xdg-output-unstable-v1-client-protocol.h"

#define MAX_OUTPUT_NAME 256

static void xwl_output_get_xdg_output(struct xwl_output *xwl_output);

static Rotation
wl_transform_to_xrandr(enum wl_output_transform transform)
{
    switch (transform) {
    default:
    case WL_OUTPUT_TRANSFORM_NORMAL:
        return RR_Rotate_0;
    case WL_OUTPUT_TRANSFORM_90:
        return RR_Rotate_90;
    case WL_OUTPUT_TRANSFORM_180:
        return RR_Rotate_180;
    case WL_OUTPUT_TRANSFORM_270:
        return RR_Rotate_270;
    case WL_OUTPUT_TRANSFORM_FLIPPED:
        return RR_Reflect_X | RR_Rotate_0;
    case WL_OUTPUT_TRANSFORM_FLIPPED_90:
        return RR_Reflect_X | RR_Rotate_90;
    case WL_OUTPUT_TRANSFORM_FLIPPED_180:
        return RR_Reflect_X | RR_Rotate_180;
    case WL_OUTPUT_TRANSFORM_FLIPPED_270:
        return RR_Reflect_X | RR_Rotate_270;
    }
}

static int
wl_subpixel_to_xrandr(int subpixel)
{
    switch (subpixel) {
    default:
    case WL_OUTPUT_SUBPIXEL_UNKNOWN:
        return SubPixelUnknown;
    case WL_OUTPUT_SUBPIXEL_NONE:
        return SubPixelNone;
    case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB:
        return SubPixelHorizontalRGB;
    case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR:
        return SubPixelHorizontalBGR;
    case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB:
        return SubPixelVerticalRGB;
    case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR:
        return SubPixelVerticalBGR;
    }
}

static void
output_handle_geometry(void *data, struct wl_output *wl_output, int x, int y,
                       int physical_width, int physical_height, int subpixel,
                       const char *make, const char *model, int transform)
{
    struct xwl_output *xwl_output = data;

    if (xwl_output->randr_output) {
        RROutputSetPhysicalSize(xwl_output->randr_output,
                                physical_width, physical_height);
        RROutputSetSubpixelOrder(xwl_output->randr_output,
                                 wl_subpixel_to_xrandr(subpixel));
    }

    /* Apply the change from wl_output only if xdg-output is not supported */
    if (!xwl_output->xdg_output) {
        xwl_output->x = x;
        xwl_output->y = y;
    }
    xwl_output->rotation = wl_transform_to_xrandr(transform);
}

static void
output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
                   int width, int height, int refresh)
{
    struct xwl_output *xwl_output = data;

    if (!(flags & WL_OUTPUT_MODE_CURRENT))
        return;

    /* Apply the change from wl_output only if xdg-output is not supported */
    if (!xwl_output->xdg_output) {
        xwl_output->width = width;
        xwl_output->height = height;
    }
    xwl_output->refresh = refresh;
}

/**
 * Decides on the maximum expanse of an output in logical space (i.e. in the
 * Wayland compositor plane) respective to some fix width and height values. The
 * function sets the provided values to these maxima on return.
 */
static inline void
output_get_new_size(struct xwl_output *xwl_output, int *width, int *height)
{
    int output_width, output_height;

    /* When we have xdg-output support the stored size is already rotated. */
    if (xwl_output->xdg_output
            || (xwl_output->rotation & (RR_Rotate_0 | RR_Rotate_180))) {
        output_width = xwl_output->width;
        output_height = xwl_output->height;
    } else {
        output_width = xwl_output->height;
        output_height = xwl_output->width;
    }

    if (*width < xwl_output->x + output_width)
        *width = xwl_output->x + output_width;

    if (*height < xwl_output->y + output_height)
        *height = xwl_output->y + output_height;
}

static int
xwl_set_pixmap_visit_window(WindowPtr window, void *data)
{
    ScreenPtr screen = window->drawable.pScreen;

    if (screen->GetWindowPixmap(window) == data) {
        screen->SetWindowPixmap(window, screen->GetScreenPixmap(screen));
        return WT_WALKCHILDREN;
    }

    return WT_DONTWALKCHILDREN;
}

static void
update_backing_pixmaps(struct xwl_screen *xwl_screen, int width, int height)
{
    ScreenPtr pScreen = xwl_screen->screen;
    WindowPtr pRoot = pScreen->root;
    PixmapPtr old_pixmap, new_pixmap;

    old_pixmap = pScreen->GetScreenPixmap(pScreen);
    new_pixmap = pScreen->CreatePixmap(pScreen, width, height,
                                       pScreen->rootDepth,
                                       CREATE_PIXMAP_USAGE_BACKING_PIXMAP);
    pScreen->SetScreenPixmap(new_pixmap);

    if (old_pixmap) {
        TraverseTree(pRoot, xwl_set_pixmap_visit_window, old_pixmap);
        pScreen->DestroyPixmap(old_pixmap);
    }

    pScreen->ResizeWindow(pRoot, 0, 0, width, height, NULL);
}

static void
update_screen_size(struct xwl_screen *xwl_screen, int width, int height)
{
    xwl_screen->width = width;
    xwl_screen->height = height;

    if (xwl_screen->root_clip_mode == ROOT_CLIP_FULL)
        SetRootClip(xwl_screen->screen, ROOT_CLIP_NONE);

    if (!xwl_screen->rootless && xwl_screen->screen->root)
        update_backing_pixmaps (xwl_screen, width, height);

    xwl_screen->screen->width = width;
    xwl_screen->screen->height = height;
    xwl_screen->screen->mmWidth = (width * 25.4) / monitorResolution;
    xwl_screen->screen->mmHeight = (height * 25.4) / monitorResolution;

    SetRootClip(xwl_screen->screen, xwl_screen->root_clip_mode);

    if (xwl_screen->screen->root) {
        BoxRec box = { 0, 0, width, height };

        xwl_screen->screen->root->drawable.width = width;
        xwl_screen->screen->root->drawable.height = height;
        RegionReset(&xwl_screen->screen->root->winSize, &box);
        RRScreenSizeNotify(xwl_screen->screen);
    }

    update_desktop_dimensions();

    RRTellChanged(xwl_screen->screen);
}

struct xwl_emulated_mode *
xwl_output_get_emulated_mode_for_client(struct xwl_output *xwl_output,
                                        ClientPtr client)
{
    struct xwl_client *xwl_client = xwl_client_get(client);
    int i;

    if (!xwl_output)
        return NULL;

    /* We don't do XRandr emulation when rootful or a fake lease display */
    if (!xwl_output->xwl_screen->rootless || !xwl_output->output)
        return NULL;

    for (i = 0; i < XWL_CLIENT_MAX_EMULATED_MODES; i++) {
        if (xwl_client->emulated_modes[i].server_output_id ==
            xwl_output->server_output_id)
            return &xwl_client->emulated_modes[i];
    }

    return NULL;
}

static void
xwl_output_add_emulated_mode_for_client(struct xwl_output *xwl_output,
                                        ClientPtr client,
                                        RRModePtr mode,
                                        Bool from_vidmode)
{
    struct xwl_client *xwl_client = xwl_client_get(client);
    struct xwl_emulated_mode *emulated_mode;
    int i;

    emulated_mode = xwl_output_get_emulated_mode_for_client(xwl_output, client);
    if (!emulated_mode) {
        /* Find a free spot in the emulated modes array */
        for (i = 0; i < XWL_CLIENT_MAX_EMULATED_MODES; i++) {
            if (xwl_client->emulated_modes[i].server_output_id == 0) {
                emulated_mode = &xwl_client->emulated_modes[i];
                break;
            }
        }
    }
    if (!emulated_mode) {
        static Bool warned;

        if (!warned) {
            ErrorF("Ran out of space for emulated-modes, not adding mode");
            warned = TRUE;
        }

        return;
    }

    emulated_mode->server_output_id = xwl_output->server_output_id;
    emulated_mode->width  = mode->mode.width;
    emulated_mode->height = mode->mode.height;
    emulated_mode->id = mode->mode.id;
    emulated_mode->from_vidmode = from_vidmode;
}

static void
xwl_output_remove_emulated_mode_for_client(struct xwl_output *xwl_output,
                                           ClientPtr client)
{
    struct xwl_emulated_mode *emulated_mode;

    emulated_mode = xwl_output_get_emulated_mode_for_client(xwl_output, client);
    if (emulated_mode) {
        DebugF("XWAYLAND: xwl_output_remove_emulated_mode: %dx%d\n",
               emulated_mode->width, emulated_mode->height);
        memset(emulated_mode, 0, sizeof(*emulated_mode));
    }
}

/* From hw/xfree86/common/xf86DefModeSet.c with some obscure modes dropped */
const int32_t xwl_output_fake_modes[][2] = {
    /* 4:3 (1.33) */
    { 2048, 1536 },
    { 1920, 1440 },
    { 1600, 1200 },
    { 1440, 1080 },
    { 1400, 1050 },
    { 1280, 1024 }, /* 5:4 (1.25) */
    { 1280,  960 },
    { 1152,  864 },
    { 1024,  768 },
    {  800,  600 },
    {  640,  480 },
    {  320,  240 },
    /* 16:10 (1.6) */
    { 2560, 1600 },
    { 1920, 1200 },
    { 1680, 1050 },
    { 1440,  900 },
    { 1280,  800 },
    { 1152,  720 },
    {  960,  600 },
    {  928,  580 },
    {  800,  500 },
    {  768,  480 },
    {  720,  480 }, /* 3:2 (1.5) */
    {  640,  400 },
    {  320,  200 },
    /* 16:9 (1.77) */
    { 5120, 2880 },
    { 4096, 2304 },
    { 3840, 2160 },
    { 3200, 1800 },
    { 2880, 1620 },
    { 2560, 1440 },
    { 2048, 1152 },
    { 1920, 1080 },
    { 1600,  900 },
    { 1368,  768 },
    { 1280,  720 },
    { 1024,  576 },
    {  864,  486 },
    {  720,  400 },
    {  640,  350 },
};

/* Build an array with RRModes the first mode is the actual output mode, the
 * rest are fake modes from the xwl_output_fake_modes list. We do this for apps
 * which want to change resolution when they go fullscreen.
 * When an app requests a mode-change, we fake it using WPviewport.
 */
static RRModePtr *
output_get_rr_modes(struct xwl_output *xwl_output,
                    int32_t width, int32_t height,
                    int *count)
{
    struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
    RRModePtr *rr_modes;
    int i;

    rr_modes = xallocarray(ARRAY_SIZE(xwl_output_fake_modes) + 1, sizeof(RRModePtr));
    if (!rr_modes)
        goto err;

    /* Add actual output mode */
    rr_modes[0] = xwayland_cvt(width, height, xwl_output->refresh / 1000.0, 0, 0);
    if (!rr_modes[0])
        goto err;

    *count = 1;

    if (!xwl_screen_has_resolution_change_emulation(xwl_screen) && !xwl_screen->force_xrandr_emulation)
        return rr_modes;

    /* Add fake modes */
    for (i = 0; i < ARRAY_SIZE(xwl_output_fake_modes); i++) {
        /* Skip actual output mode, already added */
        if (xwl_output_fake_modes[i][0] == width &&
            xwl_output_fake_modes[i][1] == height)
            continue;

        /* Skip modes which are too big, avoid downscaling */
        if (xwl_output_fake_modes[i][0] > width ||
            xwl_output_fake_modes[i][1] > height)
            continue;

        rr_modes[*count] = xwayland_cvt(xwl_output_fake_modes[i][0],
                                        xwl_output_fake_modes[i][1],
                                        xwl_output->refresh / 1000.0, 0, 0);
        if (!rr_modes[*count])
            goto err;

        (*count)++;
    }

    return rr_modes;
err:
    FatalError("Failed to allocate memory for list of RR modes");
}

RRModePtr
xwl_output_find_mode(struct xwl_output *xwl_output,
                     int32_t width, int32_t height)
{
    RROutputPtr output = xwl_output->randr_output;
    int i;

    /* width & height -1 means we want the actual output mode, which is idx 0 */
    if (width == -1 && height == -1 && output->modes)
        return output->modes[0];

    for (i = 0; i < output->numModes; i++) {
        if (output->modes[i]->mode.width == width && output->modes[i]->mode.height == height)
            return output->modes[i];
    }

    ErrorF("XWAYLAND: mode %dx%d is not available\n", width, height);
    return NULL;
}

struct xwl_output_randr_emu_prop {
    Atom atom;
    uint32_t rects[XWL_CLIENT_MAX_EMULATED_MODES][4];
    int rect_count;
};

static void
xwl_output_randr_emu_prop(struct xwl_screen *xwl_screen, ClientPtr client,
                          struct xwl_output_randr_emu_prop *prop)
{
    static const char atom_name[] = "_XWAYLAND_RANDR_EMU_MONITOR_RECTS";
    struct xwl_emulated_mode *emulated_mode;
    struct xwl_output *xwl_output;
    int index = 0;

    prop->atom = MakeAtom(atom_name, strlen(atom_name), TRUE);

    xorg_list_for_each_entry(xwl_output, &xwl_screen->output_list, link) {
        emulated_mode = xwl_output_get_emulated_mode_for_client(xwl_output, client);
        if (!emulated_mode)
            continue;

        prop->rects[index][0] = xwl_output->x;
        prop->rects[index][1] = xwl_output->y;
        prop->rects[index][2] = emulated_mode->width;
        prop->rects[index][3] = emulated_mode->height;
        index++;
    }

    prop->rect_count = index;
}

static void
xwl_output_set_randr_emu_prop(WindowPtr window,
                              struct xwl_output_randr_emu_prop *prop)
{
    if (prop->rect_count) {
        dixChangeWindowProperty(serverClient, window, prop->atom,
                                XA_CARDINAL, 32, PropModeReplace,
                                prop->rect_count * 4, prop->rects, TRUE);
    } else {
        DeleteProperty(serverClient, window, prop->atom);
    }
}

static void
xwl_output_set_randr_emu_prop_callback(void *resource, XID id, void *user_data)
{
    if (xwl_window_is_toplevel(resource))
        xwl_output_set_randr_emu_prop(resource, user_data);
}

static void
xwl_output_set_randr_emu_props(struct xwl_screen *xwl_screen, ClientPtr client)
{
    struct xwl_output_randr_emu_prop prop = {};

    xwl_output_randr_emu_prop(xwl_screen, client, &prop);
    FindClientResourcesByType(client, RT_WINDOW,
                              xwl_output_set_randr_emu_prop_callback, &prop);
}

static inline void
xwl_output_get_emulated_root_size(struct xwl_output *xwl_output,
                                  ClientPtr client,
                                  int *width,
                                  int *height)
{
    struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
    struct xwl_emulated_mode *emulated_mode;

    emulated_mode = xwl_output_get_emulated_mode_for_client(xwl_output, client);
    /* If not an emulated mode, just return the actual screen size */
    if (!emulated_mode) {
        *width = xwl_screen->width;
        *height = xwl_screen->height;
        return;
    }

    if (xwl_output->rotation & (RR_Rotate_0 | RR_Rotate_180)) {
        *width = emulated_mode->width;
        *height = emulated_mode->height;
    } else {
        *width = emulated_mode->height;
        *height = emulated_mode->width;
    }
}

static int
xwl_output_get_rr_event_mask(WindowPtr pWin, ClientPtr client)
{
    RREventPtr pRREvent, *pHead;

    dixLookupResourceByType((void **) &pHead, pWin->drawable.id,
                            RREventType, client, DixReadAccess);

    pRREvent = NULL;
    if (pHead) {
        for (pRREvent = *pHead; pRREvent; pRREvent = pRREvent->next)
            if (pRREvent->client == client)
                break;
        }

    if (pRREvent)
        return pRREvent->mask;

    return 0;
}

static void
xwl_output_notify_emulated_root_size(struct xwl_output *xwl_output,
                                     ClientPtr client,
                                     int new_emulated_root_width,
                                     int new_emulated_root_height)
{
    struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
    ScreenPtr pScreen = xwl_screen->screen;
    WindowPtr pRoot = pScreen->root;
    xEvent event = {
             .u.configureNotify.event = pRoot->drawable.id,
             .u.configureNotify.window = pRoot->drawable.id,
             .u.configureNotify.aboveSibling = None,
             .u.configureNotify.x = 0,
             .u.configureNotify.y = 0,
             .u.configureNotify.width = new_emulated_root_width,
             .u.configureNotify.height = new_emulated_root_height,
             .u.configureNotify.borderWidth = pRoot->borderWidth,
             .u.configureNotify.override = pRoot->overrideRedirect
         };
     event.u.u.type = ConfigureNotify;

     if (!client || client == serverClient || client->clientGone)
         return;

     if (EventMaskForClient(pRoot, client) & StructureNotifyMask)
         WriteEventsToClient(client, 1, &event);

     if (xwl_output_get_rr_event_mask(pRoot, client) & RRScreenChangeNotifyMask)
         RRDeliverScreenEvent(client, pRoot, pScreen);
}

void
xwl_output_set_window_randr_emu_props(struct xwl_screen *xwl_screen,
                                      WindowPtr window)
{
    struct xwl_output_randr_emu_prop prop = {};

    xwl_output_randr_emu_prop(xwl_screen, wClient(window), &prop);
    xwl_output_set_randr_emu_prop(window, &prop);
}

void
xwl_output_set_emulated_mode(struct xwl_output *xwl_output, ClientPtr client,
                             RRModePtr mode, Bool from_vidmode)
{
    int old_emulated_width, old_emulated_height;
    int new_emulated_width, new_emulated_height;

    DebugF("XWAYLAND: xwl_output_set_emulated_mode from %s: %dx%d\n",
           from_vidmode ? "vidmode" : "randr",
           mode->mode.width, mode->mode.height);

    xwl_output_get_emulated_root_size(xwl_output, client,
                                      &old_emulated_width, &old_emulated_height);

    /* modes[0] is the actual (not-emulated) output mode */
    if (mode == xwl_output->randr_output->modes[0])
        xwl_output_remove_emulated_mode_for_client(xwl_output, client);
    else
        xwl_output_add_emulated_mode_for_client(xwl_output, client, mode, from_vidmode);

    xwl_screen_check_resolution_change_emulation(xwl_output->xwl_screen);

    xwl_output_set_randr_emu_props(xwl_output->xwl_screen, client);

    xwl_output_get_emulated_root_size(xwl_output, client,
                                      &new_emulated_width, &new_emulated_height);

    if (new_emulated_width != old_emulated_width ||
        new_emulated_height != old_emulated_height)
        xwl_output_notify_emulated_root_size(xwl_output, client,
                                             new_emulated_width,
                                             new_emulated_height);
}

static void
apply_output_change(struct xwl_output *xwl_output)
{
    struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
    struct xwl_window *xwl_window;
    struct xwl_output *it;
    int mode_width, mode_height, count;
    int width = 0, height = 0, has_this_output = 0;
    RRModePtr *randr_modes;

    /* Clear out the "done" received flags */
    xwl_output->wl_output_done = FALSE;
    xwl_output->xdg_output_done = FALSE;

    /* When we have received an xdg-output for the mode size we might need to
     * rotate back the stored logical size it provided.
     */
    if (xwl_output->xdg_output == NULL
        || xwl_output->rotation & (RR_Rotate_0 | RR_Rotate_180)) {
        mode_width = xwl_output->width;
        mode_height = xwl_output->height;
    } else {
        mode_width = xwl_output->height;
        mode_height = xwl_output->width;
    }
    if (xwl_output->randr_output) {
        /* Build a fresh modes array using the current refresh rate */
        randr_modes = output_get_rr_modes(xwl_output, mode_width, mode_height, &count);
        RROutputSetModes(xwl_output->randr_output, randr_modes, count, 1);
        RRCrtcNotify(xwl_output->randr_crtc, randr_modes[0],
                     xwl_output->x, xwl_output->y,
                     xwl_output->rotation, NULL, 1, &xwl_output->randr_output);
        /* RROutputSetModes takes ownership of the passed in modes, so we only
         * have to free the pointer array.
         */
        free(randr_modes);
    }

    xorg_list_for_each_entry(it, &xwl_screen->output_list, link) {
        /* output done event is sent even when some property
         * of output is changed. That means that we may already
         * have this output. If it is true, we must not add it
         * into the output_list otherwise we'll corrupt it */
        if (it == xwl_output)
            has_this_output = 1;

        output_get_new_size(it, &width, &height);
    }

    if (!has_this_output) {
        xorg_list_append(&xwl_output->link, &xwl_screen->output_list);

        /* we did not check this output for new screen size, do it now */
        output_get_new_size(xwl_output, &width, &height);

	--xwl_screen->expecting_event;
    }

    if (xwl_screen->fixed_output == NULL)
        update_screen_size(xwl_screen, width, height);
    else
        RRTellChanged(xwl_screen->screen);

    /* If running rootful and fullscreen, make sure to match the new setup */
    if (xwl_screen->fullscreen) {
        /* The root window may not yet be created */
        if (xwl_screen->screen->root) {
            xwl_window = xwl_window_get(xwl_screen->screen->root);
            xwl_window_rootful_update_fullscreen(xwl_window, xwl_output);
        }
    }
}

static void
xwl_output_set_name(struct xwl_output *xwl_output, const char *name)
{
    if (xwl_output->randr_output == NULL)
        return; /* rootful */

    /* Check whether the compositor is sending us something useful */
    if (!name || !strlen(name)) {
        ErrorF("Not using the provided output name, invalid");
        return;
    }

    snprintf(xwl_output->randr_output->name, MAX_OUTPUT_NAME, "%s", name);
    xwl_output->randr_output->nameLength = strlen(xwl_output->randr_output->name);
}

static void
output_handle_done(void *data, struct wl_output *wl_output)
{
    struct xwl_output *xwl_output = data;

    xwl_output->wl_output_done = TRUE;
    /* Apply the changes from wl_output only if both "done" events are received,
     * if xdg-output is not supported or if xdg-output version is high enough.
     */
    if (xwl_output->xdg_output_done || !xwl_output->xdg_output ||
        zxdg_output_v1_get_version(xwl_output->xdg_output) >= 3)
        apply_output_change(xwl_output);
}

static void
output_handle_scale(void *data, struct wl_output *wl_output, int32_t factor)
{
}

static void
output_handle_name(void *data, struct wl_output *wl_output,
                   const char *name)
{
    struct xwl_output *xwl_output = data;

    xwl_output_set_name(xwl_output, name);
}

static void
output_handle_description(void *data, struct wl_output *wl_output,
                          const char *description)
{
}

static const struct wl_output_listener output_listener = {
    output_handle_geometry,
    output_handle_mode,
    output_handle_done,
    output_handle_scale,
    output_handle_name,
    output_handle_description,
};

static void
xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output,
                                   int32_t x, int32_t y)
{
    struct xwl_output *xwl_output = data;

    xwl_output->x = x;
    xwl_output->y = y;
}

static void
xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output,
                               int32_t width, int32_t height)
{
    struct xwl_output *xwl_output = data;

    xwl_output->width = width;
    xwl_output->height = height;
}

static void
xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
{
    struct xwl_output *xwl_output = data;

    xwl_output->xdg_output_done = TRUE;
    if (xwl_output->wl_output_done &&
        zxdg_output_v1_get_version(xdg_output) < 3)
        apply_output_change(xwl_output);
}

static void
xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output,
                       const char *name)
{
    struct xwl_output *xwl_output = data;

    if (wl_output_get_version(xwl_output->output) >= 4)
        return; /* wl_output.name is preferred */

    xwl_output_set_name(xwl_output, name);
}

static void
xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output,
                              const char *description)
{
}

static const struct zxdg_output_v1_listener xdg_output_listener = {
    xdg_output_handle_logical_position,
    xdg_output_handle_logical_size,
    xdg_output_handle_done,
    xdg_output_handle_name,
    xdg_output_handle_description,
};

#define XRANDR_EMULATION_PROP "RANDR Emulation"
static Atom
get_rand_emulation_property(void)
{
    const char *emulStr = XRANDR_EMULATION_PROP;

    return MakeAtom(emulStr, strlen(emulStr), TRUE);
}

static void
xwl_output_set_emulated(struct xwl_output *xwl_output)
{
    int32_t val = TRUE;

    RRChangeOutputProperty(xwl_output->randr_output,
                           get_rand_emulation_property(),
                           XA_INTEGER,
                           32, PropModeReplace, 1,
                           &val, FALSE, FALSE);
}

struct xwl_output*
xwl_output_from_wl_output(struct xwl_screen *xwl_screen,
                          struct wl_output* wl_output)
{
    struct xwl_output *xwl_output;

    xorg_list_for_each_entry(xwl_output, &xwl_screen->output_list, link) {
        if (xwl_output->output == wl_output)
            return xwl_output;
    }

    return NULL;
}

struct xwl_output *
xwl_output_create(struct xwl_screen *xwl_screen, uint32_t id,
                  Bool with_xrandr, uint32_t version)
{
    struct xwl_output *xwl_output;
    char name[MAX_OUTPUT_NAME] = { '\0', };

    xwl_output = calloc(1, sizeof *xwl_output);
    if (xwl_output == NULL) {
        ErrorF("%s ENOMEM\n", __func__);
        return NULL;
    }

    xwl_output->output = wl_registry_bind(xwl_screen->registry, id,
                                          &wl_output_interface, min(version, 4));
    if (!xwl_output->output) {
        ErrorF("Failed binding wl_output\n");
        goto err;
    }

    xwl_output->server_output_id = id;
    wl_output_add_listener(xwl_output->output, &output_listener, xwl_output);

    xwl_output->xwl_screen = xwl_screen;

    if (with_xrandr) {
        xwl_output->randr_crtc = RRCrtcCreate(xwl_screen->screen, xwl_output);
        if (!xwl_output->randr_crtc) {
            ErrorF("Failed creating RandR CRTC\n");
            goto err;
        }
        RRCrtcSetRotations (xwl_output->randr_crtc, ALL_ROTATIONS);

        /* Allocate MAX_OUTPUT_NAME data for the output name, all filled with zeros */
        xwl_output->randr_output = RROutputCreate(xwl_screen->screen, name,
                                                  MAX_OUTPUT_NAME, xwl_output);
        if (!xwl_output->randr_output) {
            ErrorF("Failed creating RandR Output\n");
            goto err;
        }
        /* Set the default output name to a sensible value */
        snprintf(name, MAX_OUTPUT_NAME, "XWAYLAND%d",
                 xwl_screen_get_next_output_serial(xwl_screen));
        xwl_output_set_name(xwl_output, name);
        xwl_output_set_emulated(xwl_output);

        RRCrtcGammaSetSize(xwl_output->randr_crtc, 256);
        RROutputSetCrtcs(xwl_output->randr_output, &xwl_output->randr_crtc, 1);
        RROutputSetConnection(xwl_output->randr_output, RR_Connected);
    }
    /* We want the output to be in the list as soon as created so we can
     * use it when binding to the xdg-output protocol...
     */
    xorg_list_append(&xwl_output->link, &xwl_screen->output_list);
    --xwl_screen->expecting_event;

    if (xwl_screen->xdg_output_manager)
        xwl_output_get_xdg_output(xwl_output);

    return xwl_output;

err:
    if (xwl_output->randr_crtc)
        RRCrtcDestroy(xwl_output->randr_crtc);
    if (xwl_output->output)
        wl_output_destroy(xwl_output->output);
    free(xwl_output);
    return NULL;
}

void
xwl_output_destroy(struct xwl_output *xwl_output)
{
    if (xwl_output->lease_connector)
        wp_drm_lease_connector_v1_destroy(xwl_output->lease_connector);
    if (xwl_output->xdg_output)
        zxdg_output_v1_destroy(xwl_output->xdg_output);
    if (xwl_output->output)
        wl_output_destroy(xwl_output->output);
    free(xwl_output);
}

void
xwl_output_remove(struct xwl_output *xwl_output)
{
    struct xwl_output *it;
    struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
    int width = 0, height = 0;

    xorg_list_del(&xwl_output->link);

    if (xwl_output->randr_output)
        RROutputSetConnection(xwl_output->randr_output, RR_Disconnected);

    if (xwl_screen->fixed_output == NULL) {
        xorg_list_for_each_entry(it, &xwl_screen->output_list, link)
            output_get_new_size(it, &width, &height);
        update_screen_size(xwl_screen, width, height);
    }

    if (xwl_output->randr_crtc)
        RRCrtcDestroy(xwl_output->randr_crtc);
    if (xwl_output->randr_output) {
        RROutputDestroy(xwl_output->randr_output);
        RRTellChanged(xwl_screen->screen);
    }
    xwl_output_destroy(xwl_output);
}

static Bool
xwl_randr_get_info(ScreenPtr pScreen, Rotation * rotations)
{
    *rotations = ALL_ROTATIONS;

    return TRUE;
}

#ifdef RANDR_10_INTERFACE
static Bool
xwl_randr_set_config(ScreenPtr pScreen,
                     Rotation rotation, int rate, RRScreenSizePtr pSize)
{
    return FALSE;
}
#endif

#if RANDR_12_INTERFACE
static Bool
xwl_randr_screen_set_size(ScreenPtr pScreen,
                          CARD16 width,
                          CARD16 height,
                          CARD32 mmWidth, CARD32 mmHeight)
{
    return TRUE;
}

static Bool
xwl_randr_crtc_set(ScreenPtr pScreen,
                   RRCrtcPtr crtc,
                   RRModePtr new_mode,
                   int x,
                   int y,
                   Rotation rotation,
                   int numOutputs, RROutputPtr * outputs)
{
    struct xwl_output *xwl_output = crtc->devPrivate;
    RRModePtr mode;

    if (new_mode) {
        mode = xwl_output_find_mode(xwl_output,
                                    new_mode->mode.width,
                                    new_mode->mode.height);
    } else {
        mode = xwl_output_find_mode(xwl_output, -1, -1);
    }
    if (!mode)
        return FALSE;

    xwl_output_set_emulated_mode(xwl_output, GetCurrentClient(), mode, FALSE);

    /* A real randr implementation would call:
     * RRCrtcNotify(xwl_output->randr_crtc, mode, xwl_output->x, xwl_output->y,
     *              xwl_output->rotation, NULL, 1, &xwl_output->randr_output);
     * here to update the mode reported to clients querying the randr settings
     * but that influences *all* clients and we do randr mode change emulation
     * on a per client basis. So we just return success here.
     */

    return TRUE;
}

static void
xwl_randr_crtc_get(ScreenPtr pScreen,
                   RRCrtcPtr crtc,
                   xRRGetCrtcInfoReply *rep)
{
    struct xwl_output *xwl_output = crtc->devPrivate;

    struct xwl_emulated_mode *mode = xwl_output_get_emulated_mode_for_client(
        xwl_output, GetCurrentClient());

    if (mode)
        rep->mode = mode->id;
}

static Bool
xwl_randr_crtc_set_gamma(ScreenPtr pScreen, RRCrtcPtr crtc)
{
    return TRUE;
}

static Bool
xwl_randr_crtc_get_gamma(ScreenPtr pScreen, RRCrtcPtr crtc)
{
    return TRUE;
}

static Bool
xwl_randr_output_set_property(ScreenPtr pScreen,
                              RROutputPtr output,
                              Atom property,
                              RRPropertyValuePtr value)
{
    /* RANDR Emulation property is read-only. */
    if (get_rand_emulation_property() == property)
        return FALSE;

    return TRUE;
}

static Bool
xwl_output_validate_mode(ScreenPtr pScreen,
                         RROutputPtr output,
                         RRModePtr mode)
{
    return TRUE;
}

static void
xwl_randr_mode_destroy(ScreenPtr pScreen, RRModePtr mode)
{
    return;
}
#endif

Bool
xwl_screen_init_output(struct xwl_screen *xwl_screen)
{
    rrScrPrivPtr rp;

    if (!RRScreenInit(xwl_screen->screen))
        return FALSE;

    RRScreenSetSizeRange(xwl_screen->screen, 16, 16, 32767, 32767);

    rp = rrGetScrPriv(xwl_screen->screen);
    rp->rrGetInfo = xwl_randr_get_info;

#if RANDR_10_INTERFACE
    rp->rrSetConfig = xwl_randr_set_config;
#endif

#if RANDR_12_INTERFACE
    rp->rrScreenSetSize = xwl_randr_screen_set_size;
    rp->rrCrtcSet = xwl_randr_crtc_set;
    rp->rrCrtcGet = xwl_randr_crtc_get;
    rp->rrCrtcSetGamma = xwl_randr_crtc_set_gamma;
    rp->rrCrtcGetGamma = xwl_randr_crtc_get_gamma;
    rp->rrOutputSetProperty = xwl_randr_output_set_property;
    rp->rrOutputValidateMode = xwl_output_validate_mode;
    rp->rrModeDestroy = xwl_randr_mode_destroy;
#endif

    rp->rrRequestLease = xwl_randr_request_lease;
    rp->rrGetLease = xwl_randr_get_lease;
    rp->rrTerminateLease = xwl_randr_terminate_lease;

    return TRUE;
}

static int
mode_sort(const void *left, const void *right)
{
    const RRModePtr *mode_a = left;
    const RRModePtr *mode_b = right;

    if ((*mode_b)->mode.width == (*mode_a)->mode.width)
        return (*mode_b)->mode.height - (*mode_a)->mode.height;

    return (*mode_b)->mode.width - (*mode_a)->mode.width;
}

Bool
xwl_randr_add_modes_fixed(struct xwl_output *xwl_output,
                          int current_width, int current_height)
{
    RRModePtr *modes = NULL;
    RRModePtr mode;
    int i, nmodes, current;

    modes = xallocarray(ARRAY_SIZE(xwl_output_fake_modes) + 1, sizeof(RRModePtr));
    if (!modes) {
        ErrorF("Failed to allocated RandR modes\n");
        return FALSE;
    }

    nmodes = 0;
    current = 0;

    /* Add fake modes */
    for (i = 0; i < ARRAY_SIZE(xwl_output_fake_modes); i++) {
        if (xwl_output_fake_modes[i][0] == current_width &&
            xwl_output_fake_modes[i][1] == current_height)
            current = 1;

        mode = xwayland_cvt(xwl_output_fake_modes[i][0],
                            xwl_output_fake_modes[i][1],
                            60, 0, 0);

        if (mode)
            modes[nmodes++] = mode;
    }

    if (!current) {
        /* Add the current mode as it's not part of the fake modes. */
        mode = xwayland_cvt(current_width, current_height, 60, 0, 0);

        if (mode)
            modes[nmodes++] = mode;
    }

    qsort(modes, nmodes, sizeof(RRModePtr), mode_sort);
    RROutputSetModes(xwl_output->randr_output, modes, nmodes, 1);
    free(modes);

    return TRUE;
}

void
xwl_output_set_mode_fixed(struct xwl_output *xwl_output, RRModePtr mode)
{
    struct xwl_screen *xwl_screen = xwl_output->xwl_screen;

    update_screen_size(xwl_screen, mode->mode.width, mode->mode.height);

    RRCrtcNotify(xwl_output->randr_crtc, mode, 0, 0, RR_Rotate_0,
                 NULL, 1, &xwl_output->randr_output);
}

static Bool
xwl_randr_set_config_fixed(ScreenPtr pScreen,
                           Rotation randr, int rate, RRScreenSizePtr pSize)
{
    struct xwl_screen *xwl_screen = xwl_screen_get(pScreen);

    update_screen_size(xwl_screen, pSize->width, pSize->height);

    return TRUE;
}

/* Create a single RR output/mode used with a fixed geometry */
Bool
xwl_screen_init_randr_fixed(struct xwl_screen *xwl_screen)
{
    struct xwl_output *xwl_output;
    rrScrPrivPtr rp;
    RRModePtr mode;

    xwl_output = calloc(1, sizeof *xwl_output);
    if (xwl_output == NULL) {
        ErrorF("%s ENOMEM\n", __func__);
        return FALSE;
    }

    if (!RRScreenInit(xwl_screen->screen))
        goto err;

    RRScreenSetSizeRange(xwl_screen->screen, 16, 16, 32767, 32767);

    rp = rrGetScrPriv(xwl_screen->screen);
    rp->rrGetInfo = xwl_randr_get_info;
    rp->rrSetConfig = xwl_randr_set_config_fixed;

    xwl_output->randr_output = RROutputCreate(xwl_screen->screen, "XWAYLAND0", 9, NULL);
    if (!xwl_output->randr_output) {
        ErrorF("Failed to create RandR output\n");
        goto err;
    }

    xwl_output->randr_crtc = RRCrtcCreate(xwl_screen->screen, xwl_output);
    if (!xwl_output->randr_crtc) {
        ErrorF("Failed to create RandR CRTC\n");
        goto err;
    }
    RRCrtcSetRotations (xwl_output->randr_crtc, RR_Rotate_0);
    RRCrtcGammaSetSize(xwl_output->randr_crtc, 256);
    RROutputSetCrtcs(xwl_output->randr_output, &xwl_output->randr_crtc, 1);

    xwl_randr_add_modes_fixed(xwl_output,
                              xwl_screen->width, xwl_screen->height);
    /* Current mode */
    mode = xwl_output_find_mode(xwl_output,
                                xwl_screen->width, xwl_screen->height);
    RRCrtcNotify(xwl_output->randr_crtc, mode, 0, 0, RR_Rotate_0,
                 NULL, 1, &xwl_output->randr_output);

    RROutputSetPhysicalSize(xwl_output->randr_output,
                            (xwl_screen->width * 25.4) / monitorResolution,
                            (xwl_screen->height * 25.4) / monitorResolution);

    RROutputSetConnection(xwl_output->randr_output, RR_Connected);

    xwl_output->xwl_screen = xwl_screen;
    xwl_screen->fixed_output = xwl_output;

    return TRUE;

err:
    if (xwl_output->randr_crtc)
        RRCrtcDestroy(xwl_output->randr_crtc);
    if (xwl_output->randr_output)
        RROutputDestroy(xwl_output->randr_output);
    free(xwl_output);

    return FALSE;
}

static void
xwl_output_get_xdg_output(struct xwl_output *xwl_output)
{
    struct xwl_screen *xwl_screen = xwl_output->xwl_screen;

    if (!xwl_output->output) {
        /* This can happen when an output is created from a leasable DRM
         * connector */
        return;
    }

    xwl_output->xdg_output =
        zxdg_output_manager_v1_get_xdg_output (xwl_screen->xdg_output_manager,
                                               xwl_output->output);

    zxdg_output_v1_add_listener(xwl_output->xdg_output,
                                &xdg_output_listener,
                                xwl_output);
}

void
xwl_screen_init_xdg_output(struct xwl_screen *xwl_screen)
{
    struct xwl_output *it;

    assert(xwl_screen->xdg_output_manager);

    xorg_list_for_each_entry(it, &xwl_screen->output_list, link)
        xwl_output_get_xdg_output(it);
}
