/*
 * iaxclient: a cross-platform IAX softphone library
 *
 * Copyrights:
 * Copyright (C) 2003-2006, Horizon Wimba, Inc.
 * Copyright (C) 2007, Wimba, Inc.
 *
 * Contributors:
 * Steve Kann <stevek@stevek.com>
 * Mihai Balea <mihai at hates dot ms>
 *
 * This program is free software, distributed under the terms of
 * the GNU Lesser (Library) General Public License.
 */

/*
 * Some comments about Theora streaming
 * Theora video codec has two problems when it comes to streaming
 * and broadcasting video:
 *
 * - Large headers that need to be passed from the encoder to the decoder
 *   to initialize it. The conventional wisdom says we should transfer the
 *   headers out of band, but that complicates things with IAX, which does
 *   not have a separate signalling channel. Also, it makes things really
 *   difficult in a video conference scenario, where video gets switched
 *   between participants regularly. To solve this issue, we initialize
 *   the encoder and the decoder at the same time, using the headers from
 *   the local encoder to initialize the decoder. This works if the
 *   endpoints use the exact same version of Theora and the exact same
 *   parameters for initialization.
 *
 * - No support for splitting the frame into multiple slices.  Frames can
 *   be relatively large. For a 320x240 video stream, you can see key
 *   frames larger than 9KB, which is the maximum UDP packet size on Mac
 *   OS X. To work around this limitation, we use the slice API to fragment
 *   encoded frames to a reasonable size that UDP can safely transport
 * 
 * Other miscellaneous comments:
 *
 * - For quality reasons, when we detect a video stream switch, we reject all
 *   incoming frames until we receive a key frame.
 *
 * - Theora only accepts video that has dimensions multiple of 16. If we combine
 *   his with a 4:3 aspect ratio requirement, we get a very limited number
 *   of available resolutions. To work around this limitation, we pad the video
 *   on encoding, up to the closest multiple of 16. On the decoding side, we
 *   remove the padding. This way, video resolution can be any multiple of 2
 *
 * We should probably look more into this (how to deal with missing and
 * out of order slices)
 */

#include <stdlib.h>
#include "iaxclient_lib.h"
#include "video.h"
#include "slice.h"
#include "codec_theora.h"
#include <theora/theora.h>

#define MAX_SLICE_SIZE		8000

struct theora_decoder
{
	theora_state            td;
	theora_info             ti;
	theora_comment          tc;
	struct deslicer_context *dsc;
	int                     got_key_frame;
};

struct theora_encoder
{
	theora_state          td;
	theora_info           ti;
	theora_comment        tc;
	int                   needs_padding;
	struct slicer_context *sc;
	unsigned char         *pad_buffer;
};

static void destroy( struct iaxc_video_codec *c)
{
	struct theora_encoder *e;
	struct theora_decoder *d;

	if ( !c )
		return;

	if ( c->encstate )
	{
		e = (struct theora_encoder *)c->encstate;
		if ( e->pad_buffer )
			free(e->pad_buffer);
		if ( e->sc )
			free_slicer_context(e->sc);
		theora_comment_clear(&e->tc);
		theora_info_clear(&e->ti);
		theora_clear(&e->td);
		free(e);
	}
	if ( c->decstate )
	{
		d = (struct theora_decoder *)c->decstate;
		if ( d->dsc )
			free_deslicer_context(d->dsc);
		theora_comment_clear(&d->tc);
		theora_info_clear(&d->ti);
		theora_clear(&d->td);
		free(c->decstate);
	}
	free(c);
}

static int decode(struct iaxc_video_codec *c, int inlen, const char *in,
		int *outlen, char *out)
{
	struct theora_decoder *d;
	ogg_packet            op;
	yuv_buffer            picture;
	unsigned int          line;
	int                   my_out_len;
	int                   w, h, ph;	
	int                   flen;
	char                  *frame;

	// Sanity checks
	if ( !c || !c->decstate || !in || inlen <= 0 || !out || !outlen )
		return -1;

	// Assemble slices
	d = (struct theora_decoder *)c->decstate;
	if ( !d->dsc )
		return -1;

	frame = deslice(in, inlen, &flen, d->dsc);
	if ( frame == NULL )
		return 1;
	
	/* decode into an OP structure */
	memset(&op, 0, sizeof(op));
	op.bytes = flen;
	op.packet = (unsigned char *)frame;

	/* reject all incoming frames until we get a key frame */
	if ( !d->got_key_frame )
	{
		if ( theora_packet_iskeyframe(&op) )
			d->got_key_frame = 1;
		else
			return 1;
	}

	if ( theora_decode_packetin(&d->td, &op) == OC_BADPACKET )
	{
		fprintf(stderr,
			"codec_theora: warning: theora_decode_packetin says bad packet\n");
		return -1;
	}

	w = d->ti.frame_width;
	h = d->ti.frame_height;
	ph = d->ti.height;

	my_out_len = d->ti.frame_width * d->ti.frame_height * 3 / 2;

	/* make sure we have enough room for the goodies */
	if ( *outlen < my_out_len )
	{
		fprintf(stderr, "codec_theora: not enough room for decoding\n");
		return -1;
	}

	/* finally, here's where we get our goodies */
	if ( theora_decode_YUVout(&d->td, &picture) )
	{
		fprintf(stderr, "codec_theora: error getting our goodies\n");
		return -1;
	}

	//clear output
	memset(out, 127, my_out_len);

	for( line = 0 ; line < d->ti.frame_height / 2 ; line++ )
	{
		// Y-even
		memcpy(out + picture.y_width * 2 * line,
		       picture.y + 2 * line * picture.y_stride,
		       picture.y_width);
		// Y-odd
		memcpy(out + picture.y_width * (2 * line + 1),
		       picture.y + (2 * line + 1) * picture.y_stride,
		       picture.y_width);
		// U + V
		memcpy(out + (d->ti.frame_width * d->ti.frame_height) + line * d->ti.frame_width / 2,
		       picture.u + line * picture.uv_stride,
		       picture.uv_width);
		memcpy(out + (d->ti.frame_width * d->ti.frame_height * 5 / 4) + line * d->ti.frame_width / 2,
		       picture.v + line * picture.uv_stride,
		       picture.uv_width);
	}

	*outlen = my_out_len;

	return 0;
}

// Pads a w by h frame to bring it up to pw by ph size using value
static void pad_channel(const char *src, int w, int h, unsigned char *dst,
		int pw, int ph, unsigned char value)
{
	int i;

	if ( w == pw )
	{
		// We don't need to pad each line, just copy the data
		memcpy(dst, src, w * h);
	} else
	{
		// We DO need to pad each line
		for ( i=0 ; i<h ; i++ )
		{
			memcpy(&dst[i*pw], &src[i*w], w);
			memset(&dst[i*pw+w], value, pw-w);
		}
	}
	// Pad the bottom of the frame if necessary
	if ( h < ph )
		memset(dst + pw * h, value, (ph - h) * pw);
}

static int encode(struct iaxc_video_codec * c, int inlen, const char * in,
		struct slice_set_t * slice_set)
{
	struct theora_encoder	*e;
	ogg_packet		op;
	yuv_buffer		picture;

	// Sanity checks
	if ( !c || !c->encstate || !in || !slice_set )
		return -1;

	e = (struct theora_encoder *)c->encstate;

	// Prepare the YUV buffer
	if ( e->needs_padding )
	{
		// We copy a padded image into the pad buffer and set up the pointers
		// Use pad_channel for each of the YUV channels
		// Use a pad value of 0 for luma and 128 for chroma
		pad_channel(in,
				e->ti.frame_width,
				e->ti.frame_height,
				e->pad_buffer,
				e->ti.width,
				e->ti.height,
				0);

		pad_channel(in + e->ti.frame_width * e->ti.frame_height,
				e->ti.frame_width / 2,
				e->ti.frame_height / 2,
				e->pad_buffer + e->ti.width * e->ti.height,
				e->ti.width / 2,
				e->ti.height / 2,
				128);

		pad_channel(in + e->ti.frame_width * e->ti.frame_height * 5 / 4,
				e->ti.frame_width / 2,
				e->ti.frame_height / 2,
				e->pad_buffer + e->ti.width * e->ti.height * 5 / 4,
				e->ti.width / 2,
				e->ti.height / 2,
				128);

		picture.y = e->pad_buffer;
	} else
	{
		// use the original buffer
		picture.y = (unsigned char *)in;
	}
	picture.u = picture.y + e->ti.width * e->ti.height;
	picture.v = picture.u + e->ti.width * e->ti.height / 4;
	picture.y_width = e->ti.width;
	picture.y_height = e->ti.height;
	picture.y_stride = e->ti.width;
	picture.uv_width = e->ti.width / 2;
	picture.uv_height = e->ti.height / 2;
	picture.uv_stride = e->ti.width / 2;

	// Send data in for encoding
	if ( theora_encode_YUVin(&e->td, &picture) )
	{
		fprintf(stderr, "codec_theora: failed theora_encode_YUVin\n");
		return -1;
	}

	// Get data from the encoder
	if ( theora_encode_packetout(&e->td, 0, &op) != 1 )
	{
		fprintf(stderr, "codec_theora: failed theora_encode_packetout\n");
		return -1;
	}

	// Check to see if we have a key frame
	slice_set->key_frame = theora_packet_iskeyframe(&op) == 1;
	
	// Slice the frame
	slice((char *)op.packet, op.bytes, slice_set, e->sc);

	return 0;
}

struct iaxc_video_codec *codec_video_theora_new(int format, int w, int h,
		int framerate, int bitrate, int fragsize)
{
	struct iaxc_video_codec *c;
	struct theora_encoder   *e;
	struct theora_decoder   *d;
	unsigned short          source_id;
	ogg_packet              headerp, commentp, tablep;

	/* Basic sanity checks */
	if ( w <= 0 || h <= 0 || framerate <= 0 || bitrate <= 0 || fragsize <= 0 )
	{
		fprintf(stderr, "codec_theora: bogus codec params: %d %d %d %d %d\n",
				w, h, framerate, bitrate, fragsize);
		return NULL;
	}

	if ( w % 2 || h % 2 )
	{
		fprintf(stderr, "codec_theora: video dimensions must be multiples of 2\n");
		return NULL;
	}

	if ( fragsize > MAX_SLICE_SIZE )
		fragsize = MAX_SLICE_SIZE;

	c = (struct iaxc_video_codec *)calloc(sizeof(struct iaxc_video_codec), 1);

	if ( !c )
		goto bail;

	c->decstate = calloc(sizeof(struct theora_decoder), 1);

	if ( !c->decstate )
		goto bail;

	c->encstate = calloc(sizeof(struct theora_encoder), 1);

	if ( !c->encstate )
		goto bail;

	c->format = format;
	c->width = w;
	c->height = h;
	c->framerate = framerate;
	c->bitrate = bitrate;
	c->fragsize = fragsize;

	c->encode = encode;
	c->decode = decode;
	c->destroy = destroy;

	e = (struct theora_encoder *)c->encstate;
	d = (struct theora_decoder *)c->decstate;

	// Initialize slicer	
	// Generate random source id
	srand((unsigned int)time(0));
	source_id = rand() & 0xffff;
	e->sc = create_slicer_context(source_id, fragsize);
	if ( !e->sc ) 
		goto bail;
	
	
	/* set up some parameters in the contexts */

	theora_info_init(&e->ti);

	/* set up common parameters */
	e->ti.frame_width = w;
	e->ti.frame_height = h;
	e->ti.width = ((w - 1) / 16 + 1) * 16;
	e->ti.height = ((h - 1) / 16 + 1) * 16;
	e->ti.offset_x = 0;
	e->ti.offset_y = 0;

	// We set up a padded frame with dimensions that are multiple of 16
	// We allocate a buffer to hold this frame
	e->needs_padding = e->ti.width != e->ti.frame_width ||
		e->ti.height != e->ti.frame_height;

	if ( e->needs_padding )
	{
		e->pad_buffer = (unsigned char *)
			malloc(e->ti.width * e->ti.height * 3 / 2);

		if ( !e->pad_buffer )
			goto bail;
	}
	else
	{
		e->pad_buffer = 0;
	}

	e->ti.fps_numerator = framerate;
	e->ti.fps_denominator = 1;

	e->ti.aspect_numerator = 1;
	e->ti.aspect_denominator = 1;

	e->ti.colorspace = OC_CS_UNSPECIFIED;
	e->ti.pixelformat = OC_PF_420;

	e->ti.target_bitrate = bitrate;

	e->ti.quality = 0;

	e->ti.dropframes_p = 0;
	e->ti.quick_p = 1;
	e->ti.keyframe_auto_p = 0;
	e->ti.keyframe_frequency = framerate;
	e->ti.keyframe_frequency_force = framerate;
	e->ti.keyframe_data_target_bitrate = bitrate * 3;
	e->ti.keyframe_auto_threshold = 80;
	e->ti.keyframe_mindistance = 8;
	e->ti.noise_sensitivity = 0;

	if ( theora_encode_init(&e->td, &e->ti) )
		goto bail;

	// Obtain the encoder headers and set up the decoder headers from
	// data in the encoder headers
	memset(&headerp, 0, sizeof(headerp));
	memset(&commentp, 0, sizeof(commentp));
	memset(&tablep, 0, sizeof(tablep));

	// Set up the decoder using the encoder headers
	theora_info_init(&d->ti);
	theora_comment_init(&d->tc);
	theora_comment_init(&e->tc);

	if ( theora_encode_header(&e->td, &headerp) )
		goto bail;

	headerp.b_o_s = 1;

	if ( theora_decode_header(&d->ti, &d->tc, &headerp) )
		goto bail;

	if ( theora_encode_comment(&e->tc, &commentp) )
		goto bail;

	if ( theora_decode_header(&d->ti, &d->tc, &commentp) )
		goto bail;

	theora_comment_clear(&e->tc);

	if ( theora_encode_tables(&e->td, &tablep) )
		goto bail;

	if ( theora_decode_header(&d->ti, &d->tc, &tablep) )
		goto bail;

	if ( theora_decode_init(&d->td, &d->ti) )
		goto bail;

	d->got_key_frame = 0;
	
	// Initialize deslicer context
	d->dsc = create_deslicer_context(c->fragsize);
	if ( !d->dsc )
		goto bail;

	strcpy(c->name, "Theora");
	return c;

bail:
	fprintf(stderr, "codec_theora: failed to initialize encoder or decoder\n");

	if ( c )
	{
		if ( c->encstate )
		{
			e = (struct theora_encoder *)c->encstate;
			if ( e->sc )
				free_slicer_context(e->sc);
			free(c->encstate);
		}
		if ( c->decstate )
		{
			d = (struct theora_decoder *)c->decstate;
			if ( d->dsc )
				free_deslicer_context(d->dsc);
			free(c->decstate);
		}
		free(c);
	}

	return NULL;
}