/*
* Copyright (C) 2009 Mamadou Diop.
*
* Contact: Mamadou Diop <diopmamadou(at)doubango.org>
*	
* This file is part of Open Source Doubango Framework.
*
* DOUBANGO 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 3 of the License, or
* (at your option) any later version.
*	
* DOUBANGO 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 DOUBANGO.
*
*/

/**@file tmedia_codec.c
 * @brief Base codec object.
 *
 * @author Mamadou Diop <diopmamadou(at)doubango.org>
 *
 * @date Created: Sat Nov 8 16:54:58 2009 mdiop
 */
#include "tinymedia/tmedia_codec.h"

#include "tsk_string.h"
#include "tsk_memory.h"
#include "tsk_debug.h"

/**@defgroup tmedia_codec_group Codecs
*/

/* pointer to all registered codecs */
const tmedia_codec_plugin_def_t* __tmedia_codec_plugins[TMED_CODEC_MAX_PLUGINS] = {0};

/**@ingroup tmedia_codec_group
* Initialize a Codec 
* @param self The codec to initialize. Could be any type of codec (e.g. @ref tmedia_codec_audio_t or @ref tmedia_codec_video_t).
* @param type
* @param name the name of the codec. e.g. "G.711u" or "G.711a" etc used in the sdp.
* @param desc full description.
* @param format the format. e.g. "0" for G.711.u or "8" for G.711a or "*" for MSRP.
* @retval Zero if succeed and non-zero error code otherwise.
*/
int tmedia_codec_init(tmedia_codec_t* self, tmedia_type_t type, const char* name, const char* desc, const char* format)
{
	if(!self || tsk_strnullORempty(name)){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	self->type = type;
	tsk_strupdate(&self->name, name);
	tsk_strupdate(&self->desc,desc);
	tsk_strupdate(&self->format, format);

	return 0;
}

/**@ingroup tmedia_codec_group
* Generic function to compare two codecs.
* @param codec1 The first codec to compare.
* @param codec2 The second codec to compare.
* @retval Returns an integral value indicating the relationship between the two codecs:
* <0 : @a codec1 less than @a codec2.<br>
* 0  : @a codec1 identical to @a codec2.<br>
* >0 : @a codec1 greater than @a codec2.<br>
*/
int tmedia_codec_cmp(const tsk_object_t* codec1, const tsk_object_t* codec2)
{
	const tmedia_codec_t* _c1 = codec1;
	const tmedia_codec_t* _c2 = codec2;

	if((_c1 && _c2) && (_c1->type == _c2->type)){
		/* Do not compare names. For example, H264 base profile 1.0 will have the 
		* same name than H264 base profile 3.0. */
		return tsk_stricmp(_c1->format, _c2->format);
	}
	else{
		return -1;
	}
}

/**@ingroup tmedia_codec_group
* Registers a codec plugin.
* @param plugin the definition of the plugin.
* @retval Zero if succeed and non-zero error code otherwise.
* @sa @ref tmedia_codec_create()
*/
int tmedia_codec_plugin_register(const tmedia_codec_plugin_def_t* plugin)
{
	tsk_size_t i;
	if(!plugin || tsk_strnullORempty(plugin->name) || tsk_strnullORempty(plugin->format)){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	/* add or replace the plugin */
	for(i = 0; i<TMED_CODEC_MAX_PLUGINS; i++){
		if(!__tmedia_codec_plugins[i] || (__tmedia_codec_plugins[i] == plugin)){
			__tmedia_codec_plugins[i] = plugin;
			return 0;
		}
	}
	
	TSK_DEBUG_ERROR("There are already %d plugins.", TMED_CODEC_MAX_PLUGINS);
	return -2;
}

/**@ingroup tmedia_codec_group
* UnRegisters a codec plugin.
* @param plugin the definition of the plugin.
* @retval Zero if succeed and non-zero error code otherwise.
*/
int tmedia_codec_plugin_unregister(const tmedia_codec_plugin_def_t* plugin)
{
	tsk_size_t i;
	tsk_bool_t found = tsk_false;
	if(!plugin){
		TSK_DEBUG_ERROR("Invalid Parameter");
		return -1;
	}

	/* find the plugin to unregister */
	for(i = 0; i<TMED_CODEC_MAX_PLUGINS; i++){
		if(__tmedia_codec_plugins[i] == plugin){
			__tmedia_codec_plugins[i] = tsk_null;
			found = tsk_true;
			break;
		}
	}

	/* compact */
	if(found){
		for(; i<(TMED_CODEC_MAX_PLUGINS - 1); i++){
			if(__tmedia_codec_plugins[i+1]){
				__tmedia_codec_plugins[i] = __tmedia_codec_plugins[i+1];
			}
			else{
				break;
			}
		}
	}
	return (found ? 0 : -2);
}

/**@ingroup tmedia_codec_group
* Creates a new codec using an already registered plugin.
* @param format The format of the codec to create (e.g. "0" for PCMU or "8" for PCMA or "*" for MSRP)
* @sa @ref tmedia_codec_plugin_register()
*/
tmedia_codec_t* tmedia_codec_create(const char* format)
{
	tmedia_codec_t* codec = tsk_null;
	const tmedia_codec_plugin_def_t* plugin;
	tsk_size_t i = 0;

	while((i < TMED_CODEC_MAX_PLUGINS) && (plugin = __tmedia_codec_plugins[i++])){
		if(plugin->objdef && tsk_striequals(plugin->format, format)){
			if((codec = tsk_object_new(plugin->objdef))){
				/* initialize the newly created codec */
				codec->dyn = plugin->dyn;
				codec->plugin = plugin;
				switch(plugin->type){
					case tmedia_audio:
						{	/* Audio codec */
							tmedia_codec_audio_t* audio = TMEDIA_CODEC_AUDIO(codec);
							tmedia_codec_audio_init(TMEDIA_CODEC(audio), plugin->name, plugin->desc, plugin->format);
							break;
						}
					case tmedia_video:
						{ /* Video codec */
							tmedia_codec_video_t* video = TMEDIA_CODEC_VIDEO(codec);
							tmedia_codec_video_init(TMEDIA_CODEC(video), plugin->name, plugin->desc, plugin->format);
							break;
						}
					case tmedia_msrp:
						{	/* Msrp codec */
							tmedia_codec_msrp_init(codec, plugin->name, plugin->desc);
							break;
						}
					default:
						{	/* Any other codec */
							tmedia_codec_init(codec, plugin->type, plugin->name, plugin->desc, plugin->format);
							break;
						}
				}
				break;
			}
		}
	}

	return codec;
}

/**@ingroup tmedia_codec_group
* Gets the rtpmap attribute associated to this code.
* @param self the codec for which to get the rtpmap attribute. Should be created using @ref tmedia_codec_create().
* @retval rtpmap string (e.g. "AMR-WB/16000/2" or "H261/90000") if succeed and Null otherwise. It's up to the caller to free the
* returned string.
*/
char* tmedia_codec_get_rtpmap(const tmedia_codec_t* self)
{
	char* rtpmap = tsk_null;

	if(!self || !self->plugin){
		TSK_DEBUG_ERROR("invalid parameter");
		return tsk_null;
	}
	switch(self->type){
		case tmedia_audio:
			{	/* audio codecs */
				/* const tmedia_codec_audio_t* audioCodec = (const tmedia_codec_audio_t*)self; */
				if(self->plugin->audio.channels > 0){
					tsk_sprintf(&rtpmap, "%s %s/%d/%d", self->format, self->name, self->plugin->rate, self->plugin->audio.channels);
				}
				else{
					tsk_sprintf(&rtpmap, "%s %s/%d", self->format, self->name, self->plugin->audio);
				}
			}
			break;
		case tmedia_video:
			{	/* video codecs */
				/* const tmedia_codec_video_t* videoCodec = (const tmedia_codec_video_t*)self; */
				tsk_sprintf(&rtpmap, "%s %s/%d", self->format, self->name, self->plugin->rate);
				break;
			}
		/* all others */
		default:
			break;
	}

	return rtpmap;
}

/**@ingroup tmedia_codec_group
* Gets the codec's fmtp attribute value.
* @param self the codec for which to get the fmtp attribute. Should be created using @ref tmedia_codec_create().
* @retval fmtp attribute string (e.g. "mode-set=0,2,5,7; mode-change-period=2; mode-change-neighbor=1"). It's up to the caller to free the
* returned string.
*/
char* tmedia_codec_get_fmtp(const tmedia_codec_t* self)
{
	char* fmtp = tsk_null;

	if(!self || !self->plugin){
		TSK_DEBUG_ERROR("invalid parameter");
		return tsk_null;
	}

	if(self->plugin->fmtp_get){ /* some codecs, like G711, won't produce fmtp */
		fmtp = self->plugin->fmtp_get(self);
	}

	return fmtp;
}

/**@ingroup tmedia_codec_group
* Indicates whether the codec can handle this fmtp.
* @param self the codec to match aginst to.
* @param fmtp the fmtp to match
* @retval @a tsk_true if the codec can handle this fmtp and @a tsk_false otherwise
*/
tsk_bool_t tmedia_codec_match_fmtp(const tmedia_codec_t* self, const char* fmtp)
{
	/* checks */
	if(!self || !self->plugin || self->plugin->fmtp_match){
		TSK_DEBUG_ERROR("invalid parameter");
		return tsk_false;
	}

	/* if fmtp is null or empty -> always match */
	if(tsk_strnullORempty(fmtp)){
		return tsk_true;
	}
	else{
		return self->plugin->fmtp_match(self, fmtp);
	}
}

/**@ingroup tmedia_codec_group
* Sets remote fmtp.
* @param self codec for which to set the remote fmtp.
* @param fmtp fmtp received from remote party (e.g. "mode-set=0,2,5,7; mode-change-period=2; mode-change-neighbor=1").
* @retval Zero if succeed and non-zero error code otherwise.
*/
int tmedia_codec_set_remote_fmtp(tmedia_codec_t* self, const char* fmtp)
{
	if(!self || !self->plugin){
		TSK_DEBUG_ERROR("invalid parameter");
		return -1;
	}

	if(self->plugin->fmtp_set){
		return self->plugin->fmtp_set(self, fmtp);
	}
	else{ /* some codecs, like G711, could ignore remote fmtp attribute */
		return 0;
	}
}

/**@ingroup tmedia_codec_group
* DeInitialize a Codec.
* @param self The codec to deinitialize. Could be any type of codec (e.g. @ref tmedia_codec_audio_t or @ref tmedia_codec_video_t).
* @retval Zero if succeed and non-zero error code otherwise.
*/
int tmedia_codec_deinit(tmedia_codec_t* self)
{
	if(!self){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	TSK_FREE(self->name);
	TSK_FREE(self->desc);
	TSK_FREE(self->format);

	return 0;
}

