/*
 * FILE:    receive.c
 * PROGRAM: RAT
 * AUTHOR:  Isidor Kouvelas + Orion Hodson
 * 
 * $Revision: 1.1.1.1 $
 * $Date: 1998/08/15 19:16:46 $
 *
 * Copyright (c) 1995,1996 University College London
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, is permitted, for non-commercial use only, provided
 * that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Computer Science
 *      Department at University College London
 * 4. Neither the name of the University nor of the Department may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 * Use of this software for commercial purposes is explicitly forbidden
 * unless prior written permission is obtained from the authors.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "bat_include.h"

#define INTERVAL_LEN	(SAMPLING_RATE *  20 / 1000)
#define HISTORY_LEN	(SAMPLING_RATE * 100 / 1000)

void
validate_buffer_length(rx_buffer_struct *buf);

static rx_interval_struct *
get_interval(rx_buffer_struct *buf, u_int32 istart)
{
	rx_interval_struct	*ip, **ipp;

	validate_buffer_length(buf);
	ipp = &buf->head_ptr;
	while (*ipp && ts_gt(istart, (*ipp)->interval_start))
		ipp = &((*ipp)->next_ptr);

	if (*ipp == NULL || (*ipp)->interval_start != istart) {
		ip = (rx_interval_struct *)block_alloc(sizeof(rx_interval_struct));
		ip->interval_start = istart;
		ip->no_units_in_chain = 0;
		ip->chain_ptr = NULL;
		buf->len++;
		ip->next_ptr = *ipp;
		if (*ipp == NULL) {
			ip->prev_ptr = buf->tail_ptr;
			buf->tail_ptr = ip;
		} else {
			ip->prev_ptr = (*ipp)->prev_ptr;
			(*ipp)->prev_ptr = ip;
		}
		*ipp = ip;
	}
	validate_buffer_length(buf);
	return (*ipp);
}

static rx_queue_element_struct *
add_unit_to_interval(rx_interval_struct *ip, rx_queue_element_struct *ru)
{
	rx_queue_element_struct **upp, *master, *slave;
        int si, mi;
        
        /* we have 2 rx units *upp and *up.  We work out which has the higher quality (master) */
        /* we then iterate through the lower quality one and see if data for that codec is     */
        /* is present.  if not we place it in the master.  The motivation being that we        */
        /* we may need the codec state even if not actually decoding it (i.e. for LPC redund)  */
        
	upp = &ip->chain_ptr;
	while (*upp && ru->dbe_source[0] != (*upp)->dbe_source[0])
		upp = &((*upp)->next_ptr);
	if (*upp) {
                if (coding_value(ru->comp_format[0].scheme) > coding_value((*upp)->comp_format[0].scheme)) {
                        master = ru;
                        slave  = *upp;
                        master->next_ptr = slave->next_ptr;
                } else {
                        master = *upp;
                        slave  = ru;
                }
                assert (master->comp_count !=0 );
                assert (slave->comp_count  != 0);
                master->interval            = ip;
                master->talk_spurt_start   |= slave->talk_spurt_start;
                master->talk_spurt_end     |= slave->talk_spurt_end;
                master->mixed              |= slave->mixed;
                mi = master->comp_count;
                si = 0;
                while ((si < slave->comp_count) &&
                       (mi < MAX_ENCODINGS)) {
                        if (coding_value(slave->comp_format[si].scheme) < coding_value(master->comp_format[mi-1].scheme)) {
                                memcpy(&(master->comp_format[mi]),
                                       &(slave->comp_format[si]),
                                       sizeof(samples_type));
                                master->comp_data[mi]         = slave->comp_data[si];
                                slave->comp_data[si]          = NULL;
                                slave->comp_format[si].scheme = -99; 
                                mi ++;
                        }
                        si ++;
                }
                master->comp_count = mi;
                *upp = master;
                free_rx_unit(&slave);
	} else {
		ru->next_ptr = NULL;
                ru->interval = ip;
		*upp = ru;
		ip->no_units_in_chain++;
	}
	(*upp)->prev_ptr = NULL;	/* Do not doubly link it */
	return (*upp);
}

rx_queue_element_struct *
new_rx_unit()
{
	rx_queue_element_struct *u;
	u = (rx_queue_element_struct *)block_alloc(sizeof(rx_queue_element_struct));
	memset(u, 0, sizeof(rx_queue_element_struct));
	return (u);
}

rx_queue_element_struct *
get_participant_unit(rx_interval_struct *ip, rtcp_dbentry *e)
{
	rx_queue_element_struct *u;

	for (u = ip->chain_ptr; u; u = u->next_ptr)
		if (*u->dbe_source == e)
			return (u);
	return (NULL);
}

static rx_queue_element_struct *
playout_buffer_add(rx_buffer_struct *buf, rx_queue_element_struct *ru)
{
	u_int32			istart;
	rx_interval_struct	*ip;
	int			i;
	rx_queue_element_struct	*dummy, *prev;

	validate_buffer_length(buf);
	istart = ru->playoutpt - ru->playoutpt % INTERVAL_LEN;
	ip = get_interval(buf, istart);
	validate_buffer_length(buf);
	prev = ru = add_unit_to_interval(ip, ru);
	validate_buffer_length(buf);
	for (i = 0; i < MAX_DUMMY && ru->talk_spurt_start == FALSE; i++) {
		if (ip->prev_ptr == NULL
		    || ip->prev_ptr->interval_start != ip->interval_start - INTERVAL_LEN) {
			ip = get_interval(buf, ip->interval_start - INTERVAL_LEN);
		} else
			ip = ip->prev_ptr;
		if (get_participant_unit(ip, *ru->dbe_source) != NULL)
			break;
		dummy                        = new_rx_unit();
		dummy->dbe_source_count      = ru->dbe_source_count;
		memcpy(dummy->dbe_source, ru->dbe_source, ru->dbe_source_count * sizeof(rtcp_dbentry *));
		dummy->playoutpt             = prev->playoutpt - INTERVAL_LEN;
		dummy->comp_format[0].scheme = DUMMY;
                dummy->comp_count            = 1;
		prev                         = add_unit_to_interval(ip, dummy);

		assert(prev == dummy);
	}
	validate_buffer_length(buf);
	return (ru);
}

static rx_interval_struct *
playout_buffer_get(rx_buffer_struct *buf, u_int32 from, u_int32 to)
{
	rx_interval_struct	*ip;
	rx_queue_element_struct *up;

	validate_buffer_length(buf);
	if (buf->last_to_get == NULL) {
		ip = buf->head_ptr;
	} else {
		ip = buf->last_to_get->next_ptr;
	}
	while (ip && ts_gt(from, ip->interval_start)) {
		buf->last_to_get = ip;
		ip = ip->next_ptr;
	}

	if (ip) {
		if (ts_gt(ip->interval_start, to))
			return (NULL);

		buf->last_to_get = ip;

		for (up = ip->chain_ptr; up; up = up->next_ptr)
			decode_unit(up);
	}
	validate_buffer_length(buf);
	return (ip);
}

void
check_history_length(session_struct *sp, rx_buffer_struct *buf)
{
	u_int32                  max_var, max_fnd, held_audio, i;
	rx_interval_struct      *ip;
	rx_queue_element_struct *rx;
	
	/* This function trims the receive buffer if grows too large.
	 * This typically happens when processing load varies suddenly,
	 * e.g. user drags window around.
	 */

	if (sp->trim_hold > 0) {
		/* bail early if we're are waiting for effects of trim to kick in */
		sp->trim_hold--;
		return;
	}

	assert(buf != NULL);
	ip = buf->head_ptr;
	if (ip == NULL) {
		return;
	}
	
	max_var = 0;
	max_fnd = FALSE;
	rx = ip->chain_ptr;
	while(rx != NULL) {
		for(i = 0; i < (u_int32)rx->dbe_source_count; i++) {
			if (rx->dbe_source[i] != NULL && rx->dbe_source[i]->peak_var > max_var) {
				max_var = rx->dbe_source[i]->peak_var;
				max_fnd = TRUE;
			}
		}
		rx = rx->next_ptr;
	}
	
	if (max_fnd == FALSE) {
		return; /* nothing to do */
	}
	
	max_var = max(2 * (max_var + SAMPLES_PER_UNIT),16000); /* this is what we call unacceptable amount of buffered audio */
	held_audio = buf->len * SAMPLES_PER_UNIT;
	
	if (held_audio > max_var) {
	/* We could examine buffer for which source is clogging up the buffer 
	 * but that is mucho work so force recalculation of playout point of 
	 * source in first interval instead.  
	 * We get a glitch anyway we do it.
	 */
#ifdef WIN32
		OutputDebugString("Trimming excessive buffered audio.\n");
#endif
		rx = ip->chain_ptr;
		while(rx != NULL) {
			for(i = 0; i < (u_int32)rx->dbe_source_count; i++) {
				assert(rx->dbe_source[i] != NULL);
				rx->dbe_source[i]->cont_toged = 8;
			}
			rx = rx->next_ptr;
		}
		sp->trim_hold = buf->len;
	}
}

void
validate_buffer_length(rx_buffer_struct *buf)
{
#ifdef DEBUG	
	char errmsg[80];
	int count = count_buffer_queue(buf);
	if (count != buf->len) {
		sprintf(errmsg,"count (%d) != buf_len (%d)\n", count, buf->len);
		OutputDebugString(errmsg);
	}
#endif
}

void
clear_old_history(rx_buffer_struct  *buf, u_int32 cur_time)
{ 
	rx_interval_struct	*ip;
	rx_queue_element_struct *up;
	u_int32	cutoff;

	cutoff = cur_time - HISTORY_LEN;
	validate_buffer_length(buf);
	while (buf->head_ptr && ts_gt(cutoff, buf->head_ptr->interval_start)) {
		ip = buf->head_ptr;
		buf->head_ptr = ip->next_ptr;

		if (buf->last_to_get == ip) {
			buf->last_to_get = NULL;
		}

		while (ip->chain_ptr) {
			up = ip->chain_ptr;
			ip->chain_ptr = up->next_ptr;
			free_rx_unit(&up);
		}
		buf->len--;
		block_free(ip, sizeof(rx_interval_struct));
		assert(buf->len >= 0);
	}

	if (buf->head_ptr) {
		buf->head_ptr->prev_ptr = NULL;
	} else {
		buf->tail_ptr = NULL;
		buf->len = 0;
	}
	validate_buffer_length(buf);
}

void 
service_receiver(cushion_struct    *cushion,
		 session_struct    *sp,
		 rx_queue_struct   *receive_queue,
		 rx_buffer_struct  *playout_buf,
		 mix_struct        *ms)
{
	rx_queue_element_struct *up;
	rx_interval_struct *ip;
	int power;
   	/* Get power for this sample - KH*/
	if (ms->tail < SAMPLES_PER_UNIT) {
			power=audio_energy(ms->mix_buffer + ms->buf_len - SAMPLES_PER_UNIT, SAMPLES_PER_UNIT); 
	} else {
			power=audio_energy(ms->mix_buffer + ms->tail - SAMPLES_PER_UNIT, SAMPLES_PER_UNIT); 
	}
        
	while (receive_queue->queue_empty_flag == FALSE) {
		up = get_unit_off_rx_queue(receive_queue);
		/* This is to compensate for clock drift.
		 * Same check should be made in case it is too early.
		 */
		if (ts_gt(sp->cur_time, up->playoutpt)) {
			up->dbe_source[0]->jit_TOGed++;
			up->dbe_source[0]->cont_toged++;
		} else {
			up->dbe_source[0]->cont_toged = 0;
		}
		up = playout_buffer_add(playout_buf, up);
		/*
		 * If we have already worked past this point then mix it!
		 */
		if (up && playout_buf->last_to_get && up->mixed == FALSE
		    && ts_gt(playout_buf->last_to_get->interval_start, up->playoutpt)
		    && ts_gt(up->playoutpt, sp->cur_time)){
			decode_unit(up);
                        if (up->decomp_data != NULL) {
                                mix_do_one_chunk(ms, up);
                                mark_active_senders(up, sp);
				if (!sp->have_device && (audio_device_take(sp) == FALSE)) {
					lbl_cb_send_request(sp);
				}
                                up->mixed = TRUE;
                        }
		}
	}
	while ((ip = playout_buffer_get(playout_buf, sp->cur_time, sp->cur_time + cushion->cushion_size))) {
		for (up = ip->chain_ptr; up; up = up->next_ptr) {
                        if ((ip->prev_ptr != NULL) &&
                            (ip->next_ptr != NULL)) {
                                if ((sp->repair != REPAIR_NONE) &&
                                    (up->comp_format[0].scheme == DUMMY) &&
                                    (count_dummies_back(ip,up)   <= MAX_DUMMY)) {
                                        patch_dummy(sp->repair, ip, up);
                                }
                        }
			if (up->decomp_data != NULL && up->mixed == FALSE) {
				mix_do_one_chunk(ms, up);
				mark_active_senders(up, sp);
				if (!sp->have_device && (audio_device_take(sp) == FALSE)) {
					lbl_cb_send_request(sp);
				}
				up->mixed = TRUE;
			}
		}
	}
	clear_old_history(playout_buf, sp->cur_time);
	check_history_length(sp, playout_buf); /* if not using push to talk we can accumulate audio
							    * through clock differences and host scheduling */
}

