#include "colormodels.h"
#include "funcprotos.h"
#include "quicktime.h"
#include "workarounds.h"
#include "divx.h"


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


static pthread_mutex_t encode_mutex;
static pthread_mutex_t decode_mutex;
static int mutex_initialized = 0;

static int delete_codec(quicktime_video_map_t *vtrack)
{
	quicktime_divx_codec_t *codec;

	codec = ((quicktime_codec_t*)vtrack->codec)->priv;
	if(codec->encode_initialized)
	{
		pthread_mutex_lock(&encode_mutex);
		encore(codec->enc_param.handle,
			ENC_OPT_RELEASE,
			0,
			0);
		pthread_mutex_unlock(&encode_mutex);
	}
	if(codec->decode_initialized)
	{
		pthread_mutex_lock(&decode_mutex);
		decore(codec->dec_param.handle,
			DEC_OPT_DESTROY,
			0,
			0);
		pthread_mutex_unlock(&decode_mutex);
	}
	if(codec->temp_frame) free(codec->temp_frame);
	if(codec->work_buffer) free(codec->work_buffer);
	free(codec);
	return 0;
}

static int reads_colormodel(quicktime_t *file, 
		int colormodel, 
		int track)
{
	return (colormodel == BC_RGB888 ||
		colormodel == BC_RGBA8888 ||
		colormodel == BC_RGB161616 ||
		colormodel == BC_RGBA16161616 ||
		colormodel == BC_YUV888 ||
		colormodel == BC_YUVA8888 ||
		colormodel == BC_YUV161616 ||
		colormodel == BC_YUVA16161616 ||
		colormodel == BC_RGB8 ||
		colormodel == BC_RGB565 ||
		colormodel == BC_BGR888 ||
		colormodel == BC_BGR8888 ||
		colormodel == BC_YUV420P ||
		colormodel == BC_YUV422);
}

static int writes_colormodel(quicktime_t *file, 
		int colormodel, 
		int track)
{
	return (colormodel == BC_RGB888 ||
		colormodel == BC_RGBA8888 ||
		colormodel == BC_RGB161616 ||
		colormodel == BC_RGBA16161616 ||
		colormodel == BC_YUV888 ||
		colormodel == BC_YUVA8888 ||
		colormodel == BC_YUV161616 ||
		colormodel == BC_YUVA16161616 ||
		colormodel == BC_YUV420P ||
		colormodel == BC_YUV422);
}








static void init_mutex()
{
	if(!mutex_initialized)
	{
		pthread_mutexattr_t attr;
		mutex_initialized = 1;
		pthread_mutexattr_init(&attr);
		pthread_mutex_init(&decode_mutex, &attr);
		pthread_mutex_init(&encode_mutex, &attr);
	}
}





static int decode(quicktime_t *file, unsigned char **row_pointers, int track)
{
	int i;
	longest bytes;
	int result = 0;
	quicktime_video_map_t *vtrack = &(file->vtracks[track]);
	quicktime_trak_t *trak = vtrack->track;
	quicktime_divx_codec_t *codec = ((quicktime_codec_t*)vtrack->codec)->priv;
	int width = trak->tkhd.track_width;
	int height = trak->tkhd.track_height;
	int width_i = (int)((float)width / 2 + 0.5) * 2;
	int height_i = (int)((float)height / 2 + 0.5) * 2;
	DEC_FRAME dec_frame;
	int use_temp = 0;
	int input_cmodel;
	char *bmp_pointers[3];

//printf("decode 1\n");

	init_mutex();
	pthread_mutex_lock(&decode_mutex);

	quicktime_set_video_position(file, vtrack->current_position, track);
	bytes = quicktime_frame_size(file, vtrack->current_position, track);
//printf("decode 1\n");

	if(!codec->work_buffer || codec->buffer_size < bytes)
	{
		if(codec->work_buffer) free(codec->work_buffer);
		codec->buffer_size = bytes;
		codec->work_buffer = calloc(1, codec->buffer_size + 100);
	}
//printf("decode 1\n");

	result = !quicktime_read_data(file, codec->work_buffer, bytes);
//printf("decode 1\n");

	if(!codec->decode_initialized)
	{
		codec->decode_initialized = 1;
		codec->dec_param.width = width_i;
		codec->dec_param.height = height_i;
		decore(0, DEC_OPT_CREATE, &codec->dec_param, NULL);


	}

//printf("decode 1\n");



	dec_frame.bitstream = codec->work_buffer;
	dec_frame.length = bytes;
	dec_frame.stride = width_i;
	dec_frame.render = 1;
	dec_frame.colorspace = DEC_CSP_YV12;

	input_cmodel = BC_YUV420P;
	if(file->color_model == input_cmodel &&
		file->out_w == width_i &&
		file->out_h == height_i &&
		file->in_x == 0 &&
		file->in_y == 0 &&
		file->in_w == width_i &&
		file->in_h == height_i)
	{
		dec_frame.dst = row_pointers[0];
		use_temp = 0;
	}
	else
	{
		if(!codec->temp_frame)
		{
			codec->temp_frame = malloc(width_i * height_i * 3 / 2);
		}
		dec_frame.dst = codec->temp_frame;
		use_temp = 1;
	}


//printf("decode 1\n");
	decore(codec->dec_param.handle, DEC_OPT_DECODE, &dec_frame, NULL);
//printf("decode 2\n");


	pthread_mutex_unlock(&decode_mutex);
//printf("decode 1 %d %d\n", use_temp, file->color_model);

	if(use_temp)
	{
		unsigned char **input_rows = malloc(sizeof(unsigned char*) * height_i);
		for(i = 0; i < height_i; i++)
			input_rows[i] = (char*)codec->temp_frame + width_i * 3;
//printf("decode 3\n");
		
		
		cmodel_transfer(row_pointers, /* Leave NULL if non existent */
			input_rows,
			row_pointers[0], /* Leave NULL if non existent */
			row_pointers[1],
			row_pointers[2],
			codec->temp_frame, /* Leave NULL if non existent */
			codec->temp_frame + width_i * height_i,
			codec->temp_frame + width_i * height_i + width_i * height_i / 4,
			file->in_x,        /* Dimensions to capture from input frame */
			file->in_y, 
			file->in_w, 
			file->in_h,
			0,       /* Dimensions to project on output frame */
			0, 
			file->out_w, 
			file->out_h,
			input_cmodel, 
			file->color_model,
			0,         /* When transfering BC_RGBA8888 to non-alpha this is the background color in 0xRRGGBB hex */
			width_i,       /* For planar use the luma rowspan */
			width);
		
//printf("decode 4\n");
		free(input_rows);
	}




//printf("decode 2\n");

	return result;
}







static int encode(quicktime_t *file, unsigned char **row_pointers, int track)
{
//printf("encode 1\n");
	longest offset = quicktime_position(file);
	quicktime_video_map_t *vtrack = &(file->vtracks[track]);
	quicktime_divx_codec_t *codec = ((quicktime_codec_t*)vtrack->codec)->priv;
	quicktime_trak_t *trak = vtrack->track;
	int width = trak->tkhd.track_width;
	int height = trak->tkhd.track_height;
	int width_i = (int)((float)width / 2 + 0.5) * 2;
	int height_i = (int)((float)height / 2 + 0.5) * 2;
	int result = 0;
	int i;
	ENC_FRAME encore_input;
	ENC_RESULT encore_result;
//printf("encode 1\n");

	init_mutex();
	pthread_mutex_lock(&encode_mutex);

	if(!codec->encode_initialized)
	{
		codec->encode_initialized = 1;
		codec->enc_param.x_dim = width_i;
		codec->enc_param.y_dim = height_i;
		codec->enc_param.framerate = quicktime_frame_rate(file, track);
		codec->enc_param.bitrate = codec->bitrate;
		codec->enc_param.rc_period = codec->rc_period;
		codec->enc_param.rc_reaction_period = codec->rc_reaction_period;
		codec->enc_param.rc_reaction_ratio = codec->rc_reaction_ratio;
		codec->enc_param.max_quantizer = codec->max_quantizer;
		codec->enc_param.min_quantizer = codec->min_quantizer;
		codec->enc_param.max_key_interval = codec->max_key_interval;
		codec->enc_param.use_bidirect = 0;
		codec->enc_param.deinterlace = 0;
		codec->enc_param.quality = 5;
		codec->enc_param.obmc = 0;
		


		encore(0, ENC_OPT_INIT, &codec->enc_param, NULL);
	}

//printf("encode 1\n");

// Assume planes are contiguous
	if(file->color_model == BC_YUV420P &&
		width == width_i &&
		height == height_i)
	{
		encore_input.image = row_pointers[0];
//printf("encode 1\n");
	}
// Convert to YUV420P
	else
	{
//printf("encode 2\n");
		if(!codec->temp_frame)
		{
			codec->temp_frame = malloc(width_i * height_i * 3 / 2);
		}
//printf("encode 2 %d %d %d %d %d %d\n", file->color_model, width, height, width_i, height_i);

		cmodel_transfer(0, /* Leave NULL if non existent */
			row_pointers,
			codec->temp_frame, /* Leave NULL if non existent */
			codec->temp_frame + width_i * height_i,
			codec->temp_frame + width_i * height_i + width_i * height_i / 4,
			row_pointers[0], /* Leave NULL if non existent */
			row_pointers[1],
			row_pointers[2],
			0,        /* Dimensions to capture from input frame */
			0, 
			width, 
			height,
			0,       /* Dimensions to project on output frame */
			0, 
			width_i, 
			height_i,
			file->color_model, 
			BC_YUV420P,
			0,         /* When transfering BC_RGBA8888 to non-alpha this is the background color in 0xRRGGBB hex */
			width,       /* For planar use the luma rowspan */
			width_i);
		
//printf("encode 2\n");
	
		encore_input.image = codec->temp_frame;
	}
//printf("encode 1\n");

	if(!codec->work_buffer)
	{
		codec->buffer_size = width * height;
		codec->work_buffer = malloc(codec->buffer_size);
	}
//printf("encode 1\n");

	encore_input.bitstream = codec->work_buffer;
	encore_input.length = codec->buffer_size;
	encore_input.colorspace = ENC_CSP_YV12;
	encore_input.quant = codec->enc_param.min_quantizer;

//printf("encode 1\n");
	encore(codec->enc_param.handle,	
			ENC_OPT_ENCODE,	
			&encore_input,
			&encore_result);
	pthread_mutex_unlock(&encode_mutex);
//printf("encode 1\n");

	result = !quicktime_write_data(file, codec->work_buffer, encore_input.length);
	quicktime_update_tables(file,
						file->vtracks[track].track,
						offset,
						file->vtracks[track].current_chunk,
						file->vtracks[track].current_position,
						1,
						encore_input.length);
//printf("encode 1\n");

	file->vtracks[track].current_chunk++;
//printf("encode 2\n");

	return result;
}

static int set_parameter(quicktime_t *file, 
		int track, 
		char *key, 
		void *value)
{
	quicktime_video_map_t *vtrack = &(file->vtracks[track]);
	quicktime_divx_codec_t *codec = ((quicktime_codec_t*)vtrack->codec)->priv;

	if(!strcasecmp(key, "v_bitrate"))
		codec->bitrate = *(int*)value;
	else
	if(!strcasecmp(key, "v_rc_period"))
		codec->rc_period = *(int*)value;
	else
	if(!strcasecmp(key, "v_rc_reaction_ratio"))
		codec->rc_reaction_ratio = *(int*)value;
	else
	if(!strcasecmp(key, "v_rc_reaction_period"))
		codec->rc_reaction_period = *(int*)value;
	else
	if(!strcasecmp(key, "v_max_key_interval"))
		codec->max_key_interval = *(int*)value;
	else
	if(!strcasecmp(key, "v_max_quantizer"))
		codec->max_quantizer = *(int*)value;
	else
	if(!strcasecmp(key, "v_min_quantizer"))
		codec->min_quantizer = *(int*)value;
	else
	if(!strcasecmp(key, "v_search_range"))
		codec->search_range = *(int*)value;
}


void quicktime_init_codec_divx(quicktime_video_map_t *vtrack)
{
	int i;
	quicktime_divx_codec_t *codec;

/* Init public items */
	((quicktime_codec_t*)vtrack->codec)->priv = calloc(1, sizeof(quicktime_divx_codec_t));
	((quicktime_codec_t*)vtrack->codec)->delete_vcodec = delete_codec;
	((quicktime_codec_t*)vtrack->codec)->decode_video = decode;
	((quicktime_codec_t*)vtrack->codec)->encode_video = encode;
	((quicktime_codec_t*)vtrack->codec)->reads_colormodel = reads_colormodel;
	((quicktime_codec_t*)vtrack->codec)->writes_colormodel = writes_colormodel;
	((quicktime_codec_t*)vtrack->codec)->set_parameter = set_parameter;
}

