/*
 * 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 <apr_general.h>
#include <sofia-sip/sdp.h>
#include "mrcp_description.h"
#include "apt_text_stream.h"

#define MRCP_CLIENT_SDP_ORIGIN "OpenMRCPClient"
#define MRCP_SERVER_SDP_ORIGIN "OpenMRCPServer"

/* generates sdp_media_audio (sdp string) by mrcp_media_audio */
static size_t mrcp_sdp_media_audio_generate(char *buffer, size_t size, mrcp_descriptor_t *descriptor, mrcp_media_audio_t *audio_media)
{
	size_t i;
	size_t offset = 0;
	offset += sprintf(buffer+offset,"m=audio %d RTP/AVP", 
		audio_media->base.state == MRCP_MEDIA_ENABLED ? audio_media->base.port : 0);
	for(i=0; i<audio_media->codec_count; i++) {
		offset += sprintf(buffer+offset," %d", audio_media->codec_list[i]->payload_type);
	}
	offset += sprintf(buffer+offset,"\r\n");
	if(descriptor->ip && audio_media->base.ip && apt_str_compare(descriptor->ip,audio_media->base.ip) != TRUE) {
		offset += sprintf(buffer+offset,"c=IN IP4 %s\r\n",audio_media->base.ip);
	}
	if(audio_media->base.state == MRCP_MEDIA_ENABLED) {
		for(i=0; i<audio_media->codec_count; i++) {
			offset += sprintf(buffer+offset,"a=rtpmap:%d %s/%d\r\n",
				audio_media->codec_list[i]->payload_type,
				audio_media->codec_list[i]->name,
				audio_media->codec_list[i]->sampling_rate);
		}
		offset += sprintf(buffer+offset,"a=%s\r\n",
			mrcp_audio_direction_str_get(audio_media->direction));
		if(audio_media->ptime) {
			offset += sprintf(buffer+offset,"a=ptime:%hu\r\n",
				audio_media->ptime);
		}
	}
	offset += sprintf(buffer+offset,"a=mid:%d\r\n",audio_media->mid);
	return offset;
}

/* generates offer (sdp string) by mrcp_descriptor */
size_t mrcp_sdp_offer_generate_by_mrcp_descriptor(char *buffer, size_t size, mrcp_descriptor_t *descriptor)
{
	size_t i;
	size_t offset = 0;
	mrcp_media_control_t *control_media;
	mrcp_media_audio_t *audio_media;
	buffer[0] = '\0';
	offset += sprintf(buffer+offset,
			"v=0\r\n"
			"o=%s 0 0 IN IP4 %s\r\n"
			"s=-\r\n"
			"c=IN IP4 %s\r\n"
			"t=0 0\r\n",
			MRCP_CLIENT_SDP_ORIGIN,
			descriptor->ip,
			descriptor->ip);
	for(i=0;i<descriptor->media_count; i++) {
		if(!descriptor->media[i]) {
			continue;
		}
		switch(descriptor->media[i]->type) {
			case MRCP_MEDIA_TYPE_CONTROL:
				control_media = (mrcp_media_control_t*)descriptor->media[i];
				offset += sprintf(buffer+offset,
					"m=application %d %s 1\r\n"
					"a=setup:%s\r\n"
					"a=connection:%s\r\n"
					"a=resource:%s\r\n"
					"a=cmid:%d\r\n",
					(control_media->base.state == MRCP_MEDIA_ENABLED) ? control_media->base.port : 0,
					mrcp_proto_get(control_media->proto),
					mrcp_control_setup_type_get(control_media->setup_type),
					mrcp_control_connection_type_get(control_media->connection_type),
					control_media->resource_name,
					control_media->cmid);
				break;
			case MRCP_MEDIA_TYPE_AUDIO:
				audio_media = (mrcp_media_audio_t*)descriptor->media[i];
				offset += mrcp_sdp_media_audio_generate(buffer+offset,size-offset,descriptor,audio_media);
				break;
		}

	}
	return offset;
}

/* generates answer (sdp string) by mrcp_descriptor */
size_t mrcp_sdp_answer_generate_by_mrcp_descriptor(char *buffer, size_t size, mrcp_descriptor_t *descriptor)
{
	size_t i;
	size_t offset = 0;
	mrcp_media_control_t *control_media;
	mrcp_media_audio_t *audio_media;
	buffer[0] = '\0';
	offset += sprintf(buffer+offset,
			"v=0\r\n"
			"o=%s 0 0 IN IP4 %s\r\n"
			"s=-\r\n"
			"c=IN IP4 %s\r\n"
			"t=0 0\r\n",
			MRCP_SERVER_SDP_ORIGIN,
			descriptor->ip,
			descriptor->ip);
	for(i=0;i<descriptor->media_count; i++) {
		if(!descriptor->media[i]) {
			continue;
		}
		switch(descriptor->media[i]->type) {
			case MRCP_MEDIA_TYPE_CONTROL:
				control_media = (mrcp_media_control_t*)descriptor->media[i];
				offset += sprintf(buffer+offset,
					"m=application %d %s 1\r\n"
					"a=setup:%s\r\n"
					"a=connection:%s\r\n"
					"a=channel:%s@%s\r\n"
					"a=cmid:%d\r\n",
					(control_media->base.state == MRCP_MEDIA_ENABLED) ? control_media->base.port : 0,
					mrcp_proto_get(control_media->proto),
					mrcp_control_setup_type_get(control_media->setup_type),
					mrcp_control_connection_type_get(control_media->connection_type),
					control_media->session_id.hex_str,
					control_media->resource_name,
					control_media->cmid);
				break;
			case MRCP_MEDIA_TYPE_AUDIO:
				audio_media = (mrcp_media_audio_t*)descriptor->media[i];
				offset += mrcp_sdp_media_audio_generate(buffer+offset,size-offset,descriptor,audio_media);
				break;
		}
	}
	return offset;
}


/* generates mrcp_media_control by sdp_media_application */
static mrcp_status_t mrcp_control_media_generate_by_sdp_media(mrcp_media_control_t *mrcp_media, sdp_media_t *sdp_media, apr_pool_t *pool)
{
	mrcp_control_attrib_id id;
	sdp_attribute_t *attrib = NULL;
	mrcp_media->proto = mrcp_proto_find(sdp_media->m_proto_name);
	if(mrcp_media->proto != MRCP_PROTO_TCP) {
		apt_log(APT_PRIO_INFO,"Not supported SDP Proto [%s], expected [%s]\n",sdp_media->m_proto_name,mrcp_proto_get(MRCP_PROTO_TCP));
		return MRCP_STATUS_FAILURE;
	}
	
	for(attrib = sdp_media->m_attributes; attrib; attrib=attrib->a_next) {
		id = mrcp_control_attrib_id_find(attrib->a_name);
		switch(id) {
			case MRCP_CONTROL_ATTRIB_SETUP:
				mrcp_media->setup_type = mrcp_control_setup_type_find(attrib->a_value);
				break;
			case MRCP_CONTROL_ATTRIB_CONNECTION:
				mrcp_media->connection_type = mrcp_control_connection_type_find(attrib->a_value);
				break;
			case MRCP_CONTROL_ATTRIB_RESOURCE:
				mrcp_media->resource_name = apr_pstrdup(pool,attrib->a_value);
				break;
			case MRCP_CONTROL_ATTRIB_CHANNEL:
			{
				char *str = apr_pstrdup(pool,attrib->a_value);
				mrcp_media->session_id.hex_str = apt_read_field(&str,'@',1);
				mrcp_media->session_id.length = str - mrcp_media->session_id.hex_str - 1;
				mrcp_media->resource_name = str;
				break;
			}
			case MRCP_CONTROL_ATTRIB_CMID:
				mrcp_media->cmid = atoi(attrib->a_value);
				break;
			default:
				break;
		}
	}
	return MRCP_STATUS_SUCCESS;
}

/* generates mrcp_media_audio by sdp_media_audio */
static mrcp_status_t mrcp_audio_media_generate_by_sdp_media(mrcp_media_audio_t *audio_media, sdp_media_t *sdp_media, apr_pool_t *pool)
{
	mrcp_audio_attrib_id id;
	sdp_attribute_t *attrib = NULL;
	sdp_rtpmap_t *map;
	codec_descriptor_t *codec;
	for(attrib = sdp_media->m_attributes; attrib; attrib=attrib->a_next) {
		id = mrcp_audio_attrib_id_find(attrib->a_name);
		switch(id) {
			case MRCP_AUDIO_ATTRIB_MID:
				audio_media->mid = atoi(attrib->a_value);
				break;
			case MRCP_AUDIO_ATTRIB_PTIME:
				audio_media->ptime = (unsigned short)atoi(attrib->a_value);
				break;
			default:
				break;
		}
	}

	audio_media->codec_count = 0;
	for(map = sdp_media->m_rtpmaps; map; map = map->rm_next) {
		codec = apr_palloc(pool,sizeof(codec_descriptor_t));
		codec_descriptor_init(codec);
		codec->payload_type = (apr_byte_t)map->rm_pt;
		codec->name = apr_pstrdup(pool,map->rm_encoding);
		codec->sampling_rate = (apr_uint16_t)map->rm_rate;
		codec->channel_count = 1;
		if(audio_media->codec_count < MRCP_MAX_CODEC_COUNT) {
			audio_media->codec_list[audio_media->codec_count++] = codec;
		}
	}

	switch(sdp_media->m_mode) {
		case sdp_inactive:
			audio_media->direction = MRCP_AUDIO_NONE;
			break;
		case sdp_sendonly:
			audio_media->direction = MRCP_AUDIO_SEND;
			break;
		case sdp_recvonly:
			audio_media->direction = MRCP_AUDIO_RECEIVE;
			break;
		case sdp_sendrecv:
			audio_media->direction = MRCP_AUDIO_DUPLEX;
			break;
	}
	return MRCP_STATUS_SUCCESS;
}

/* generates mrcp_media_base by sdp_media */
static mrcp_status_t mrcp_media_generate_by_sdp_media(mrcp_media_base_t *mrcp_media, sdp_media_t *sdp_media, const char *ip, apr_pool_t *pool)
{
	if(sdp_media->m_connections) {
		mrcp_media->ip = apr_pstrdup(pool,sdp_media->m_connections->c_address);
	}
	else {
		mrcp_media->ip = ip;
	}
	if(sdp_media->m_port) {
		mrcp_media->port = (unsigned short)sdp_media->m_port;
		mrcp_media->state = MRCP_MEDIA_ENABLED;
	}
	else {
		mrcp_media->state = MRCP_MEDIA_DISABLED;
	}
	return MRCP_STATUS_SUCCESS;
}


/* generates mrcp_descriptor by sdp_session */
mrcp_status_t mrcp_descriptor_generate_by_sdp_session(mrcp_descriptor_t *descriptor, sdp_session_t *sdp, apr_pool_t *pool)
{
	sdp_media_t *sdp_media;
	size_t i=0;
	mrcp_media_base_t *mrcp_media;
	if(sdp->sdp_connection) {
		descriptor->ip = apr_pstrdup(pool,sdp->sdp_connection->c_address);
	}
	for(sdp_media=sdp->sdp_media; sdp_media; sdp_media=sdp_media->m_next, i++) {
		if(i >= descriptor->media_count) {
			apt_log(APT_PRIO_INFO,"Media Count Mismatch Detected\n");
			break;
		}
		mrcp_media = descriptor->media[i];
		if(!mrcp_media) {
			continue;
		}
		switch(sdp_media->m_type) {
			case sdp_media_audio:
				if(mrcp_media->type == MRCP_MEDIA_TYPE_AUDIO) {
					mrcp_media_audio_t *audio_media = (mrcp_media_audio_t*)mrcp_media;
					mrcp_audio_media_generate_by_sdp_media(audio_media,sdp_media,pool);
				}
				else {
					apt_log(APT_PRIO_INFO,"Unexpected SDP Media [%s]\n", sdp_media->m_type_name);
					mrcp_media = NULL;
				}
				break;
			case sdp_media_application:
				if(mrcp_media->type == MRCP_MEDIA_TYPE_CONTROL) {
					mrcp_media_control_t *control_media = (mrcp_media_control_t*)mrcp_media;
					mrcp_control_media_generate_by_sdp_media(control_media,sdp_media,pool);
				}
				else {
					apt_log(APT_PRIO_INFO,"Unexpected SDP Media [%s]\n", sdp_media->m_type_name);
					mrcp_media = NULL;
				}
				break;
			default:
				apt_log(APT_PRIO_INFO,"Not Supported SDP Media [%s]\n", sdp_media->m_type_name);
				break;
		}
		if(mrcp_media) {
			mrcp_media_generate_by_sdp_media(mrcp_media,sdp_media,descriptor->ip,pool);
		}
	}
	return MRCP_STATUS_SUCCESS;
}

/* generates mrcp_descriptor by sdp offer */
mrcp_status_t mrcp_descriptor_generate_by_sdp_offer(mrcp_descriptor_t *descriptor, sdp_session_t *sdp, apr_pool_t *pool)
{
	sdp_media_t *sdp_media;
	size_t i=0;
	mrcp_media_base_t *mrcp_media;
	if(sdp->sdp_connection) {
		descriptor->ip = apr_pstrdup(pool,sdp->sdp_connection->c_address);
	}
	/* updated media */
	for(sdp_media=sdp->sdp_media; sdp_media && i<descriptor->media_count; sdp_media=sdp_media->m_next, i++) {
		mrcp_media = descriptor->media[i];
		if(!mrcp_media) {
			continue;
		}
		switch(sdp_media->m_type) {
			case sdp_media_audio:
				if(mrcp_media->type == MRCP_MEDIA_TYPE_AUDIO) {
					mrcp_media_audio_t *audio_media = (mrcp_media_audio_t*)mrcp_media;
					mrcp_audio_media_generate_by_sdp_media(audio_media,sdp_media,pool);
				}
				else {
					apt_log(APT_PRIO_INFO,"Unexpected SDP Media [%s]\n", sdp_media->m_type_name);
					mrcp_media = NULL;
				}
				break;
			case sdp_media_application:
				if(mrcp_media->type == MRCP_MEDIA_TYPE_CONTROL) {
					mrcp_media_control_t *control_media = (mrcp_media_control_t*)mrcp_media;
					mrcp_control_media_generate_by_sdp_media(control_media,sdp_media,pool);
				}
				else {
					apt_log(APT_PRIO_INFO,"Unexpected SDP Media [%s]\n", sdp_media->m_type_name);
					mrcp_media = NULL;
				}
				break;
			default:
				apt_log(APT_PRIO_INFO,"Not Supported SDP Media [%s]\n", sdp_media->m_type_name);
				break;
		}
		if(mrcp_media) {
			mrcp_media_generate_by_sdp_media(mrcp_media,sdp_media,descriptor->ip,pool);
		}
	}

	/* new media */
	for(; sdp_media; sdp_media=sdp_media->m_next) {
		mrcp_media = NULL;
		switch(sdp_media->m_type) {
			case sdp_media_audio:
			{
				mrcp_media_audio_t *audio_media = mrcp_audio_media_create(pool);
				mrcp_media = &audio_media->base;
				mrcp_descriptor_media_add(descriptor,mrcp_media);
				mrcp_audio_media_generate_by_sdp_media(audio_media,sdp_media,pool);
				break;
			}
			case sdp_media_application:
			{
				mrcp_media_control_t *control_media = mrcp_control_media_create(pool);
				mrcp_media = &control_media->base;
				mrcp_descriptor_media_add(descriptor,mrcp_media);
				mrcp_control_media_generate_by_sdp_media(control_media,sdp_media,pool);
				break;
			}
			default:
				apt_log(APT_PRIO_INFO,"Not Supported SDP Media [%s]\n", sdp_media->m_type_name);
				break;
		}
		if(mrcp_media) {
			mrcp_media_generate_by_sdp_media(mrcp_media,sdp_media,descriptor->ip,pool);
		}
	}
	return MRCP_STATUS_SUCCESS;
}

/* generates resource discover answer (sdp string) by mrcp_description */
size_t mrcp_resource_discover_sdp_generate(char *buffer, size_t size, mrcp_descriptor_t *descriptor)
{
	size_t i;
	size_t offset = 0;
	mrcp_status_t initiated = MRCP_STATUS_FAILURE;
	mrcp_media_control_t *control_media = NULL;
	mrcp_media_audio_t *audio_media = NULL;
	buffer[0] = '\0';
	offset += sprintf(buffer+offset,
			"v=0\r\n"
			"o=%s 0 0 IN IP4 %s\r\n"
			"s=-\r\n"
			"c=IN IP4 %s\r\n"
			"t=0 0\r\n",
			MRCP_SERVER_SDP_ORIGIN,
			descriptor->ip,
			descriptor->ip);

	for(i=0;i<descriptor->media_count; i++) {
		if(!descriptor->media[i]) {
			continue;
		}
		if(descriptor->media[i]->type == MRCP_MEDIA_TYPE_CONTROL) {
			control_media = (mrcp_media_control_t*)descriptor->media[i];
			if(initiated == MRCP_STATUS_FAILURE) {
				initiated = MRCP_STATUS_SUCCESS;
				offset += sprintf(buffer+offset,
					"m=application 0 %s 1\r\n",
					mrcp_proto_get(control_media->proto));
			}
			offset += sprintf(buffer+offset,
				"a=resource:%s\r\n",
				control_media->resource_name);
		}
		else if(descriptor->media[i]->type == MRCP_MEDIA_TYPE_AUDIO) {
			audio_media = (mrcp_media_audio_t*)descriptor->media[i];
		}
	}

	if(audio_media) {
		offset += sprintf(buffer+offset,"m=audio 0 RTP/AVP 0 8\r\n");
	}
	return offset;
}
