/*
* Copyright (C) 2010-2011 Mamadou Diop.
*
* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 tcomp_compartment.c
 * @brief  SigComp compartment.
 * An application-specific grouping of messages that relate to a peer endpoint.  Depending on the signaling protocol, this grouping may
 * relate to application concepts such as "session", "dialog", "connection", or "association".  The application allocates state
 * memory on a per-compartment basis, and determines when a compartment should be created or closed.
 *
 * @author Mamadou Diop <diopmamadou(at)yahoo.fr>
 *

 */
#include "tcomp_compartment.h"

#include "tsk_debug.h"

#include <assert.h>

tcomp_compartment_t* tcomp_compartment_create(uint64_t id, uint32_t sigCompParameters, tsk_bool_t useOnlyACKedStates)
{
	tcomp_compartment_t *compartment;
	if((compartment = tsk_object_new(tcomp_compartment_def_t))){
		

		/*
		  +---+---+---+---+---+---+---+---+
		  |  cpb  |    dms    |    sms    |
		  +---+---+---+---+---+---+---+---+
		  |        SigComp_version        |
		  +---+---+---+---+---+---+---+---+
		*/

		// I always assume that remote params are equal to local params

		/* Identifier */
		compartment->identifier = id;

		/* Remote parameters */
		compartment->remote_parameters = tcomp_params_create();
		tcomp_params_setParameters(compartment->remote_parameters, sigCompParameters);

		/* Local parameters */
		compartment->local_parameters = tcomp_params_create();
		tcomp_params_setParameters(compartment->local_parameters, sigCompParameters);

		/* Total size */
		compartment->total_memory_size = compartment->total_memory_left = compartment->local_parameters->smsValue;

		/* Empty list. */
		compartment->nacks = tsk_list_create();
		
		/* Empty list. */
		compartment->local_states = tsk_list_create();

		/* Whether to use only ACKed states */
		compartment->useOnlyACKedStates = useOnlyACKedStates;
	}
	else{
		TSK_DEBUG_ERROR("Null Compartment");
	}

	return compartment;
}

int tcomp_compartment_setUseOnlyACKedStates(tcomp_compartment_t* self, tsk_bool_t useOnlyACKedStates)
{
	if(!self){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	self->useOnlyACKedStates = useOnlyACKedStates;
	return 0;
}

/**Sets remote parameters
*/
void tcomp_compartment_setRemoteParams(tcomp_compartment_t *compartment, tcomp_params_t *lpParams)
{
	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter");
		return;
	}

	/* CPB||DMS||SMS [1-BYTE] */
	if(tcomp_params_hasCpbDmsSms(lpParams)){
		tcomp_params_setCpbCode(compartment->remote_parameters, lpParams->cpbCode);
		tcomp_params_setDmsCode(compartment->remote_parameters, lpParams->dmsCode);
		tcomp_params_setSmsCode(compartment->remote_parameters, lpParams->smsCode);
	}

	/* SigComp version */
	if(lpParams->SigComp_version){
		compartment->remote_parameters->SigComp_version = lpParams->SigComp_version;
	}

	/*
	*	Returned states
	*	FIXME: check states about quota
	*	FIXME: not tic tac
	*	FIXME: what about returned feedback?
	*/
	if(lpParams->returnedStates && tcomp_buffer_getSize(lpParams->returnedStates)){
		TSK_OBJECT_SAFE_FREE(compartment->remote_parameters->returnedStates);
		/* swap */
		compartment->remote_parameters->returnedStates = lpParams->returnedStates;
		lpParams->returnedStates = 0;
	}
}

/**Sets requested feedback
*/
void tcomp_compartment_setReqFeedback(tcomp_compartment_t *compartment, tcomp_buffer_handle_t *feedback)
{
	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter.");
		return;
	}

	tsk_safeobj_lock(compartment);

	/* Delete old */
	TSK_OBJECT_SAFE_FREE(compartment->lpReqFeedback);

	compartment->lpReqFeedback = tcomp_buffer_create(tcomp_buffer_getBuffer(feedback), tcomp_buffer_getSize(feedback));

	tsk_safeobj_unlock(compartment);
}

/**Sets returned feedback
*/
void tcomp_compartment_setRetFeedback(tcomp_compartment_t *compartment, tcomp_buffer_handle_t *feedback)
{
	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter.");
		return;
	}
	
	tsk_safeobj_lock(compartment);
	
	// Delete old
	TSK_OBJECT_SAFE_FREE(compartment->lpRetFeedback);
	
	compartment->lpRetFeedback = tcomp_buffer_create(tcomp_buffer_getBuffer(feedback), tcomp_buffer_getSize(feedback));

	/*
	* ACK STATE ==> Returned feedback contains the partial state-id.
	*/
	if(compartment->compressorData/* && !compartment->compressorData_isStream*/){
		tcomp_buffer_handle_t *stateid = tcomp_buffer_create(tcomp_buffer_getBufferAtPos(feedback, 1), tcomp_buffer_getSize(feedback)-1);
		compartment->ackGhost(compartment->compressorData, stateid);
		TSK_OBJECT_SAFE_FREE(stateid);
	}
	
	tsk_safeobj_unlock(compartment);
}

/**Clears are compartment from the history.
*/
void tcomp_compartment_clearStates(tcomp_compartment_t *compartment)
{
	if(!compartment){
		TSK_DEBUG_ERROR("NULL sigcomp compartment.");
		return;
	}

	tsk_safeobj_lock(compartment);

	tsk_list_clear_items(compartment->local_states);
	compartment->total_memory_left = compartment->total_memory_size;

	tsk_safeobj_unlock(compartment);
}

/**Removes a state from the compartment by priority.
*/
void tcomp_compartment_freeStateByPriority(tcomp_compartment_t *compartment)
{
	tcomp_state_t *lpState;
	tsk_list_item_t *item;

	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter.");
		return;
	}

	tsk_safeobj_lock(compartment);

	lpState = 0;
	item = 0;

	/*
	 * The order in which the existing state items are freed is determined by the state_retention_priority, which is set when the
	 * state items are created.  The state_retention_priority of 65535 is reserved for locally available states; these states must always be
	 * freed first.  Apart from this special case, states with the lowest state_retention_priority are always freed first.  In the event of
	 * a tie, then the state item created first in the compartment is also the first to be freed.
	*/
	tsk_list_foreach(item, compartment->local_states){
		tcomp_state_t *curr = item->data;

		if(!curr){
			continue;
		}
		
		/* Local state ? */
		if(curr->retention_priority == 65535){
			lpState = curr;
			break;
		}

		/* Lower priority? */
		if(!lpState || curr->retention_priority < lpState->retention_priority){
			lpState = curr;
			continue;
		}
	}

	if(lpState){
		compartment->total_memory_left += TCOMP_GET_STATE_SIZE(lpState);
		tsk_list_remove_item_by_data(compartment->local_states, lpState);
	}

	tsk_safeobj_unlock(compartment);
}

/**Removes a state from the compartment.
*/
void tcomp_compartment_freeState(tcomp_compartment_t *compartment, tcomp_state_t **lpState)
{
	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter.");
		return;
	}

	tsk_safeobj_lock(compartment);

	compartment->total_memory_left += TCOMP_GET_STATE_SIZE(*lpState);
	tsk_list_remove_item_by_data(compartment->local_states, *lpState);
	*lpState = tsk_null;

	tsk_safeobj_unlock(compartment);
}

/**Remove states
*/
void tcomp_compartment_freeStates(tcomp_compartment_t *compartment, tcomp_tempstate_to_free_t **tempStates, uint8_t size)
{
	uint8_t i;
	tcomp_state_t *lpState;
	tsk_list_item_t *item;

	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter.");
		return;
	}

	if(!tempStates || !size){
		return;
	}
	
	lpState = tsk_null;
	item = tsk_null;

	for (i = 0; i < size; i++){
		/* lock */
		tsk_safeobj_lock(compartment);

		tsk_list_foreach(item, compartment->local_states){
			tcomp_state_t *curr = item->data;
			
			/* Compare current state with provided partial state */
			if(tcomp_buffer_startsWith(curr->identifier, tempStates[i]->identifier)){
				/*
				* If more than one state item in the compartment matches the partial state identifier, 
				* then the state free request is ignored.
				*/
				if(lpState){
					lpState = 0;
					break;
				}
				else{
					lpState = curr;
				}
			}
		}
		
		/* unlock */
		tsk_safeobj_unlock(compartment);

		if(lpState){
			tcomp_compartment_freeState(compartment, &lpState);
		}
	}	
}

/**Adds a state to the compartment.
*/
void tcomp_compartment_addState(tcomp_compartment_t *compartment, tcomp_state_t **lpState)
{
	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter.");
		return;
	}

	tsk_safeobj_lock(compartment);

	tcomp_state_makeValid(*lpState);
	compartment->total_memory_left -= TCOMP_GET_STATE_SIZE(*lpState);
	TSK_DEBUG_INFO("SigComp - Add new state with id=");
	tcomp_buffer_print((*lpState)->identifier);
	tsk_list_push_back_data(compartment->local_states, ((void**) lpState));
	
	*lpState = tsk_null;

	tsk_safeobj_unlock(compartment);
}

/**Finds a state.
*/
uint32_t tcomp_compartment_findState(tcomp_compartment_t *compartment, const tcomp_buffer_handle_t *partial_identifier, tcomp_state_t **lpState)
{
	uint32_t count = 0;
	tsk_list_item_t *item;

	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter");
		return 0;
	}
	
	tsk_safeobj_lock(compartment);

	item = tsk_null;
	
	tsk_list_foreach(item, compartment->local_states){
		tcomp_state_t *curr = item->data;
		
		if(tcomp_buffer_startsWith(curr->identifier, partial_identifier)){
			*lpState = curr; // override
			count++;
		}
	}

	tsk_safeobj_unlock(compartment);

	return count;
}

/**Removes a Ghost state.
*/
void tcomp_compartment_freeGhostState(tcomp_compartment_t *compartment)
{
	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter.");
		return;
	}

	tsk_safeobj_lock(compartment);

	if(compartment->compressorData){
		compartment->freeGhostState(compartment->compressorData);
	}
	else{
		TSK_DEBUG_WARN("No compression data to free.");
	}

	tsk_safeobj_unlock(compartment);
}

/**Adds a NACK to the compartment.
*/
void tcomp_compartment_addNack(tcomp_compartment_t *compartment, const uint8_t nackId[TSK_SHA1_DIGEST_SIZE])
{
	tcomp_buffer_handle_t *id;

	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter.");
		return;
	}

#if 0
	{
		int i;
		TSK_DEBUG_INFO("Save NACK with id:");
		for(i = 0; i < TSK_SHA1_DIGEST_SIZE; ++i){
			printf("%x ", nackId[i]);
		}
		printf("\n");
	}
#endif

	tsk_safeobj_lock(compartment);

	if(compartment->nacks_history_count >= NACK_MAX_HISTORY_SIZE){
		tsk_list_remove_last_item(compartment->nacks);
		compartment->nacks_history_count--;
	}

	id = tcomp_buffer_create(nackId, TSK_SHA1_DIGEST_SIZE);
	tsk_list_push_back_data(compartment->nacks, ((void**) &id));
	compartment->nacks_history_count++;

	tsk_safeobj_unlock(compartment);
}

/**Checks if the NACK exist.
*/
tsk_bool_t tcomp_compartment_hasNack(tcomp_compartment_t *compartment, const tcomp_buffer_handle_t *nackId)
{
	tsk_bool_t ret = tsk_false;
	tsk_list_item_t *item;

	if(!compartment){
		TSK_DEBUG_ERROR("Invalid parameter.");
		return tsk_false;
	}

	tsk_safeobj_lock(compartment);

	item = tsk_null;
	
	tsk_list_foreach(item, compartment->nacks){
		tcomp_buffer_handle_t *curr = item->data;
	
		if(tcomp_buffer_equals(curr, nackId)){
			TSK_DEBUG_INFO("SigComp - Nack found.");
			ret = tsk_true;
			break;
		}
	}

	tsk_safeobj_unlock(compartment);

	return ret;
}










//========================================================
//	State object definition
//

static tsk_object_t* tcomp_compartment_ctor(tsk_object_t* self, va_list * app)
{
	tcomp_compartment_t *compartment = self;
	if(compartment){
		/* Initialize safeobject */
		tsk_safeobj_init(compartment);
	}

	return self;
}

static tsk_object_t* tcomp_compartment_dtor(tsk_object_t* self)
{
	tcomp_compartment_t *compartment = self;
	if(compartment){
		/* Deinitialize safeobject */
		tsk_safeobj_deinit(compartment);

		/* Delete all states */
		TSK_OBJECT_SAFE_FREE(compartment->local_states);
		
		/* Delete feedbacks */
		TSK_OBJECT_SAFE_FREE(compartment->lpReqFeedback);
		TSK_OBJECT_SAFE_FREE(compartment->lpRetFeedback);
		
		/* Delete Nacks */
		TSK_OBJECT_SAFE_FREE(compartment->nacks);
		
		/* Delete Compressor data */
		TSK_OBJECT_SAFE_FREE(compartment->compressorData);
		compartment->ackGhost = 0;
		compartment->freeGhostState = 0;
		
		/* Delete params */
		TSK_OBJECT_SAFE_FREE(compartment->local_parameters);
		TSK_OBJECT_SAFE_FREE(compartment->remote_parameters);

		/* Delete NACKS */
		TSK_OBJECT_SAFE_FREE(compartment->nacks);

		/* Delete local states */
		TSK_OBJECT_SAFE_FREE(compartment->local_states);
	}
	else{
		TSK_DEBUG_ERROR("Null Compartment");
	}
	
	return self;
}

static int tcomp_compartment_cmp(const tsk_object_t *obj1, const tsk_object_t *obj2)
{
	const tcomp_compartment_t *compartment1 = obj1;
	const tcomp_compartment_t *compartment2 = obj2;
	int64_t res = (compartment1->identifier - compartment2->identifier);
	return res > 0 ? (int)1 : (res < 0 ? (int)-1 : (int)0);
}

static const tsk_object_def_t tsk_compartment_def_s = 
{
	sizeof(tcomp_compartment_t),
	tcomp_compartment_ctor,
	tcomp_compartment_dtor,
	tcomp_compartment_cmp
};
const tsk_object_def_t *tcomp_compartment_def_t = &tsk_compartment_def_s;
