#include <errno.h>
#include <fcntl.h>
#include <libudev.h>
#include <linux/input.h>
#include <linux/memfd.h>
#include <linux/vt.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-event.h>
#include <systemd/sd-login.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

#include <shell.h>
#include <keymap.h>

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>

#define array_size(a) (sizeof(a) / sizeof((a)[0]))

#define BYTES_PER_PIXEL 4

enum {
    DISPLAY_CONTROLLER_CHOSEN = 1,
    DISPLAY_HAS_FRAME_BUFFERS = 2,
    DISPLAY_SAVED = 4,
};

enum {
    NUM_WORKSPACES = 4,
    NUM_WINDOWS_PER_WORKSPACE = 3,
};

struct display_buffer {
    void *mem;
    uint64_t size;
    uint32_t width;
    uint32_t height;
    uint32_t stride;
    uint32_t handle;
    uint32_t fb;
};

struct display {
    int fd;
    struct display_buffer buffers[2];
    unsigned int back_buffer_index:1;
    unsigned int back_buffer_is_locked:1;
    unsigned int is_draw_needed:1;

    uint32_t state;

    drmModeModeInfo mode;
    uint32_t conn;
    uint32_t crtc;
    drmModeCrtc *saved_crtc;
};

struct window {
    uint32_t x;
    uint32_t y;
    uint32_t width;
    uint32_t height;
    unsigned int front_buffer:1;
    void *mem;
    int socket_fd;
    pid_t pid;
};

struct workspace {
    struct window windows[NUM_WINDOWS_PER_WORKSPACE];
    unsigned focus;
};

struct keyboard {
    int fd;
    uint32_t major;
    uint32_t minor;
    uint8_t key_bits[SHELL_KEY_BITS_SIZE];
};

struct root {
    bool is_active;
    struct workspace workspaces[NUM_WORKSPACES];
    unsigned focus;
    sd_bus *bus;
    sd_event *event;
    struct keyboard keyboard;
    struct display display;
};

_Noreturn static void
die(const char *m)
{
    fprintf(stderr, "%s\n", m);
    exit(1);
}

static void *
xmalloc(size_t size)
{
    if (size == 0)
        die("Memory allocation error.");
    void *x = malloc(size);
    if (x == NULL)
        die("Memory allocation failed.");
    return x;
}

static void
display_init(struct display *disp)
{
    disp->state = 0;
    disp->back_buffer_index = 1;
    disp->back_buffer_is_locked = 0;
    disp->is_draw_needed = 0;
}

static struct display_buffer *
display_front_buffer(struct display *disp)
{
    return &disp->buffers[1 - disp->back_buffer_index];
}

static struct display_buffer *
display_back_buffer(struct display *disp)
{
    return &disp->buffers[disp->back_buffer_index];
}

static void
display_connect(struct display *disp, int fd)
{
    disp->fd = fd;

    //  We need "dumb buffer" support so fail if the device doesn't have that.

    {
        uint64_t has_dumb;
        int r = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb);
        if (r < 0 || !has_dumb)
            die("Failed to connect to the display.");
    }

    //  We choose an arbitrary connector that's currently active, a mode to use
    //  on it (determines resolution), and a controller (CRTC) that's
    //  compatible with one of the connector's encoders.

    {
        drmModeRes *res = drmModeGetResources(disp->fd);
        if (!res)
            die("Failed to connect to display.");
        bool did_choose_crtc = false;
        for (int i = 0; i < res->count_connectors; i++) {
            drmModeConnector *conn =
                drmModeGetConnector(disp->fd, res->connectors[i]);
            if (!conn)
                continue;
            if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) {
                drmModeModeInfo *mode;
                {
                    int j = conn->count_modes - 1;
                    while (j > 0 && !(conn->modes[j].type & DRM_MODE_TYPE_PREFERRED))
                        j--;
                    mode = &conn->modes[j];
                }
                for (int j = 0; j < conn->count_encoders; j++) {
                    drmModeEncoder *enc =
                        drmModeGetEncoder(disp->fd, conn->encoders[j]);
                    if (!enc)
                        continue;
                    for (int k = 0; k < res->count_crtcs; k++) {
                        if (enc->possible_crtcs & (1 << k)) {
                            disp->crtc = res->crtcs[k];
                            disp->conn = conn->connector_id;
                            disp->mode = *mode;
                            did_choose_crtc = true;
                            break;
                        }
                    }
                    drmModeFreeEncoder(enc);
                    if (did_choose_crtc)
                        break;
                }
            }
            drmModeFreeConnector(conn);
            if (did_choose_crtc)
                break;
        }
        drmModeFreeResources(res);
        if (!did_choose_crtc)
            die("Failed to connect to display.");
    }

    disp->state |= DISPLAY_CONTROLLER_CHOSEN;

    //  Allocate and mmap the two full-screen pixel buffers.

    for (int i = 0; i < 2; i++) {
        struct display_buffer *buffer = &disp->buffers[i];
        buffer->width = disp->mode.hdisplay;
        buffer->height = disp->mode.vdisplay;
        struct drm_mode_create_dumb ioctl_create = {
            .width = buffer->width,
            .height = buffer->height,
            .bpp = 32,
        };
        int r = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &ioctl_create);
        if (r < 0)
            die("Failed to connect to display.");
        buffer->stride = ioctl_create.pitch;
        buffer->size = ioctl_create.size;
        buffer->handle = ioctl_create.handle;
        r = drmModeAddFB(fd, buffer->width, buffer->height, 24, 32,
            buffer->stride, buffer->handle, &buffer->fb);
        if (r)
            die("Failed to connect to display.");
        struct drm_mode_map_dumb ioctl_map = {
            .handle = buffer->handle,
        };
        r = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &ioctl_map);
        if (r)
            die("Failed to connect to display.");
        buffer->mem = mmap(0, buffer->size, PROT_READ | PROT_WRITE,
            MAP_SHARED, fd, ioctl_map.offset);
        if (buffer->mem == MAP_FAILED)
            die("Failed to connect to display.");
    }

    disp->state |= DISPLAY_HAS_FRAME_BUFFERS;

    //  Save the current state for the CRTC we are about to take over. We will
    //  restore this state when we disconnect from the display.
    //
    //  Take over the connector and CRTC that we selected above.

    {
        struct display_buffer *front_buffer = display_front_buffer(disp);

        disp->saved_crtc = drmModeGetCrtc(disp->fd, disp->crtc);

        int r = drmModeSetCrtc(disp->fd, disp->crtc, front_buffer->fb, 0, 0,
            &disp->conn, 1, &disp->mode);
        if (r)
            die("Failed to connect to display.");
    }

    disp->state |= DISPLAY_SAVED;
}

static void
display_disconnect(struct display *disp)
{
    if (disp->state & DISPLAY_SAVED) {
        drmModeSetCrtc(
            disp->fd,
            disp->saved_crtc->crtc_id,
            disp->saved_crtc->buffer_id,
            disp->saved_crtc->x,
            disp->saved_crtc->y,
            &disp->conn,
            1,
            &disp->saved_crtc->mode);
        drmModeFreeCrtc(disp->saved_crtc);
        disp->state &= ~DISPLAY_SAVED;
    }
    if (disp->state & DISPLAY_HAS_FRAME_BUFFERS) {
        for (int i = 0; i < 2; i++) {
            struct display_buffer *buffer = &disp->buffers[i];
            munmap(buffer->mem, buffer->size);
            drmModeRmFB(disp->fd, buffer->fb);
            drmIoctl(disp->fd, DRM_IOCTL_MODE_DESTROY_DUMB,
                &(struct drm_mode_destroy_dumb){
                    .handle = buffer->handle,
                });
        }
        disp->state &= ~DISPLAY_HAS_FRAME_BUFFERS;
    }
}

static void
set_bit(uint8_t *bits, unsigned i)
{
    bits[i / 8] |= (1 << (i % 8));
}

static void
clear_bit(uint8_t *bits, unsigned i)
{
    bits[i / 8] &= ~(1 << (i % 8));
}

static int
fetch_bit(const uint8_t *bits, unsigned i)
{
    return (bits[i / 8] & (1 << (i % 8))) ? 1 : 0;
}

struct keyboard *
keyboard_new(void)
{
    struct keyboard *keyboard = xmalloc(sizeof(*keyboard));
    keyboard->fd = -1;
    return keyboard;
}

int
keyboard_connect(struct keyboard *keyboard, int fd,
        unsigned int major, unsigned int minor)
{
    keyboard->fd = fd;
    keyboard->major = major;
    keyboard->minor = minor;

    int r = ioctl(fd, EVIOCGKEY(SHELL_KEY_BITS_SIZE), keyboard->key_bits);
    if (r == -1)
        die("Failed to read keyboard state.");

    return 0;
}

static void
blit(void *dest, uint32_t dest_width, uint32_t x, uint32_t y,
        void *source, uint32_t source_width, uint32_t source_height)
{
    for (uint32_t i = 0; i < source_height; i++) {
        memmove(dest + BYTES_PER_PIXEL * (dest_width * (y + i) + x),
                source + BYTES_PER_PIXEL * source_width * i,
                BYTES_PER_PIXEL * source_width);
    }
}

static uint32_t
encode_color_xrgb(uint8_t r, uint8_t g, uint8_t b)
{
    union {
        uint32_t u;
        uint8_t bytes[4];
    } x = {
        .bytes = { b, g, r, 0 },
    };
    return x.u;
}

static void
clear_buffer(void *buffer, uint32_t width, uint32_t height)
{
    uint32_t color = encode_color_xrgb(0xff, 0xff, 0xff);
    uint32_t *pixel = buffer;
    for (unsigned int j = 0; j < width; j++)
        for (unsigned int k = 0; k < height; k++)
            *pixel++ = color;
}

static void
draw_frame(void *mem, uint32_t width, struct window *window, bool has_focus)
{
    int x0 = window->x - 5;
    int y0 = window->y - 5;
    int x1 = window->x + window->width + 4;
    int y1 = window->y + window->height + 4;

    uint32_t color = has_focus
        ? encode_color_xrgb(0x48, 0x48, 0x48)
        : encode_color_xrgb(0xcc, 0xcc, 0xcc);

    uint32_t *pixels = mem;

    for (int x = x0, i = width * y0 + x0; x <= x1; x++, i++)
        pixels[i] = color;

    for (int x = x0, i = width * y1 + x0; x <= x1; x++, i++)
        pixels[i] = color;

    for (int y = y0, i = width * y0 + x0; y <= y1; y++, i += width)
        pixels[i] = color;

    for (int y = y0, i = width * y0 + x1; y <= y1; y++, i += width)
        pixels[i] = color;
}

static void
draw_window(void *back_buffer, uint32_t disp_width, struct window *window)
{
    if (window->pid == -1)
        return;
    void *buffer = window->mem + (window->front_buffer * BYTES_PER_PIXEL *
            window->width * window->height);
    blit(back_buffer, disp_width, window->x, window->y,
            buffer, window->width, window->height);
}

static void
draw(struct root *root)
{
    struct display *disp = &root->display;

    if (disp->back_buffer_is_locked || !root->is_active) {
        disp->is_draw_needed = 1;
        return;
    }

    struct display_buffer *back_buffer = display_back_buffer(disp);
    uint32_t disp_width = back_buffer->width;
    uint32_t disp_height = back_buffer->height;

    clear_buffer(back_buffer->mem, disp_width, disp_height);

    struct workspace *workspace = &root->workspaces[root->focus];
    for (int i = 0; i < NUM_WINDOWS_PER_WORKSPACE; i++) {
        struct window *window = &workspace->windows[i];
        draw_frame(back_buffer->mem, disp_width, window, i == workspace->focus);
        draw_window(back_buffer->mem, disp_width, window);
    }

    int r = drmModePageFlip(disp->fd, disp->crtc, back_buffer->fb,
        DRM_MODE_PAGE_FLIP_EVENT, root);
    if (r)
        fprintf(stderr, "Failed to flip back buffer onto display.");

    disp->back_buffer_index = !disp->back_buffer_index;
    disp->back_buffer_is_locked = 1;
    disp->is_draw_needed = 0;
}

static void
root_init(struct root *root, sd_bus *bus, sd_event *event,
        uint32_t disp_width, uint32_t disp_height)
{
    root->is_active = true;
    root->bus = bus;
    root->event = event;
    root->focus = 0;

    for (int i = 0; i < NUM_WORKSPACES; i++) {
        struct workspace *workspace = &root->workspaces[i];

        workspace->focus = 0;

        for (int j = 0; j < NUM_WINDOWS_PER_WORKSPACE; j++) {
            struct window *window = &workspace->windows[j];
            switch (j) {
            case 0:
                window->x = 20;
                window->y = 20;
                window->width = (disp_width / 2) - 30;
                window->height = disp_height - 40;
                break;
            case 1:
                window->x = (disp_width / 2) + 10;
                window->y = 20;
                window->width = (disp_width / 2) - 30;
                window->height = (disp_height / 2) - 30;
                break;
            case 2:
                window->x = (disp_width / 2) + 10;
                window->y = (disp_height / 2) + 10;
                window->width = (disp_width / 2) - 30;
                window->height = (disp_height / 2) - 30;
                break;
            default:
                die("Failed to initialize workspaces.");
                break;
            }
            window->front_buffer = 0;
            window->mem = NULL;
            window->socket_fd = -1;
            window->pid = -1;
        }
    }
}

static struct window *
root_lookup_window_by_fd(struct root *root, int fd)
{
    for (int i = 0; i < NUM_WORKSPACES; i++) {
        struct workspace *workspace = &root->workspaces[i];
        for (int j = 0; j < NUM_WINDOWS_PER_WORKSPACE; j++) {
            struct window *window = &workspace->windows[j];
            if (window->socket_fd == fd)
                return window;
        }
    }
    return NULL;
}

static void send_keyboard_detached(struct window *window);
static void send_keyboard_attached(struct window *window, uint8_t *key_bits);

static void
root_set_workspace_focus(struct root *root, int focus)
{
    if (root->focus == focus)
        return;

    struct workspace *workspace;
    struct window *window;

    workspace = &root->workspaces[root->focus];
    window = &workspace->windows[workspace->focus];
    send_keyboard_detached(window);

    root->focus = focus;

    workspace = &root->workspaces[root->focus];
    window = &workspace->windows[workspace->focus];
    send_keyboard_attached(window, root->keyboard.key_bits);

    draw(root);
}

static void
choose_keyboard(uint32_t *major, uint32_t *minor)
{
    struct udev *udev = udev_new();
    if (udev == NULL)
        die("Failed to choose keyboard.");
    struct udev_enumerate *enumerate = udev_enumerate_new(udev);
    if (enumerate == NULL)
        die("Failed to choose keyboard.");
    int r;
    r = udev_enumerate_add_match_property(enumerate, "ID_INPUT_KEYBOARD", "1");
    if (r < 0)
        die("Failed to choose keyboard.");
    r = udev_enumerate_scan_devices(enumerate);
    if (r < 0)
        die("Failed to choose keyboard.");
    struct udev_list_entry *first_entry = udev_enumerate_get_list_entry(enumerate);
    if (first_entry == NULL)
        die("Failed to choose keyboard.");
    struct udev_list_entry *entry;
    *major = 0;
    *minor = 0;
    udev_list_entry_foreach(entry, first_entry) {
        const char *name = udev_list_entry_get_name(entry);
        if (name == NULL)
            continue;
        struct udev_device *device = udev_device_new_from_syspath(udev, name);
        if (device == NULL)
            continue;
        dev_t dev = udev_device_get_devnum(device);
        if (major(dev) == 0 && minor(dev) == 0) {
            udev_device_unref(device);
            continue;
        }
        *major = major(dev);
        *minor = minor(dev);
        udev_device_unref(device);
        break;
    }
    udev_enumerate_unref(enumerate);
    udev_unref(udev);
    if (*major == 0 && *minor == 0)
        die("Failed to choose keyboard.");
}

static void
switch_to_vt(int n)
{
    int fd = open("/dev/tty", O_RDWR);
    if (fd == -1)
        die("Failed to switch VT.");

    int r;

    r = ioctl(fd, VT_ACTIVATE, n);
    if (r == -1)
        die("Failed to switch VT.");

    r = ioctl(fd, VT_WAITACTIVE, n);
    if (r == -1)
        die("Failed to switch VT.");

    close(fd);
}

static int
sigchld_handler(sd_event_source *source,
        const struct signalfd_siginfo *siginfo, void *data)
{
    struct root *root = data;

    (void)source;
    (void)siginfo;

    for (;;) {
        int status;
        pid_t pid;

        pid = waitpid(-1, &status, WUNTRACED|WCONTINUED|WNOHANG);
        if (pid == 0)
            break;
        if (pid == -1) {
            if (errno == ECHILD)
                break;
            die("Failed to handle SIGCHLD.");
        }

        if (WIFEXITED(status)) {
            fprintf(stderr, "Child exited with status %d.\n",
                    (int)WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            fprintf(stderr, "Child was terminated by signal %d.\n",
                    (int)WTERMSIG(status));
        } else if (WIFSTOPPED(status)) {
            fprintf(stderr, "Child was stopped.\n");
            continue;
        } else if (WIFCONTINUED(status)) {
            fprintf(stderr, "Child was continued.\n");
            continue;
        }

        for (int i = 0; i < NUM_WORKSPACES; i++) {
            struct workspace *workspace = &root->workspaces[i];
            for (int j = 0; j < NUM_WINDOWS_PER_WORKSPACE; j++) {
                struct window *window = &workspace->windows[j];
                if (window->pid == pid) {
                    window->front_buffer = 0;
                    size_t mem_size =
                        2 * BYTES_PER_PIXEL * window->width * window->height;
                    int r = munmap(window->mem, mem_size);
                    if (r == -1)
                        die("Failed to release draw buffers.");
                    window->mem = NULL;
                    close(window->socket_fd);
                    window->socket_fd = -1;
                    window->pid = -1;
                }
            }
        }
    }

    draw(root);

    return 0;
}

static void
send_welcome(struct window *window)
{
    struct shell_message_welcome message = {
        .class = SHELL_MESSAGE_WELCOME,
        .width = window->width,
        .height = window->height,
    };

    ssize_t r = write(window->socket_fd, &message, sizeof(message));
    // TODO Rethink this.
    /*
    if (r == -1 && errno == EPIPE) {
        kill(window->pid, SIGKILL);
        return;
    }
    */
    if (r != sizeof(message)) {
        //die("Failed to communicate with client process.");
        perror("Failed to send welcome message to child process");
        return;
    }
}

static void
send_keyboard_event(struct window *window, struct input_event *event)
{
    if (window->pid == -1)
        return;

    struct shell_message_keyboard_event message = {
        .class = SHELL_MESSAGE_KEYBOARD_EVENT,
        .code = event->code,
        .value = event->value,
    };

    ssize_t r = write(window->socket_fd, &message, sizeof(message));
    // TODO Rethink this.
    /*
    if (r == -1 && (errno == EPIPE || errno == EBADF)) {
        kill(window->pid, SIGKILL);
        return;
    }
    */
    if (r != sizeof(message)) {
        //die("Failed to communicate with client process.");
        perror("Failed to send keyboard event message to child process");
        //exit(1);
        return;
    }
}

static void
send_reset_completed(struct window *window)
{
    struct shell_message_reset_completed message = {
        .class = SHELL_MESSAGE_RESET_COMPLETED,
    };

    ssize_t r = write(window->socket_fd, &message, sizeof(message));
    // TODO Rethink this.
    /*
    if (r == -1 && (errno == EPIPE || errno == EBADF)) {
        kill(window->pid, SIGKILL);
        return;
    }
    */
    if (r != sizeof(message)) {
        //die("Failed to communicate with client process.");
        perror("Failed to send buffer unlocked message to child process");
        //exit(1);
        return;
    }
}

static void
send_buffer_unlocked(struct window *window)
{
    if (window->pid == -1)
        return;

    struct shell_message_buffer_unlocked message = {
        .class = SHELL_MESSAGE_BUFFER_UNLOCKED,
    };

    ssize_t r = write(window->socket_fd, &message, sizeof(message));
    // TODO Rethink this.
    /*
    if (r == -1 && (errno == EPIPE || errno == EBADF)) {
        kill(window->pid, SIGKILL);
        return;
    }
    */
    if (r != sizeof(message)) {
        //die("Failed to communicate with client process.");
        perror("Failed to send buffer unlocked message to child process");
        //exit(1);
        return;
    }
}

static void
send_keyboard_detached(struct window *window)
{
    if (window->pid == -1)
        return;

    struct shell_message_keyboard_detached message = {
        .class = SHELL_MESSAGE_KEYBOARD_DETACHED,
    };

    ssize_t r = write(window->socket_fd, &message, sizeof(message));
    // TODO Rethink this.
    /*
    if (r == -1 && (errno == EPIPE || errno == EBADF)) {
        kill(window->pid, SIGKILL);
        return;
    }
    */
    if (r != sizeof(message)) {
        perror("Failed to send keyboard detached message to child process");
        return;
    }
}

static void
send_keyboard_attached(struct window *window, uint8_t *key_bits)
{
    if (window->pid == -1)
        return;

    struct shell_message_keyboard_attached message = {
        .class = SHELL_MESSAGE_KEYBOARD_ATTACHED,
    };
    memmove(message.key_bits, key_bits, SHELL_KEY_BITS_SIZE);

    ssize_t r = write(window->socket_fd, &message, sizeof(message));
    // TODO Rethink this.
    if (r != sizeof(message)) {
        perror("Failed to send keyboard attached message to child process");
        return;
    }
}

static int
child_message_event_handler(sd_event_source *source, int fd, uint32_t events,
        void *data)
{
    int drop(void) {
        sd_event_source_unref(source);
        return 0;
    }

    //  TODO What kinds of errors (EPOLLERR) can we get? How should they be
    //  handled?

    if (events & EPOLLERR)
        return drop();
    if (events & EPOLLHUP)
        return drop();
    if (events != EPOLLIN)
        die("Unexpected condition on socket.");

    static _Alignas(max_align_t) char packet[SHELL_PACKET_SIZE_MAX];
    size_t packet_size;
    for (;;) {
        ssize_t r = read(fd, packet, SHELL_PACKET_SIZE_MAX);
        if (r == -1 && errno == EINTR)
            continue;
        if (r == -1)
            return drop();
        packet_size = r;
        break;
    }

    struct root *root = data;
    struct window *window = root_lookup_window_by_fd(root, fd);
    if (window == NULL)
        return drop();

    //  TODO The following error should not be fatal. We need a general way to
    //  deal with protocol errors.

    if (packet_size < sizeof(uint16_t))
        die("Protocol error on socket.");

    uint16_t class = *(uint16_t *)packet;
    switch (class) {
    case SHELL_MESSAGE_RESET:
        if (window->front_buffer != 0) {
            size_t buffer_size = BYTES_PER_PIXEL * window->width * window->height;
            memmove(window->mem, window->mem + buffer_size, buffer_size);
            window->front_buffer = 0;
        }
        send_reset_completed(window);
        send_welcome(window);
        break;
    case SHELL_MESSAGE_SHOW:
        window->front_buffer = !window->front_buffer;
        send_buffer_unlocked(window);
        draw(root);
        break;
    default:
        fprintf(stderr, "Unexpected message class received from client.");
        break;
    }
    return 0;
}

static void
enable_cloexec(int fd)
{
    const char *error_message = "Failed to enable CLOEXEC on file.";
    int r = fcntl(fd, F_GETFD);
    if (r == -1)
        die(error_message);
    int flags = r;
    flags |= FD_CLOEXEC;
    r = fcntl(fd, F_SETFD, flags);
    if (r == -1)
        die(error_message);
}

static void
disable_nonblock(int fd)
{
    const char *error_message = "Failed to disable NONBLOCK on file.";
    int r = fcntl(fd, F_GETFL);
    if (r == -1)
        die(error_message);
    int flags = r;
    flags &= ~O_NONBLOCK;
    r = fcntl(fd, F_SETFL, flags);
    if (r == -1)
        die(error_message);
}

static void
launch_client(struct root *root, const char *path, const char *arg0)
{
    struct workspace *workspace = &root->workspaces[root->focus];
    struct window *window = &workspace->windows[workspace->focus];

    if (window->pid != -1)
        return;

    int r;

    size_t mem_size = 2 * BYTES_PER_PIXEL * window->width * window->height;
    char filename[64];
    r = snprintf(filename, sizeof(filename), "buffer_%u_%u",
            root->focus, workspace->focus);
    if (r >= sizeof(filename))
        die("Failed to create draw buffers for child process.");
    int mem_fd = syscall(__NR_memfd_create, filename, 0);
    if (mem_fd == -1)
        die("Failed to create draw buffers for child process.");
    do { r = ftruncate(mem_fd, mem_size); } while (r == -1 && errno == EINTR);
    if (r == -1)
        die("Failed to create draw buffers for child process.");
    void *mem = mmap(NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED,
            mem_fd, 0);
    if (mem == MAP_FAILED)
        die("Failed to create draw buffers for child process.");

    window->mem = mem;

    int socket_fds[2];
    r = socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_NONBLOCK, 0, socket_fds);
    if (r == -1)
        die("Failed to create socket for child process.");

    window->socket_fd = socket_fds[0];
    send_welcome(window);

    //  TODO This is weird. We pass the event_source parameter only to trigger
    //  a certain behavior; otherwise, the library allocates an event_source
    //  object that it deallocates when the event loop is deallocated. We have
    //  no use for the actual object here but we want to be able to deallocate
    //  it when the child terminates.

    sd_event_source *event_source;
    r = sd_event_add_io(root->event, &event_source, window->socket_fd,
            EPOLLIN, child_message_event_handler, root);
    if (r < 0)
        die("Failed to add child message event handler.");

    clear_buffer(window->mem, window->width, window->height);

    pid_t pid = fork();
    switch (pid) {
    case -1:
        die("Failed to fork child process.");
        break;
    case 0:
        {
            sigset_t sigset;
            int r;
            const char error_message[] =
                "Failed to initialize signal handling for child process.";
            r = sigemptyset(&sigset);
            if (r == -1)
                die(error_message);
            r = sigprocmask(SIG_SETMASK, &sigset, NULL);
            if (r == -1)
                die(error_message);
            signal(SIGPIPE, SIG_DFL);
            signal(SIGINT, SIG_DFL);
            signal(SIGTERM, SIG_DFL);
            signal(SIGCHLD, SIG_DFL);
        }
        close(socket_fds[0]);
        if (socket_fds[1] != 3) {

            //  TODO We close fd 3 here without any guarantee that mem_fd != 3.
            //  How to fix this problem?

            do {
                r = dup2(socket_fds[1], 3);
            } while (r == -1 && errno == EINTR);
            if (r == -1)
                die("Failed while launching child process.");
            close(socket_fds[1]);
        }
        if (mem_fd != 4) {
            do {
                r = dup2(mem_fd, 4);
            } while (r == -1 && errno == EINTR);
            if (r == -1)
                die("Failed while launching child process.");
            close(mem_fd);
        }
        execlp(path, arg0, (char *)NULL);
        die("Failed to exec child process.");
        break;
    default:
        window->pid = pid;
        close(mem_fd);
        close(socket_fds[1]);
        enable_cloexec(socket_fds[0]);
        break;
    }
}

static void
on_key_release(sd_event_source *source, int code)
{
    struct root *root = sd_event_source_get_userdata(source);

    struct keyboard *keyboard = &root->keyboard;
    if (code >= 0 && code <= KEY_MAX)
        clear_bit(keyboard->key_bits, (unsigned)code);
}

static void
on_key_press(sd_event_source *source, int code)
{
    int r;

    struct root *root = sd_event_source_get_userdata(source);

    sd_event *event = sd_event_source_get_event(source);

    struct keyboard *keyboard = &root->keyboard;
    if (code >= 0 && code <= KEY_MAX)
        set_bit(keyboard->key_bits, (unsigned)code);

    if (!fetch_bit(keyboard->key_bits, KEY_LEFTMETA) &&
            !fetch_bit(keyboard->key_bits, KEY_RIGHTMETA))
        return;

    switch (code) {
    case KEY_ESC:
        r = sd_event_exit(event, 0);
        if (r < 0)
            die("Failed to handle keyboard event.");
        return;
    case KEY_TAB:
        {
            struct workspace *workspace = &root->workspaces[root->focus];
            struct window *window = &workspace->windows[workspace->focus];
            uint8_t key_bits[SHELL_KEY_BITS_SIZE];
            memmove(key_bits, keyboard->key_bits, SHELL_KEY_BITS_SIZE);
            clear_bit(key_bits, KEY_LEFTMETA);
            clear_bit(key_bits, KEY_RIGHTMETA);
            clear_bit(key_bits, KEY_TAB);
            send_keyboard_detached(window);
            workspace->focus =
                (workspace->focus + 1) % NUM_WINDOWS_PER_WORKSPACE;
            window = &workspace->windows[workspace->focus];
            send_keyboard_attached(window, key_bits);
            draw(root);
        }
        return;
    case KEY_1:
        root_set_workspace_focus(root, 0);
        return;
    case KEY_2:
        root_set_workspace_focus(root, 1);
        return;
    case KEY_3:
        root_set_workspace_focus(root, 2);
        return;
    case KEY_4:
        root_set_workspace_focus(root, 3);
        return;
    case KEY_F1:
        switch_to_vt(1);
        return;
    case KEY_F2:
        switch_to_vt(2);
        return;
    case KEY_F3:
        switch_to_vt(3);
        return;
    case KEY_F4:
        switch_to_vt(4);
        return;
    }
    switch (keymap_lookup(code)) {
    case 'g':
        launch_client(root, "./germ", "germ");
        return;
    case 'l':
        launch_client(root, "./logo", "logo");
        return;
    case 't':
        launch_client(root, "./term", "term");
        return;
    case 's':
        {
            struct display_buffer *buf = display_front_buffer(&root->display);
            int w = buf->width;
            int h = buf->height;
            int stride = w;
            uint8_t *color = buf->mem;
            uint8_t *gray = malloc(w * h);
            if (gray == NULL)
                return;
            for (int i = 0; i < w * h; i++)
                gray[i] = color[4 * i];
            stbi_write_png("screenshot.png", w, h, 1, gray, stride);
            free(gray);
        }
        return;
    }
}

static int
bus_event_handler(sd_event_source *source, int fd, uint32_t revents,
        void *data)
{
    struct root *root = data;
    int r;

    for (;;) {
        r = sd_bus_process(root->bus, NULL);
        if (r < 0)
            die("Failed to process input from bus.");
        if (r == 0)
            break;
    }

    return 0;
}

static void
page_flip_handler(int device, unsigned int frame, unsigned int sec,
        unsigned int usec, void *data)
{
    struct root *root = data;
    struct display *disp = &root->display;
    disp->back_buffer_is_locked = 0;
    if (disp->is_draw_needed)
        draw(root);
}

static int
display_event_handler(sd_event_source *source, int fd, uint32_t revents,
        void *data)
{
    drmEventContext event_context = {
        .version = DRM_EVENT_CONTEXT_VERSION,
        .page_flip_handler = page_flip_handler,
    };
    int r;
    do {
        r = drmHandleEvent(fd, &event_context);
    } while (r == -1 && errno == EINTR);
    if (r)
        fprintf(stderr, "Failed to flip back buffer onto display.");

    return 0;
}

static int
keyboard_event_handler(sd_event_source *source, int fd, uint32_t revents,
        void *data)
{
    struct root *root = data;
    struct keyboard *keyboard = &root->keyboard;

    // TODO Review the handling of the various revents cases.

    if (revents & EPOLLERR)
        return -1;
    if (revents & EPOLLHUP)
        return -1;
    if (revents != EPOLLIN)
        die("Unexpected condition on keyboard.");

    struct input_event input_events[16];

    int ret;
    do {
        ret = read(fd, input_events, sizeof(input_events));
    } while (ret == -1 && errno == EINTR);

    if (ret == 0)
        die("Failed while reading keyboard.");
    if (ret % sizeof(struct input_event) != 0)
        die("Failed while reading keyboard.");

    int num_events = ret / sizeof(struct input_event);

    for (int i = 0; i < num_events; i++) {
        struct input_event *ev = &input_events[i];
        if (ev->type != EV_KEY)
            continue;
        switch (ev->value) {
        case 0:
            on_key_release(source, ev->code);
            break;
        case 1:
        case 2:
            on_key_press(source, ev->code);
            break;
        }
        struct workspace *workspace = &root->workspaces[root->focus];
        struct window *window = &workspace->windows[workspace->focus];
        if (window->pid == -1)
            continue;
        if (ev->code == KEY_LEFTMETA || ev->code == KEY_RIGHTMETA)
            continue;
        if (fetch_bit(keyboard->key_bits, KEY_LEFTMETA))
            continue;
        if (fetch_bit(keyboard->key_bits, KEY_RIGHTMETA))
            continue;
        send_keyboard_event(window, ev);
    }

    return 0;
}

static int
properties_changed_handler(sd_bus_message *m, void *data,
        sd_bus_error *ret_error)
{
    struct root *root = data;

    root->is_active = !root->is_active;

    if (root->is_active)
        fprintf(stderr, "Session is active.\n");
    else
        fprintf(stderr, "Session is not active.\n");

    if (root->is_active)
        draw(root);

    return 0;
}

static int
pause_device_handler(sd_bus_message *message, void *data,
        sd_bus_error *ret_error)
{
    int r;

    uint32_t major, minor;
    char *reason;

    r = sd_bus_message_read(message, "uus", &major, &minor, &reason);
    if (r < 0)
        die("Failed to read message from DBus.");

    fprintf(stderr, "Device paused: %d %d %s.\n", (int)major, (int)minor, reason);

    return 0;
}

static int
resume_device_handler(sd_bus_message *message, void *data,
        sd_bus_error *ret_error)
{
    int r;

    struct root *root = data;
    struct keyboard *keyboard = &root->keyboard;

    uint32_t major, minor;
    int fd;

    r = sd_bus_message_read(message, "uuh", &major, &minor, &fd);
    if (r < 0)
        die("Failed to communicate with DBus.");

    fprintf(stderr, "Device resumed: %d %d.\n", (int)major, (int)minor);

    if (major == keyboard->major && minor == keyboard->minor) {
        fd = dup(fd);
        enable_cloexec(fd);
        r = keyboard_connect(keyboard, fd, keyboard->major, keyboard->minor);
        if (r)
            die("Failed to connect to the keyboard.");

        r = sd_event_add_io(root->event, NULL, fd, EPOLLIN,
                keyboard_event_handler, root);
        if (r < 0)
            die("Failed to add keyboard event handler.");
    } else if (major == 226 && minor == 0) {
        struct display *disp = &root->display;
        if (disp->state & DISPLAY_CONTROLLER_CHOSEN) {
            struct display_buffer *front_buffer = display_front_buffer(disp);
            r = drmModeSetCrtc(disp->fd, disp->crtc, front_buffer->fb, 0, 0,
                    &disp->conn, 1, &disp->mode);
            if (r)
                die("Failed to reconnect to display.");

            draw(root);
        }
    }

    return 0;
}

/*
 *  easy_sprintf is like sprintf but it allocates a buffer for you.
 */
static char *
easy_sprintf(const char *template, ...) __attribute__((format(printf, 1, 2)));

static char *
easy_sprintf(const char *template, ...)
{
    /*
     *  We use vsnprintf twice: once to find out how much space is needed and
     *  once to store the result.
     */

    va_list args;

    int ret;
    char scratch[1];

    va_start(args, template);
    ret = vsnprintf(scratch, 0, template, args);
    va_end(args);
    if (ret <= 0)
        goto fail;

    size_t size = ret + 1;
    char *s = xmalloc(size);

    va_start(args, template);
    ret = vsnprintf(s, size, template, args);
    va_end(args);
    if (ret + 1 != size)
        goto fail;

    return s;

fail:
    die("Failed while formatting a string.");
}

int
main(void)
{
    int r;
    struct root root;
    sd_bus *bus = NULL;
    sd_bus_error error = SD_BUS_ERROR_NULL;
    sd_bus_message *message = NULL;
    sd_event *event = NULL;
    char *session_path = NULL;

    r = sd_bus_default_system(&bus);
    if (r < 0)
        die("Failed to connect to the system bus.");

    // Determine the logind object path for our current session.

    {
        char *session_short_name = NULL;
        r = sd_pid_get_session(getpid(), &session_short_name);
        if (r < 0)
            die("Failed to determine user session name.");
        session_path = easy_sprintf("/org/freedesktop/login1/session/%s",
                session_short_name);
        free(session_short_name);
    }

    // Take control of the session.

    r = sd_bus_call_method(bus, "org.freedesktop.login1", session_path,
            "org.freedesktop.login1.Session", "TakeControl", &error, &message,
            "b", (int32_t)0);
    if (r < 0)
        die("Failed to take control of session.");

    sd_bus_message_unref(message);

    // Take control of the display.

    struct display *disp = &root.display;
    display_init(disp);
    uint32_t disp_width;
    uint32_t disp_height;
    {
        uint32_t major = 226;
        uint32_t minor = 0;
        int32_t paused;
        int fd;

        r = sd_bus_call_method(bus, "org.freedesktop.login1", session_path,
                "org.freedesktop.login1.Session", "TakeDevice",
                &error, &message,
                "uu", major, minor);
        if (r < 0)
            die("Failed while communicating with logind.");

        r = sd_bus_message_read(message, "hb", &fd, &paused);
        if (r < 0)
            die("Failed while communicating with logind.");

        if (paused)
            die("Failed to take control of the display.");

        fd = dup(fd);
        sd_bus_message_unref(message);

        enable_cloexec(fd);
        disable_nonblock(fd);

        display_connect(disp, fd);

        struct display_buffer *back_buffer = display_back_buffer(disp);
        disp_width = back_buffer->width;
        disp_height = back_buffer->height;
    }

    // Take control of the keyboard.

    struct keyboard *keyboard = &root.keyboard;
    {
        int fd;
        uint32_t major;
        uint32_t minor;
        int32_t paused;

        choose_keyboard(&major, &minor);

        r = sd_bus_call_method(bus, "org.freedesktop.login1", session_path,
                "org.freedesktop.login1.Session", "TakeDevice",
                &error, &message,
                "uu", major, minor);
        if (r < 0)
            die("Failed while communicating with logind.");

        r = sd_bus_message_read(message, "hb", &fd, &paused);
        if (r < 0)
            die("Failed while communicating with logind.");

        if (paused)
            die("Failed to take control of the keyboard.");

        fd = dup(fd);
        enable_cloexec(fd);

        sd_bus_message_unref(message);

        r = keyboard_connect(keyboard, fd, major, minor);
        if (r)
            die("Failed to connect to the keyboard.");
    }

    // Create the event loop object.

    r = sd_event_default(&event);
    if (r < 0)
        die("Failed to initialize the event loop.");

    //  Initialize the root.
    //
    //  TODO This whole initialization process needs attention.

    root_init(&root, bus, event, disp_width, disp_height);

    // Subscribe to DBus signals.

    {
        char *pattern = NULL;

        pattern = easy_sprintf("type='signal',path='%s',"
                "interface='org.freedesktop.login1.Session',"
                "member='PauseDevice'",
                session_path);
        r = sd_bus_add_match(bus, NULL, pattern, pause_device_handler, &root);
        if (r < 0)
            die("Failed to subscribe to PauseDevice signal.");
        free(pattern);

        pattern = easy_sprintf("type='signal',path='%s',"
                "interface='org.freedesktop.login1.Session',"
                "member='ResumeDevice'",
                session_path);
        r = sd_bus_add_match(bus, NULL, pattern, resume_device_handler, &root);
        if (r < 0)
            die("Failed to subscribe to ResumeDevice signal.");
        free(pattern);

        pattern = easy_sprintf("type='signal',path='%s',"
                "interface='org.freedesktop.DBus.Properties',"
                "member='PropertiesChanged'",
                session_path);
        r = sd_bus_add_match(bus, NULL, pattern, properties_changed_handler,
                &root);
        if (r < 0)
            die("Failed to subscribe to PropertiesChanged signal.");
        free(pattern);
    }

    // Add signals to the event loop.

    {
        sigset_t sigset;
        const char error_message[] = "Failed to initialize signal handling.";

        signal(SIGPIPE, SIG_IGN);

        r = sigemptyset(&sigset);
        if (r == -1)
            die(error_message);

        r = sigaddset(&sigset, SIGTERM);
        if (r == -1)
            die(error_message);

        r = sigaddset(&sigset, SIGINT);
        if (r == -1)
            die(error_message);

        r = sigaddset(&sigset, SIGCHLD);
        if (r == -1)
            die(error_message);

        r = sigprocmask(SIG_BLOCK, &sigset, NULL);
        if (r == -1)
            die(error_message);

        r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
        if (r < 0)
            die(error_message);

        r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
        if (r < 0)
            die(error_message);

        r = sd_event_add_signal(event, NULL, SIGCHLD, sigchld_handler, &root);
        if (r < 0)
            die(error_message);
    }

    // Add the display to the event loop.

    r = sd_event_add_io(event, NULL, disp->fd, EPOLLIN,
            display_event_handler, &root);
    if (r < 0)
        die("Failed to add display event handler.");

    // Add the keyboard to the event loop.

    r = sd_event_add_io(event, NULL, keyboard->fd, EPOLLIN,
            keyboard_event_handler, &root);
    if (r < 0)
        die("Failed to add keyboard event handler.");

    // Add the bus to the event loop.

    {
        int fd = sd_bus_get_fd(bus);
        if (fd < 0)
            die("Failed to initialize DBus monitoring.");

        r = sd_event_add_io(event, NULL, fd, EPOLLIN, bus_event_handler, &root);
        if (r < 0)
            die("Failed to initialize DBus monitoring.");
    }

    // Draw the first frame and enter the event loop.

    draw(&root);

    r = sd_event_loop(event);
    if (r < 0)
        die("Failed while processing events.");

    // Disconnect from display.
    
    display_disconnect(disp);

    // Release control of the session.

    r = sd_bus_call_method(bus, "org.freedesktop.login1", session_path,
            "org.freedesktop.login1.Session", "ReleaseControl",
            &error, &message,
            "");
    if (r < 0)
        die("Failed to release control of session.");

    sd_bus_message_unref(message);

    // Cleanup and exit.

    sd_event_unref(event);
    free(session_path);
    sd_bus_unref(bus);

    return 0;
}
