/*
  This file is part of Ingen.
  Copyright 2007-2016 David Robillard <http://drobilla.net/>

  Ingen is free software: you can redistribute it and/or modify it under the
  terms of the GNU Affero General Public License as published by the Free
  Software Foundation, either version 3 of the License, or 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 Affero General Public License for details.

  You should have received a copy of the GNU Affero General Public License
  along with Ingen.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdint.h>

#include <cassert>
#include <cmath>

#include <glibmm/miscutils.h>
#include <glibmm/convert.h>

#include "lv2/lv2plug.in/ns/ext/morph/morph.h"
#include "lv2/lv2plug.in/ns/ext/presets/presets.h"
#include "lv2/lv2plug.in/ns/ext/options/options.h"
#include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h"
#include "lv2/lv2plug.in/ns/ext/state/state.h"

#include "raul/Maid.hpp"
#include "raul/Array.hpp"

#include "ingen/Log.hpp"
#include "ingen/URIMap.hpp"
#include "ingen/URIs.hpp"

#include "Buffer.hpp"
#include "Engine.hpp"
#include "GraphImpl.hpp"
#include "InputPort.hpp"
#include "LV2Block.hpp"
#include "LV2Plugin.hpp"
#include "OutputPort.hpp"
#include "PortImpl.hpp"
#include "RunContext.hpp"
#include "Worker.hpp"

using namespace std;

namespace Ingen {
namespace Server {

/** Partially construct a LV2Block.
 *
 * Object is not usable until instantiate() is called with success.
 * (It _will_ crash!)
 */
LV2Block::LV2Block(LV2Plugin*          plugin,
                   const Raul::Symbol& symbol,
                   bool                polyphonic,
                   GraphImpl*          parent,
                   SampleRate          srate)
	: BlockImpl(plugin, symbol, polyphonic, parent, srate)
	, _lv2_plugin(plugin)
	, _worker_iface(NULL)
{
	assert(_lv2_plugin);
}

LV2Block::~LV2Block()
{
	// Explicitly drop instances first to prevent reference cycles
	drop_instances(_instances);
	drop_instances(_prepared_instances);
}

SPtr<LV2Block::Instance>
LV2Block::make_instance(URIs&      uris,
                        SampleRate rate,
                        uint32_t   voice,
                        bool       preparing)
{
	const LilvPlugin* lplug  = _lv2_plugin->lilv_plugin();
	LilvInstance*     inst   = lilv_plugin_instantiate(
		lplug, rate, _features->array());

	if (!inst) {
		parent_graph()->engine().log().error(
			fmt("Failed to instantiate <%1%>\n")
			% _lv2_plugin->uri().c_str());
		return SPtr<Instance>();
	}

	const LV2_Options_Interface* options_iface = NULL;
	if (lilv_plugin_has_extension_data(lplug, uris.opt_interface)) {
		options_iface = (const LV2_Options_Interface*)
			lilv_instance_get_extension_data(inst, LV2_OPTIONS__interface);
	}

	for (uint32_t p = 0; p < num_ports(); ++p) {
		PortImpl* const port   = _ports->at(p);
		Buffer* const   buffer = (preparing)
			? port->prepared_buffer(voice).get()
			: port->buffer(voice).get();
		if (port->is_morph() && port->is_a(PortType::CV)) {
			if (options_iface) {
				const LV2_URID port_type = uris.lv2_CVPort;
				const LV2_Options_Option options[] = {
					{ LV2_OPTIONS_PORT, p, uris.morph_currentType,
					  sizeof(LV2_URID), uris.atom_URID, &port_type },
					{ LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL }
				};
				options_iface->set(inst->lv2_handle, options);
			}
		}

		if (buffer) {
			if (port->is_a(PortType::CV) || port->is_a(PortType::CONTROL)) {
				buffer->set_block(port->value().get<float>(), 0, buffer->nframes());
			} else {
				buffer->clear();
			}
		}
	}

	if (options_iface) {
		for (uint32_t p = 0; p < num_ports(); ++p) {
			PortImpl* const port = _ports->at(p);
			if (port->is_auto_morph()) {
				LV2_Options_Option options[] = {
					{ LV2_OPTIONS_PORT, p, uris.morph_currentType, 0, 0, NULL },
					{ LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, 0 }
				};

				options_iface->get(inst->lv2_handle, options);
				if (options[0].value) {
					LV2_URID type = *(const LV2_URID*)options[0].value;
					if (type == _uris.lv2_ControlPort) {
						port->set_type(PortType::CONTROL, 0);
					} else if (type == _uris.lv2_CVPort) {
						port->set_type(PortType::CV, 0);
					} else {
						parent_graph()->engine().log().error(
							fmt("%1% auto-morphed to unknown type %2%\n")
							% port->path().c_str() % type);
						return SPtr<Instance>();
					}
				} else {
					parent_graph()->engine().log().error(
						fmt("Failed to get auto-morphed type of %1%\n")
						% port->path().c_str());
				}
			}
		}
	}

	return std::make_shared<Instance>(inst);
}

bool
LV2Block::prepare_poly(BufferFactory& bufs, uint32_t poly)
{
	if (!_polyphonic)
		poly = 1;

	BlockImpl::prepare_poly(bufs, poly);

	if (_polyphony == poly)
		return true;

	const SampleRate rate = bufs.engine().sample_rate();
	assert(!_prepared_instances);
	_prepared_instances = bufs.maid().make_managed<Instances>(
		poly, *_instances, SPtr<Instance>());
	for (uint32_t i = _polyphony; i < _prepared_instances->size(); ++i) {
		SPtr<Instance> inst = make_instance(bufs.uris(), rate, i, true);
		if (!inst) {
			_prepared_instances.reset();
			return false;
		}

		_prepared_instances->at(i) = inst;

		if (_activated) {
			lilv_instance_activate(inst->instance);
		}
	}

	return true;
}

bool
LV2Block::apply_poly(RunContext& context, uint32_t poly)
{
	if (!_polyphonic)
		poly = 1;

	if (_prepared_instances) {
		_instances = std::move(_prepared_instances);
	}
	assert(poly <= _instances->size());

	return BlockImpl::apply_poly(context, poly);
}

/** Instantiate self from LV2 plugin descriptor.
 *
 * Implemented as a seperate function (rather than in the constructor) to
 * allow graceful error-catching of broken plugins.
 *
 * Returns whether or not plugin was successfully instantiated.  If return
 * value is false, this object may not be used.
 */
bool
LV2Block::instantiate(BufferFactory& bufs, const LilvState* state)
{
	const Ingen::URIs& uris      = bufs.uris();
	Ingen::World*      world     = bufs.engine().world();
	const LilvPlugin*  plug      = _lv2_plugin->lilv_plugin();
	Ingen::Forge&      forge     = bufs.forge();
	const uint32_t     num_ports = lilv_plugin_get_num_ports(plug);

	LilvNode* lv2_connectionOptional = lilv_new_uri(
		bufs.engine().world()->lilv_world(), LV2_CORE__connectionOptional);

	_ports = bufs.maid().make_managed<BlockImpl::Ports>(num_ports, nullptr);

	bool ret = true;

	float* min_values = new float[num_ports];
	float* max_values = new float[num_ports];
	float* def_values = new float[num_ports];
	lilv_plugin_get_port_ranges_float(plug, min_values, max_values, def_values);
	uint32_t max_sequence_size = 0;

	// Get all the necessary information about ports
	for (uint32_t j = 0; j < num_ports; ++j) {
		const LilvPort* id = lilv_plugin_get_port_by_index(plug, j);

		/* LV2 port symbols are guaranteed to be unique, valid C identifiers,
		   and Lilv guarantees that lilv_port_get_symbol() is valid. */
		const Raul::Symbol port_sym(
			lilv_node_as_string(lilv_port_get_symbol(plug, id)));

		// Get port type
		Atom     val;
		PortType port_type     = PortType::UNKNOWN;
		LV2_URID buffer_type   = 0;
		bool     is_morph      = false;
		bool     is_auto_morph = false;
		if (lilv_port_is_a(plug, id, uris.lv2_ControlPort)) {
			if (lilv_port_is_a(plug, id, uris.morph_MorphPort)) {
				is_morph = true;
				LilvNodes* types = lilv_port_get_value(
					plug, id, uris.morph_supportsType);
				LILV_FOREACH(nodes, i, types) {
					const LilvNode* type = lilv_nodes_get(types, i);
					if (lilv_node_equals(type, uris.lv2_CVPort)) {
						port_type   = PortType::CV;
						buffer_type = uris.atom_Sound;
					}
				}
				lilv_nodes_free(types);
			}
			if (port_type == PortType::UNKNOWN) {
				port_type   = PortType::CONTROL;
				buffer_type = uris.atom_Float;
			}
		} else if (lilv_port_is_a(plug, id, uris.lv2_CVPort)) {
			port_type   = PortType::CV;
			buffer_type = uris.atom_Sound;
		} else if (lilv_port_is_a(plug, id, uris.lv2_AudioPort)) {
			port_type   = PortType::AUDIO;
			buffer_type = uris.atom_Sound;
		} else if (lilv_port_is_a(plug, id, uris.atom_AtomPort)) {
			port_type = PortType::ATOM;
		}

		if (lilv_port_is_a(plug, id, uris.morph_AutoMorphPort)) {
			is_auto_morph = true;
		}

		// Get buffer type if necessary (atom ports)
		if (!buffer_type) {
			LilvNodes* types = lilv_port_get_value(
				plug, id, uris.atom_bufferType);
			LILV_FOREACH(nodes, i, types) {
				const LilvNode* type = lilv_nodes_get(types, i);
				if (lilv_node_is_uri(type)) {
					buffer_type = bufs.engine().world()->uri_map().map_uri(
						lilv_node_as_uri(type));
				}
			}
			lilv_nodes_free(types);
		}

		const bool optional = lilv_port_has_property(
			plug, id, lv2_connectionOptional);

		uint32_t port_buffer_size = bufs.default_size(buffer_type);
		if (port_buffer_size == 0 && !optional) {
			parent_graph()->engine().log().error(
				fmt("<%1%> port `%2%' has unknown buffer type\n")
				% _lv2_plugin->uri().c_str() % port_sym.c_str());
			ret = false;
			break;
		}

		if (port_type == PortType::ATOM) {
			// Get default value, and its length
			LilvNodes* defaults = lilv_port_get_value(plug, id, uris.lv2_default);
			LILV_FOREACH(nodes, i, defaults) {
				const LilvNode* d = lilv_nodes_get(defaults, i);
				if (lilv_node_is_string(d)) {
					const char*    str_val     = lilv_node_as_string(d);
					const uint32_t str_val_len = strlen(str_val);
					val = forge.alloc(str_val);
					port_buffer_size = std::max(port_buffer_size, str_val_len);
				} else if (lilv_node_is_uri(d)) {
					const char* uri_val = lilv_node_as_uri(d);
					val = forge.make_urid(
						bufs.engine().world()->uri_map().map_uri(uri_val));
				}
			}
			lilv_nodes_free(defaults);

			if (!val.type() && buffer_type == _uris.atom_URID) {
				val = forge.make_urid(0);
			}

			// Get minimum size, if set in data
			LilvNodes* sizes = lilv_port_get_value(plug, id, uris.rsz_minimumSize);
			LILV_FOREACH(nodes, i, sizes) {
				const LilvNode* d = lilv_nodes_get(sizes, i);
				if (lilv_node_is_int(d)) {
					uint32_t size_val = lilv_node_as_int(d);
					port_buffer_size = std::max(port_buffer_size, size_val);
				}
			}
			lilv_nodes_free(sizes);
			max_sequence_size = std::max(port_buffer_size, max_sequence_size);
			bufs.set_seq_size(max_sequence_size);
		}

		enum { UNKNOWN, INPUT, OUTPUT } direction = UNKNOWN;
		if (lilv_port_is_a(plug, id, uris.lv2_InputPort)) {
			direction = INPUT;
		} else if (lilv_port_is_a(plug, id, uris.lv2_OutputPort)) {
			direction = OUTPUT;
		}

		if ((port_type == PortType::UNKNOWN && !optional) ||
		    direction == UNKNOWN) {
			parent_graph()->engine().log().error(
				fmt("<%1%> port `%2%' has unknown type or direction\n")
				% _lv2_plugin->uri().c_str() % port_sym.c_str());
			ret = false;
			break;
		}

		if (!val.type() && (port_type != PortType::ATOM)) {
			// Ensure numeric ports have a value, use 0 by default
			val = forge.make(isnan(def_values[j]) ? 0.0f : def_values[j]);
		}

		PortImpl* port = (direction == INPUT)
			? static_cast<PortImpl*>(
				new InputPort(bufs, this, port_sym, j, _polyphony,
				              port_type, buffer_type, val))
			: static_cast<PortImpl*>(
				new OutputPort(bufs, this, port_sym, j, _polyphony,
				               port_type, buffer_type, val));

		port->set_morphable(is_morph, is_auto_morph);
		if (direction == INPUT && (port_type == PortType::CONTROL
		                           || port_type == PortType::CV)) {
			port->set_value(val);
			if (!isnan(min_values[j])) {
				port->set_minimum(forge.make(min_values[j]));
			}
			if (!isnan(max_values[j])) {
				port->set_maximum(forge.make(max_values[j]));
			}
		}

		// Inherit certain properties from plugin port
		const LilvNode* preds[] = { uris.lv2_designation,
		                            uris.lv2_portProperty,
		                            uris.atom_supports,
		                            0 };
		for (int p = 0; preds[p]; ++p) {
			LilvNodes* values = lilv_port_get_value(plug, id, preds[p]);
			LILV_FOREACH(nodes, v, values) {
				const LilvNode* val = lilv_nodes_get(values, v);
				if (lilv_node_is_uri(val)) {
					port->add_property(Raul::URI(lilv_node_as_uri(preds[p])),
					                   forge.make_urid(Raul::URI(lilv_node_as_uri(val))));
				}
			}
			lilv_nodes_free(values);
		}

		port->cache_properties();

		_ports->at(j) = port;
	}

	delete[] min_values;
	delete[] max_values;
	delete[] def_values;

	lilv_node_free(lv2_connectionOptional);

	if (!ret) {
		_ports.reset();
		return ret;
	}

	_features = world->lv2_features().lv2_features(world, this);

	// Actually create plugin instances and port buffers.
	const SampleRate rate = bufs.engine().sample_rate();
	_instances = bufs.maid().make_managed<Instances>(
		_polyphony, SPtr<Instance>());
	for (uint32_t i = 0; i < _polyphony; ++i) {
		_instances->at(i) = make_instance(bufs.uris(), rate, i, false);
		if (!_instances->at(i)) {
			return false;
		}
	}

	// Load initial state if no state is explicitly given
	LilvState* default_state = NULL;
	if (!state) {
		state = default_state = load_preset(_lv2_plugin->uri());
	}

	// Apply state
	if (state) {
		apply_state(NULL, state);
	}

	if (default_state) {
		lilv_state_free(default_state);
	}

	// FIXME: Polyphony + worker?
	if (lilv_plugin_has_feature(plug, uris.work_schedule)) {
		_worker_iface = (const LV2_Worker_Interface*)
			lilv_instance_get_extension_data(instance(0),
			                                 LV2_WORKER__interface);
	}

	return ret;
}

bool
LV2Block::save_state(const std::string& dir) const
{
	World*     world  = _lv2_plugin->world();
	LilvWorld* lworld = world->lilv_world();

	LilvState* state = lilv_state_new_from_instance(
		_lv2_plugin->lilv_plugin(), const_cast<LV2Block*>(this)->instance(0),
		&world->uri_map().urid_map_feature()->urid_map,
		NULL, dir.c_str(), dir.c_str(), dir.c_str(), NULL, NULL,
		LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE, NULL);

	if (!state) {
		return false;
	} else if (lilv_state_get_num_properties(state) == 0) {
		lilv_state_free(state);
		return false;
	}

	lilv_state_save(lworld,
	                &world->uri_map().urid_map_feature()->urid_map,
	                &world->uri_map().urid_unmap_feature()->urid_unmap,
	                state,
	                NULL,
	                dir.c_str(),
	                "state.ttl");

	lilv_state_free(state);

	return true;
}

BlockImpl*
LV2Block::duplicate(Engine&             engine,
                    const Raul::Symbol& symbol,
                    GraphImpl*          parent)
{
	const SampleRate rate = engine.sample_rate();

	// Get current state
	LilvState* state = lilv_state_new_from_instance(
		_lv2_plugin->lilv_plugin(), instance(0),
		&engine.world()->uri_map().urid_map_feature()->urid_map,
		NULL, NULL, NULL, NULL, NULL, NULL, LV2_STATE_IS_NATIVE, NULL);

	// Duplicate and instantiate block
	LV2Block* dup = new LV2Block(_lv2_plugin, symbol, _polyphonic, parent, rate);
	if (!dup->instantiate(*engine.buffer_factory(), state)) {
		delete dup;
		return NULL;
	}
	dup->set_properties(properties());

	// Set duplicate port values and properties to the same as ours
	for (uint32_t p = 0; p < num_ports(); ++p) {
		const Atom& val = port_impl(p)->value();
		if (val.is_valid()) {
			dup->port_impl(p)->set_value(val);
		}
		dup->port_impl(p)->set_properties(port_impl(p)->properties());
	}

	return dup;
}

void
LV2Block::activate(BufferFactory& bufs)
{
	BlockImpl::activate(bufs);

	for (uint32_t i = 0; i < _polyphony; ++i)
		lilv_instance_activate(instance(i));
}

void
LV2Block::deactivate()
{
	BlockImpl::deactivate();

	for (uint32_t i = 0; i < _polyphony; ++i)
		lilv_instance_deactivate(instance(i));
}

LV2_Worker_Status
LV2Block::work_respond(LV2_Worker_Respond_Handle handle,
                       uint32_t                  size,
                       const void*               data)
{
	LV2Block* block = (LV2Block*)handle;
	LV2Block::Response* r = new LV2Block::Response(size, data);
	block->_responses.push_back(*r);
	return LV2_WORKER_SUCCESS;
}

LV2_Worker_Status
LV2Block::work(uint32_t size, const void* data)
{
	if (_worker_iface) {
		std::lock_guard<std::mutex> lock(_work_mutex);

		LV2_Handle        inst = lilv_instance_get_handle(instance(0));
		LV2_Worker_Status st   = _worker_iface->work(inst, work_respond, this, size, data);
		if (st) {
			parent_graph()->engine().log().error(
				fmt("Error calling %1% work method\n") % _path);
		}
		return st;
	}
	return LV2_WORKER_ERR_UNKNOWN;
}

void
LV2Block::run(RunContext& context)
{
	for (uint32_t i = 0; i < _polyphony; ++i)
		lilv_instance_run(instance(i), context.nframes());
}

void
LV2Block::post_process(RunContext& context)
{
	/* Handle any worker responses.  Note that this may write to output ports,
	   so must be done first to prevent clobbering worker responses and
	   monitored notification ports. */
	if (_worker_iface) {
		LV2_Handle inst = lilv_instance_get_handle(instance(0));
		while (!_responses.empty()) {
			Response& r = _responses.front();
			_worker_iface->work_response(inst, r.size, r.data);
			_responses.pop_front();
			context.engine().maid()->dispose(&r);
		}

		if (_worker_iface->end_run) {
			_worker_iface->end_run(inst);
		}
	}

	/* Run cycle truly finished, finalise output ports. */
	BlockImpl::post_process(context);
}

LilvState*
LV2Block::load_preset(const Raul::URI& uri)
{
	World*     world  = _lv2_plugin->world();
	LilvWorld* lworld = world->lilv_world();
	LilvNode*  preset = lilv_new_uri(lworld, uri.c_str());

	// Load preset into world if necessary
	lilv_world_load_resource(lworld, preset);

	// Load preset from world
	LV2_URID_Map* map   = &world->uri_map().urid_map_feature()->urid_map;
	LilvState*    state = lilv_state_new_from_world(lworld, map, preset);

	lilv_node_free(preset);
	return state;
}

LilvState*
LV2Block::load_state(World* world, const std::string& path)
{
	LilvWorld*        lworld  = world->lilv_world();
	const std::string uri     = Glib::filename_to_uri(path);
	LilvNode*         subject = lilv_new_uri(lworld, uri.c_str());

	LilvState* state = lilv_state_new_from_file(
		lworld,
		&world->uri_map().urid_map_feature()->urid_map,
		subject,
		path.c_str());

	lilv_node_free(subject);
	return state;
}

void
LV2Block::apply_state(Worker* worker, const LilvState* state)
{
	World*            world = parent_graph()->engine().world();
	SPtr<LV2_Feature> sched;
	if (worker) {
		sched = worker->schedule_feature()->feature(world, this);
	}

	const LV2_Feature* state_features[2] = { NULL, NULL };
	if (sched) {
		state_features[0] = sched.get();
	}

	for (uint32_t v = 0; v < _polyphony; ++v) {
		lilv_state_restore(state, instance(v), NULL, NULL, 0, state_features);
	}
}

static const void*
get_port_value(const char* port_symbol,
               void*       user_data,
               uint32_t*   size,
               uint32_t*   type)
{
	LV2Block* const block = (LV2Block*)user_data;
	PortImpl* const port  = block->port_by_symbol(port_symbol);

	if (port && port->is_input() && port->value().is_valid()) {
		*size = port->value().size();
		*type = port->value().type();
		return port->value().get_body();
	}

	return NULL;
}

boost::optional<Resource>
LV2Block::save_preset(const Raul::URI&  uri,
                      const Properties& props)
{
	World*          world  = parent_graph()->engine().world();
	LilvWorld*      lworld = _lv2_plugin->world()->lilv_world();
	LV2_URID_Map*   lmap   = &world->uri_map().urid_map_feature()->urid_map;
	LV2_URID_Unmap* lunmap = &world->uri_map().urid_unmap_feature()->urid_unmap;

	const std::string path     = Glib::filename_from_uri(uri);
	const std::string dirname  = Glib::path_get_dirname(path);
	const std::string basename = Glib::path_get_basename(path);

	LilvState* state = lilv_state_new_from_instance(
		_lv2_plugin->lilv_plugin(), instance(0), lmap,
		NULL, NULL, NULL, path.c_str(),
		get_port_value, this, LV2_STATE_IS_NATIVE, NULL);

	if (state) {
		const Properties::const_iterator l = props.find(_uris.rdfs_label);
		if (l != props.end() && l->second.type() == _uris.atom_String) {
			lilv_state_set_label(state, l->second.ptr<char>());
		}

		lilv_state_save(lworld, lmap, lunmap, state, NULL,
		                dirname.c_str(), basename.c_str());

		const Raul::URI   uri(lilv_node_as_uri(lilv_state_get_uri(state)));
		const std::string label(lilv_state_get_label(state)
		                        ? lilv_state_get_label(state)
		                        : basename);
		lilv_state_free(state);

		Resource preset(_uris, uri);
		preset.set_property(_uris.rdf_type, _uris.pset_Preset);
		preset.set_property(_uris.rdfs_label, world->forge().alloc(label));
		preset.set_property(_uris.lv2_appliesTo,
		                    world->forge().make_urid(_lv2_plugin->uri()));

		LilvNode* lbundle = lilv_new_uri(
			lworld, Glib::filename_to_uri(dirname + "/").c_str());
		lilv_world_load_bundle(lworld, lbundle);
		lilv_node_free(lbundle);

		return preset;
	}

	return boost::optional<Resource>();
}

void
LV2Block::set_port_buffer(uint32_t    voice,
                          uint32_t    port_num,
                          BufferRef   buf,
                          SampleCount offset)
{
	BlockImpl::set_port_buffer(voice, port_num, buf, offset);
	lilv_instance_connect_port(
		instance(voice),
		port_num,
		buf ? buf->port_data(_ports->at(port_num)->type(), offset) : NULL);
}

} // namespace Server
} // namespace Ingen