/* -*- Mode: C ; c-basic-offset: 2 -*- */
/*
 * ALSA SEQ < - > JACK MIDI bridge
 *
 * Copyright (c) 2006,2007 Dmitry S. Baikov <c0ff@konstruktiv.org>
 * Copyright (c) 2007,2008,2009 Nedko Arnaudov <nedko@arnaudov.name>
 *
 *  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; version 2 of the License.
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 */

#include <stdbool.h>
#include <semaphore.h>
#include <alsa/asoundlib.h>
#include <jack/jack.h>
#include <jack/ringbuffer.h>

#include "list.h"
#include "a2j.h"
#include "port.h"
#include "port_hash.h"
#include "port_thread.h"

struct a2j_port *
a2j_find_port_by_addr (
	struct a2j_stream * stream_ptr,
	snd_seq_addr_t addr)
{
	struct list_head * node_ptr;
	struct a2j_port * port_ptr;

	list_for_each (node_ptr, &stream_ptr->list){
		port_ptr = list_entry (node_ptr, struct a2j_port, siblings);
		if (port_ptr->remote.client == addr.client && port_ptr->remote.port == addr.port) {
			return port_ptr;
		}
	}

	return NULL;
}

struct a2j_port *
a2j_find_port_by_jack_port_name (
	struct a2j_stream * stream_ptr,
	const char * jack_port)
{
	struct list_head * node_ptr;
	struct a2j_port * port_ptr;

	list_for_each (node_ptr, &stream_ptr->list){
		port_ptr = list_entry (node_ptr, struct a2j_port, siblings);
		if (strcmp (port_ptr->name, jack_port) == 0) {
			return port_ptr;
		}
	}

	return NULL;
}

/*
 * ==================== Port add/del handling thread ==============================
 */

static
void
a2j_update_port_type (alsa_midi_driver_t * driver, int dir, snd_seq_addr_t addr, int caps, const snd_seq_port_info_t * info)
{
	struct a2j_stream * stream_ptr;
	int alsa_mask;
	struct a2j_port * port_ptr;

	a2j_debug ("update_port_type(%d:%d)", addr.client, addr.port);

	stream_ptr = &driver->stream[dir];
	port_ptr = a2j_find_port_by_addr (stream_ptr, addr);

	if (dir == A2J_PORT_CAPTURE) {
		alsa_mask = SND_SEQ_PORT_CAP_SUBS_READ;
	} else {
		alsa_mask = SND_SEQ_PORT_CAP_SUBS_WRITE;
	}

	if (port_ptr != NULL && (caps & alsa_mask) != alsa_mask) {
		a2j_debug ("setdead: %s", port_ptr->name);
		port_ptr->is_dead = true;
	}

	if (port_ptr == NULL && (caps & alsa_mask) == alsa_mask) {
		if (jack_ringbuffer_write_space (stream_ptr->new_ports) >= sizeof(port_ptr)) {
			port_ptr = a2j_port_create (driver, dir, addr, info);
			if (port_ptr != NULL) {
				jack_ringbuffer_write (stream_ptr->new_ports, (char*)&port_ptr, sizeof(port_ptr));
			}
		} else {
			a2j_error ( "dropping new port event... increase MAX_PORTS" );
		}
	}
}

void
a2j_update_port (alsa_midi_driver_t * driver, snd_seq_addr_t addr, const snd_seq_port_info_t * info)
{
	unsigned int port_caps = snd_seq_port_info_get_capability (info);
	unsigned int port_type = snd_seq_port_info_get_type (info);

	a2j_debug ("port %u:%u", addr.client, addr.port);
	a2j_debug ("port type: 0x%08X", port_type);
	a2j_debug ("port caps: 0x%08X", port_caps);

	if (port_type & SND_SEQ_PORT_TYPE_SPECIFIC) {
		a2j_debug ("SPECIFIC");
	}

	if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) {
		a2j_debug ("MIDI_GENERIC");
	}

	if (port_type & SND_SEQ_PORT_TYPE_MIDI_GM) {
		a2j_debug ("MIDI_GM");
	}

	if (port_type & SND_SEQ_PORT_TYPE_MIDI_GS) {
		a2j_debug ("MIDI_GS");
	}

	if (port_type & SND_SEQ_PORT_TYPE_MIDI_XG) {
		a2j_debug ("MIDI_XG");
	}

	if (port_type & SND_SEQ_PORT_TYPE_MIDI_MT32) {
		a2j_debug ("MIDI_MT32");
	}

	if (port_type & SND_SEQ_PORT_TYPE_MIDI_GM2) {
		a2j_debug ("MIDI_GM2");
	}

	if (port_type & SND_SEQ_PORT_TYPE_SYNTH) {
		a2j_debug ("SYNTH");
	}

	if (port_type & SND_SEQ_PORT_TYPE_DIRECT_SAMPLE) {
		a2j_debug ("DIRECT_SAMPLE");
	}

	if (port_type & SND_SEQ_PORT_TYPE_SAMPLE) {
		a2j_debug ("SAMPLE");
	}

	if (port_type & SND_SEQ_PORT_TYPE_HARDWARE) {
		a2j_debug ("HARDWARE");
	}

	if (port_type & SND_SEQ_PORT_TYPE_SOFTWARE) {
		a2j_debug ("SOFTWARE");
	}

	if (port_type & SND_SEQ_PORT_TYPE_SYNTHESIZER) {
		a2j_debug ("SYNTHESIZER");
	}

	if (port_type & SND_SEQ_PORT_TYPE_PORT) {
		a2j_debug ("PORT");
	}

	if (port_type & SND_SEQ_PORT_TYPE_APPLICATION) {
		a2j_debug ("APPLICATION");
	}

	if (port_type == 0) {
		a2j_debug ("Ignoring port of type 0");
		return;
	}

	if (port_caps & SND_SEQ_PORT_CAP_NO_EXPORT) {
		a2j_debug ("Ignoring no-export port");
		return;
	}

	a2j_update_port_type (driver, A2J_PORT_CAPTURE, addr, port_caps, info);
	a2j_update_port_type (driver, A2J_PORT_PLAYBACK, addr, port_caps, info);
}

void
a2j_free_ports (alsa_midi_driver_t * driver)
{
	struct a2j_port *port;
	int sz;

	while ((sz = jack_ringbuffer_read (driver->port_del, (char*)&port, sizeof(port)))) {
		assert (sz == sizeof(port));
		a2j_debug ("port deleted: %s", port->name);
		list_del (&port->siblings);
		a2j_port_free (port);
	}
}

void
a2j_update_ports (alsa_midi_driver_t * driver, snd_seq_addr_t addr)
{
	snd_seq_port_info_t * info;
	int err;

	assert (addr.client != driver->client_id);

	snd_seq_port_info_alloca (&info);

	if ((err = snd_seq_get_any_port_info (driver->seq, addr.client, addr.port, info)) >= 0) {
		a2j_debug ("updating: %d:%d", addr.client, addr.port);
		a2j_update_port (driver, addr, info);
	} else {
		a2j_debug ("setting dead: %d:%d", addr.client, addr.port);
		a2j_port_setdead (driver->stream[A2J_PORT_CAPTURE].port_hash, addr);
		a2j_port_setdead (driver->stream[A2J_PORT_PLAYBACK].port_hash, addr);
	}
}

void
a2j_new_ports (alsa_midi_driver_t * driver, snd_seq_addr_t addr)
{
	snd_seq_port_info_t * port_info;

	assert (addr.client != driver->client_id);

	snd_seq_port_info_alloca (&port_info);

	a2j_debug ("adding new port: %d:%d", addr.client, addr.port);
	snd_seq_port_info_set_client (port_info, addr.client);
	snd_seq_port_info_set_port (port_info, -1);
	while (snd_seq_query_next_port (driver->seq, port_info) >= 0) {
		addr.port = snd_seq_port_info_get_port (port_info);
		a2j_update_port (driver, addr, port_info);
	}
}
