/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            pluginlv2.cc
 *
 *  Sun Feb  7 15:15:24 CET 2016
 *  Copyright 2016 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of PluginGizmo.
 *
 *  PluginGizmo is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  PluginGizmo 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with PluginGizmo; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "pluginlv2.h"

#include <sstream>
#include <assert.h>

#include "midievent.h"

#define LV2_PLUGIN_URI__atom LV2_PLUGIN_URI "/atom#"
#define LV2_PLUGIN_URI__instance LV2_PLUGIN_URI "#plugin-instance"
#define LV2_PLUGIN_URI__ui LV2_PLUGIN_URI "#ui"

#include "lv2/lv2plug.in/ns/ext/atom/util.h"
#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
#include <lv2/lv2plug.in/ns/ext/instance-access/instance-access.h>

void PluginLV2::init()
{
	for(auto& m : midnamData)
	{
		m.first = -1; // Mark midnam slot as unsued.
		m.second.reserve(64); // Reserve 64 characters for the midnam name.
	}
}

bool PluginLV2::getFreeWheel() const
{
	return free_wheel;
}

float PluginLV2::getSamplerate()
{
	return sample_rate;
}

std::size_t PluginLV2::getFramesize()
{
	return frame_size;
}

bool PluginLV2::getActive()
{
	return active;
}

float PluginLV2::getLatency()
{
	if(latency_port)
	{
		return *latency_port;
	}

	return 0.0f;
}

void PluginLV2::setLatency(float latency)
{
	if(latency_port)
	{
		*latency_port = latency;
	}
}

LV2_Handle PluginLV2::instantiate(const LV2_Descriptor* descriptor,
                                  double sample_rate,
                                  const char* bundle_path,
                                  const LV2_Feature *const *features)
{
	PluginLV2* plugin_lv2 = createEffectInstance();

	plugin_lv2->sample_rate = sample_rate;

	plugin_lv2->input_event_ports.resize(plugin_lv2->getNumberOfMidiInputs(),
	                                     nullptr);
	plugin_lv2->output_event_ports.resize(plugin_lv2->getNumberOfMidiOutputs(),
	                                      nullptr);

	plugin_lv2->input_audio_ports.resize(plugin_lv2->getNumberOfAudioInputs());
	plugin_lv2->output_audio_ports.resize(plugin_lv2->getNumberOfAudioOutputs());

	for(auto& port : plugin_lv2->output_audio_ports)
	{
		port = nullptr;
	}

	for(auto& port : plugin_lv2->input_audio_ports)
	{
		port = nullptr;
	}

	while(*features != nullptr)
	{
		std::string uri = (*features)->URI;
		void* data = (*features)->data;

		if(uri == LV2_URID__map)
		{
			plugin_lv2->map = (LV2_URID_Map*)data;
		}

#ifdef DISPLAY_INTERFACE
		if(uri == LV2_INLINEDISPLAY__queue_draw)
		{
			plugin_lv2->queue_draw = (LV2_Inline_Display*)data;
		}
#endif

#ifdef MIDNAM_INTERFACE
		if(uri == LV2_MIDNAM__update)
		{
			plugin_lv2->midnam = (LV2_Midnam*)data;
		}
#endif

		++features;
	}

	// Only reported on creation.
	plugin_lv2->onSamplerateChange(sample_rate);

	return (LV2_Handle)plugin_lv2;
}

void PluginLV2::connectPort(LV2_Handle instance, uint32_t port,
                            void *data_location)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)instance;

	if(port == static_cast<uint32_t>(LV2Ports::FreeWheel))
	{
		plugin_lv2->free_wheel_port = (float*)data_location;
		if(plugin_lv2->free_wheel_port)
		{
			plugin_lv2->free_wheel = (*plugin_lv2->free_wheel_port != 0.0f);
			// Signal first time.
			plugin_lv2->onFreeWheelChange(plugin_lv2->free_wheel);
		}
	}

	if(port == static_cast<uint32_t>(LV2Ports::Latency))
	{
		plugin_lv2->latency_port = (float*)data_location;
	}

	uint32_t port_offset = static_cast<uint32_t>(LV2Ports::PortOffset);

	if((port >= port_offset) &&
	   (port < (port_offset + plugin_lv2->getNumberOfMidiInputs())))
	{
		int port_index = port - port_offset;
		plugin_lv2->input_event_ports[port_index] =
			(LV2_Atom_Sequence*)data_location;
	}

	port_offset += plugin_lv2->getNumberOfMidiInputs();

	if((port >= port_offset) &&
	   (port < (port_offset + plugin_lv2->getNumberOfMidiOutputs())))
	{
		int port_index = port - port_offset;
		plugin_lv2->output_event_ports[port_index] =
			(LV2_Atom_Sequence*)data_location;
	}

	port_offset += plugin_lv2->getNumberOfMidiOutputs();

	if((port >= port_offset) &&
	   (port < (port_offset + plugin_lv2->getNumberOfAudioInputs())))
	{
		int port_index = port - port_offset;
		plugin_lv2->input_audio_ports[port_index] = (float*)data_location;
	}

	port_offset += plugin_lv2->getNumberOfAudioInputs();

	if((port >= port_offset) &&
	   (port < (port_offset + plugin_lv2->getNumberOfAudioOutputs())))
	{
		int port_index = port - port_offset;
		plugin_lv2->output_audio_ports[port_index] = (float*)data_location;
	}
}

class Sequence {
public:
	Sequence(LV2_URID_Map& map, void* buffer, std::size_t buffer_size);
	void clear();
	void addMidiEvent(std::size_t pos, const char* data, std::size_t size);
	void* data();

private:
	void *buffer;
	std::size_t buffer_size;
	LV2_Atom_Sequence *seq;
	LV2_URID_Map& map;
};

Sequence::Sequence(LV2_URID_Map& map, void* buffer, std::size_t buffer_size)
	: map(map)
{
	this->buffer = buffer;
	this->buffer_size = buffer_size;

	seq = (LV2_Atom_Sequence*)buffer;

	seq->atom.size = sizeof(LV2_Atom_Sequence_Body);
	seq->atom.type = map.map(map.handle, LV2_ATOM__Sequence);
	seq->body.unit = 0;
	seq->body.pad = 0;
}

// Keep this to support atom extension from lv2 < 1.10
static inline void _lv2_atom_sequence_clear(LV2_Atom_Sequence* seq)
{
	seq->atom.size = sizeof(LV2_Atom_Sequence_Body);
}

void Sequence::clear()
{
	_lv2_atom_sequence_clear(seq);
}

// Keep this to support atom extension from lv2 < 1.10
static inline LV2_Atom_Event*
_lv2_atom_sequence_append_event(LV2_Atom_Sequence* seq,
                                uint32_t capacity,
                                const LV2_Atom_Event* event)
{
	const uint32_t total_size = (uint32_t)sizeof(*event) + event->body.size;

	if(capacity - seq->atom.size < total_size)
	{
	  return nullptr;
	}

	LV2_Atom_Event* e = lv2_atom_sequence_end(&seq->body, seq->atom.size);
	memcpy(e, event, total_size);

	seq->atom.size += lv2_atom_pad_size(total_size);

	return e;
}

void Sequence::addMidiEvent(std::size_t pos, const char* data, std::size_t size)
{
	struct MIDINoteEvent {
		LV2_Atom_Event event;
		uint8_t        msg[6];
	};

	MIDINoteEvent ev;
	ev.event.time.frames = pos;
	ev.event.body.type = map.map(map.handle, LV2_MIDI__MidiEvent);
	ev.event.body.size = size;

	assert(size <= sizeof(ev.msg)); // Assert that we have room for the message

	memcpy(ev.msg, data, size);

	_lv2_atom_sequence_append_event(seq, this->buffer_size, &ev.event);
}

void* Sequence::data()
{
	return buffer;
}

void PluginLV2::run(LV2_Handle instance, uint32_t sample_count)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)instance;

	// Handle free-wheel state
	if(plugin_lv2->free_wheel_port != nullptr)
	{
		bool new_free_wheel = *plugin_lv2->free_wheel_port != 0.0f;
		if(new_free_wheel != plugin_lv2->free_wheel)
		{
			plugin_lv2->free_wheel = new_free_wheel;
			plugin_lv2->onFreeWheelChange(plugin_lv2->free_wheel);
		}
	}

	// Handle frame size
	if(plugin_lv2->frame_size != sample_count)
	{
		plugin_lv2->frame_size = sample_count;
		plugin_lv2->onFramesizeChange(plugin_lv2->frame_size);
	}

	// Convert input lv2 events to input events.
	std::vector<MidiEvent> input_events;
	for(std::size_t port = 0; port < plugin_lv2->getNumberOfMidiInputs(); ++port)
	{
		if(plugin_lv2->input_event_ports[port] == nullptr)
		{
			continue; //  Not yet connected.
		}

		auto& event_port = plugin_lv2->input_event_ports[port];

		for(LV2_Atom_Event* ev = lv2_atom_sequence_begin(&event_port->body);
		    !lv2_atom_sequence_is_end(&event_port->body, event_port->atom.size, ev);
		    ev = lv2_atom_sequence_next(ev))
		{
			if(ev->body.type != plugin_lv2->map->map(plugin_lv2->map->handle,
			                                         LV2_MIDI__MidiEvent))
			{
				continue; // not a midi event.
			}

			const char* data = (char*)(ev + 1);
			input_events.emplace_back(ev->time.frames, data, ev->body.size);
		}
	}

	std::vector<MidiEvent> output_events;

	// Process events and audio
	plugin_lv2->process(plugin_lv2->pos,
	                    input_events,
	                    output_events,
	                    plugin_lv2->input_audio_ports,
	                    plugin_lv2->output_audio_ports,
	                    sample_count);

	// Convert output events to lv2 events
	if(plugin_lv2->getNumberOfMidiOutputs() > 0)
	{
		if(plugin_lv2->map != nullptr)
		{
			if(plugin_lv2->output_event_ports[0] != nullptr) //  Not yet connected?
			{
				auto& event_port = plugin_lv2->output_event_ports[0]; // TODO: Split?
				Sequence seq(*plugin_lv2->map,
				             &event_port->body + 1,
				             event_port->atom.size);
				for(auto midi_event : output_events)
				{
					seq.addMidiEvent(midi_event.getTime(),
					                 midi_event.getData(),
					                 midi_event.getSize());
				}
			}
		}
	}

	plugin_lv2->pos += sample_count;

#ifdef MIDNAM_INTERFACE
	if(plugin_lv2->midnam && plugin_lv2->midnam_changed.load())
	{
		plugin_lv2->midnam->update(plugin_lv2->midnam->handle);
		plugin_lv2->midnam_changed.store(false);
	}
#endif

#ifdef DISPLAY_INTERFACE
	if(plugin_lv2->queue_draw)
	{
		plugin_lv2->queue_draw->queue_draw(plugin_lv2->queue_draw->handle);
	}
#endif
}

void PluginLV2::activate(LV2_Handle instance)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)instance;
	plugin_lv2->active = true;
	plugin_lv2->onActiveChange(plugin_lv2->active);
}

void PluginLV2::deactivate(LV2_Handle instance)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)instance;
	plugin_lv2->active = false;
	plugin_lv2->onActiveChange(plugin_lv2->active);
}

void PluginLV2::cleanup(LV2_Handle instance)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)instance;
	delete plugin_lv2;
}

//
// State handling
//

LV2_State_Status PluginLV2::save(LV2_Handle instance,
                                 LV2_State_Store_Function store,
                                 LV2_State_Handle handle,
                                 uint32_t flags,
                                 const LV2_Feature *const * features)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)instance;

	if(plugin_lv2->map == nullptr)
	{
		// Missing urid feature?
		return  LV2_STATE_ERR_NO_FEATURE;
	}

	std::string config = plugin_lv2->onStateSave();

	store(handle,
	      plugin_lv2->map->map(plugin_lv2->map->handle,
	                           LV2_PLUGIN_URI__atom "config"),
	      config.data(),
	      config.length(),
	      plugin_lv2->map->map(plugin_lv2->map->handle, LV2_ATOM__Chunk),
	      LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);

	return LV2_STATE_SUCCESS;
}

LV2_State_Status PluginLV2::restore(LV2_Handle instance,
                                    LV2_State_Retrieve_Function retrieve,
                                    LV2_State_Handle handle,
                                    uint32_t flags,
                                    const LV2_Feature *const * features)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)instance;

	if(plugin_lv2->map == nullptr)
	{
		// Missing urid feature?
		return  LV2_STATE_ERR_NO_FEATURE;
	}

	std::size_t size;
	uint32_t type;

	const char* data =
		(const char*)retrieve(handle,
		                      plugin_lv2->map->map(plugin_lv2->map->handle,
		                                           LV2_PLUGIN_URI__atom "config"),
		                      &size, &type, &flags);

	if(data && size)
	{
		std::string config;

		config.append(data, size);
		plugin_lv2->onStateRestore(config);
	}

	return LV2_STATE_SUCCESS;
}

static LV2_State_Interface persist = {
	PluginLV2::save,
	PluginLV2::restore
};

LV2_Inline_Display_Image_Surface* PluginLV2::inlineRender(LV2_Handle instance,
                                                          uint32_t w,
                                                          uint32_t max_h)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)instance;

	plugin_lv2->onInlineRedraw(w, max_h, plugin_lv2->drawContext);

	plugin_lv2->surf.width = plugin_lv2->drawContext.width;
	plugin_lv2->surf.height = plugin_lv2->drawContext.height;
	plugin_lv2->surf.stride = plugin_lv2->surf.width * 4; // stride is in bytes

	plugin_lv2->surf.data = plugin_lv2->drawContext.data;

	return &plugin_lv2->surf;
}

void PluginLV2::setMidnamData(const std::vector<std::pair<int, std::string>>& midnam)
{
	auto idx = 0u;
	for(const auto& m : midnam)
	{
		midnamData[idx].first = m.first;
		// Duplicate name making sure no reallocation is being performed.
		midnamData[idx].second =
			m.second.substr(0, midnamData[idx].second.capacity() - 1);
		idx++;
	}
	for(;idx < this->midnamData.size(); ++idx)
	{
		midnamData[idx].first = -1; // Mark as unused
	}
	midnam_changed.store(true);
}

#ifdef MIDNAM_INTERFACE
char* PluginLV2::MidnamFile (LV2_Handle instance)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)instance;
	std::stringstream ss;
	ss <<
		"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
		"<!DOCTYPE MIDINameDocument PUBLIC \"-//MIDI Manufacturers Association//DTD MIDINameDocument 1.0//EN\" \"http://dev.midi.org/dtds/MIDINameDocument10.dtd\">\n"
		"<MIDINameDocument>\n"
		"  <Author/>\n"
		"  <MasterDeviceNames>\n"
		"    <Manufacturer>" << plugin_lv2->getVendorString() << "</Manufacturer>\n"
		"    <Model>" << plugin_lv2->getProductString() << ":" << ((const void *) instance) << "</Model>\n"; //<< needs to match MidnamModel()

	ss << "    <CustomDeviceMode Name=\"Default\">\n";
	ss << "      <ChannelNameSetAssignments>\n";
	for (int c = 0; c < 16; ++c)
	{
		ss << "        <ChannelNameSetAssign Channel=\"" << (c + 1) << "\" NameSet=\"Presets\"/>\n";
	}
	ss << "      </ChannelNameSetAssignments>\n";
	ss << "    </CustomDeviceMode>\n";


	ss <<
		"    <ChannelNameSet Name=\"Presets\">\n"
		"      <AvailableForChannels>\n";
	for (int c = 0; c < 16; ++c) {
		ss << "        <AvailableChannel Channel=\"" << (c + 1) << "\" Available=\"true\"/>\n";
	}
	ss <<
		"      </AvailableForChannels>\n"
		"      <UsesNoteNameList Name=\"Notes\"/>\n"
		"      <PatchBank Name=\"User Patches\">\n"
		"        <PatchNameList Name=\"User Patches\"/>\n"
		"       </PatchBank>\n"
		"    </ChannelNameSet>\n"
		"    <NoteNameList Name=\"Notes\">\n";
	// TODO: Fill in empty slots for the notes that are not in the map
	// Does Ardour preserve the note names for the unspecified notes?
	// ... and is this a bug??
	for(const auto& m : plugin_lv2->midnamData)
	{
		if(m.first == -1)
		{
			continue;
		}
		ss << "      <Note Number=\"" << m.first << "\" Name=\""<< m.second << "\"/>\n";
	}

	ss <<
		"    </NoteNameList>\n"
		"  </MasterDeviceNames>\n"
		"</MIDINameDocument>";

	return strdup (ss.str().c_str());
}

char* PluginLV2::MidnamModel(LV2_Handle instance)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)instance;
	char* rv = (char*) malloc (64 * sizeof (char));
	snprintf(rv, 64, "%s:%p",
	         plugin_lv2->getProductString().data(), (void*) instance);
	rv[63] = 0;
	return rv;
}

void  PluginLV2::MidnamFree  (char* v)
{
	free (v);
}
#endif

const void* PluginLV2::extensionData(const char *uri)
{
	if(!strcmp(uri, LV2_STATE__interface))
	{
		return &persist;
	}

#ifdef DISPLAY_INTERFACE
	static const LV2_Inline_Display_Interface display  = { inlineRender };
	if(!strcmp(uri, LV2_INLINEDISPLAY__interface))
	{
		return &display;
	}
#endif

#ifdef MIDNAM_INTERFACE
	static const LV2_Midnam_Interface midnam = { MidnamFile, MidnamModel, MidnamFree };
	if (!strcmp (uri, LV2_MIDNAM__interface))
	{
		return &midnam;
	}
#endif

	return nullptr;
}


//
// GUI
//
LV2UI_Handle PluginLV2::uiInstantiate(const LV2UI_Descriptor*descriptor,
                                      const char * plugin_uri,
                                      const char * bundle_path,
                                      LV2UI_Write_Function write_function,
                                      LV2UI_Controller controller,
                                      LV2UI_Widget* widget,
                                      const LV2_Feature * const * features)
{
	LV2_Handle instance = nullptr;
	void* parent = nullptr;
	LV2UI_Resize* resize = nullptr;

	while(*features != nullptr) {
		std::string uri = (*features)->URI;
		void *data = (*features)->data;

		if(uri == LV2_INSTANCE_ACCESS_URI)
		{
			instance = (LV2_Handle)data;
		}

		if(uri == LV2_UI__parent)
		{
			parent = data;
		}

		if(uri == LV2_UI__resize)
		{
	    resize = (LV2UI_Resize*)data;
		}

		features++;
	}

	if(instance == nullptr)
	{
		return nullptr;
	}

	PluginLV2* plugin_lv2 = (PluginLV2*)instance;

	// Do we have a GUI?
	if(plugin_lv2->hasGUI() == false)
	{
		return nullptr;
	}

	plugin_lv2->resize = resize;

	*widget = plugin_lv2->createWindow(parent);

	return plugin_lv2;
}

void PluginLV2::resizeWindow(std::size_t width, std::size_t height)
{
	if(resize)
	{
		resize->ui_resize(resize->handle, width, height);
	}
}

void PluginLV2::closeWindow()
{
}

static const LV2_Descriptor descriptor = {
	LV2_PLUGIN_URI,
	PluginLV2::instantiate,
	PluginLV2::connectPort,
	PluginLV2::activate,
	PluginLV2::run,
	PluginLV2::deactivate,
	PluginLV2::cleanup,
	PluginLV2::extensionData
};

void PluginLV2::uiCleanup(LV2UI_Handle handle)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)handle;
	plugin_lv2->onDestroyWindow () ;

}

int PluginLV2::uiIdle(LV2UI_Handle handle)
{
	PluginLV2* plugin_lv2 = (PluginLV2*)handle;
	plugin_lv2->onIdle();
	return 0;
}

static const LV2UI_Idle_Interface idle_iface = {
	PluginLV2::uiIdle
};

const void* PluginLV2::uiExtensionData(const char* uri)
{
	if(!strcmp(uri, LV2_UI__idleInterface))
	{
		return &idle_iface;
	}

	return NULL;
}

static LV2UI_Descriptor ui_descriptor = {
	LV2_PLUGIN_URI__ui,
	PluginLV2::uiInstantiate,
	PluginLV2::uiCleanup,
	nullptr,//PluginLV2::ui_port_event,
	PluginLV2::uiExtensionData
};

#ifdef __cplusplus
extern "C" {
#endif

LV2_SYMBOL_EXPORT
const LV2_Descriptor* lv2_descriptor(uint32_t index)
{
	switch (index) {
	case 0:
		return &descriptor;
	default:
		return nullptr;
	}
}

LV2_SYMBOL_EXPORT
const LV2UI_Descriptor *lv2ui_descriptor(uint32_t index)
{
	switch(index) {
	case 0:
		return &ui_descriptor;
	default:
		return nullptr;
	}
}

//
// DynManifest experiments failed, but the code is kept here for nostalgic
// reasons :-)
//
//
//int lv2_dyn_manifest_open(LV2_Dyn_Manifest_Handle* handle,
//                          const LV2_Feature* const* features)
//{
//	*handle = createEffectInstance();
//	return 0;
//}
//
//int lv2_dyn_manifest_get_subjects(LV2_Dyn_Manifest_Handle handle, FILE* fp)
//{
////	PluginLV2* plugin_lv2 = (PluginLV2*)handle;
//
//	fprintf(fp, "@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .\n");
//	fprintf(fp, "<" LV2_PLUGIN_URI "> a lv2:Plugin .\n");
//	return 0;
//}
//
//int lv2_dyn_manifest_get_data(LV2_Dyn_Manifest_Handle handle, FILE* fp,
//                              const char* uri)
//{
//	//PluginLV2* plugin_lv2 = (PluginLV2*)handle;
//	printf("%s '%s'\n", __PRETTY_FUNCTION__, uri);
//	return 0;
//}
//
//void lv2_dyn_manifest_close(LV2_Dyn_Manifest_Handle handle)
//{
//	PluginLV2* plugin_lv2 = (PluginLV2*)handle;
//	delete plugin_lv2;
//}

#ifdef __cplusplus
}
#endif
