#include <pipewire/pipewire.h>
#include "../../../l1/core/util.h"
#include <spa/param/audio/format-utils.h>
#include <math.h>
#include <stdio.h>
#include "../../../l1/system/pthread.h"
#include "../../margaret/time_utils.h"
#include "../../marie/graphics_geom.h"
#include "../../marie/rasterization.h"

#include <fcntl.h>
#include <limits.h>
#include <sys/mman.h>
#include <wayland-client.h>
#include "../../../../gen/l_wl_protocols/xdg-shell-client.h"
#include <xkbcommon/xkbcommon.h>

#include "liza_collection.h"

#define DEFAULT_RATE 44100
#define DEFAULT_CHANNELS 2
#define DEFAULT_VOLUME 0.1

typedef struct {
    bool stop;
    VecBoxLizaSound new_sounds;
} AudioThreadCommands;

void AudioThreadCommands_drop(AudioThreadCommands self) {
    VecBoxLizaSound_drop(self.new_sounds);
}

/* Not movable and even so more it isn't trivially movable */
typedef struct {
    pthread_mutex_t mut;
    AudioThreadCommands cmd;
} AudioThreadBridge;

/* non-copyable trivially movable (because of BoxLizaSound field) */
typedef struct {
    BoxLizaSound gen;
    size_t remaining_frames;
} PlayingSound;

void PlayingSound_drop(PlayingSound self) {
    BoxLizaSound_drop(self.gen);
}

#include "../../../../gen/l1/eve/r2/VecPlayingSound.h"

double fix_loud_sound(double a) {
    const double L = 0.5;
    const double b = (1 - L) / exp(-L / (1 - L));
    const double d = 1 / (1 - L);
    if (a < -L) {
        return - (1 - d * exp(b * a));
    } else if (a < L) {
        return a;
    } else {
        return 1 - d * exp(-b * a);
    }
}

typedef struct {
    struct pw_main_loop *pw_main_loop;
    struct pw_context *pw_context;
    struct pw_core *pw_core;
    struct pw_registry *pw_registry;
    struct spa_hook registry_listener_hook;
    struct pw_stream *pw_stream;
    struct spa_hook stream_listener_hook;
    AudioThreadBridge* bridge;

    VecPlayingSound playing_sounds;
    double time_written;
} audio_thread_state_t;

static void r2a_app_h_pw_stream_process(void *ug){
    audio_thread_state_t* state = ug;
    safe_pthread_mutex_lock(&state->bridge->mut);
    AudioThreadCommands* incoming_cmd = &state->bridge->cmd;
    if (incoming_cmd->stop) {
        pw_main_loop_quit(state->pw_main_loop);
        safe_pthread_mutex_unlock(&state->bridge->mut);
        return;
    }
    for (size_t i = 0; i < incoming_cmd->new_sounds.len; i++) {
        /* effectively moved out (will resize incoming_cmd->new_sounds to 0 soon) */
        BoxLizaSound snd = *VecBoxLizaSound_at(&incoming_cmd->new_sounds, i);
        VecPlayingSound_append(&state->playing_sounds, (PlayingSound){
            .gen = snd, .remaining_frames = snd.t->get_duration(snd.m)
        });
    }
    incoming_cmd->new_sounds.len = 0; /* We moved everything out */
    safe_pthread_mutex_unlock(&state->bridge->mut);

    struct pw_buffer *b = pw_stream_dequeue_buffer(state->pw_stream);
    if (!b) {
        pw_log_warn("out of buffers");
        return;
    }
    struct spa_buffer *buf = b->buffer;
    uint32_t stride = sizeof(int16_t) * DEFAULT_CHANNELS;
    uint32_t n_frames = buf->datas[0].maxsize / stride;
    if (b->requested)
        n_frames = SPA_MIN(b->requested, n_frames);

    int16_t *dst = buf->datas[0].data;
    if (dst == NULL)
        return;

    for (uint32_t f = 0; f < n_frames; f++) {
        double a = 0;
        for (size_t i = 0; i < state->playing_sounds.len;) {
            PlayingSound* snd = VecPlayingSound_mat(&state->playing_sounds, i);
            if (snd->remaining_frames == 0) {
                /* snd invalidated, but iteration continues */
                VecPlayingSound_unordered_pop_and_drop(&state->playing_sounds, i);
                continue;
            }
            a += snd->gen.t->next(snd->gen.m);
            snd->remaining_frames--;
            i++;
        }
        int16_t val = (int16_t)round(fix_loud_sound(a * DEFAULT_VOLUME) * 32767.0);
        for (int c = 0; c < DEFAULT_CHANNELS; c++)
            *dst++ = val;
    }
    state->time_written += (double)n_frames / DEFAULT_RATE;


    buf->datas[0].chunk->offset = 0;
    buf->datas[0].chunk->stride = (int32_t)stride;
    buf->datas[0].chunk->size = n_frames * stride;

    pw_stream_queue_buffer(state->pw_stream, b);
}

const static struct pw_stream_events r2a_app_h_pw_stream_listener = {
    PW_VERSION_STREAM_EVENTS,
    .process = r2a_app_h_pw_stream_process
};

void AudioThreadBridge_init(AudioThreadBridge* place) {
    safe_pthread_mutex_init(&place->mut, NULL);
    memset(&place->cmd, 0, sizeof(AudioThreadCommands));
}

void AudioThreadBridge_destroy(AudioThreadBridge* resid) {
    AudioThreadCommands_drop(resid->cmd);
    pthread_mutex_destroy(&resid->mut);
}

/* Full of audio */
void* audio_thread(void* ug) {
    audio_thread_state_t state = { .playing_sounds = VecPlayingSound_new(), .bridge = ug };

    state.pw_main_loop = pw_main_loop_new(NULL /* properties */);
    if (!state.pw_main_loop)
        abortf("pw_main_loop_new");
    state.pw_context = pw_context_new(pw_main_loop_get_loop(state.pw_main_loop), NULL, 0);
    if (!state.pw_context)
        abortf("pw_context_new\n");
    state.pw_core = pw_context_connect(state.pw_context, NULL /* properties */, 0 /* user_data size */);
    if (!state.pw_core)
        abortf("pw_context_connect\n");
    state.pw_registry = pw_core_get_registry(state.pw_core, PW_VERSION_REGISTRY, 0 /* user_data size */);
    if (!state.pw_registry)
        abortf("pw_core_get_registry\n");

    state.pw_stream = pw_stream_new(state.pw_core, "audio-src", pw_properties_new(
        PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Music", NULL));

    struct spa_hook stream_listener_hook;
    pw_stream_add_listener(state.pw_stream, &stream_listener_hook, &r2a_app_h_pw_stream_listener, &state);

    const struct spa_pod *stream_params[1];
    uint8_t pod_buffer[1024];
    struct spa_pod_builder stream_params_b = SPA_POD_BUILDER_INIT(pod_buffer, sizeof(pod_buffer));
    stream_params[0] = spa_format_audio_raw_build(&stream_params_b, SPA_PARAM_EnumFormat,
                        &SPA_AUDIO_INFO_RAW_INIT(
                                .format = SPA_AUDIO_FORMAT_S16,
                                .channels = DEFAULT_CHANNELS,
                                .rate = DEFAULT_RATE ));

    pw_stream_connect(state.pw_stream,
                  PW_DIRECTION_OUTPUT,
                  PW_ID_ANY,
                  PW_STREAM_FLAG_AUTOCONNECT |
                  PW_STREAM_FLAG_MAP_BUFFERS |
                  PW_STREAM_FLAG_RT_PROCESS,
                  stream_params, 1);

    pw_main_loop_run(state.pw_main_loop);
    printf("Exited mainloop\n");

    pw_stream_destroy(state.pw_stream);
    pw_proxy_destroy((struct pw_proxy*)state.pw_registry);
    pw_core_disconnect(state.pw_core);
    pw_context_destroy(state.pw_context);
    pw_main_loop_destroy(state.pw_main_loop);

    VecPlayingSound_drop(state.playing_sounds);
    return NULL;
}

/* No we specify what happens in the main thread */

#define MAX_BUFFER_WIDTH 1000
#define MAX_BUFFER_HEIGHT 800
#define SWAPCHAIN_SLOTS 2

_Static_assert(INT32_MAX / MAX_BUFFER_WIDTH / MAX_BUFFER_HEIGHT / 4 / SWAPCHAIN_SLOTS > 1, "Swapchain is too big");

static int create_shm_file(){
    char name[] = "/prototype1-XXXXXXXXXXXX";
    U64 v = ((U64)rand() << 32) + (U64)rand();
    for (int i = 0; i < 12; i++) {
        name[sizeof(name) - 1 - 12 + i] = 'A' + (v&15) + (v&16)*2;
        v >>= 5;
    }
    int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
    if (fd < 0)
        abortf("shm_open(\"%s\")", name);
    if (shm_unlink(name) < 0)
        abortf("shm_unlink(\"%s\")", name);
    return fd;
}

static int allocate_shm_file(size_t size) {
    int fd = create_shm_file();
    int ret;
    do {
        ret = ftruncate(fd, size);
    } while (ret < 0 && errno == EINTR);
    if (ret < 0) {
        abortf("ftruncate(%ld)", size);
    }
    return fd;
}

typedef struct {
    int fd;
    uint32_t* data;
    struct wl_shm_pool* pool;
    bool want_to_draw;
    struct wl_buffer* used_buffers[SWAPCHAIN_SLOTS];
} swapchain_t;

typedef struct {
    BoxLizaInstrument liza;
    float turn_progress;
} MyInstrument;

MyInstrument MyInstrument_new(BoxLizaInstrument liza) {
    return (MyInstrument){liza, 0};
}

void MyInstrument_drop(MyInstrument self) {
    BoxLizaInstrument_drop(self.liza);
}

#include "../../../../gen/l1/eve/liza/VecMyInstrument.h"

typedef struct {
    /* Globals */
    struct wl_display *wl_display;
    struct wl_registry *wl_registry;
    struct wl_shm *wl_shm;
    struct wl_compositor *wl_compositor;
    struct xdg_wm_base *xdg_wm_base;
    struct wl_seat *wl_seat;
    /* Objects */
    swapchain_t swapchain;
    struct wl_surface *wl_surface;
    struct wl_callback* wl_callback;
    struct xdg_surface *xdg_surface;
    struct xdg_toplevel *xdg_toplevel;
    /* inputs */
    struct wl_pointer* pointer;
    struct wl_keyboard* keyboard;
    /* xkb */
    struct xkb_state* xkb_state;
    struct xkb_context* xkb_context;
    struct xkb_keymap* xkb_keymap;
    /* app state */
    vec2 pointer_pos;
    bool first_0x80_keys[0x80];
    uint32_t last_frame_time;
    int32_t width;
    int32_t height;
    bool closed;
    /* instruments */
    VecMyInstrument instruments;
    size_t selected_instrument;
    /* Bridge to audio thread */
    AudioThreadBridge* audio_thread_bridge;
} wl_app_state_t;

typedef struct {
    uint32_t* data;
    int32_t width;
    int32_t height;
} WhereToDrawTriangle;

U32 color_vec4_to_color_u32(vec4 color) {
    return (((U32)roundf(color.w * 255)) << 24) + (((U32)roundf(color.x * color.w * 255)) << 16) +
        (((U32)roundf(color.y * color.w * 255)) << 8) + (((U32)roundf(color.z * color.w * 255)) << 0);
}

void draw_frame_h_rasterizer_cb(void* ug, S32 x, S32 y, vec4 attr) {
    WhereToDrawTriangle* g = ug;
    if (0 <= x && x < g->width && 0 <= y && y < g->height)
        g->data[y * g->width + x] = color_vec4_to_color_u32(attr);
}

void draw_frame(wl_app_state_t* state, uint32_t* data, int32_t width, int32_t height) {
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            data[y * width + x] = 0xFF00FFFF;
        }
    }

    for (size_t i = 0; i < state->instruments.len; i++) {
        MyInstrument* inst = VecMyInstrument_mat(&state->instruments, i);
        vec2 pos = {60 + 80 * (float)i, 60};
        WhereToDrawTriangle aboba = {data, width, height};
        vec2 triangle[3] = {};
        for (int v = 0; v < 3; v++) {
            triangle[v] = vec2_add_vec2(pos, vec2_mul_scal(
                marie_trigonom_circle(3 * M_PIf / 2 + (float)v * 2 * M_PIf / 3 + M_PIf * inst->turn_progress),
                10 + 10 * inst->turn_progress));
        }
        vec4 color = {1, 1 - inst->turn_progress, 1 - inst->turn_progress, 1};
        marie_rasterize_triangle_with_attr(
            (MariePlaneVertAttr){.pos = triangle[0], .attr = color},
            (MariePlaneVertAttr){.pos = triangle[1], .attr = color},
            (MariePlaneVertAttr){.pos = triangle[2], .attr = color},
            (FnMarieRasterizerCallback){.fn = draw_frame_h_rasterizer_cb, .guest = &aboba});
    }
}

void update_state(wl_app_state_t* state, uint32_t fl) {
    const float width = (float)(state->width < MAX_BUFFER_WIDTH ? state->width : MAX_BUFFER_WIDTH);
    const float height = (float)(state->height < MAX_BUFFER_HEIGHT ? state->height : MAX_BUFFER_HEIGHT);
    float dur = (float)fl / 1000;
    for (size_t i = 0; i < state->instruments.len; i++) {
        MyInstrument* inst = VecMyInstrument_mat(&state->instruments, i);
        if (i == state->selected_instrument) {
            inst->turn_progress = MIN_float(1, inst->turn_progress + dur * 1);
        } else {
            inst->turn_progress = MAX_float(0, inst->turn_progress - dur * 1);
        }
    }
}

static void r2a_app_h_wl_buffer_release(void *data, struct wl_buffer *buffer) {
    wl_app_state_t* state = data;
    for (int ij = 0; ij < SWAPCHAIN_SLOTS; ij++) {
        if (state->swapchain.used_buffers[ij] == buffer) {
            // printf("Buffer %p was released and destroyed\n", buffer);
            wl_buffer_destroy(buffer);
            state->swapchain.used_buffers[ij] = NULL;
            return;
        }
    }
    abortf("HUH??!!!\n");
}

static const struct wl_buffer_listener r2a_app_h_wl_buffer_listener = {
    .release = r2a_app_h_wl_buffer_release,
};

int swapchain_take_slot(wl_app_state_t *state) {
    for (int ij = 0; ij < SWAPCHAIN_SLOTS; ij++) {
        if (!state->swapchain.used_buffers[ij]) {
            const int32_t width = state->width < MAX_BUFFER_WIDTH ? state->width : MAX_BUFFER_WIDTH;
            const int32_t height = state->height < MAX_BUFFER_HEIGHT ? state->height : MAX_BUFFER_HEIGHT;
            struct wl_buffer* s = wl_shm_pool_create_buffer(
                state->swapchain.pool, ij * 4 * MAX_BUFFER_WIDTH * MAX_BUFFER_HEIGHT,
                width, height, 4 * width, WL_SHM_FORMAT_ARGB8888);
            state->swapchain.used_buffers[ij] = s;
            if (!s)
                abortf("wl_shm_pool_create_buffer\n");
            wl_buffer_add_listener(s, &r2a_app_h_wl_buffer_listener, state);

            return ij;
        }
    }
    return -1;
}

void try_drawing_frame(wl_app_state_t *state){
    if (!state->swapchain.want_to_draw)
        return;
    int ij = swapchain_take_slot(state);
    // printf("Got slot %d", ij);
    if (ij < 0)
        return;
    assert(ij < SWAPCHAIN_SLOTS);
    state->swapchain.want_to_draw = false;
    const int32_t width = state->width < MAX_BUFFER_WIDTH ? state->width : MAX_BUFFER_WIDTH;
    const int32_t height = state->height < MAX_BUFFER_HEIGHT ? state->height : MAX_BUFFER_HEIGHT;

    /* Draw checkerboxed background */
    uint32_t* data = state->swapchain.data + ij * MAX_BUFFER_WIDTH * MAX_BUFFER_HEIGHT;
    draw_frame(state, data, width, height);
    wl_surface_attach(state->wl_surface, state->swapchain.used_buffers[ij], 0, 0);
    wl_surface_damage_buffer(state->wl_surface, 0, 0, INT32_MAX, INT32_MAX);
    wl_surface_commit(state->wl_surface);
}

static void r2a_app_h_xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial){
    wl_app_state_t *state = data;
    printf("XDG surface configured!\n");
    xdg_surface_ack_configure(xdg_surface, serial);
    // todo: synchronize with frame event
    state->swapchain.want_to_draw = true;
    try_drawing_frame(state);
}

static void r2a_app_h_xdg_toplevel_configure(
    void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states
    ){
    wl_app_state_t *state = data;
    printf("XDG toplevel configured to (%d %d)\n", width, height);
    if (width <= 0 || height < 0)
        return;
    state->width = width;
    state->height = height;
}

static void r2a_app_h_xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel){
    wl_app_state_t *state = data;
    state->closed = true;
}

static const struct xdg_toplevel_listener r2a_app_h_xdg_toplevel_listener = {
    .configure = r2a_app_h_xdg_toplevel_configure,
    .close = r2a_app_h_xdg_toplevel_close,
};


static const struct xdg_surface_listener xdg_surface_listener = {
    .configure = r2a_app_h_xdg_surface_configure,
};

static void r2a_app_h_xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial){
    xdg_wm_base_pong(xdg_wm_base, serial);
}

static const struct xdg_wm_base_listener r2a_app_h_xdg_wm_base_listener = {
    .ping = r2a_app_h_xdg_wm_base_ping,
};



static void r2a_app_h_wl_keyboard_keymap(
    void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size
    ) {
    wl_app_state_t* state = data;
    if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
        abortf("O_o");
    char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
    if (map_shm == MAP_FAILED)
        abortf("Couldn't mmap new keymap ha-ha\n");

    xkb_keymap_unref(state->xkb_keymap);
    xkb_state_unref(state->xkb_state);
    state->xkb_keymap = xkb_keymap_new_from_string(
         state->xkb_context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
    munmap(map_shm, size);
    close(fd);
    state->xkb_state = xkb_state_new(state->xkb_keymap);
    printf("Keymap changed!\n");
    memset(&state->first_0x80_keys, 0, sizeof(state->first_0x80_keys));
}

static void r2a_app_h_wl_keyboard_enter(
    void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys
    ) {
    wl_app_state_t* state = data;
    // todo: fill first_0x80_keys array
    // todo: move all of this into a separate framework
}

static void r2a_app_h_wl_keyboard_leave(
    void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface
    ) {
    wl_app_state_t* state = data;
    memset(&state->first_0x80_keys, 0, sizeof(state->first_0x80_keys));
}

static void r2a_app_h_wl_keyboard_key(
    void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t key_action
    ) {
    wl_app_state_t* state = data;
    uint32_t actual_key = key + 8;
    /* It actually could be nokey keysym. You never know without checking */
    xkb_keysym_t keysym = xkb_state_key_get_one_sym(state->xkb_state, actual_key);
    if (keysym < 0x80 && key_action == WL_KEYBOARD_KEY_STATE_RELEASED) {
        state->first_0x80_keys[keysym] = false;
    } else if (keysym < 0x80 && key_action == WL_KEYBOARD_KEY_STATE_PRESSED) {
        state->first_0x80_keys[keysym] = true;
    }
    if (keysym == XKB_KEY_0 && key_action == WL_KEYBOARD_KEY_STATE_PRESSED) {
        state->selected_instrument = 10;
    }
    if (XKB_KEY_1 <= keysym && keysym <= XKB_KEY_9 && key_action == WL_KEYBOARD_KEY_STATE_PRESSED) {
        state->selected_instrument = keysym - XKB_KEY_1;
    }
    const xkb_keysym_t octave[] = {
    XKB_KEY_a, XKB_KEY_w, XKB_KEY_s, XKB_KEY_e, XKB_KEY_d, XKB_KEY_r,
        XKB_KEY_f, XKB_KEY_t, XKB_KEY_g, XKB_KEY_y, XKB_KEY_h, XKB_KEY_u
    };
    if (state->selected_instrument < state->instruments.len) {
        // todo: add box to ref and box to mref methods
        const BoxLizaInstrument* instrument = &VecMyInstrument_at(&state->instruments, state->selected_instrument)->liza;
        for (int i = 0; i < ARRAY_SIZE(octave); i++) {
            if (keysym == octave[i] && key_action == WL_KEYBOARD_KEY_STATE_PRESSED) {
                safe_pthread_mutex_lock(&state->audio_thread_bridge->mut);
                VecBoxLizaSound_append(&state->audio_thread_bridge->cmd.new_sounds,
                    instrument->t->ding(instrument->m, 440 * pow(2, (double)i / 12), 0.9));
                safe_pthread_mutex_unlock(&state->audio_thread_bridge->mut);
                break;
            }
        }
    }
}

static void r2a_app_h_wl_keyboard_modifiers(
    void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
    uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group
    ) {
    wl_app_state_t* state = data;
    xkb_state_update_mask(state->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
}

static void r2a_app_h_wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay){
    printf("Repeat timings changed: rate = %d, delay = %d", rate, delay);
}

static const struct wl_keyboard_listener r2a_app_h_wl_keyboard_listener = {
    .keymap = r2a_app_h_wl_keyboard_keymap,
    .enter = r2a_app_h_wl_keyboard_enter,
    .leave = r2a_app_h_wl_keyboard_leave,
    .key = r2a_app_h_wl_keyboard_key,
    .modifiers = r2a_app_h_wl_keyboard_modifiers,
    .repeat_info = r2a_app_h_wl_keyboard_repeat_info,
};


static void r2a_app_h_wl_pointer_enter(
    void *data, struct wl_pointer *wl_pointer, uint32_t serial,
    struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y
    ) {
    wl_app_state_t *state = data;
    state->pointer_pos = (vec2){(float)surface_x / 256.f, (float)surface_y / 256.f};
}

static void r2a_app_h_wl_pointer_leave(
    void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface
    ) {
    wl_app_state_t *state = data;
}

static void r2a_app_h_wl_pointer_motion(
    void *data,struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y
    ) {
    wl_app_state_t *state = data;
    state->pointer_pos = (vec2){(float)surface_x / 256.f, (float)surface_y / 256.f};
}

static void r2a_app_h_wl_pointer_button(
    void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t btn_action
    ) {
    wl_app_state_t *state = data;
    if (btn_action == WL_POINTER_BUTTON_STATE_PRESSED) {
        printf("button = %u\n", button);
        if (button == 0x110) {
            printf("Bup!\n");
        }
    }
}

static void r2a_app_h_wl_pointer_axis(
    void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value
    ) {
    wl_app_state_t *state = data;
}

static void r2a_app_h_wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) {
    wl_app_state_t *state = data;
}

const struct wl_pointer_listener r2a_app_h_wl_pointer_listener = {
    .enter = r2a_app_h_wl_pointer_enter,
    .leave = r2a_app_h_wl_pointer_leave,
    .motion = r2a_app_h_wl_pointer_motion,
    .button = r2a_app_h_wl_pointer_button,
    .axis = r2a_app_h_wl_pointer_axis,
    .frame = r2a_app_h_wl_pointer_frame
};

static void r2a_app_h_wl_seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) {
    wl_app_state_t* state = data;
    if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
        state->pointer = wl_seat_get_pointer(wl_seat);
        if (!state->pointer)
            abortf("wl_seat_get_pointer\n");
        wl_pointer_add_listener(state->pointer, &r2a_app_h_wl_pointer_listener, state);
    }
    if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
        state->keyboard = wl_seat_get_keyboard(wl_seat);
        if (!state->keyboard)
            abortf("wl_seat_get_keyboard\n");
        wl_keyboard_add_listener(state->keyboard, &r2a_app_h_wl_keyboard_listener, state);
    }
}

static void r2a_app_h_wl_seat_name(void* data, struct wl_seat* wl_seat, const char* name) {
    printf("Our seat name: %s\n", name);
}

static const struct wl_seat_listener r2a_app_h_wl_seat_listener = {
    .capabilities = r2a_app_h_wl_seat_capabilities,
    .name = r2a_app_h_wl_seat_name,
};

static void r2a_app_h_wl_registry_global(
    void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version
    ) {
    wl_app_state_t *state = data;
    if (strcmp(interface, wl_shm_interface.name) == 0) {
        state->wl_shm = wl_registry_bind(wl_registry, name, &wl_shm_interface, 1);
        if (!state->wl_shm)
            abortf("wl_registry_bind\n");
    } else if (strcmp(interface, wl_compositor_interface.name) == 0) {
        state->wl_compositor = wl_registry_bind(wl_registry, name, &wl_compositor_interface, 4);
        if (!state->wl_compositor)
            abortf("wl_registry_bind\n");
    } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
        state->xdg_wm_base = wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, 1);
        if (!state->xdg_wm_base)
            abortf("wl_registry_bind\n");
        xdg_wm_base_add_listener(state->xdg_wm_base, &r2a_app_h_xdg_wm_base_listener, state);
    } else if (strcmp(interface, wl_seat_interface.name) == 0) {
        if (state->wl_seat) {
            printf("We got second seat, but we only need one\n");
            return;
        }
        state->wl_seat = wl_registry_bind(wl_registry, name, &wl_seat_interface, 4);
        if (!state->wl_seat)
            abortf("wl_registry_bind\n");
        wl_seat_add_listener(state->wl_seat, &r2a_app_h_wl_seat_listener, state);
    }
}

static void r2a_app_h_wl_registry_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name){
    // todo: delete seats
}

static const struct wl_registry_listener r2a_app_h_wl_registry_listener = {
    .global = r2a_app_h_wl_registry_global,
    .global_remove = r2a_app_h_wl_registry_global_remove,
};

static const struct wl_callback_listener r2a_app_h_wl_surface_frame_listener;

static void r2a_app_h_wl_surface_frame_done(void *data, struct wl_callback *cb, uint32_t time){
    wl_app_state_t *state = data;
    wl_callback_destroy(cb);
    // todo: when I add multiple surfaces, gonna need to think of something smarter
    state->wl_callback = wl_surface_frame(state->wl_surface);
    if (!state->wl_callback)
        abortf("wl_surface_frame\n");
    wl_callback_add_listener(state->wl_callback, &r2a_app_h_wl_surface_frame_listener, state);

    if (state->last_frame_time != 0) {
        update_state(state, time - state->last_frame_time);
    }

    state->swapchain.want_to_draw = true;
    try_drawing_frame(state);

    state->last_frame_time = time;
}

static const struct wl_callback_listener r2a_app_h_wl_surface_frame_listener = {
    .done = r2a_app_h_wl_surface_frame_done,
};


void r2a_app(AudioThreadBridge *audio_thread_bridge) {
    wl_app_state_t state = { .width = 800, .height = 480,
        .instruments = VecMyInstrument_new(),
        .selected_instrument = UINT64_MAX, .audio_thread_bridge = audio_thread_bridge
    };
    VecMyInstrument_append(&state.instruments, MyInstrument_new(BoxLizaInstrument_from_WeirdGuitar(WeirdGuitar_new(DEFAULT_RATE))));
    VecMyInstrument_append(&state.instruments, MyInstrument_new(BoxLizaInstrument_from_AMKeys(AMKeys_new(DEFAULT_RATE))));
    VecMyInstrument_append(&state.instruments, MyInstrument_new(BoxLizaInstrument_from_FMKeys(FMKeys_new(DEFAULT_RATE))));

    state.wl_display = wl_display_connect(NULL);
    if (!state.wl_display)
        abortf("Could not connect");
    state.wl_registry = wl_display_get_registry(state.wl_display);
    if (!state.wl_registry)
        abortf("wl_display_get_registry");
    wl_registry_add_listener(state.wl_registry, &r2a_app_h_wl_registry_listener, &state);
    wl_display_roundtrip(state.wl_display);
    if (!state.wl_shm)
        abortf("No wl_shm");
    if (!state.wl_compositor)
        abortf("No wl_compositor");
    if (!state.xdg_wm_base)
        abortf("No xdg_wm_base");
    {
        size_t size = SWAPCHAIN_SLOTS * MAX_BUFFER_WIDTH * MAX_BUFFER_HEIGHT * 4;
        assert(size < INT32_MAX);
        state.swapchain.fd = allocate_shm_file(size);
        if (state.swapchain.fd == -1) {
            abortf("AAA");
        }

        state.swapchain.data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, state.swapchain.fd, 0);
        if (state.swapchain.data == MAP_FAILED) {
            close(state.swapchain.fd);
            abortf("what the dog doing");
        }

        state.swapchain.pool = wl_shm_create_pool(state.wl_shm, state.swapchain.fd, size);
        if (!state.swapchain.pool)
            abortf("wl_shm_create_pool");
        close(state.swapchain.fd);
    }
    state.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
    if (!state.xkb_context)
        abortf("xkb_context_new\n");
    state.wl_surface = wl_compositor_create_surface(state.wl_compositor);
    if (!state.wl_surface)
        abortf("wl_compositor_create_surface\n");
    state.xdg_surface = xdg_wm_base_get_xdg_surface(
            state.xdg_wm_base, state.wl_surface);
    if (!state.xdg_surface)
        abortf("xdg_wm_base_get_xdg_surface\n");
    xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state);
    state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);
    if (!state.xdg_toplevel)
        abortf("xdg_surface_get_toplevel\n");
    xdg_toplevel_add_listener(state.xdg_toplevel, &r2a_app_h_xdg_toplevel_listener, &state);
    xdg_toplevel_set_title(state.xdg_toplevel, "r2a");
    wl_surface_commit(state.wl_surface);

    state.wl_callback = wl_surface_frame(state.wl_surface);
    if (!state.wl_callback)
        abortf("wl_surface_frame\n");
    wl_callback_add_listener(state.wl_callback, &r2a_app_h_wl_surface_frame_listener, &state);

    while (wl_display_dispatch(state.wl_display)) {
        if (state.closed)
            break;
    }
    if (state.wl_callback)
        wl_callback_destroy(state.wl_callback);
    xdg_toplevel_destroy(state.xdg_toplevel);
    xdg_surface_destroy(state.xdg_surface);
    xdg_wm_base_destroy(state.xdg_wm_base);
    wl_surface_destroy(state.wl_surface);
    wl_compositor_destroy(state.wl_compositor);
    if (state.pointer)
        wl_pointer_destroy(state.pointer);
    xkb_context_unref(state.xkb_context);
    xkb_keymap_unref(state.xkb_keymap);
    xkb_state_unref(state.xkb_state);
    if (state.keyboard)
        wl_keyboard_destroy(state.keyboard);
    if (state.wl_seat)
        wl_seat_destroy(state.wl_seat);
    munmap(state.swapchain.data, SWAPCHAIN_SLOTS * MAX_BUFFER_WIDTH * MAX_BUFFER_HEIGHT * 2);
    for (size_t i = 0; i < SWAPCHAIN_SLOTS; i++) {
        if (state.swapchain.used_buffers[i])
            wl_buffer_destroy(state.swapchain.used_buffers[i]);
    }
    wl_shm_pool_destroy(state.swapchain.pool);
    wl_shm_destroy(state.wl_shm);
    wl_registry_destroy(state.wl_registry);
    wl_display_disconnect(state.wl_display);

    /* Cleaning Up my state */
    VecMyInstrument_drop(state.instruments);
}


int main() {
    pw_init(NULL, NULL);

    AudioThreadBridge bridge;
    AudioThreadBridge_init(&bridge);
    pthread_t th;
    if (pthread_create(&th, NULL, audio_thread, &bridge))
        abortf("pthread_create\n");

    r2a_app(&bridge);

    safe_pthread_mutex_lock(&bridge.mut);
    bridge.cmd.stop = true;
    safe_pthread_mutex_unlock(&bridge.mut);
    safe_pthread_join(th);
    printf("The End!\n");
    AudioThreadBridge_destroy(&bridge);

    pw_deinit();
    return 0;
}
