/* -*- mode: c; c-file-style: "linux"; -*- */
/*
   NetJack Driver

   Copyright (C) 2008 Pieter Palmers <pieterpalmers@users.sourceforge.net>
   Copyright (C) 2006 Torben Hohn <torbenh@gmx.de>
   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.

   $Id: net_driver.c,v 1.17 2006/04/16 20:16:10 torbenh Exp $
 */

#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 "engine.h"
#include <sysdeps/time.h>

#include <sys/types.h>

#include "config.h"


#include "netjack.h"
#include "netjack_packet.h"
#include "net_driver.h"

#undef DEBUG_WAKEUP


#define MIN(x, y) ((x) < (y) ? (x) : (y))

static jack_transport_state_t last_transport_state;
static int sync_state = TRUE;

static jack_nframes_t
net_driver_wait (net_driver_t *driver, int extra_fd, int *status, float *delayed_usecs)
{
	netjack_driver_state_t *netj = &( driver->netj );
	int delay;

	delay = netjack_wait ( netj, driver->engine->get_microseconds );
	if ( delay ) {
		//driver->engine->delay( driver->engine, (float)delay );
		jack_error ( "netxruns amount: %dms", delay / 1000 );
	}


	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 */
	/* XXX: this might not be the case anymore */
	/*      the delayed _usecs is a resync or something. */
	*delayed_usecs = 0;     /* lie about it */
	*status = 0;
	return netj->period_size;
}

static int
net_driver_run_cycle (net_driver_t *driver)
{
	jack_engine_t *engine = driver->engine;
	//netjack_driver_state_t *netj = &(driver->netj);
	int wait_status = -1;
	float delayed_usecs;

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

	// XXX: xrun code removed.
	//      especially with celt there are no real xruns anymore.
	//      things are different on the net.

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

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

static int
net_driver_null_cycle (net_driver_t* driver, jack_nframes_t nframes)
{
	// TODO: talk to paul about this.
	//       do i wait here ?
	//       just sending out a packet marked with junk ?

	netjack_driver_state_t *netj = &(driver->netj);
	int sync_state = (driver->engine->control->sync_remain <= 1);

	netjack_send_silence ( netj, sync_state );

	return 0;
}

static int
net_driver_bufsize (net_driver_t* driver, jack_nframes_t nframes)
{
	netjack_driver_state_t *netj = &(driver->netj);

	if (nframes != netj->period_size) {
		return EINVAL;
	}

	return 0;
}

static int
net_driver_read (net_driver_t* driver, jack_nframes_t nframes)
{
	netjack_driver_state_t *netj = &(driver->netj);

	jack_position_t local_trans_pos;
	jack_transport_state_t local_trans_state;

	unsigned int *packet_buf, *packet_bufX;

	if ( !netj->packet_data_valid ) {
		render_payload_to_jack_ports (netj->bitdepth, NULL, netj->net_period_down, netj->capture_ports, netj->capture_srcs, nframes, netj->dont_htonl_floats );
		return 0;
	}
	packet_buf = netj->rx_buf;

	jacknet_packet_header *pkthdr = (jacknet_packet_header*)packet_buf;

	packet_bufX = packet_buf + sizeof(jacknet_packet_header) / sizeof(jack_default_audio_sample_t);

	netj->reply_port = pkthdr->reply_port;
	netj->latency = pkthdr->latency;

	// Special handling for latency=0
	if ( netj->latency == 0 ) {
		netj->resync_threshold = 0;
	} else {
		netj->resync_threshold = MIN ( 15, pkthdr->latency - 1 );
	}

	// check whether, we should handle the transport sync stuff, or leave trnasports untouched.
	if (netj->handle_transport_sync) {
		int compensated_tranport_pos = (pkthdr->transport_frame + (pkthdr->latency * nframes) + netj->codec_latency);

		// read local transport info....
		local_trans_state = jack_transport_query (netj->client, &local_trans_pos);

		// Now check if we have to start or stop local transport to sync to remote...
		switch (pkthdr->transport_state) {
		case JackTransportStarting:
			// the master transport is starting... so we set our reply to the sync_callback;
			if (local_trans_state == JackTransportStopped) {
				jack_transport_start (netj->client);
				last_transport_state = JackTransportStopped;
				sync_state = FALSE;
				jack_info ("locally stopped... starting...");
			}

			if (local_trans_pos.frame != compensated_tranport_pos) {
				jack_transport_locate (netj->client, compensated_tranport_pos);
				last_transport_state = JackTransportRolling;
				sync_state = FALSE;
				jack_info ("starting locate to %d", compensated_tranport_pos );
			}
			break;
		case JackTransportStopped:
			sync_state = TRUE;
			if (local_trans_pos.frame != (pkthdr->transport_frame)) {
				jack_transport_locate (netj->client, (pkthdr->transport_frame));
				jack_info ("transport is stopped locate to %d", pkthdr->transport_frame);
			}
			if (local_trans_state != JackTransportStopped) {
				jack_transport_stop (netj->client);
			}
			break;
		case JackTransportRolling:
			sync_state = TRUE;
//		                if(local_trans_pos.frame != (pkthdr->transport_frame + (pkthdr->latency) * nframes)) {
//				    jack_transport_locate(netj->client, (pkthdr->transport_frame + (pkthdr->latency + 2) * nframes));
//				    jack_info("running locate to %d", pkthdr->transport_frame + (pkthdr->latency)*nframes);
//		                }
			if (local_trans_state != JackTransportRolling) {
				jack_transport_start (netj->client);
			}
			break;

		case JackTransportLooping:
			break;
		}
	}

	render_payload_to_jack_ports (netj->bitdepth, packet_bufX, netj->net_period_down, netj->capture_ports, netj->capture_srcs, nframes, netj->dont_htonl_floats );
	packet_cache_release_packet (netj->packcache, netj->expected_framecnt );

	return 0;
}

static int
net_driver_write (net_driver_t* driver, jack_nframes_t nframes)
{
	netjack_driver_state_t *netj = &(driver->netj);

	int sync_state = (driver->engine->control->sync_remain <= 1);;

	uint32_t *packet_buf, *packet_bufX;

	int packet_size = get_sample_size (netj->bitdepth) * netj->playback_channels * netj->net_period_up + sizeof(jacknet_packet_header);
	jacknet_packet_header *pkthdr;

	packet_buf = alloca (packet_size);
	pkthdr = (jacknet_packet_header*)packet_buf;

	if ( netj->running_free ) {
		return 0;
	}

	// offset packet_bufX by the packetheader.
	packet_bufX = packet_buf + sizeof(jacknet_packet_header) / sizeof(jack_default_audio_sample_t);

	// clear unused header fields
	pkthdr->capture_channels_audio = 0;
	pkthdr->playback_channels_audio = 0;
	pkthdr->capture_channels_midi = 0;
	pkthdr->playback_channels_midi = 0;
	pkthdr->period_size = 0;
	pkthdr->sample_rate = 0;
	pkthdr->transport_frame = 0;
	pkthdr->transport_state = 0;
	pkthdr->framecnt = 0;
	pkthdr->reply_port = 0;
	pkthdr->mtu = 0;

	// set used header fields
	pkthdr->sync_state = sync_state;
	pkthdr->latency = netj->time_to_deadline;
	//printf( "time to deadline = %d  goodness=%d\n", (int)netj->time_to_deadline, netj->deadline_goodness );
	pkthdr->framecnt = netj->expected_framecnt;


	render_jack_ports_to_payload (netj->bitdepth, netj->playback_ports, netj->playback_srcs, nframes, packet_bufX, netj->net_period_up, netj->dont_htonl_floats );

	packet_header_hton (pkthdr);
	if (netj->srcaddress_valid) {
		int r;

#ifndef MSG_CONFIRM
		static const int flag = 0;
#else
		static const int flag = MSG_CONFIRM;
#endif

		if (netj->reply_port) {
			netj->syncsource_address.sin_port = htons (netj->reply_port);
		}

		for ( r = 0; r < netj->redundancy; r++ )
			netjack_sendto (netj->sockfd, (char*)packet_buf, packet_size,
					flag, (struct sockaddr*)&(netj->syncsource_address), sizeof(struct sockaddr_in), netj->mtu);
	}

	return 0;
}


static int
net_driver_attach (net_driver_t *driver)
{
	netjack_driver_state_t *netj = &( driver->netj );

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

	netjack_attach ( netj );
	return 0;
}

static int
net_driver_detach (net_driver_t *driver)
{
	netjack_driver_state_t *netj = &( driver->netj );

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

	netjack_detach ( netj );
	return 0;
}

static void
net_driver_delete (net_driver_t *driver)
{
	netjack_driver_state_t *netj = &( driver->netj );

	netjack_release ( netj );
	jack_driver_nt_finish ((jack_driver_nt_t*)driver);
	free (driver);
}

static jack_driver_t *
net_driver_new (jack_client_t * client,
		char *name,
		unsigned int capture_ports,
		unsigned int playback_ports,
		unsigned int capture_ports_midi,
		unsigned int playback_ports_midi,
		jack_nframes_t sample_rate,
		jack_nframes_t period_size,
		unsigned int listen_port,
		unsigned int transport_sync,
		unsigned int resample_factor,
		unsigned int resample_factor_up,
		unsigned int bitdepth,
		unsigned int use_autoconfig,
		unsigned int latency,
		unsigned int redundancy,
		int dont_htonl_floats,
		int always_deadline,
		int jitter_val)
{
	net_driver_t * driver;

	jack_info ("creating net driver ... %s|%" PRIu32 "|%" PRIu32
		   "|%u|%u|%u|transport_sync:%u", name, sample_rate, period_size, listen_port,
		   capture_ports, playback_ports, transport_sync);

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

	jack_driver_nt_init ((jack_driver_nt_t*)driver);

	driver->write         = (JackDriverWriteFunction)net_driver_write;
	driver->read          = (JackDriverReadFunction)net_driver_read;
	driver->null_cycle    = (JackDriverNullCycleFunction)net_driver_null_cycle;
	driver->nt_attach     = (JackDriverNTAttachFunction)net_driver_attach;
	driver->nt_detach     = (JackDriverNTDetachFunction)net_driver_detach;
	driver->nt_bufsize    = (JackDriverNTBufSizeFunction)net_driver_bufsize;
	driver->nt_run_cycle  = (JackDriverNTRunCycleFunction)net_driver_run_cycle;

	driver->last_wait_ust = 0;
	driver->engine = NULL;

	netjack_driver_state_t *netj = &(driver->netj);

	netjack_init ( netj,
		       client,
		       name,
		       capture_ports,
		       playback_ports,
		       capture_ports_midi,
		       playback_ports_midi,
		       sample_rate,
		       period_size,
		       listen_port,
		       transport_sync,
		       resample_factor,
		       resample_factor_up,
		       bitdepth,
		       use_autoconfig,
		       latency,
		       redundancy,
		       dont_htonl_floats,
		       always_deadline,
		       jitter_val      );

	netjack_startup ( netj );

	jack_info ("netjack: period   : up: %d / dn: %d", netj->net_period_up, netj->net_period_down);
	jack_info ("netjack: framerate: %d", netj->sample_rate);
	jack_info ("netjack: audio    : cap: %d / pbk: %d)", netj->capture_channels_audio, netj->playback_channels_audio);
	jack_info ("netjack: midi     : cap: %d / pbk: %d)", netj->capture_channels_midi, netj->playback_channels_midi);
	jack_info ("netjack: buffsize : rx: %d)", netj->rx_bufsize);
	driver->period_usecs = netj->period_usecs;

	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, "net");
	desc->nparams = 18;

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

	i = 0;
	strcpy (params[i].name, "audio-ins");
	params[i].character  = 'i';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 2U;
	strcpy (params[i].short_desc, "Number of capture channels (defaults to 2)");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "audio-outs");
	params[i].character  = 'o';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 2U;
	strcpy (params[i].short_desc, "Number of playback channels (defaults to 2)");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "midi-ins");
	params[i].character  = 'I';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 1U;
	strcpy (params[i].short_desc, "Number of midi capture channels (defaults to 1)");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "midi-outs");
	params[i].character  = 'O';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 1U;
	strcpy (params[i].short_desc, "Number of midi playback channels (defaults to 1)");
	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, "num-periods");
	params[i].character  = 'n';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 5U;
	strcpy (params[i].short_desc,
		"Network latency setting in no. of periods");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "listen-port");
	params[i].character  = 'l';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 3000U;
	strcpy (params[i].short_desc,
		"The socket port we are listening on for sync packets");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "factor");
	params[i].character  = 'f';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 1U;
	strcpy (params[i].short_desc,
		"Factor for sample rate reduction (deprecated)");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "upstream-factor");
	params[i].character  = 'u';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 0U;
	strcpy (params[i].short_desc,
		"Factor for sample rate reduction on the upstream (deprecated)");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "celt");
	params[i].character  = 'c';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 0U;
	strcpy (params[i].short_desc,
		"sets celt encoding and kbits value one channel is encoded at");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "bit-depth");
	params[i].character  = 'b';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 0U;
	strcpy (params[i].short_desc,
		"Sample bit-depth (0 for float, 8 for 8bit and 16 for 16bit)");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "transport-sync");
	params[i].character  = 't';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 1U;
	strcpy (params[i].short_desc,
		"Whether to slave the transport to the master transport");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "autoconf");
	params[i].character  = 'a';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 1U;
	strcpy (params[i].short_desc,
		"Whether to use Autoconfig, or just start.");
	strcpy (params[i].long_desc, params[i].short_desc);
	i++;
	strcpy (params[i].name, "redundancy");
	params[i].character  = 'R';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 1U;
	strcpy (params[i].short_desc,
		"Send packets N times");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "native-endian");
	params[i].character  = 'e';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 0U;
	strcpy (params[i].short_desc,
		"Don't convert samples to network byte order.");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "jitterval");
	params[i].character  = 'J';
	params[i].type       = JackDriverParamInt;
	params[i].value.i   = 0;
	strcpy (params[i].short_desc,
		"attempted jitterbuffer microseconds on master");
	strcpy (params[i].long_desc, params[i].short_desc);

	i++;
	strcpy (params[i].name, "always-deadline");
	params[i].character  = 'D';
	params[i].type       = JackDriverParamUInt;
	params[i].value.ui   = 0U;
	strcpy (params[i].short_desc,
		"Always wait until deadline");
	strcpy (params[i].long_desc, params[i].short_desc);
	desc->params = params;

	return desc;
}

const char driver_client_name[] = "net_pcm";

jack_driver_t *
driver_initialize (jack_client_t *client, const JSList * params)
{
	jack_nframes_t sample_rate = 48000;
	jack_nframes_t resample_factor = 1;
	jack_nframes_t period_size = 1024;
	unsigned int capture_ports = 2;
	unsigned int playback_ports = 2;
	unsigned int capture_ports_midi = 1;
	unsigned int playback_ports_midi = 1;
	unsigned int listen_port = 3000;
	unsigned int resample_factor_up = 0;
	unsigned int bitdepth = 0;
	unsigned int handle_transport_sync = 1;
	unsigned int use_autoconfig = 1;
	unsigned int latency = 5;
	unsigned int redundancy = 1;
	int dont_htonl_floats = 0;
	int always_deadline = 0;
	int jitter_val = 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 'i':
			capture_ports = param->value.ui;
			break;

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

		case 'I':
			capture_ports_midi = param->value.ui;
			break;

		case 'O':
			playback_ports_midi = param->value.ui;
			break;

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

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

		case 'l':
			listen_port = param->value.ui;
			break;

		case 'f':
#if HAVE_SAMPLERATE
			resample_factor = param->value.ui;
#else
			printf ( "not built with libsamplerate support\n" );
			exit (10);
#endif
			break;

		case 'u':
#if HAVE_SAMPLERATE
			resample_factor_up = param->value.ui;
#else
			printf ( "not built with libsamplerate support\n" );
			exit (10);
#endif
			break;

		case 'b':
			bitdepth = param->value.ui;
			break;

		case 'c':
#if HAVE_CELT
			bitdepth = 1000;
			resample_factor = param->value.ui;
#else
			printf ( "not built with celt support\n" );
			exit (10);
#endif
			break;

		case 't':
			handle_transport_sync = param->value.ui;
			break;

		case 'a':
			use_autoconfig = param->value.ui;
			break;

		case 'n':
			latency = param->value.ui;
			break;

		case 'R':
			redundancy = param->value.ui;
			break;

		case 'e':
			dont_htonl_floats = param->value.ui;
			break;
		case 'J':
			jitter_val = param->value.i;
			break;
		case 'D':
			always_deadline = param->value.ui;
			break;
		}
	}

	return net_driver_new (client, "net_pcm", capture_ports, playback_ports,
			       capture_ports_midi, playback_ports_midi,
			       sample_rate, period_size,
			       listen_port, handle_transport_sync,
			       resample_factor, resample_factor_up, bitdepth,
			       use_autoconfig, latency, redundancy,
			       dont_htonl_floats, always_deadline, jitter_val);
}

void
driver_finish (jack_driver_t *driver)
{
	net_driver_delete ((net_driver_t*)driver);
}
