/*
 *   ALSA sequencer System services Client
 *   Copyright (c) 1998 by Frank van de Pol <frank@vande-pol.demon.nl>
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   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.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "../../include/driver.h"
#include "seq_system.h"
#include "seq_timer.h"
#include "seq_queue.h"

/* define TRACE_ANNOUNCEMENTS to get every announcement traced to syslog/console */
#undef TRACE_ANNOUNCEMENTS 1


/* internal client that provide system services, access to timer etc. */

/*
 * Port "Timer"
 *      - send tempo /start/stop etc. events to this port to manipulate the 
 *        queue's timer. The queue address is specified in the destination
 *        part of the address
 *      - this port supports subscription. The received timer events are 
 *        broadcasted to all subscribed clients.
 *
 * Port "Announce"
 *      - does not receive message
 *      - supports supscription. For each client or port attaching to or 
 *        detaching from the system an announcement is send to the subscribed
 *        clients.
 *
 * Idea: the subscription mechanism might also work handy for distributing 
 * synchronisation and timing information. In this case we would ideally have
 * a list of subscribers for each type of sync (time, tick), for each timing
 * queue.
 *
 */


/* client id of our system client */
static int sysclient = -1;

/* port id numbers for this client */
static int announce_port = -1;



/* fill standard header data, source port & channel are filled in */
static int setheader(snd_seq_event_t * ev, int client, int port)
{
	if (announce_port < 0)
		return -ENODEV;

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

	ev->flags = SND_SEQ_EVENT_LENGTH_FIXED|SND_SEQ_DEST_DIRECT;

	ev->source.queue = SND_SEQ_ADDRESS_UNKNOWN;
	ev->source.client = sysclient;
	ev->source.port = announce_port;

	ev->dest.queue = SND_SEQ_ADDRESS_SUBSCRIBERS;

	ev->flags |= SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;

	/* fill data */
	ev->data.addr.queue = SND_SEQ_ADDRESS_UNKNOWN;
	ev->data.addr.client = client;
	ev->data.addr.port = port;

	return 0;
}


/* entry points for broadcasting system events */
void snd_seq_system_client_ev_client_start(int client)
{
	snd_seq_event_t ev;
	
	if (setheader(&ev, client, 0) < 0)
		return;
	ev.type = SND_SEQ_EVENT_CLIENT_START;
	snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);

#ifdef TRACE_ANNOUNCEMENTS
	snd_printk("seq: broadcast client %d start\n", client);
#endif
}

void snd_seq_system_client_ev_client_exit(int client)
{
	snd_seq_event_t ev;
	
	if (setheader(&ev, client, 0) < 0)
		return;
	ev.type = SND_SEQ_EVENT_CLIENT_EXIT;
	snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);
	
#ifdef TRACE_ANNOUNCEMENTS
	snd_printk("seq: broadcast client %d exit\n", client);
#endif
}

void snd_seq_system_client_ev_client_change(int client)
{
	snd_seq_event_t ev;
	
	if (setheader(&ev, client, 0) < 0)
		return;
	ev.type = SND_SEQ_EVENT_CLIENT_CHANGE;
	snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);
	
#ifdef TRACE_ANNOUNCEMENTS
	snd_printk("seq: broadcast client %d change\n", client);
#endif
}

void snd_seq_system_client_ev_port_start(int client, int port)
{
	snd_seq_event_t ev;
	
	if (port < 0 || port >= SND_SEQ_MAX_PORTS)
		return;
	if (setheader(&ev, client, port) < 0)
		return;
	ev.type = SND_SEQ_EVENT_PORT_START;
	snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);
	
#ifdef TRACE_ANNOUNCEMENTS
	snd_printk("seq: broadcast client %d, port %d start\n", client, port);
#endif
}

void snd_seq_system_client_ev_port_exit(int client, int port)
{
	snd_seq_event_t ev;
	
	if (setheader(&ev, client, port) < 0)
		return;
	ev.type = SND_SEQ_EVENT_PORT_EXIT;
	snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);
	
#ifdef TRACE_ANNOUNCEMENTS
	snd_printk("seq: broadcast client %d, port %d exit\n", client, port);
#endif
}

void snd_seq_system_client_ev_port_change(int client, int port)
{
	snd_seq_event_t ev;
	
	if (setheader(&ev, client, port) < 0)
		return;
	ev.type = SND_SEQ_EVENT_PORT_START;
	snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);
	
#ifdef TRACE_ANNOUNCEMENTS
	snd_printk("seq: broadcast client %d, port %d start\n", client, port);
#endif
}


/* send timer event to all subscribed ports */
static void broadcast_timer_event(snd_seq_event_t *ev, int atomic, int hop)
{
	snd_seq_event_t sev;

	memcpy(&sev, ev, sizeof(snd_seq_event_t));
	
	sev.dest.queue = SND_SEQ_ADDRESS_SUBSCRIBERS;
	sev.dest.client = 0;
	sev.dest.port = 0;
	sev.dest.channel = 0;

	sev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_REL;
	sev.flags |= SND_SEQ_DEST_DIRECT;
	sev.time.real.tv_sec = 0;
	sev.time.real.tv_nsec = 0;

	sev.data.addr.queue = ev->dest.queue;
	sev.data.addr.client = ev->source.client;
	sev.data.addr.port = 0;
	sev.data.addr.channel = 0;
			
	/* send event */
	snd_seq_kernel_client_dispatch(sysclient, &sev, atomic, hop);
}


/* call-back handler for timer events */
static int event_input_timer(snd_seq_event_t * ev, int direct, void *private_data, int atomic, int hop)
{
	/* handle Timer events */
	switch (ev->type) {
			/* timer stuff */
		case SND_SEQ_EVENT_START:
			if (snd_seq_queue_timer_start(ev->dest.queue, ev->source.client) >= 0)
				broadcast_timer_event(ev, atomic, hop);
			break;

		case SND_SEQ_EVENT_CONTINUE:
			if (snd_seq_queue_timer_continue(ev->dest.queue, ev->source.client) >= 0)
				broadcast_timer_event(ev, atomic, hop);
			break;

		case SND_SEQ_EVENT_STOP:
			if (snd_seq_queue_timer_stop(ev->dest.queue, ev->source.client) >= 0)
				broadcast_timer_event(ev, atomic, hop);
			break;

		case SND_SEQ_EVENT_TEMPO:
			if (snd_seq_queue_timer_set_tempo(ev->dest.queue, ev->source.client, ev->data.control.value) >= 0)
				broadcast_timer_event(ev, atomic, hop);
			break;
	}
	return 0;		/* success */
}

/* register our internal client */
void snd_seq_system_client_init(void)
{

	snd_seq_client_callback_t callbacks;
	snd_seq_port_callback_t pcallbacks;
	snd_seq_client_info_t inf;
	snd_seq_port_info_t port;

	memset(&callbacks, 0, sizeof(callbacks));
	memset(&pcallbacks, 0, sizeof(pcallbacks));
	memset(&inf, 0, sizeof(inf));
	memset(&port, 0, sizeof(port));
	pcallbacks.event_input = event_input_timer;

	/* register client */
	callbacks.allow_input = callbacks.allow_output = 1;
	sysclient = snd_seq_create_kernel_client(NULL, 0, &callbacks);

	/* set our name */
	inf.client = 0;
	inf.type = KERNEL_CLIENT;
	strcpy(inf.name, "System");
	strcpy(inf.group, SND_SEQ_GROUP_SYSTEM);
	snd_seq_kernel_client_ctl(sysclient, SND_SEQ_IOCTL_SET_CLIENT_INFO, &inf);

	/* register timer */
	strcpy(port.name, "Timer");
	port.capability = SND_SEQ_PORT_CAP_WRITE; /* accept queue control */
	port.capability |= SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ; /* for broadcast */
	port.kernel = &pcallbacks;
	port.type = 0;
	snd_seq_kernel_client_ctl(sysclient, SND_SEQ_IOCTL_CREATE_PORT, &port);

	/* register announcement port */
	strcpy(port.name, "Announce");
	port.capability = SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */
	port.kernel = NULL;
	port.type = 0;
	snd_seq_kernel_client_ctl(sysclient, SND_SEQ_IOCTL_CREATE_PORT, &port);
	announce_port = port.port;
}


/* unregister our internal client */
void snd_seq_system_client_done(void)
{
	int oldsysclient = sysclient;

	if (oldsysclient >= 0) {
		sysclient = -1;
		announce_port = -1;
		snd_seq_delete_kernel_client(oldsysclient);
	}
}
