/** @file
 * Wrappers for creating an ALSA MIDI sequencer client with i/o ports.
 */

#include "alsa.h"


/** Create a new ALSA sequencer client. */
static snd_seq_t* open_client(const char* name) {
    snd_seq_t* handle;
    if (snd_seq_open(&handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
        return NULL;
    }
    if (snd_seq_set_client_name(handle, name) < 0) {
        (void)snd_seq_close(handle);
        return NULL;
    }
    return handle;
}


/** Create input and output ports. */
static int open_ports(snd_seq_t* handle, int* in_port, int* out_port) {
    *in_port = snd_seq_create_simple_port(handle, "in", SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
                                          SND_SEQ_PORT_TYPE_APPLICATION);
    if (*in_port < 0) {
        return -1;
    }

    *out_port = snd_seq_create_simple_port(handle, "out", SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ,
                                           SND_SEQ_PORT_TYPE_APPLICATION);
    if (*out_port < 0) {
        (void)snd_seq_delete_simple_port(handle, *in_port);
        return -1;
    }

    int client_id = snd_seq_client_id(handle);
    if (client_id < 0) {
        (void)snd_seq_delete_simple_port(handle, *out_port);
        (void)snd_seq_delete_simple_port(handle, *in_port);
        return -1;
    }

    return client_id;
}


int client_open(alsa_seq_client_t* client, const char* name) {
    client->handle = open_client(name);
    if (client->handle == NULL) {
        return -1;
    }

    client->client_id = open_ports(client->handle, &client->in_port, &client->out_port);
    if (client->client_id < 0) {
        snd_seq_close(client->handle);
        return -1;
    }

    return 0;
}


void client_close(alsa_seq_client_t* client) {
    (void)snd_seq_delete_simple_port(client->handle, client->in_port);
    (void)snd_seq_delete_simple_port(client->handle, client->out_port);
    (void)snd_seq_close(client->handle);
}


void client_input_loop(alsa_seq_client_t* client, client_loop_cb_t cb, void* cb_ctx) {
    do {
        snd_seq_event_t* ev;
        if (snd_seq_event_input(client->handle, &ev) < 0 || ev == NULL) {
            break;
        }

        ev = cb(ev, cb_ctx);

        snd_seq_ev_set_source(ev, (unsigned char)client->out_port);
        snd_seq_ev_set_subs(ev);
        snd_seq_ev_set_direct(ev);
        snd_seq_event_output_direct(client->handle, ev);
        snd_seq_free_event(ev);
    } while (snd_seq_event_input_pending(client->handle, 0) > 0);
}