/*
  Copyright (C) 2004 Paul Davis 

  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.

  $Id: alsa_sequencer_midiport.cc 4075 2008-11-01 01:51:47Z paul $
*/

#include <fcntl.h>
#include <cerrno>

#include <pbd/failed_constructor.h>
#include <pbd/error.h>
#include <pbd/xml++.h>

#include <midi++/types.h>
#include <midi++/alsa_sequencer.h>

#include "i18n.h"

//#define DOTRACE 1

#ifdef DOTRACE
#define TR_FN() (cerr << __FUNCTION__ << endl)
#define TR_VAL(v) (cerr << __FILE__  " " << __LINE__ << " " #v  "=" << v << endl)
#else
#define TR_FN()
#define TR_VAL(v)
#endif

using namespace std;
using namespace MIDI;
using namespace PBD;

snd_seq_t* ALSA_SequencerMidiPort::seq = 0;

ALSA_SequencerMidiPort::ALSA_SequencerMidiPort (const XMLNode& node)
	: Port (node)
	, decoder (0) 
	, encoder (0) 
	, port_id (-1)
{
	TR_FN();
	int err;
	Descriptor desc (node);

	if (!seq && init_client (desc.device) < 0) {
		_ok = false; 

	} else {
		
		if (0 <= (err = create_ports (desc)) &&
		    0 <= (err = snd_midi_event_new (1024, &decoder)) &&	// Length taken from ARDOUR::Session::midi_read ()
		    0 <= (err = snd_midi_event_new (64, &encoder))) {	// Length taken from ARDOUR::Session::mmc_buffer
			snd_midi_event_init (decoder);
			snd_midi_event_init (encoder);
			_ok = true;
		} 
	}

	set_state (node);
}

ALSA_SequencerMidiPort::~ALSA_SequencerMidiPort ()
{
	if (decoder) {
		snd_midi_event_free (decoder);
	}
	if (encoder) {
		snd_midi_event_free (encoder);
	}
	if (port_id >= 0) {
		snd_seq_delete_port (seq, port_id);
	}
}

int 
ALSA_SequencerMidiPort::selectable () const
{
	struct pollfd pfd[1];
	if (0 <= snd_seq_poll_descriptors (seq, pfd, 1, POLLIN | POLLOUT)) {
		return pfd[0].fd;
	}
	return -1;
}

int 
ALSA_SequencerMidiPort::write (byte *msg, size_t msglen)	
{
	TR_FN ();
	int R;
	int totwritten = 0;
	snd_midi_event_reset_encode (encoder);
	int nwritten = snd_midi_event_encode (encoder, msg, msglen, &SEv);
	TR_VAL (nwritten);
	while (0 < nwritten) {
		if (0 <= (R = snd_seq_event_output (seq, &SEv))  &&
		    0 <= (R = snd_seq_drain_output (seq))) {
			bytes_written += nwritten;
			totwritten += nwritten;
			if (output_parser) {
				output_parser->raw_preparse (*output_parser, msg, nwritten);
				for (int i = 0; i < nwritten; i++) {
					output_parser->scanner (msg[i]);
				}
				output_parser->raw_postparse (*output_parser, msg, nwritten);
			}
		} else {
			TR_VAL(R);
			return R;
		}

		msglen -= nwritten;
		msg += nwritten;
		if (msglen > 0) {
			nwritten = snd_midi_event_encode (encoder, msg, msglen, &SEv);
			TR_VAL(nwritten);
		}
		else {
			break;
		}
	}

	return totwritten;
}

int 
ALSA_SequencerMidiPort::read (byte *buf, size_t max)
{
	TR_FN();
	int err;
	snd_seq_event_t *ev;
	if (0 <= (err = snd_seq_event_input (seq, &ev))) {
		TR_VAL(err);
		err = snd_midi_event_decode (decoder, buf, max, ev);
	}

	if (err > 0) {
		bytes_read += err;

		if (input_parser) {
			input_parser->raw_preparse (*input_parser, buf, err);
			for (int i = 0; i < err; i++) {
				input_parser->scanner (buf[i]);
			}	
			input_parser->raw_postparse (*input_parser, buf, err);
		}
	}
	return -ENOENT == err ? 0 : err;
}

int 
ALSA_SequencerMidiPort::create_ports (const Port::Descriptor& desc)
{
	int err;
	unsigned int caps = 0;

	if (desc.mode == O_WRONLY  ||  desc.mode == O_RDWR)
		caps |= SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
	if (desc.mode == O_RDONLY  ||  desc.mode == O_RDWR)
		caps |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;

	if (0 <= (err = snd_seq_create_simple_port (seq, desc.tag.c_str(), caps, 
						    (SND_SEQ_PORT_TYPE_MIDI_GENERIC|
						     SND_SEQ_PORT_TYPE_SOFTWARE|
						     SND_SEQ_PORT_TYPE_APPLICATION)))) {
		
		port_id = err;

		snd_seq_ev_clear (&SEv);
		snd_seq_ev_set_source (&SEv, port_id);
		snd_seq_ev_set_subs (&SEv);
		snd_seq_ev_set_direct (&SEv);
		
		return 0;
	}

	return err;
}

int
ALSA_SequencerMidiPort::init_client (std::string name)
{
	static bool called = false;

	if (called) {
		return -1;
	}

	called = true;

	if (snd_seq_open (&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) >= 0) {
		snd_seq_set_client_name (seq, name.c_str());
		return 0;
	} else {
		warning << "The ALSA MIDI system is not available. No ports based on it will be created"
			<< endmsg;
		return -1;
	}
}

int
ALSA_SequencerMidiPort::discover (vector<PortSet>& ports)
{
	 int n = 0;

	 snd_seq_client_info_t *client_info;
	 snd_seq_port_info_t   *port_info;

	 snd_seq_client_info_alloca (&client_info);
	 snd_seq_port_info_alloca (&port_info);
	 snd_seq_client_info_set_client (client_info, -1);

	 while (snd_seq_query_next_client(seq, client_info) >= 0) {

		 int alsa_client;

		 if ((alsa_client = snd_seq_client_info_get_client(client_info)) <= 0) {
			 break;
		 }

		 snd_seq_port_info_set_client(port_info, alsa_client);
		 snd_seq_port_info_set_port(port_info, -1);

		 char client[256];
		 snprintf (client, sizeof (client), "%d:%s", alsa_client, snd_seq_client_info_get_name(client_info));

		 ports.push_back (PortSet (client));

		 while (snd_seq_query_next_port(seq, port_info) >= 0) {

#if 0
			 int type = snd_seq_port_info_get_type(pinfo);
			 if (!(type & SND_SEQ_PORT_TYPE_PORT)) {
				 continue;
			 }
#endif

			 unsigned int port_capability = snd_seq_port_info_get_capability(port_info);

			 if ((port_capability & SND_SEQ_PORT_CAP_NO_EXPORT) == 0) {

				 int alsa_port = snd_seq_port_info_get_port(port_info);

				 char port[256];
				 snprintf (port, sizeof (port), "%d:%s", alsa_port, snd_seq_port_info_get_name(port_info));

				 std::string mode;

				 if (port_capability & SND_SEQ_PORT_CAP_READ) {
					 if (port_capability & SND_SEQ_PORT_CAP_WRITE) {
						 mode = "duplex";
					 } else {
						 mode = "output";
					 } 
				 } else if (port_capability & SND_SEQ_PORT_CAP_WRITE) {
					 if (port_capability & SND_SEQ_PORT_CAP_READ) {
						 mode = "duplex";
					 } else {
						 mode = "input";
					 } 
				 }

				 XMLNode node (X_("MIDI-port"));
				 node.add_property ("device", client);
				 node.add_property ("tag", port);
				 node.add_property ("mode", mode);
				 node.add_property ("type", "alsa/sequencer");
				 
				 ports.back().ports.push_back (node);
				 ++n;
			 }
		 }
	 }
	 
	 return n;
}

void
ALSA_SequencerMidiPort::get_connections (vector<SequencerPortAddress>& connections, int dir) const
{
	snd_seq_query_subscribe_t *subs;
	snd_seq_addr_t seq_addr;

	snd_seq_query_subscribe_alloca (&subs);

	// Get port connections...
	
	if (dir) {
		snd_seq_query_subscribe_set_type(subs, SND_SEQ_QUERY_SUBS_WRITE);
	} else {
		snd_seq_query_subscribe_set_type(subs, SND_SEQ_QUERY_SUBS_READ);
	}

	snd_seq_query_subscribe_set_index(subs, 0);
	seq_addr.client = snd_seq_client_id (seq);
	seq_addr.port   = port_id;
	snd_seq_query_subscribe_set_root(subs, &seq_addr);

	while (snd_seq_query_port_subscribers(seq, subs) >= 0) {

		if (snd_seq_query_subscribe_get_time_real (subs)) {
			/* interesting connection */

			seq_addr = *snd_seq_query_subscribe_get_addr (subs);
			
			connections.push_back (SequencerPortAddress (seq_addr.client,
								     seq_addr.port));
		}

		snd_seq_query_subscribe_set_index(subs, snd_seq_query_subscribe_get_index(subs) + 1);
	}
}

XMLNode&
ALSA_SequencerMidiPort::get_state () const
{
	XMLNode& root (Port::get_state ());
	vector<SequencerPortAddress> connections;
	XMLNode* sub = 0;
	char buf[256];

	get_connections (connections, 1);

	if (!connections.empty()) {
		if (!sub) {
			sub = new XMLNode (X_("connections"));
		}
		for (vector<SequencerPortAddress>::iterator i = connections.begin(); i != connections.end(); ++i) {
			XMLNode* cnode = new XMLNode (X_("read"));
			snprintf (buf, sizeof (buf), "%d:%d", i->first, i->second);
			cnode->add_property ("dest", buf);
			sub->add_child_nocopy (*cnode);
		}
	}
	
	connections.clear ();
	get_connections (connections, 0);

	if (!connections.empty()) {
		if (!sub) {
			sub = new XMLNode (X_("connections"));
		}
		for (vector<SequencerPortAddress>::iterator i = connections.begin(); i != connections.end(); ++i) {
			XMLNode* cnode = new XMLNode (X_("write"));
			snprintf (buf, sizeof (buf), "%d:%d", i->first, i->second);
			cnode->add_property ("dest", buf);
			sub->add_child_nocopy (*cnode);
		}
	}

	if (sub) {
		root.add_child_nocopy (*sub);
	}

	return root;
}

void
ALSA_SequencerMidiPort::set_state (const XMLNode& node)
{
	Port::set_state (node);

	XMLNodeList children (node.children());
	XMLNodeIterator iter;

	for (iter = children.begin(); iter != children.end(); ++iter) {

		if ((*iter)->name() == X_("connections")) {

			XMLNodeList gchildren ((*iter)->children());
			XMLNodeIterator gciter;

			for (gciter = gchildren.begin(); gciter != gchildren.end(); ++gciter) {
				XMLProperty* prop;

				if ((prop = (*gciter)->property ("dest")) != 0) {
					int client;
					int port;

					if (sscanf (prop->value().c_str(), "%d:%d", &client, &port) == 2) {

						snd_seq_port_subscribe_t *sub;
						snd_seq_addr_t seq_addr;
						
						snd_seq_port_subscribe_alloca(&sub);

						if ((*gciter)->name() == X_("write")) {
							
							seq_addr.client = snd_seq_client_id (seq);
							seq_addr.port   = port_id;
							snd_seq_port_subscribe_set_sender(sub, &seq_addr);
							
							seq_addr.client = client;
							seq_addr.port   = port;
							snd_seq_port_subscribe_set_dest(sub, &seq_addr);

						} else {
							
							seq_addr.client = snd_seq_client_id (seq);
							seq_addr.port   = port_id;
							snd_seq_port_subscribe_set_dest(sub, &seq_addr);
							
							seq_addr.client = client;
							seq_addr.port   = port;
							snd_seq_port_subscribe_set_sender(sub, &seq_addr);
						}

						snd_seq_subscribe_port (seq, sub);
					}
				}
			}

			break;
		}
	}
}
