/* This file is part of Ingen.
 * Copyright (C) 2007-2009 Dave Robillard <http://drobilla.net>
 *
 * Ingen 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.
 *
 * Ingen 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.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "NodeBase.hpp"
#include <cassert>
#include <stdint.h>
#include "raul/List.hpp"
#include "raul/Array.hpp"
#include "util.hpp"
#include "PluginImpl.hpp"
#include "ClientBroadcaster.hpp"
#include "PortImpl.hpp"
#include "PatchImpl.hpp"
#include "EngineStore.hpp"
#include "ThreadManager.hpp"

using namespace std;

namespace Ingen {


NodeBase::NodeBase(PluginImpl* plugin, const string& name, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size)
	: NodeImpl(parent, name, polyphonic)
	, _plugin(plugin)
	, _polyphony((polyphonic && parent) ? parent->internal_polyphony() : 1)
	, _srate(srate)
	, _buffer_size(buffer_size)
	, _valid_ports(NULL)
	, _input_ready(1)
	, _process_lock(0)
	, _n_inputs_ready(0)
	, _ports(NULL)
	, _providers(new Raul::List<NodeImpl*>())
	, _dependants(new Raul::List<NodeImpl*>())
	, _activated(false)
	, _traversed(false)
{
	assert(_plugin);
	assert(_polyphony > 0);
	assert(_parent == NULL || (_polyphony == parent->internal_polyphony() || _polyphony == 1));
}


NodeBase::~NodeBase()
{
	if (_activated)
		deactivate();

	delete _providers;
	delete _dependants;

	free(_valid_ports);
}


Shared::Port*
NodeBase::port(uint32_t index) const
{
	return (*_ports)[index];
}


const Shared::Plugin*
NodeBase::plugin() const
{
	return _plugin;
}


void
NodeBase::activate()
{
	assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);
	assert(!_activated);
	_activated = true;
}


void
NodeBase::deactivate()
{
	// FIXME: Not true witn monolithic GUI/engine
	//assert(ThreadManager::current_thread_id() == THREAD_POST_PROCESS);
	assert(_activated);
	_activated = false;
}


bool
NodeBase::prepare_poly(BufferFactory& bufs, uint32_t poly)
{
	assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);

	if (!_polyphonic)
		return true;

	if (_ports)
		for (size_t i=0; i < _ports->size(); ++i)
			_ports->at(i)->prepare_poly(bufs, poly);

	return true;
}


bool
NodeBase::apply_poly(Raul::Maid& maid, uint32_t poly)
{
	assert(ThreadManager::current_thread_id() == THREAD_PROCESS);

	if (!_polyphonic)
		return true;

	for (size_t i=0; i < num_ports(); ++i) {
		_ports->at(i)->apply_poly(maid, poly);
		assert(_ports->at(i)->poly() == poly);
	}

	for (uint32_t i=0; i < num_ports(); ++i)
		for (uint32_t j=0; j < _polyphony; ++j)
			set_port_buffer(j, i, _ports->at(i)->prepared_buffer(j));

	return true;
}


void
NodeBase::set_buffer_size(BufferFactory& bufs, size_t size)
{
	assert(ThreadManager::current_thread_id() == THREAD_PROCESS);

	_buffer_size = size;

	if (_ports)
		for (size_t i=0; i < _ports->size(); ++i)
			_ports->at(i)->set_buffer_size(bufs, size);
}


void
NodeBase::reset_input_ready()
{
	_n_inputs_ready = 0;
	_process_lock = 0;
	_input_ready.reset(0);
}


bool
NodeBase::process_lock()
{
	return _process_lock.compare_and_exchange(0, 1);
}


void
NodeBase::process_unlock()
{
	_process_lock = 0;
}


void
NodeBase::wait_for_input(size_t num_providers)
{
	assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
	assert(_process_lock.get() == 1);

	while ((unsigned)_n_inputs_ready.get() < num_providers)
		_input_ready.wait();
}


void
NodeBase::signal_input_ready()
{
	assert(ThreadManager::current_thread_id() == THREAD_PROCESS);
	++_n_inputs_ready;
	_input_ready.post();
}


/** Prepare to run a cycle (in the audio thread)
 */
void
NodeBase::pre_process(Context& context)
{
	assert(ThreadManager::current_thread_id() == THREAD_PROCESS);

	// Mix down input ports
	for (uint32_t i = 0; i < num_ports(); ++i) {
		PortImpl* const port = _ports->at(i);
		if (port->context() == Context::AUDIO)
			port->pre_process(context);
	}
}


/** Prepare to run a cycle (in the audio thread)
 */
void
NodeBase::post_process(Context& context)
{
	assert(ThreadManager::current_thread_id() == THREAD_PROCESS);

	// Write output ports
	for (size_t i = 0; _ports && i < _ports->size(); ++i) {
		PortImpl* const port = _ports->at(i);
		if (port->context() == Context::AUDIO)
			_ports->at(i)->post_process(context);
	}
}


/** Flag a port as set (for message context)
 */
void
NodeBase::set_port_valid(uint32_t port_index)
{
	// Allocate enough space for one bit per port
	if (!_valid_ports)
		_valid_ports = calloc(num_ports() / 8, 1);
	lv2_contexts_set_port_valid(_valid_ports, port_index);
}


void*
NodeBase::valid_ports()
{
	return _valid_ports;
}


void
NodeBase::reset_valid_ports()
{
	if (_valid_ports)
		memset(_valid_ports, '\0', num_ports() / 8);
}


} // namespace Ingen