/*

    Copyright (C) 2001-2002 Stefan Westerfeld
                            stefan@space.twc.de

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
  
    This library 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
    Library General Public License for more details.
   
    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

    */

#include "alsamidiport_impl.h"

#if defined(HAVE_ARTS_LIBASOUND2) || defined(HAVE_ARTS_LIBASOUND)
#include <arts/debug.h>

#ifdef HAVE_ARTS_LIBASOUND
#define snd_seq_queue_status_alloca(x) \
	*x = (snd_seq_queue_status_t *)alloca(sizeof(snd_seq_queue_status_t))
#define snd_seq_queue_status_get_tick_time(x) x->tick
#define snd_seq_queue_status_get_real_time(x) (&(x->time))
#endif

using namespace std;
using namespace Arts;

AlsaMidiPort_impl::AlsaMidiPort_impl(snd_seq_t *seq, long client, long port)
	: _client(client), _port(port), alsaSeq(seq)
{
	opened = false;
}

/* interface MidiPort */
Arts::TimeStamp AlsaMidiPort_impl::time()
{
	snd_seq_queue_status_t *status;
	snd_seq_queue_status_alloca(&status);

	snd_seq_get_queue_status(alsaSeq, alsaQueue, status);
	snd_seq_tick_time_t ttime = snd_seq_queue_status_get_tick_time(status);
	const snd_seq_real_time_t *rtime =
		snd_seq_queue_status_get_real_time(status);

	return Arts::TimeStamp(rtime->tv_sec, rtime->tv_nsec / 1000);
}

Arts::TimeStamp AlsaMidiPort_impl::playTime()
{
	return time();
}

void AlsaMidiPort_impl::fillAlsaEvent(snd_seq_event_t *ev,
		                              const MidiCommand& command)
{
	ev->source = alsaSourceAddr;
	ev->dest = alsaDestAddr;

	mcopbyte channel = command.status & mcsChannelMask;

	switch(command.status & mcsCommandMask)
	{
		case mcsNoteOn:
			snd_seq_ev_set_noteon(ev, channel, command.data1, command.data2);
			break;
		case mcsNoteOff:
			snd_seq_ev_set_noteoff(ev, channel, command.data1, command.data2);
			break;
		case mcsProgram:
			snd_seq_ev_set_pgmchange(ev, channel, command.data1);
			break;
		case mcsParameter:
			snd_seq_ev_set_controller(ev, channel, command.data1, command.data2);
			break;
		default:
			/* unhandled */
			return;
	}
}

void AlsaMidiPort_impl::sendAlsaEvent(snd_seq_event_t *ev)
{
	int ret = snd_seq_event_output(alsaSeq, ev);
	if (ret < 0) {
		arts_warning("AlsaMidiPort: error writing note %s\n",
				     snd_strerror(ret));
		return;
	}
	flushAlsa();
}

void AlsaMidiPort_impl::processCommand(const MidiCommand& command)
{
	snd_seq_event_t ev;
	snd_seq_ev_clear(&ev);

	fillAlsaEvent(&ev, command);
	sendAlsaEvent(&ev);
}

void AlsaMidiPort_impl::processEvent(const MidiEvent& event)
{
	snd_seq_event_t ev;
	snd_seq_real_time_t time;

	time.tv_sec = event.time.sec;
	time.tv_nsec = event.time.usec * 1000;

	snd_seq_ev_clear(&ev);
	snd_seq_ev_schedule_real(&ev, alsaQueue, 0, &time);

	fillAlsaEvent(&ev, event.command);
	sendAlsaEvent(&ev);
}

/* interface AlsaMidiPort */
void AlsaMidiPort_impl::client(long newClient)
{
	if(newClient != _client)
	{
		_client = newClient;

		if(opened)
		{
			close();
			open();
		}

		client_changed(newClient);
	}
}

long AlsaMidiPort_impl::client()
{
	return _client;
}

void AlsaMidiPort_impl::port(long newPort)
{
	if(newPort != _port)
	{
		_port = newPort;

		if(opened)
		{
			close();
			open();
		}

		port_changed(newPort);
	}
}

long AlsaMidiPort_impl::port()
{
	return _port;
}

bool AlsaMidiPort_impl::open()
{
	arts_return_val_if_fail(opened == false, false);

	alsaQueue = snd_seq_alloc_queue(alsaSeq);
	alsaClientId = snd_seq_client_id(alsaSeq);

	alsaPort = snd_seq_create_simple_port(alsaSeq, "aRts",
									      SND_SEQ_PORT_CAP_WRITE |
									      SND_SEQ_PORT_CAP_SUBS_WRITE |
									      SND_SEQ_PORT_CAP_READ,
									      SND_SEQ_PORT_TYPE_MIDI_GENERIC);
	if (alsaPort < 0) {
		arts_warning("AlsaMidiPort: can't creating port %s\n",
				     snd_strerror(alsaPort));
		return false;
	}

	alsaSourceAddr.client = alsaClientId;
	alsaSourceAddr.port = alsaPort;

	alsaDestAddr.client = _client;
	alsaDestAddr.port = _port;

	int ret;
	ret = snd_seq_connect_to(alsaSeq, alsaPort,
			                 alsaDestAddr.client,
							 alsaDestAddr.port);
	if (ret < 0) {
		arts_warning("AlsaMidiPort: error connecting port %s\n",
				     snd_strerror(ret));
		/* FIXME: destroy port here */
		return false;
	}

	snd_seq_start_queue(alsaSeq, alsaQueue, 0);
	flushAlsa();

	opened = true;
	return true;
}

void AlsaMidiPort_impl::close()
{
	if(!opened)
		return;

	opened = false;
}

void AlsaMidiPort_impl::flushAlsa()
{
#ifdef HAVE_ARTS_LIBASOUND2
	snd_seq_drain_output(alsaSeq);
#else
	int err;
	while((err = snd_seq_flush_output(alsaSeq)) > 0)
	{
		arts_debug("alsa flush error %d\n",snd_strerror(err));
		usleep(2000);
	}
#endif
}
#endif