/* -*- mode: c; c-file-style: "linux"; -*- */
/*
    Copyright (C) 2003 Robert Ham <rah@bash.sh>
    Copyright (C) 2001 Paul Davis

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

 */

#include <math.h>
#include <stdio.h>
#include <memory.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/mman.h>

#include <jack/types.h>
#include "internal.h"
#include "engine.h"
#include <sysdeps/time.h>

#include "dummy_driver.h"

#undef DEBUG_WAKEUP

/* this is used for calculate what counts as an xrun */
#define PRETEND_BUFFER_SIZE 4096

void
FakeVideoSync ( dummy_driver_t *driver )
{
	#define VIDEO_SYNC_PERIOD (48000 / 30)
	static int vidCounter = VIDEO_SYNC_PERIOD;

	int period = driver->period_size;
	jack_position_t *position = &driver->engine->control->current_time;

	if ( period >= VIDEO_SYNC_PERIOD ) {
		jack_error ("JACK driver period size too large for simple video sync emulation. Halting.");
		exit (0);
	}

	//enable video sync, whether it occurs in this period or not
	position->audio_frames_per_video_frame = VIDEO_SYNC_PERIOD;
	position->valid = (jack_position_bits_t)(position->valid | JackAudioVideoRatio);

	//no video pulse found in this period, just decrement the counter
	if ( vidCounter > period ) {
		vidCounter -= period;
	}

	//video pulse occurs in this period
	if ( vidCounter <= period ) {
		int remainder = period - vidCounter;
		vidCounter = VIDEO_SYNC_PERIOD - remainder;

		position->video_offset = vidCounter;
		position->valid = (jack_position_bits_t)(position->valid | JackVideoFrameOffset);
	}
}

#if HAVE_CLOCK_GETTIME && HAVE_CLOCK_NANOSLEEP
static inline unsigned long long ts_to_nsec (struct timespec ts)
{
	return ts.tv_sec * 1000000000LL + ts.tv_nsec;
}

static inline struct timespec nsec_to_ts (unsigned long long nsecs)
{
	struct timespec ts;

	ts.tv_sec = nsecs / (1000000000LL);
	ts.tv_nsec = nsecs % (1000000000LL);
	return ts;
}

static inline struct timespec add_ts (struct timespec ts, unsigned int usecs)
{
	unsigned long long nsecs = ts_to_nsec (ts);

	nsecs += usecs * 1000LL;
	return nsec_to_ts (nsecs);
}

static inline int cmp_lt_ts (struct timespec ts1, struct timespec ts2)
{
	if (ts1.tv_sec < ts2.tv_sec) {
		return 1;
	} else if (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec < ts2.tv_nsec) {
		return 1;
	} else { return 0; }
}

static jack_nframes_t
dummy_driver_wait (dummy_driver_t *driver, int extra_fd, int *status,
		   float *delayed_usecs)
{
	jack_nframes_t nframes = driver->period_size;
	struct timespec now;
    struct timespec ts;

	*status = 0;
	/* this driver doesn't work so well if we report a delay */
	*delayed_usecs = 0;             /* lie about it */

	clock_gettime (CLOCK_REALTIME, &now);

	if (cmp_lt_ts (driver->next_wakeup, now)) {
		if (driver->next_wakeup.tv_sec == 0) {
			/* first time through */
			clock_gettime (CLOCK_REALTIME, &driver->next_wakeup);
		} else if ((ts_to_nsec (now) - ts_to_nsec (driver->next_wakeup)) / 1000LL
			   > (PRETEND_BUFFER_SIZE * 1000000LL
			      / driver->sample_rate)) {
			/* xrun */
			jack_error ("**** dummy: xrun of %ju usec",
				    (uintmax_t)(ts_to_nsec (now) - ts_to_nsec (driver->next_wakeup)) / 1000LL);
			nframes = 0;
			driver->next_wakeup.tv_sec = 0;
		} else {
			/* late, but handled by our "buffer"; try to
			 * get back on track */
		}
		driver->next_wakeup = add_ts (driver->next_wakeup, driver->wait_time);
	} else {
        ts.tv_sec = 0;
        ts.tv_nsec = ts_to_nsec(driver->next_wakeup) - ts_to_nsec(now);
		if (nanosleep (&ts, NULL)) {
			jack_error ("error while sleeping");
			*status = -1;
		} else {
			clock_gettime (CLOCK_REALTIME, &now);
			// guaranteed to sleep long enough for this to be correct
			*delayed_usecs = (ts_to_nsec (now) - ts_to_nsec (driver->next_wakeup));
			*delayed_usecs /= 1000.0;
		}
		driver->next_wakeup = add_ts (driver->next_wakeup, driver->wait_time);
	}

	driver->last_wait_ust = driver->engine->get_microseconds ();
	driver->engine->transport_cycle_start (driver->engine,
					       driver->last_wait_ust);

	return nframes;
}

static int dummy_driver_nt_start (dummy_driver_t *drv)
{
	drv->next_wakeup.tv_sec = 0;
	return 0;
}

#else

static jack_nframes_t
dummy_driver_wait (dummy_driver_t *driver, int extra_fd, int *status,
		   float *delayed_usecs)
{
	jack_time_t now = driver->engine->get_microseconds ();

	if (driver->next_time < now) {
		if (driver->next_time == 0) {
			/* first time through */
			driver->next_time = now + driver->wait_time;
		} else if (now - driver->next_time
			   > (PRETEND_BUFFER_SIZE * 1000000LL
			      / driver->sample_rate)) {
			/* xrun */
			jack_error ("**** dummy: xrun of %ju usec",
				    (uintmax_t)now - driver->next_time);
			driver->next_time = now + driver->wait_time;
		} else {
			/* late, but handled by our "buffer"; try to
			 * get back on track */
			driver->next_time += driver->wait_time;
		}
	} else {
		jack_time_t wait = driver->next_time - now;
		struct timespec ts = { .tv_sec	= wait / 1000000,
				       .tv_nsec = (wait % 1000000) * 1000 };
		nanosleep (&ts, NULL);
		driver->next_time += driver->wait_time;
	}

	driver->last_wait_ust = driver->engine->get_microseconds ();
	driver->engine->transport_cycle_start (driver->engine,
					       driver->last_wait_ust);

	/* this driver doesn't work so well if we report a delay */
	*delayed_usecs = 0;             /* lie about it */
	*status = 0;
	return driver->period_size;
}

static int dummy_driver_nt_start (dummy_driver_t *drv)
{
	drv->next_time = 0;
	return 0;
}
#endif

static inline int
dummy_driver_run_cycle (dummy_driver_t *driver)
{
	jack_engine_t *engine = driver->engine;
	int wait_status;
	float delayed_usecs;

	jack_nframes_t nframes = dummy_driver_wait (driver, -1, &wait_status,
						    &delayed_usecs);

	if (nframes == 0) {
		/* we detected an xrun and restarted: notify
		 * clients about the delay. */
		engine->delay (engine, delayed_usecs);
		return 0;
	}

	// FakeVideoSync (driver);

	if (wait_status == 0) {
		return engine->run_cycle (engine, nframes, delayed_usecs);
	}

	if (wait_status < 0) {
		return -1;
	} else {
		return 0;
	}
}

static int
dummy_driver_null_cycle (dummy_driver_t* driver, jack_nframes_t nframes)
{
	return 0;
}

static int
dummy_driver_bufsize (dummy_driver_t* driver, jack_nframes_t nframes)
{
	driver->period_size = nframes;
	driver->period_usecs = driver->wait_time =
				       (jack_time_t)floor ((((float)nframes) / driver->sample_rate)
							   * 1000000.0f);

	/* tell the engine to change its buffer size */
	if (driver->engine->set_buffer_size (driver->engine, nframes)) {
		jack_error ("dummy: cannot set engine buffer size to %d (check MIDI)", nframes);
		return -1;
	}

	return 0;
}

static int
dummy_driver_write (dummy_driver_t* driver, jack_nframes_t nframes)
{
	return 0;
}


static int
dummy_driver_attach (dummy_driver_t *driver)
{
	jack_port_t * port;
	char buf[32];
	unsigned int chn;
	int port_flags;

	if (driver->engine->set_buffer_size (driver->engine, driver->period_size)) {
		jack_error ("dummy: cannot set engine buffer size to %d (check MIDI)", driver->period_size);
		return -1;
	}
	driver->engine->set_sample_rate (driver->engine, driver->sample_rate);

	port_flags = JackPortIsOutput | JackPortIsPhysical | JackPortIsTerminal;

	for (chn = 0; chn < driver->capture_channels; chn++) {
		snprintf (buf, sizeof(buf) - 1, "capture_%u", chn + 1);

		port = jack_port_register (driver->client, buf,
					   JACK_DEFAULT_AUDIO_TYPE,
					   port_flags, 0);
		if (!port) {
			jack_error ("DUMMY: cannot register port for %s", buf);
			break;
		}

		driver->capture_ports =
			jack_slist_append (driver->capture_ports, port);
	}

	port_flags = JackPortIsInput | JackPortIsPhysical | JackPortIsTerminal;

	for (chn = 0; chn < driver->playback_channels; chn++) {
		snprintf (buf, sizeof(buf) - 1, "playback_%u", chn + 1);

		port = jack_port_register (driver->client, buf,
					   JACK_DEFAULT_AUDIO_TYPE,
					   port_flags, 0);

		if (!port) {
			jack_error ("DUMMY: cannot register port for %s", buf);
			break;
		}

		driver->playback_ports =
			jack_slist_append (driver->playback_ports, port);
	}

	jack_activate (driver->client);

	return 0;
}

static int
dummy_driver_detach (dummy_driver_t *driver)
{
	JSList * node;

	if (driver->engine == 0) {
		return 0;
	}

	for (node = driver->capture_ports; node; node = jack_slist_next (node))
		jack_port_unregister (driver->client,
				      ((jack_port_t*)node->data));

	jack_slist_free (driver->capture_ports);
	driver->capture_ports = NULL;


	for (node = driver->playback_ports; node; node = jack_slist_next (node))
		jack_port_unregister (driver->client,
				      ((jack_port_t*)node->data));

	jack_slist_free (driver->playback_ports);
	driver->playback_ports = NULL;

	return 0;
}


static void
dummy_driver_delete (dummy_driver_t *driver)
{
	jack_driver_nt_finish ((jack_driver_nt_t*)driver);
	free (driver);
}

static jack_driver_t *
dummy_driver_new (jack_client_t * client,
		  char *name,
		  unsigned int capture_ports,
		  unsigned int playback_ports,
		  jack_nframes_t sample_rate,
		  jack_nframes_t period_size,
		  unsigned long wait_time)
{
	dummy_driver_t * driver;

	jack_info ("creating dummy driver ... %s|%" PRIu32 "|%" PRIu32
		   "|%lu|%u|%u", name, sample_rate, period_size, wait_time,
		   capture_ports, playback_ports);

	driver = (dummy_driver_t*)calloc (1, sizeof(dummy_driver_t));

	jack_driver_nt_init ((jack_driver_nt_t*)driver);

	driver->write         = (JackDriverReadFunction)dummy_driver_write;
	driver->null_cycle    = (JackDriverNullCycleFunction)dummy_driver_null_cycle;
	driver->nt_attach     = (JackDriverNTAttachFunction)dummy_driver_attach;
	driver->nt_start      = (JackDriverNTStartFunction)dummy_driver_nt_start;
	driver->nt_detach     = (JackDriverNTDetachFunction)dummy_driver_detach;
	driver->nt_bufsize    = (JackDriverNTBufSizeFunction)dummy_driver_bufsize;
	driver->nt_run_cycle  = (JackDriverNTRunCycleFunction)dummy_driver_run_cycle;

	driver->period_usecs =
		(jack_time_t)floor ((((float)period_size) / sample_rate)
				    * 1000000.0f);
	driver->sample_rate = sample_rate;
	driver->period_size = period_size;
	driver->wait_time   = wait_time;
	//driver->next_time   = 0; // not needed since calloc clears the memory
	driver->last_wait_ust = 0;

	driver->capture_channels  = capture_ports;
	driver->capture_ports     = NULL;
	driver->playback_channels = playback_ports;
	driver->playback_ports    = NULL;

	driver->client = client;
	driver->engine = NULL;

	return (jack_driver_t*)driver;
}


/* DRIVER "PLUGIN" INTERFACE */

jack_driver_desc_t *
driver_get_descriptor ()
{
	jack_driver_desc_t * desc;
	jack_driver_param_desc_t * params;
	unsigned int i;

	desc = calloc (1, sizeof(jack_driver_desc_t));
	strcpy (desc->name, "dummy");
	desc->nparams = 5;

	params = calloc (desc->nparams, sizeof(jack_driver_param_desc_t));

	i = 0;
	strcpy (params[i].name, "capture");
	params[i].character  = 'C';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 2U;
	strcpy (params[i].short_desc, "Number of capture ports");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "playback");
	params[i].character  = 'P';
	params[i].type       = JackDriverParamUInt;
	params[1].value.ui   = 2U;
	strcpy (params[i].short_desc, "Number of playback ports");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "rate");
	params[i].character  = 'r';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 48000U;
	strcpy (params[i].short_desc, "Sample rate");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "period");
	params[i].character  = 'p';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 1024U;
	strcpy (params[i].short_desc, "Frames per period");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "wait");
	params[i].character  = 'w';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 21333U;
	strcpy (params[i].short_desc,
		"Number of usecs to wait between engine processes");
	strcpy (params[i].long_desc, params[i].short_desc);

	desc->params = params;

	return desc;
}

const char driver_client_name[] = "dummy_pcm";

jack_driver_t *
driver_initialize (jack_client_t *client, const JSList * params)
{
	jack_nframes_t sample_rate = 48000;
	jack_nframes_t period_size = 1024;
	unsigned int capture_ports = 2;
	unsigned int playback_ports = 2;
	int wait_time_set = 0;
	unsigned long wait_time = 0;
	const JSList * node;
	const jack_driver_param_t * param;

	for (node = params; node; node = jack_slist_next (node)) {
		param = (const jack_driver_param_t*)node->data;

		switch (param->character) {

		case 'C':
			capture_ports = param->value.ui;
			break;

		case 'P':
			playback_ports = param->value.ui;
			break;

		case 'r':
			sample_rate = param->value.ui;
			break;

		case 'p':
			period_size = param->value.ui;
			break;

		case 'w':
			wait_time = param->value.ui;
			wait_time_set = 1;
			break;

		}
	}

	if (!wait_time_set) {
		wait_time = (((float)period_size) / ((float)sample_rate)) * 1000000.0;
	}

	return dummy_driver_new (client, "dummy_pcm", capture_ports,
				 playback_ports, sample_rate, period_size,
				 wait_time);
}

void
driver_finish (jack_driver_t *driver)
{
	dummy_driver_delete ((dummy_driver_t*)driver);
}

