/*
    Copyright (C) 2004-2006 Ian Esten
    Copyright (C) 2006 Dave Robillard

    This program 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 2.1 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <jack/jack.h>
#include <jack/midiport.h>

#include "port.h"

enum { MIDI_INLINE_MAX = 4 }; /* 4 bytes for default event size */

typedef struct _jack_midi_port_info_private {
	jack_nframes_t nframes;         /**< Number of frames in buffer */
	uint32_t buffer_size;           /**< Size of buffer in bytes */
	uint32_t event_count;           /**< Number of events stored in this buffer */
	jack_nframes_t last_write_loc;  /**< Used for both writing and mixdown */
	uint32_t events_lost;           /**< Number of events lost in this buffer */
} POST_PACKED_STRUCTURE jack_midi_port_info_private_t;

typedef struct _jack_midi_port_internal_event {
	uint16_t time;  /* offset within buffer limit to 64k */
	uint16_t size;  /* event size limited to 64k */
	union {
		jack_shmsize_t byte_offset;
		jack_midi_data_t inline_data[MIDI_INLINE_MAX];
	} POST_PACKED_STRUCTURE;
} POST_PACKED_STRUCTURE jack_midi_port_internal_event_t;

size_t
jack_midi_internal_event_size ()
{
	return sizeof(jack_midi_port_internal_event_t);
}

static inline jack_midi_data_t*
jack_midi_event_data (void* port_buffer,
		      const jack_midi_port_internal_event_t* event)
{
	if (event->size <= MIDI_INLINE_MAX) {
		return (jack_midi_data_t*)event->inline_data;
	} else {
		return ((jack_midi_data_t*)port_buffer) + event->byte_offset;
	}
}


/* jack_midi_port_functions.buffer_init */
static void
jack_midi_buffer_init (void  *port_buffer,
		       size_t buffer_size,
		       jack_nframes_t nframes)
{
	jack_midi_port_info_private_t *info =
		(jack_midi_port_info_private_t*)port_buffer;

	/* We can also add some magic field to midi buffer to validate client calls */
	info->nframes = nframes;
	info->buffer_size = buffer_size;
	info->event_count = 0;
	info->last_write_loc = 0;
	info->events_lost = 0;
}


uint32_t
jack_midi_get_event_count (void           *port_buffer)
{
	jack_midi_port_info_private_t *info =
		(jack_midi_port_info_private_t*)port_buffer;

	return info->event_count;
}


int
jack_midi_event_get (jack_midi_event_t *event,
		     void              *port_buffer,
		     uint32_t event_idx)
{
	jack_midi_port_internal_event_t *port_event;
	jack_midi_port_info_private_t *info =
		(jack_midi_port_info_private_t*)port_buffer;

	if (event_idx >= info->event_count)
#ifdef ENODATA
	{ return ENODATA; }
#else
	{ return ENOMSG; }
#endif

	port_event = (jack_midi_port_internal_event_t*)(info + 1);
	port_event += event_idx;
	event->time = port_event->time;
	event->size = port_event->size;
	event->buffer = jack_midi_event_data (port_buffer, port_event);

	return 0;
}


size_t
jack_midi_max_event_size (void           *port_buffer)
{
	jack_midi_port_info_private_t *info =
		(jack_midi_port_info_private_t*)port_buffer;
	size_t buffer_size =
		info->buffer_size;

	/* (event_count + 1) below accounts for jack_midi_port_internal_event_t
	 * which would be needed to store the next event */
	size_t used_size = sizeof(jack_midi_port_info_private_t)
			   + info->last_write_loc
			   + ((info->event_count + 1)
			      * sizeof(jack_midi_port_internal_event_t));

	if (used_size > buffer_size) {
		return 0;
	} else if ((buffer_size - used_size) < MIDI_INLINE_MAX) {
		return MIDI_INLINE_MAX;
	} else {
		return buffer_size - used_size;
	}
}


jack_midi_data_t*
jack_midi_event_reserve (void           *port_buffer,
			 jack_nframes_t time,
			 size_t data_size)
{
	jack_midi_data_t *retbuf = (jack_midi_data_t*)port_buffer;

	jack_midi_port_info_private_t *info =
		(jack_midi_port_info_private_t*)port_buffer;
	jack_midi_port_internal_event_t *event_buffer =
		(jack_midi_port_internal_event_t*)(info + 1);
	size_t buffer_size =
		info->buffer_size;

	if (time < 0 || time >= info->nframes) {
		goto failed;
	}

	if (info->event_count > 0 && time < event_buffer[info->event_count - 1].time) {
		goto failed;
	}

	/* Check if data_size is >0 and there is enough space in the buffer for the event. */
	if (data_size <= 0) {
		goto failed; // return NULL?
	} else if (jack_midi_max_event_size (port_buffer) < data_size) {
		goto failed;
	} else {
		jack_midi_port_internal_event_t *event = &event_buffer[info->event_count];

		event->time = time;
		event->size = data_size;
		if (data_size <= MIDI_INLINE_MAX) {
			retbuf = event->inline_data;
		} else {
			info->last_write_loc += data_size;
			retbuf = &retbuf[buffer_size - 1 - info->last_write_loc];
			event->byte_offset =
				buffer_size - 1 - info->last_write_loc;
		}
		info->event_count += 1;
		return retbuf;
	}
failed:
	info->events_lost++;
	return NULL;
}


int
jack_midi_event_write (void                   *port_buffer,
		       jack_nframes_t time,
		       const jack_midi_data_t *data,
		       size_t data_size)
{
	jack_midi_data_t *retbuf =
		jack_midi_event_reserve (port_buffer, time, data_size);

	if (retbuf) {
		memcpy (retbuf, data, data_size);
		return 0;
	} else {
		return ENOBUFS;
	}
}


/* Can't check to make sure this port is an output anymore.  If this gets
 * called on an input port, all clients after the client that calls it
 * will think there are no events in the buffer as the event count has
 * been reset.
 */
void
jack_midi_clear_buffer (void           *port_buffer)
{
	jack_midi_port_info_private_t *info =
		(jack_midi_port_info_private_t*)port_buffer;

	info->event_count = 0;
	info->last_write_loc = 0;
	info->events_lost = 0;
}


/* jack_midi_port_functions.mixdown */
static void
jack_midi_port_mixdown (jack_port_t    *port, jack_nframes_t nframes)
{
	JSList         *node;
	jack_port_t    *input;
	jack_nframes_t num_events = 0;
	jack_nframes_t i          = 0;
	int err        = 0;
	jack_nframes_t lost_events = 0;

	/* The next (single) event to mix in to the buffer */
	jack_midi_port_info_private_t   *earliest_info;
	jack_midi_port_internal_event_t *earliest_event;
	jack_midi_data_t                *earliest_buffer;

	jack_midi_port_info_private_t   *in_info;       /* For finding next event */
	jack_midi_port_internal_event_t *in_events;     /* Corresponds to in_info */
	jack_midi_port_info_private_t   *out_info;      /* Output 'buffer' */

	jack_midi_clear_buffer (port->mix_buffer);

	out_info = (jack_midi_port_info_private_t*)port->mix_buffer;

	/* This function uses jack_midi_port_info_private_t.last_write_loc of the
	 * source ports to store indices of the last event read from that buffer
	 * so far.  This is OK because last_write_loc is used when writing events
	 * to a buffer, which at this stage is already complete so the value
	 * can be safely smashed. */

	/* Iterate through all connections to see how many events we need to mix,
	 * and initialise their 'last event read' (last_write_loc) to 0 */
	for (node = port->connections; node; node = jack_slist_next (node)) {
		input = (jack_port_t*)node->data;
		in_info =
			(jack_midi_port_info_private_t*)jack_output_port_buffer (input);
		num_events += in_info->event_count;
		lost_events += in_info->events_lost;
		in_info->last_write_loc = 0;
	}

	/* Write the events in the order of their timestamps */
	for (i = 0; i < num_events; ++i) {
		earliest_info = NULL;
		earliest_event = NULL;
		earliest_buffer = NULL;

		/* Find the earliest unread event, to mix next
		 * (search for an event earlier than earliest_event) */
		for (node = port->connections; node; node = jack_slist_next (node)) {
			in_info = (jack_midi_port_info_private_t*)
				  jack_output_port_buffer (((jack_port_t*)node->data));
			in_events = (jack_midi_port_internal_event_t*)(in_info + 1);

			/* If there are unread events left in this port.. */
			if (in_info->event_count > in_info->last_write_loc) {
				/* .. and this event is the new earliest .. */
				/* NOTE: that's why we compare time with <, not <= */
				if (earliest_info == NULL
				    || in_events[in_info->last_write_loc].time
				    < earliest_event->time) {
					/* .. then set this event as the next earliest */
					earliest_info = in_info;
					earliest_event = (jack_midi_port_internal_event_t*)
							 (&in_events[in_info->last_write_loc]);
				}
			}
		}

		if (earliest_info && earliest_event) {
			earliest_buffer = (jack_midi_data_t*)earliest_info;

			/* Write event to output */
			err = jack_midi_event_write (
				jack_port_buffer (port),
				earliest_event->time,
				jack_midi_event_data (earliest_buffer, earliest_event),
				earliest_event->size);

			earliest_info->last_write_loc++;

			if (err) {
				out_info->events_lost = num_events - i;
				break;
			}
		}
	}
	assert (out_info->event_count == num_events - out_info->events_lost);

	// inherit total lost events count from all connected ports.
	out_info->events_lost += lost_events;
}


uint32_t
jack_midi_get_lost_event_count (void           *port_buffer)
{
	return ((jack_midi_port_info_private_t*)port_buffer)->events_lost;
}

jack_port_functions_t jack_builtin_midi_functions = {
	.buffer_init	= jack_midi_buffer_init,
	.mixdown	= jack_midi_port_mixdown,
};
