
/*
 * Beat Rat -- copy/cut/paste registers, plus best-effort X11 CLIPBOARD
 * export for the system register.
 *
 * Deliberately one-way: register_claim_clipboard() lets OTHER apps pull
 * from Beat Rat's system register (if they happen to ask for a target
 * we serve), but Beat Rat never reads FROM the X11 clipboard to fill
 * its own registers. Paste always uses this module's own stored copy,
 * so it keeps working even after some other app takes clipboard
 * ownership away from us in between a copy and a paste.
 */

#include "register.h"
#include "log.h"

#include <X11/Xatom.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void register_clear(Register *r) {
    for (int i = 0; i < r->num_clips; i++)
        free(r->clips[i].samples);
    r->num_clips = 0;
    r->is_track_copy = 0;
    r->valid = 0;
}

void register_begin(Register *r, int is_track_copy) {
    register_clear(r);
    r->is_track_copy = is_track_copy;
}

void register_add_clip(Register *r, const char *name, int16_t *samples,
                        size_t num_samples, uint32_t sample_rate) {
    if (r->num_clips >= REGISTER_MAX_CLIPS) {
        LOG_WARN("register: full (max %d clips), dropping '%s'", REGISTER_MAX_CLIPS, name ? name : "");
        free(samples);
        return;
    }
    RegisterClip *c = &r->clips[r->num_clips];
    snprintf(c->name, sizeof(c->name), "%s", name ? name : "");
    c->samples = samples;
    c->num_samples = num_samples;
    c->sample_rate = sample_rate;
    r->num_clips++;
    r->valid = 1;
}

/* Minimal 16-bit mono PCM WAV, built directly in memory -- deliberately
 * not reusing wav.c's file-based writer, since this only ever needs to
 * hand a small in-memory blob to XChangeProperty(), not touch disk. */
static unsigned char *build_wav_bytes(const RegisterClip *c, size_t *out_len) {
    size_t data_bytes = c->num_samples * sizeof(int16_t);
    size_t total = 44 + data_bytes;
    unsigned char *buf = malloc(total);
    if (!buf) {
        *out_len = 0;
        return NULL;
    }

    uint32_t byte_rate = c->sample_rate * 1 * 16 / 8;
    uint16_t block_align = 1 * 16 / 8;

    memcpy(buf + 0, "RIFF", 4);
    uint32_t riff_size = (uint32_t)(36 + data_bytes);
    memcpy(buf + 4, &riff_size, 4);
    memcpy(buf + 8, "WAVE", 4);
    memcpy(buf + 12, "fmt ", 4);
    uint32_t fmt_size = 16;
    memcpy(buf + 16, &fmt_size, 4);
    uint16_t audio_format = 1; /* PCM */
    memcpy(buf + 20, &audio_format, 2);
    uint16_t channels = 1;
    memcpy(buf + 22, &channels, 2);
    memcpy(buf + 24, &c->sample_rate, 4);
    memcpy(buf + 28, &byte_rate, 4);
    memcpy(buf + 32, &block_align, 2);
    uint16_t bits_per_sample = 16;
    memcpy(buf + 34, &bits_per_sample, 2);
    memcpy(buf + 36, "data", 4);
    uint32_t data_size = (uint32_t)data_bytes;
    memcpy(buf + 40, &data_size, 4);
    if (data_bytes > 0)
        memcpy(buf + 44, c->samples, data_bytes);

    *out_len = total;
    return buf;
}

static void describe_register(const Register *r, char *out, size_t out_size) {
    if (!r->valid || r->num_clips == 0) {
        snprintf(out, out_size, "[beatrat: empty register]");
        return;
    }
    if (r->num_clips == 1) {
        const RegisterClip *c = &r->clips[0];
        double secs = c->sample_rate > 0 ? (double)c->num_samples / (double)c->sample_rate : 0.0;
        snprintf(out, out_size, "[beatrat clip: %s%s%.1fs @ %uHz]",
                 c->name[0] ? c->name : "", c->name[0] ? ", " : "", secs, c->sample_rate);
    } else {
        snprintf(out, out_size, "[beatrat: %d %s]", r->num_clips,
                 r->is_track_copy ? "tracks" : "track slices");
    }
}

void register_claim_clipboard(Display *dpy, Window win, Atom clipboard_atom,
                               Atom beatrat_audio_atom, const Register *r) {
    (void)beatrat_audio_atom; /* kept in the signature for symmetry with register_handle_selection_request */
    XSetSelectionOwner(dpy, clipboard_atom, win, CurrentTime);
    XFlush(dpy);
    char desc[128];
    describe_register(r, desc, sizeof(desc));
    LOG_INFO("register: claimed CLIPBOARD for system register %s", desc);
}

void register_handle_selection_request(Display *dpy, const XSelectionRequestEvent *req,
                                        Atom targets_atom, Atom beatrat_audio_atom,
                                        const Register *system_register) {
    XSelectionEvent sev;
    memset(&sev, 0, sizeof(sev));
    sev.type = SelectionNotify;
    sev.requestor = req->requestor;
    sev.selection = req->selection;
    sev.target = req->target;
    sev.time = req->time;
    sev.property = None; /* None = "can't provide this target", per ICCCM */

    Atom utf8_atom = XInternAtom(dpy, "UTF8_STRING", False);

    if (req->target == targets_atom) {
        Atom targets[4] = { targets_atom, XA_STRING, utf8_atom, beatrat_audio_atom };
        int n = 3; /* TARGETS, STRING, UTF8_STRING always offered */
        if (system_register->valid && system_register->num_clips == 1)
            n = 4; /* real audio bytes only make sense for a single, unambiguous clip */
        XChangeProperty(dpy, req->requestor, req->property, XA_ATOM, 32, PropModeReplace,
                         (unsigned char *)targets, n);
        sev.property = req->property;
    } else if (req->target == beatrat_audio_atom && system_register->valid && system_register->num_clips == 1) {
        size_t len = 0;
        unsigned char *wav = build_wav_bytes(&system_register->clips[0], &len);
        if (wav) {
            XChangeProperty(dpy, req->requestor, req->property, beatrat_audio_atom, 8,
                             PropModeReplace, wav, (int)len);
            sev.property = req->property;
            free(wav);
        }
    } else if (req->target == XA_STRING || req->target == utf8_atom) {
        char desc[128];
        describe_register(system_register, desc, sizeof(desc));
        XChangeProperty(dpy, req->requestor, req->property, req->target, 8, PropModeReplace,
                         (unsigned char *)desc, (int)strlen(desc));
        sev.property = req->property;
    }

    XSendEvent(dpy, req->requestor, False, 0, (XEvent *)&sev);
}
