/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <dlfcn.h>
#include <syslog.h>
#include <ladspa.h>

#include "cras_dsp_module.h"

#define PLUGIN_PATH_PREFIX "/usr/lib/ladspa"
#define PLUGIN_PATH_MAX 256

struct ladspa_data {
	void *dlopen_handle;  /* the handle returned by dlopen() */
	const LADSPA_Descriptor *descriptor;
	LADSPA_Handle *handle;	/* returned by instantiate() */
	int activated;
};

static void activate(struct dsp_module *module)
{
	struct ladspa_data *data = module->data;
	const LADSPA_Descriptor *desc = data->descriptor;
	data->activated = 1;
	if (!desc->activate)
		return;
	desc->activate(data->handle);
}

static void deactivate(struct dsp_module *module)
{
	struct ladspa_data *data = module->data;
	const LADSPA_Descriptor *desc = data->descriptor;
	data->activated = 0;
	if (!desc->deactivate)
		return;
	desc->deactivate(data->handle);
}

static int instantiate(struct dsp_module *module, unsigned long sample_rate)
{
	struct ladspa_data *data = module->data;
	const LADSPA_Descriptor *desc = data->descriptor;

	data->handle = desc->instantiate(desc, sample_rate);
	if (!data->handle) {
		syslog(LOG_ERR, "instantiate failed for %s, rate %ld",
		       desc->Label, sample_rate);
		return -1;
	}
	return 0;
}

static void deinstantiate(struct dsp_module *module)
{
	struct ladspa_data *data = module->data;
	const LADSPA_Descriptor *desc = data->descriptor;

	if (data->activated)
		deactivate(module);
	desc->cleanup(data->handle);
	data->handle = NULL;
}

static void connect_port(struct dsp_module *module, unsigned long port,
			     float *data_location)
{
	struct ladspa_data *data = module->data;
	const LADSPA_Descriptor *desc = data->descriptor;
	desc->connect_port(data->handle, port, data_location);
}

static int get_delay(struct dsp_module *module)
{
	return 0;
}

static void run(struct dsp_module *module, unsigned long sample_count)
{
	struct ladspa_data *data = module->data;
	const LADSPA_Descriptor *desc = data->descriptor;

	if (!data->activated)
		activate(module);
	desc->run(data->handle, sample_count);
}

static void free_module(struct dsp_module *module)
{
	struct ladspa_data *data = module->data;
	if (data->activated)
		deactivate(module);
	if (data->dlopen_handle) {
		dlclose(data->dlopen_handle);
		data->dlopen_handle = NULL;
	}
	free(module->data);
	free(module);
}

static int get_properties(struct dsp_module *module)
{
	struct ladspa_data *data = module->data;
	int properties = 0;
	if (LADSPA_IS_INPLACE_BROKEN(data->descriptor->Properties))
		properties |= MODULE_INPLACE_BROKEN;
	return properties;
}

static void dump(struct dsp_module *module, struct dumper *d)
{
	struct ladspa_data *data = module->data;
	const LADSPA_Descriptor *descriptor = data->descriptor;

	dumpf(d, "  LADSPA: dlopen=%p, desc=%p, handle=%p, activated=%d\n",
	      data->dlopen_handle, data->descriptor, data->handle,
	      data->activated);
	if (descriptor) {
		dumpf(d, "   Name=%s\n", descriptor->Name);
		dumpf(d, "   Maker=%s\n", descriptor->Maker);
	}
}

static int verify_plugin_descriptor(struct plugin *plugin,
				    const LADSPA_Descriptor *desc)
{
	int i;
	struct port *port;

	if (desc->PortCount != ARRAY_COUNT(&plugin->ports)) {
		syslog(LOG_ERR, "port count mismatch: %s", plugin->title);
		return -1;
	}

	FOR_ARRAY_ELEMENT(&plugin->ports, i, port) {
		LADSPA_PortDescriptor port_desc = desc->PortDescriptors[i];
		if ((port->direction == PORT_INPUT) !=
		    !!(port_desc & LADSPA_PORT_INPUT)) {
			syslog(LOG_ERR, "port direction mismatch: %s:%d!",
			       plugin->title, i);
			return -1;
		}

		if ((port->type == PORT_CONTROL) !=
		    !!(port_desc & LADSPA_PORT_CONTROL)) {
			syslog(LOG_ERR, "port type mismatch: %s:%d!",
			       plugin->title, i);
			return -1;
		}
	}

	return 0;
}

struct dsp_module *cras_dsp_module_load_ladspa(struct plugin *plugin)
{
	char path[PLUGIN_PATH_MAX];
	int index;
	LADSPA_Descriptor_Function desc_func;
	struct ladspa_data *data = calloc(1, sizeof(struct ladspa_data));
	struct dsp_module *module;

	snprintf(path, sizeof(path), "%s/%s", PLUGIN_PATH_PREFIX,
		 plugin->library);

	data->dlopen_handle = dlopen(path, RTLD_NOW);
	if (!data->dlopen_handle) {
		syslog(LOG_ERR, "cannot open plugin from %s: %s", path,
		       dlerror());
		goto bail;
	}

	desc_func = (LADSPA_Descriptor_Function)dlsym(data->dlopen_handle,
						      "ladspa_descriptor");

	if (!desc_func) {
		syslog(LOG_ERR, "cannot find descriptor function from %s: %s",
		       path, dlerror());
		goto bail;
	}

	for (index = 0; ; index++) {
		const LADSPA_Descriptor *desc = desc_func(index);
		if (desc == NULL) {
			syslog(LOG_ERR, "cannot find label %s from %s",
			       plugin->label, path);
			goto bail;
		}
		if (strcmp(desc->Label, plugin->label) == 0) {
			syslog(LOG_DEBUG, "plugin '%s' loaded from %s",
			       plugin->label, path);
			if (verify_plugin_descriptor(plugin, desc) != 0)
				goto bail;
			data->descriptor = desc;
			break;
		}
	}

	module = calloc(1, sizeof(struct dsp_module));
	module->data = data;
	module->instantiate = &instantiate;
	module->connect_port = &connect_port;
	module->get_delay = &get_delay;
	module->run = &run;
	module->deinstantiate = &deinstantiate;
	module->get_properties = &get_properties;
	module->free_module = &free_module;
	module->dump = &dump;
	return module;
bail:
	if (data->dlopen_handle)
		dlclose(data->dlopen_handle);
	free(data);
	return NULL;
}