/* 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 "InputPort.h"
#include <iostream>
#include <cstdlib>
#include <cassert>
#include "ConnectionBase.h"
#include "OutputPort.h"
#include "PortInfo.h"
#include "Node.h"
#include "Om.h"
#include "util.h"

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


namespace Om {


template <typename T>
InputPort<T>::InputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size)
: PortBase<T>(node, name, index, poly, port_info, buffer_size)
{
	assert(port_info->is_input() && !port_info->is_output());
}
template InputPort<sample>::InputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size);
template InputPort<MidiMessage>::InputPort(Node* node, const string& name, size_t index, size_t poly, PortInfo* port_info, size_t buffer_size);


/** Add a connection.  Realtime safe.
 *
 * The buffer of this port will be set directly to the connection's buffer
 * if there is only one connection, since no mixing needs to take place.
 */
template<typename T>
void
InputPort<T>::add_connection(ListNode<ConnectionBase<T>*>* const c)
{
	m_connections.push_back(c);

	bool modify_buffers = !m_fixed_buffers;
	if (modify_buffers && m_is_tied)
		modify_buffers = !m_tied_port->fixed_buffers();
	
	if (modify_buffers) {
		if (m_connections.size() == 1) {
			// Use buffer directly to avoid copying
			for (size_t i=0; i < m_poly; ++i) {
				m_buffers.at(i)->join(c->elem()->buffer(i));
				if (m_is_tied)
					m_tied_port->buffer(i)->join(m_buffers.at(i));
				assert(m_buffers.at(i)->data() == c->elem()->buffer(i)->data());
			}
		} else if (m_connections.size() == 2) {
			// Used to directly use single connection buffer, now there's two
			// so have to use local ones again and mix down
			for (size_t i=0; i < m_poly; ++i) {
				m_buffers.at(i)->unjoin();
				if (m_is_tied)
					m_tied_port->buffer(i)->join(m_buffers.at(i));
			}
		}
		update_buffers();
	}
		
	assert( ! m_is_tied || m_tied_port != NULL);
	assert( ! m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data());
}
template void InputPort<sample>::add_connection(ListNode<ConnectionBase<sample>*>* const c);
template void InputPort<MidiMessage>::add_connection(ListNode<ConnectionBase<MidiMessage>*>* const c);


/** Remove a connection.  Realtime safe.
 */
template <typename T>
ListNode<ConnectionBase<T>*>*
InputPort<T>::remove_connection(const OutputPort<T>* const src_port)
{
	bool modify_buffers = !m_fixed_buffers;
	if (modify_buffers && m_is_tied)
		modify_buffers = !m_tied_port->fixed_buffers();
	
	typedef typename List<ConnectionBase<T>*>::iterator ConnectionBaseListIterator;
	bool found = false;
	ListNode<ConnectionBase<T>*>* connection = NULL;
	for (ConnectionBaseListIterator i = m_connections.begin(); i != m_connections.end(); ++i) {
		if ((*i)->src_port()->path() == src_port->path()) {
			connection = m_connections.remove(i);
			found = true;
		}
	}

	if ( ! found) {
		cerr << "WARNING:  [InputPort<T>::remove_connection] Connection not found !" << endl;
		exit(EXIT_FAILURE);
	} else {
		if (m_connections.size() == 0) {
			for (size_t i=0; i < m_poly; ++i) {
				// Use a local buffer
				if (modify_buffers && m_buffers.at(i)->is_joined())
					m_buffers.at(i)->unjoin();
				m_buffers.at(i)->clear(); // Write silence
				if (m_is_tied)
					m_tied_port->buffer(i)->join(m_buffers.at(i));
			}
		} else if (modify_buffers && m_connections.size() == 1) {
			// Share a buffer
			for (size_t i=0; i < m_poly; ++i) {
				m_buffers.at(i)->join((*m_connections.begin())->buffer(i));
				if (m_is_tied)
					m_tied_port->buffer(i)->join(m_buffers.at(i));
			}
		}	
	}

	if (modify_buffers)
		update_buffers();

	assert( ! m_is_tied || m_tied_port != NULL);
	assert( ! m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data());

	return connection;
}
template ListNode<ConnectionBase<sample>*>*
InputPort<sample>::remove_connection(const OutputPort<sample>* const src_port);
template ListNode<ConnectionBase<MidiMessage>*>*
InputPort<MidiMessage>::remove_connection(const OutputPort<MidiMessage>* const src_port);


/** Update any changed buffers with the plugin this is a port on.
 *
 * This calls ie the LADSPA connect_port function when buffers have been changed
 * due to a connection or disconnection.
 */
template <typename T>
void
InputPort<T>::update_buffers()
{
	for (size_t i=0; i < m_poly; ++i)
		InputPort<T>::parent_node()->set_port_buffer(i, m_index, m_buffers.at(i)->data());
}
template void InputPort<sample>::update_buffers();
template void InputPort<MidiMessage>::update_buffers();


/** Returns whether this port is connected to the passed port.
 */
template <typename T>
bool
InputPort<T>::is_connected_to(const OutputPort<T>* const port) const
{
	typedef typename List<ConnectionBase<T>*>::const_iterator ConnectionBaseListIterator;
	for (ConnectionBaseListIterator i = m_connections.begin(); i != m_connections.end(); ++i)
		if ((*i)->src_port() == port)
			return true;
	
	return false;
}
template bool InputPort<sample>::is_connected_to(const OutputPort<sample>* const port) const;
template bool InputPort<MidiMessage>::is_connected_to(const OutputPort<MidiMessage>* const port) const;


/** "Ties" this port to an OutputPort, so they share the same buffer.
 *
 * This is used by OutputNode and InputNode to provide two different ports
 * (internal and external) that share a buffer.
 */
template <typename T>
void
InputPort<T>::tie(OutputPort<T>* const port)
{
	assert((Port*)port != (Port*)this);
	assert(port->poly() == this->poly());
	assert(!m_is_tied);
	assert(m_tied_port == NULL);

	if (Port::parent_node() != NULL) {
		assert(m_poly == port->poly());
	
		for (size_t i=0; i < m_poly; ++i)
			port->buffer(i)->join(m_buffers.at(i));
	}
	m_is_tied = true;
	m_tied_port = port;
	port->set_tied_port(this);

	assert(m_buffers.at(0)->data() == port->buffer(0)->data());

	//cerr << "*** Tied " << this->path() << " <-> " << port->path() << endl;
}
template void InputPort<sample>::tie(OutputPort<sample>* const port);
template void InputPort<MidiMessage>::tie(OutputPort<MidiMessage>* const port);


/** Prepare buffer for access, mixing if necessary.  Realtime safe.
 *  FIXME: nframes parameter not used,
 */
template<>
void
InputPort<sample>::prepare_buffers(size_t nframes)
{
	assert(!m_is_tied || m_tied_port != NULL);

	typedef List<ConnectionBase<sample>*>::iterator ConnectionBaseListIterator;
	bool do_mixdown = true;
	
	if (m_connections.size() == 0) return;

	for (ConnectionBaseListIterator c = m_connections.begin(); c != m_connections.end(); ++c)
		(*c)->prepare_buffers();

	// If only one connection, buffer is (maybe) used directly (no copying)
	if (m_connections.size() == 1) {
		// Buffer changed since connection
		if (m_buffers.at(0)->data() != (*m_connections.begin())->buffer(0)->data()) {
			if (m_fixed_buffers || (m_is_tied && m_tied_port->fixed_buffers())) {
				// can't change buffer, must copy
				do_mixdown = true;
			} else {
				// zero-copy
				assert(m_buffers.at(0)->is_joined());
				m_buffers.at(0)->join((*m_connections.begin())->buffer(0));
				do_mixdown = false;
			}
		} else {
			do_mixdown = false;
		}
		update_buffers();
	}

	if (!do_mixdown) {
		assert(m_buffers.at(0)->data() == (*m_connections.begin())->buffer(0)->data());
		return;
	}

	assert(!m_is_tied || m_tied_port != NULL);
	assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data());

	for (size_t voice=0; voice < m_poly; ++voice) {
		m_buffers.at(voice)->copy((*m_connections.begin())->buffer(voice), 0, m_buffer_size-1);
		
		if (m_connections.size() > 1) {
			// Copy first connection
			ConnectionBaseListIterator c = m_connections.begin();
			
			// Add all other connections
			for (++c; c != m_connections.end(); ++c)
				m_buffers.at(voice)->accumulate((*c)->buffer(voice), 0, m_buffer_size-1);
		}
	}
}


/** Prepare buffer for access, realtime safe.
 *
 * MIDI mixing not yet implemented.
 */
template <>
void
InputPort<MidiMessage>::prepare_buffers(size_t nframes)
{
	assert(!m_is_tied || m_tied_port != NULL);
	
	const size_t num_ins = m_connections.size();
	bool         do_mixdown = true;
	
	assert(num_ins == 0 || num_ins == 1);
	
	typedef List<ConnectionBase<MidiMessage>*>::iterator ConnectionBaseListIterator;
	assert(m_poly == 1);
	
	for (ConnectionBaseListIterator c = m_connections.begin(); c != m_connections.end(); ++c)
		(*c)->prepare_buffers();
	

	// If only one connection, buffer is used directly (no copying)
	if (num_ins == 1) {
		// Buffer changed since connection
		if (m_buffers.at(0) != (*m_connections.begin())->buffer(0)) {
			if (m_fixed_buffers || (m_is_tied && m_tied_port->fixed_buffers())) {
				// can't change buffer, must copy
				do_mixdown = true;
			} else {
				// zero-copy
				assert(m_buffers.at(0)->is_joined());
				m_buffers.at(0)->join((*m_connections.begin())->buffer(0));
				if (m_is_tied)
					m_tied_port->buffer(0)->join(m_buffers.at(0));
				do_mixdown = false;
			}
			update_buffers();
		} else {
			do_mixdown = false;
		}
		assert(!m_is_tied || m_tied_port != NULL);
		assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data());
		assert(!m_is_tied || m_buffers.at(0)->filled_size() == m_tied_port->buffer(0)->filled_size());
		assert(do_mixdown || m_buffers.at(0)->filled_size() ==
				(*m_connections.begin())->src_port()->buffer(0)->filled_size());
	}
	
	// Get valid buffer size from inbound connections, unless a port on a top-level
	// patch (which will be fed by the MidiDriver)
	if (m_parent->parent() != NULL) {
		if (num_ins == 1) {
			m_buffers.at(0)->filled_size(
				(*m_connections.begin())->src_port()->buffer(0)->filled_size());
			
			if (m_is_tied)
				m_tied_port->buffer(0)->filled_size(m_buffers.at(0)->filled_size());
			
			assert(m_buffers.at(0)->filled_size() ==
				(*m_connections.begin())->src_port()->buffer(0)->filled_size());
		} else {
			// Mixing not implemented
			m_buffers.at(0)->clear();
		}
	}

	assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data());

	if (!do_mixdown || m_buffers.at(0)->filled_size() == 0 || num_ins == 0)
		return;

	//cerr << path() << " - Copying MIDI buffer" << endl;
	
	// Be sure buffers are the same as tied port's, if joined
	assert(!m_is_tied || m_tied_port != NULL);
	assert(!m_is_tied || m_buffers.at(0)->data() == m_tied_port->buffer(0)->data());

	if (num_ins > 0)
		for (size_t i=0; i < m_buffers.at(0)->filled_size(); ++i)
			m_buffers.at(0)[i] = (*m_connections.begin())->buffer(0)[i];
}


} // namespace Om