/* This file is part of Om.  Copyright (C) 2006 Dave Robillard.
 * 
 * Om 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.
 * 
 * Om 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 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 "JackAudioDriver.h"
#include "config.h"
#include "tuning.h"
#include <iostream>
#include <cstdlib>
#include "Om.h"
#include "OmApp.h"
#include "util.h"
#include "Event.h"
#include "QueuedEvent.h"
#include "EventSource.h"
#include "PostProcessor.h"
#include "util/Queue.h"
#include "Node.h"
#include "Patch.h"
#include "Port.h"
#include "PortInfo.h"
#include "MidiDriver.h"
#include "List.h"
#include "PortBase.h"
#ifdef HAVE_LASH
#include "LashDriver.h"
#endif

using std::cout; using std::cerr; using std::endl;


namespace Om {

	
//// JackAudioPort ////

JackAudioPort::JackAudioPort(JackAudioDriver* driver, PortBase<sample>* patch_port)
: DriverPort(),
  ListNode<JackAudioPort*>(this),
  m_driver(driver),
  m_jack_port(NULL),
  m_jack_buffer(NULL),
  m_patch_port(patch_port)
{
	assert(patch_port->tied_port() != NULL);
	assert(patch_port->poly() == 1);

	m_jack_port = jack_port_register(m_driver->jack_client(),
		patch_port->path().c_str(), JACK_DEFAULT_AUDIO_TYPE,
		(patch_port->port_info()->is_input()) ? JackPortIsInput : JackPortIsOutput,
		0);

	m_jack_buffer = new DriverBuffer<jack_sample_t>(driver->buffer_size());
	
	patch_port->fixed_buffers(true);
}	


JackAudioPort::~JackAudioPort()
{
	jack_port_unregister(m_driver->jack_client(), m_jack_port);
}


void
JackAudioPort::add_to_driver()
{
	m_driver->add_port(this);
}


void
JackAudioPort::remove_from_driver()
{
	m_driver->remove_port(this);
}

void
JackAudioPort::prepare_buffer(jack_nframes_t nframes)
{
	// Technically this doesn't need to be done every time for output ports
	m_jack_buffer->set_data((jack_default_audio_sample_t*)
		jack_port_get_buffer(m_jack_port, nframes));
	
	assert(m_patch_port->tied_port() != NULL);
	
	// FIXME: fixed_buffers switch on/off thing can be removed once this shit
	// gets figured out and assertions can go away
	m_patch_port->fixed_buffers(false);
	m_patch_port->buffer(0)->join(m_jack_buffer);
	m_patch_port->tied_port()->buffer(0)->join(m_jack_buffer);

	m_patch_port->fixed_buffers(true);
	
	assert(m_patch_port->buffer(0)->data() == m_patch_port->tied_port()->buffer(0)->data());
	assert(m_patch_port->buffer(0)->data() == m_jack_buffer->data());
}

	
//// JackAudioDriver ////

JackAudioDriver::JackAudioDriver()
: m_client(NULL),
  m_buffer_size(0),
  m_sample_rate(0),
  m_is_activated(false),
  m_local_client(true),
  m_root_patch(NULL),
  m_start_of_current_cycle(0),
  m_start_of_last_cycle(0)
{
	m_client = jack_client_new("Om");
	if (m_client == NULL) {
		cerr << "[JackAudioDriver] Unable to connect to Jack.  Exiting." << endl;
		exit(EXIT_FAILURE);
	}

	jack_on_shutdown(m_client, shutdown_cb, this);

	m_buffer_size = jack_get_buffer_size(m_client);
	m_sample_rate = jack_get_sample_rate(m_client);

	jack_set_sample_rate_callback(m_client, sample_rate_cb, this);
	jack_set_buffer_size_callback(m_client, buffer_size_cb, this);
}

JackAudioDriver::JackAudioDriver(jack_client_t* jack_client)
: m_client(jack_client),
  m_buffer_size(jack_get_buffer_size(jack_client)),
  m_sample_rate(jack_get_sample_rate(jack_client)),
  m_is_activated(false),
  m_local_client(false),
  m_start_of_current_cycle(0),
  m_start_of_last_cycle(0)
{
	jack_on_shutdown(m_client, shutdown_cb, this);

	jack_set_sample_rate_callback(m_client, sample_rate_cb, this);
	jack_set_buffer_size_callback(m_client, buffer_size_cb, this);
}


JackAudioDriver::~JackAudioDriver()
{
	deactivate();
	
	if (m_local_client)
		jack_client_close(m_client);
}


void
JackAudioDriver::activate()
{	
	if (m_is_activated) {
		cerr << "[JackAudioDriver] Jack driver already activated." << endl;
		return;
	}

	jack_set_process_callback(m_client, process_cb, this);

	m_is_activated = true;

	if (jack_activate(m_client)) {
		cerr << "[JackAudioDriver] Could not activate Jack client, aborting." << endl;
		exit(EXIT_FAILURE);
	} else {
		cout << "[JackAudioDriver] Activated Jack client." << endl;
#ifdef HAVE_LASH
	lash_driver->set_jack_client_name("Om"); // FIXME: unique name
#endif
	}
}


void
JackAudioDriver::deactivate()
{
	if (m_is_activated) {
		// FIXME
		reinterpret_cast<EventSource*>(om->osc_receiver())->stop();
	
		jack_deactivate(m_client);
		m_is_activated = false;
	
		for (List<JackAudioPort*>::iterator i = m_ports.begin(); i != m_ports.end(); ++i)
			jack_port_unregister(m_client, (*i)->jack_port());
	
		m_ports.clear();
	
		cout << "[JackAudioDriver] Deactivated Jack client." << endl;
		
		om->post_processor()->stop();
	}
}


/** Add a Jack port.
 *
 * Realtime safe, this is to be called at the beginning of a process cycle to
 * insert (and actually begin using) a new port.
 * 
 * See create_port() and remove_port().
 */
void
JackAudioDriver::add_port(JackAudioPort* port)
{
	m_ports.push_back(port);
}


/** Remove a Jack port.
 *
 * Realtime safe.  This is to be called at the beginning of a process cycle to
 * remove the port from the lists read by the audio thread, so the port
 * will no longer be used and can be removed afterwards.
 *
 * It is the callers responsibility to delete the returned port.
 */
JackAudioPort*
JackAudioDriver::remove_port(JackAudioPort* port)
{
	for (List<JackAudioPort*>::iterator i = m_ports.begin(); i != m_ports.end(); ++i)
		if ((*i) == port)
			return m_ports.remove(i)->elem();
	
	cerr << "[JackAudioDriver::remove_port] WARNING: Failed to find Jack port to remove!" << endl;
	return NULL;
}


DriverPort*
JackAudioDriver::create_port(PortBase<sample>* patch_port)
{
	if (patch_port->buffer_size() == m_buffer_size)
		return new JackAudioPort(this, patch_port);
	else
		return NULL;
}


/** Process all the pending events for this cycle.
 *
 * Called from the realtime thread once every process cycle.
 */
void
JackAudioDriver::process_events(jack_nframes_t block_start, jack_nframes_t block_end)
{
	Event* ev = NULL;

	/* Limit the maximum number of queued events to process per cycle.  This
	 * makes the process callback truly realtime-safe by preventing being
	 * choked by events coming in faster than they can be processed.
	 * FIXME: run the math on this and figure out a good value */
	const int MAX_SLOW_EVENTS = m_buffer_size / 100;

	int num_events_processed = 0;
	int offset = 0;
	
	// Process the "slow" events first, because it's possible some of the
	// RT events depend on them
	
	// FIXME
	while ((ev = reinterpret_cast<EventSource*>(om->osc_receiver())
			->pop_earliest_event_before(block_end)) != NULL) {
		ev->execute(0);  // QueuedEvents are not sample accurate
		om->post_processor()->push(ev);
		if (++num_events_processed > MAX_SLOW_EVENTS)
			break;
	}
	
	while (!om->event_queue()->is_empty()
			&& om->event_queue()->front()->time_stamp() < block_end) {
		ev = om->event_queue()->pop();
		offset = ev->time_stamp() - block_start;
		if (offset < 0) offset = 0; // this can happen if we miss a cycle
		ev->execute(offset);
		om->post_processor()->push(ev);
		++num_events_processed;
	}
	
	if (num_events_processed > 0)
		om->post_processor()->signal();
}



/**** Jack Callbacks ****/



/** Jack process callback, drives entire audio thread.
 *
 * \callgraph
 */
int
JackAudioDriver::m_process_cb(jack_nframes_t nframes) 
{
	// FIXME: support nframes != buffer_size, even though that never damn well happens
	assert(nframes == m_buffer_size);

	// Note that jack can elect to not call this function for a cycle, if things aren't
	// keeping up
	m_start_of_current_cycle = jack_last_frame_time(m_client);
	m_start_of_last_cycle = m_start_of_current_cycle - nframes;

	assert(m_start_of_current_cycle - m_start_of_last_cycle == nframes);

	m_transport_state = jack_transport_query(m_client, &m_position);
	
	process_events(m_start_of_last_cycle, m_start_of_current_cycle);
	om->midi_driver()->prepare_block(m_start_of_last_cycle, m_start_of_current_cycle);
	
	// Set buffers of patch ports to Jack port buffers (zero-copy processing)
	for (List<JackAudioPort*>::iterator i = m_ports.begin(); i != m_ports.end(); ++i)
		(*i)->prepare_buffer(nframes);
	
	// Run root patch
	assert(m_root_patch != NULL);
	m_root_patch->run(nframes);

	return 0;
}


void 
JackAudioDriver::m_shutdown_cb() 
{
	cout << "[JackAudioDriver] Jack shutdown.  Exiting." << endl;
	om->quit();
}


int
JackAudioDriver::m_sample_rate_cb(jack_nframes_t nframes) 
{
	if (m_is_activated) {
		cerr << "[JackAudioDriver] Om does not support changing sample rate on the fly (yet).  Aborting." << endl;
		exit(EXIT_FAILURE);
	} else {
		m_sample_rate = nframes;
	}
	return 0;
}


int
JackAudioDriver::m_buffer_size_cb(jack_nframes_t nframes) 
{
	if (m_is_activated) {
		cerr << "[JackAudioDriver] Om does not support chanding buffer size on the fly (yet).  Aborting." << endl;
		exit(EXIT_FAILURE);
	} else {
		m_buffer_size = nframes;
	}
	return 0;
}


} // namespace Om