/*
 * OpenMRCP - Open Source Media Resource Control Protocol Stack
 * Copyright (C) 2007, Cepstral LLC
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Author(s):
 * Arsen Chaloyan <achaloyan@yahoo.com>
 *
 * Contributor(s):
 *
 */

#include "jitter_buffer.h"

#define MAX_DISSECT_FRAME_COUNT 20

struct jitter_buffer_t {
	jitter_buffer_config_t *config;

	apr_byte_t             *raw_data;
	media_frame_t          *frames;
	apr_size_t              frame_count;
	apr_size_t              frame_ts;

	apr_size_t              playout_delay_ts;

	apr_byte_t              write_sync;
	int                     write_ts_offset;
	
	apr_size_t              write_ts;
	apr_size_t              read_ts;

	apr_pool_t             *pool;
};


jitter_buffer_t* jitter_buffer_create(jitter_buffer_config_t *jb_config, apr_size_t sampling_rate, apr_pool_t *pool)
{
	size_t i;
	size_t frame_size;
	jitter_buffer_t *jb = apr_palloc(pool,sizeof(jitter_buffer_t));
	jb->config = jb_config;

	jb->frame_ts = CODEC_FRAME_TIME_BASE * sampling_rate / 1000;
	frame_size = /*pcm16*/2 * jb->frame_ts;
	jb->frame_count = jb->config->max_playout_delay / CODEC_FRAME_TIME_BASE;
	jb->raw_data = apr_palloc(pool,frame_size*jb->frame_count);
	jb->frames = apr_palloc(pool,sizeof(media_frame_t)*jb->frame_count);
	for(i=0; i<jb->frame_count; i++) {
		media_frame_init(&jb->frames[i]);
		jb->frames[i].codec_frame.buffer = jb->raw_data + i*frame_size;
	}

	jb->playout_delay_ts = jb->config->initial_playout_delay * sampling_rate / 1000;

	jb->write_sync = 1;
	jb->write_ts_offset = 0;
	jb->write_ts = jb->read_ts = 0;

	return jb;
}

apr_status_t jitter_buffer_destroy(jitter_buffer_t *jb)
{
	return APR_SUCCESS;
}

apr_status_t jitter_buffer_restart(jitter_buffer_t *jb)
{
	jb->write_sync = 1;
	jb->write_ts_offset = 0;
	jb->write_ts = jb->read_ts;
	return APR_SUCCESS;
}

static APR_INLINE media_frame_t* jitter_buffer_frame_get(jitter_buffer_t *jb, apr_size_t ts)
{
	apr_size_t index = (ts / jb->frame_ts) % jb->frame_count;
	return &jb->frames[index];
}

static APR_INLINE jb_result_t jitter_buffer_write_prepare(jitter_buffer_t *jb, apr_uint32_t ts, apr_size_t *write_ts)
{
	jb_result_t result = JB_OK;
	if(jb->write_sync) {
		jb->write_ts_offset = ts - jb->write_ts;
		jb->write_sync = 0;
	}

	*write_ts = ts - jb->write_ts_offset + jb->playout_delay_ts;
	if(*write_ts % jb->frame_ts != 0) {
		/* not frame alligned */
		return JB_DISCARD_NOT_ALLIGNED;
	}

	if(*write_ts >= jb->write_ts) {
		if(*write_ts - jb->write_ts > jb->frame_ts) {
			/* gap */
		}
		/* normal write */
	}
	else {
		if(*write_ts >= jb->read_ts) {
			/* backward write */
		}
		else {
			/* too late */
			result = JB_DISCARD_TOO_LATE;
		}
	}
	return result;
}

jb_result_t jitter_buffer_write(jitter_buffer_t *jb, codec_handle_t *codec, void *buffer, apr_size_t size, apr_uint32_t ts)
{
	media_frame_t *media_frame;
	codec_frame_t frames[MAX_DISSECT_FRAME_COUNT];
	apr_size_t frame_count;
	apr_size_t i;

	apr_size_t write_ts;
	jb_result_t result = jitter_buffer_write_prepare(jb,ts,&write_ts);
	if(result != JB_OK) {
		return result;
	}

	/* dissect codec frames */
	frame_count = codec->manipulator->dissect(
									codec,
									buffer,
									size,
									frames,
									MAX_DISSECT_FRAME_COUNT);
	if(frame_count + (write_ts - jb->read_ts)/jb->frame_ts > jb->frame_count) {
		/* too early */
		apr_size_t count = (write_ts - jb->read_ts)/jb->frame_ts;
		result = JB_DISCARD_TOO_EARLY;
		if(jb->frame_count > count) {
			frame_count = jb->frame_count - count;
		}
		else {
			frame_count = 0;
		}
	}
	for(i=0; i<frame_count; i++) {
		media_frame = jitter_buffer_frame_get(jb,write_ts);
		if(codec->manipulator->decode(codec,&frames[i],&media_frame->codec_frame) == APR_SUCCESS) {
			media_frame->type |= MEDIA_FRAME_TYPE_AUDIO;
		}
		
		write_ts += jb->frame_ts;
	}

	if(write_ts > jb->write_ts) {
		jb->write_ts = write_ts;
	}
	return result;
}

jb_result_t jitter_buffer_write_named_event(jitter_buffer_t *jb, named_event_frame_t *named_event, apr_uint32_t ts)
{
	return JB_OK;
}

apr_status_t jitter_buffer_read(jitter_buffer_t *jb, media_frame_t *media_frame)
{
	media_frame_t *src_media_frame = jitter_buffer_frame_get(jb,jb->read_ts);
	if(jb->write_ts > jb->read_ts) {
		/* normal read */
		media_frame->type = src_media_frame->type;
		if(media_frame->type & MEDIA_FRAME_TYPE_AUDIO) {
			media_frame->codec_frame.size = src_media_frame->codec_frame.size;
			memcpy(media_frame->codec_frame.buffer,src_media_frame->codec_frame.buffer,media_frame->codec_frame.size);
		}
		if(media_frame->type & MEDIA_FRAME_TYPE_EVENT) {
			media_frame->event_frame = src_media_frame->event_frame;
		}
	}
	else {
		/* underflow */
		media_frame->type = MEDIA_FRAME_TYPE_NONE;
		jb->write_ts += jb->frame_ts;
	}
	src_media_frame->type = MEDIA_FRAME_TYPE_NONE;
	jb->read_ts += jb->frame_ts;
	return APR_SUCCESS;
}
