/*
 * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
 *
 * This program 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.
 *
 * This program 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 more 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>

/* LV2 */
#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
#include "lv2/lv2plug.in/ns/ext/atom/atom.h"
#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
#include "lv2/lv2plug.in/ns/ext/midi/midi.h"

#define RSY_URI "https://community.ardour.org/node/7596"

/* the synth interface */
static void *   synth_alloc      (void);
static void     synth_init       (void *, double rate);
static void     synth_free       (void *);
static void     synth_parse_midi (void *, const uint8_t *data, const size_t size);
static uint32_t synth_sound      (void *, uint32_t written, uint32_t nframes, float **out);

#include "rsynth.c"

typedef enum {
	RSY_MIDIIN = 0,
	RSY_OUTL,
	RSY_OUTR
} PortIndex;

typedef struct {
	const LV2_Atom_Sequence* midiin;
	float* outL;
	float* outR;

	LV2_URID_Map* map;
	LV2_URID midi_MidiEvent;

	double SampleRateD;
	void *synth;
	bool xmas;
} RSynth;

/* main LV2 */

static LV2_Handle
instantiate (const LV2_Descriptor*     descriptor,
             double                    rate,
             const char*               bundle_path,
             const LV2_Feature* const* features)
{
	(void) descriptor; /* unused variable */
	(void) bundle_path; /* unused variable */

	if (rate < 8000) {
		fprintf(stderr, "RSynth.lv2 error: unsupported sample-rate (must be > 8k)\n");
		return NULL;
	}
	RSynth* self = (RSynth*)calloc(1, sizeof(RSynth));
	if(!self) {
		return NULL;
	}

	self->SampleRateD = rate;

	int i;
	for (i=0; features[i]; ++i) {
		if (!strcmp(features[i]->URI, LV2_URID__map)) {
			self->map = (LV2_URID_Map*)features[i]->data;
		}
	}

	if (!self->map) {
		fprintf(stderr, "RSynth.lv2 error: Host does not support urid:map\n");
		free(self);
		return NULL;
	}

	self->midi_MidiEvent = self->map->map(self->map->handle, LV2_MIDI__MidiEvent);

	self->synth = synth_alloc();
	synth_init(self->synth, rate);

	if (getenv("ITSXMAS")) {
		printf("reasonable synth.lv2 says: happy holidays!\n");
		self->xmas = true;
	}

	return (LV2_Handle)self;
}

static void
connect_port (LV2_Handle handle,
              uint32_t   port,
              void*      data)
{
  RSynth* self = (RSynth*)handle;

  switch ((PortIndex)port) {
    case RSY_MIDIIN:
      self->midiin = (const LV2_Atom_Sequence*)data;
      break;
    case RSY_OUTL:
      self->outL = (float*)data;
      break;
    case RSY_OUTR:
      self->outR = (float*)data;
      break;
  }
}

static void
run (LV2_Handle handle, uint32_t n_samples)
{
	RSynth* self = (RSynth*)handle;
	float* audio[2];

	audio[0] = self->outL;
	audio[1] = self->outR;

	uint32_t written = 0;

	/* Process incoming MIDI events */
	if (self->midiin) {
		LV2_Atom_Event const* ev = (LV2_Atom_Event const*)((uintptr_t)((&(self->midiin)->body) + 1)); // lv2_atom_sequence_begin
		while( // !lv2_atom_sequence_is_end
				(const uint8_t*)ev < ((const uint8_t*) &(self->midiin)->body + (self->midiin)->atom.size)
				)
		{
			if (ev->body.type == self->midi_MidiEvent) {
#ifdef DEBUG_MIDI_EVENT // debug midi messages in synth -- not rt-safe(!)
				printf ("%5d (%d):", ev->time.frames,  ev->body.size);
				for (uint8_t i = 0; i < ev->body.size; ++i) {
					printf (" %02x", ((const uint8_t*)(ev+1))[i]);
				}
				printf ("\n");
#endif
				if (written + BUFFER_SIZE_SAMPLES < ev->time.frames
						&& ev->time.frames < n_samples) {
					/* first synthesize sound up until the message timestamp */
					written = synth_sound(self->synth, written, ev->time.frames, audio);
				}
				/* send midi message to synth */
				if (self->xmas) {
					synth_parse_xmas(self->synth, (const uint8_t*)(ev+1), ev->body.size);
				} else {
					synth_parse_midi(self->synth, (const uint8_t*)(ev+1), ev->body.size);
				}
			}
			ev = (LV2_Atom_Event const*) // lv2_atom_sequence_next()
				((uintptr_t)((const uint8_t*)ev + sizeof(LV2_Atom_Event) + ((ev->body.size + 7) & ~7)));
		}
	}

	/* synthesize [remaining] sound */
	synth_sound(self->synth, written, n_samples, audio);
}

static void
cleanup(LV2_Handle handle)
{
	RSynth* self = (RSynth*)handle;
	synth_free(self->synth);
	free(handle);
}

static const void*
extension_data(const char* uri)
{
	(void) uri; /* unused variable */
	return NULL;
}

static const LV2_Descriptor descriptor = {
	RSY_URI,
	instantiate,
	connect_port,
	NULL,
	run,
	NULL,
	cleanup,
	extension_data
};

#if defined(COMPILER_MSVC)
__declspec(dllexport)
#else
__attribute__ ((visibility ("default")))
#endif
const LV2_Descriptor*
lv2_descriptor(uint32_t idx)
{
	switch (idx) {
		case 0:
			return &descriptor;
		default:
			return NULL;
	}
}
