/*
 * File: seqlib.m - Interface to the alsa sequencer library.
 * 
 * Copyright (C) 1999 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 */

#include "config.h"

#ifdef HAVE_ALSA

#include "glib.h"
#include "alsaIntl.h"

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "alsaSeqlib.h"
#include "alsaSeqpriv.h"


/*
 * Create a main client for an application. A queue is
 * allocated and a client is created.
 */
seq_context_t * seq_create_context()
{
	seq_context_t *ctxp;
	int  q;

	ctxp = g_new(seq_context_t, 1);
	ctxp->main = ctxp; /* This is the main context */

	if (snd_seq_open(&ctxp->handle, SND_SEQ_OPEN) < 0)
		printf("Could not open sequencer: %s", snd_strerror(errno));

	q = snd_seq_alloc_queue(ctxp->handle);
	ctxp->client = snd_seq_client_id(ctxp->handle);
	ctxp->queue = q;

	ctxp->destlist = g_array_new(0, 0, sizeof(snd_seq_addr_t));

	ctxp->queue = q;
	ctxp->source.client = ctxp->client;
	ctxp->source.port = 0;

	seq_new_port(ctxp);

	return ctxp;
}

/*
 * Create another client context based on the specified
 * context. The same queue will be used for both clients. A new
 * client will be created.
 *  Arguments:
 *    ctxp      - 
 */
seq_context_t *
seq_new_client(seq_context_t *ctxp)
{

	return NULL;/*XXX*/

}

/*
 * Free a context and any associated data and resources. If the
 * main context is freed then the sequencer is closed. TODO:
 * link all together so can all be closed at once.
 */
void 
seq_free_context(seq_context_t *cxtp)
{

	if (cxtp->main == cxtp) {
		/* This is the main context */
		snd_seq_free_queue(cxtp->handle, cxtp->queue);
		snd_seq_close(cxtp->handle);
	}

	g_free(cxtp);
}

/*
 * Creates a new port on the specified context and returns the
 * port number.
 *  Arguments:
 *    ctxp      - Context to create the port for
 */
int 
seq_new_port(seq_context_t *ctxp)
{
	int  ret;

	ret = snd_seq_create_simple_port(ctxp->handle, NULL,
					 SND_SEQ_PORT_CAP_OUT |
					 SND_SEQ_PORT_CAP_SUBSCRIPTION |
					 SND_SEQ_PORT_CAP_IN,
					 SND_SEQ_PORT_TYPE_MIDI_GENERIC);
	if (ret < 0) {
		printf("Error creating port %s\n", snd_strerror(errno));
		return -1;
	}
	ctxp->port_count++;
	ctxp->source.port = ret;

	return ret;/*XXX*/
}

void 
seq_destroy_port(seq_context_t *cxtp, int port)
{
}

/*
 * Set up the context so that all subsequent events will be sent
 * to the specified client and port combination. A
 * subscription is made to the client/port combination.
 *  Arguments:
 *    ctxp      - Context to modify
 *    client    - Client to send subsequent events to
 *    port      - Port on the client to send events to
 */
int 
seq_connect_add(seq_context_t *ctxp, int client, int port)
{
	snd_seq_addr_t addr;
	int  ret;

	memset(&addr, 0, sizeof(addr));
	addr.client = client;
	addr.port = port;

	g_array_append_val(ctxp->destlist, addr);

	ret = snd_seq_connect_to(ctxp->handle, ctxp->source.port, client, port);

	return ret;
}

/*
 * Set the initial time base and tempo. This should only be used
 * for initialisation when there is nothing playing. To
 * change the tempo during a song tempo change events are used.
 * If realtime is false the resolution is in ticks per quarter
 * note. If true, the the resolution is microseconds. There is
 * a macro XXX to convert from SMPTE codes.
 * 
 *  Arguments:
 *    ctxp      - Application context
 *    resolution - Ticks per quarter note or realtime resolution
 *    tempo     - Beats per minute
 *    realtime  - True if absolute time base
 */
int 
seq_init_tempo(seq_context_t *ctxp, int resolution, int tempo, int realtime)
{
	snd_seq_queue_tempo_t qtempo;
	int  ret;

	memset(&qtempo, 0, sizeof(qtempo));
	qtempo.queue = ctxp->queue;
	qtempo.ppq = resolution;
	qtempo.tempo = 60*1000000/tempo;

	ret = snd_seq_set_queue_tempo(ctxp->handle, ctxp->queue, &qtempo);

	return ret;
}

/*
 * Set the context to use the specified queue. All events sent
 * on this context will now use this queue.
 *
 *  Arguments:
 *    ctxp      - Context to modify
 *    q         - The queue number
 */
void 
seq_set_queue(seq_context_t *ctxp, int q)
{
}

/*
 * Send the event to the specified client and port.
 * 
 *  Arguments:
 *    ctxp      - Client context
 *    ev        - Event to send
 *    client    - Client to send the event to
 *    port      - Port to send the event to
 */
int 
seq_sendto(seq_context_t *ctxp, snd_seq_event_t *ev, int client, int port)
{
	ev->source = ctxp->source;
	ev->queue = ctxp->queue;
	ev->dest.client = client;
	ev->dest.port = port;
	seq_write(ctxp, ev);

	return 0;
}

/*
 * Start the timer. (What about timers other than the system
 * one?)
 */
void 
seq_start_timer(seq_context_t *ctxp)
{
	seq_control_timer(ctxp, SND_SEQ_EVENT_START);
}

/*
 * Stop the timer. (What about timers other than the system
 * one?)
 */
void 
seq_stop_timer(seq_context_t *ctxp)
{
	seq_control_timer(ctxp, SND_SEQ_EVENT_STOP);
}

void 
seq_control_timer(seq_context_t *ctxp, int onoff)
{
	snd_seq_event_t ev;

	memset(&ev, 0, sizeof(ev));

	ev.queue = ctxp->queue;
	ev.dest.client = SND_SEQ_CLIENT_SYSTEM;	/* system */
	ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;	/* timer */

	ev.data.queue.queue = ctxp->queue;

	ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_REL;
	ev.time.time.tv_sec = 0;
	ev.time.time.tv_nsec = 0;

	ev.type = onoff;

	snd_seq_event_output(ctxp->handle, &ev);
	snd_seq_flush_output(ctxp->handle);
}

/*
 * Write out the event. This routine blocks until
 * successfully written. 
 * 
 *  Arguments:
 *    ctxp      - Application context
 *    ep        - Event
 */
int 
seq_write(seq_context_t *ctxp, snd_seq_event_t *ep)
{
	int  err = 0;

	err = snd_seq_event_output(ctxp->handle, ep);
	if (err < 0)
		return err;

#ifndef SND_SEQ_IOCTL_GET_CLIENT_POOL
	/*
	 * If this is not defined then block mode writes will not be
	 * working correctly.  Therefore loop until all events are flushed
	 * out.
	 */
	err = 0;
	do {
		err = snd_seq_flush_output(ctxp->handle);
		if (err > 0)
			usleep(2000);
	} while (err > 0);
#endif

	return err;
}

/*
 * Return the handle to the underlying snd_seq stream.
 *  Arguments:
 *    ctxp      - Application context
 */
void *
seq_handle(seq_context_t *ctxp)
{
	return ctxp->handle;
}

#endif
