/*
 *  LibNoPoll: A websocket library
 *  Copyright (C) 2015 Advanced Software Production Line, S.L.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.
 *
 *  This program 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free
 *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307 USA
 *  
 *  You may find a copy of the license under this software is released
 *  at COPYING file. This is LGPL software: you are welcome to develop
 *  proprietary applications using this library without any royalty or
 *  fee but returning back any change, improvement or addition in the
 *  form of source code, project image, documentation patches, etc.
 *
 *  For commercial support on build Websocket enabled solutions
 *  contact us:
 *          
 *      Postal address:
 *         Advanced Software Production Line, S.L.
 *         Edificio Alius A, Oficina 102,
 *         C/ Antonio Suarez Nº 10,
 *         Alcalá de Henares 28802 Madrid
 *         Spain
 *
 *      Email address:
 *         info@aspl.es - http://www.aspl.es/nopoll
 */
#include <nopoll_msg.h>
#include <nopoll_private.h>

/** 
 * \defgroup nopoll_msg noPoll Message: functions for handling and using noPoll messages (websocket messages)
 */

/** 
 * \addtogroup nopoll_msg
 * @{
 */

/** 
 * @internal function that creates an empty message holder.
 * @return A newly created reference or NULL if it fails. 
 */
noPollMsg  * nopoll_msg_new (void)
{
	noPollMsg * msg = nopoll_new (noPollMsg, 1);
	if (msg == NULL)
		return NULL;

	msg->refs = 1;
	msg->ref_mutex = nopoll_mutex_create ();

	return msg;
}

/** 
 * @brief Allows to get a reference to the payload content inside the
 * provided websocket message.
 *
 * @param msg The websocket message to get the payload from.
 *
 * @return A reference to the payload or NULL if it fails. See \ref
 * nopoll_msg_get_payload_size to get payload size.
 */
const unsigned char * nopoll_msg_get_payload (noPollMsg * msg)
{
	if (msg == NULL)
		return NULL;
	return msg->payload;
}

/** 
 * @brief Allows to get the payload byte length stored on the provided
 * message.
 *
 * @param msg The websocket message to get the payload from.
 *
 * @return The payload size or -1 if it fails (only when msg is NULL).
 */
int          nopoll_msg_get_payload_size (noPollMsg * msg)
{
	if (msg == NULL)
		return -1;
	return msg->payload_size;
}

/** 
 * @brief Allows to acquire a reference to the provided websocket
 * message.
 *
 * @param msg The websocket message to acquire a reference.
 *
 * @return nopoll_true if the reference was acquired, otherwise
 * nopoll_false is returned.
 */
nopoll_bool  nopoll_msg_ref (noPollMsg * msg)
{
	/* check recieved reference */
	if (msg == NULL)
		return nopoll_false;

	/* acquire mutex here */
	nopoll_mutex_lock (msg->ref_mutex);

	msg->refs++;

	/* release mutex here */
	nopoll_mutex_unlock (msg->ref_mutex);

	return nopoll_true;
}

/** 
 * @brief Allows to get current reference counting for the provided
 * message.
 *
 * @param msg The message for which we are requesting for the
 * reference counting.
 *
 * @return Reference counting or -1 if it fails (returned when msg
 * reference received is NULL).
 */
int          nopoll_msg_ref_count (noPollMsg * msg)
{
	int result;

	/* check recieved reference */
	if (msg == NULL)
		return -1;

	/* acquire mutex here */
	nopoll_mutex_lock (msg->ref_mutex);

	result = msg->refs;

	/* release mutex here */
	nopoll_mutex_unlock (msg->ref_mutex);

	return result;
}

/** 
 * @brief Allows to get if the provided message reference has FIN flag
 * on (or off) to indicate if it is a final frame.
 *
 * When a series of messages are received and they conform together a
 * single message, the last message is flagged with FIN = 1 while the
 * rest before go with FIN = 0. 
 *
 * For example, if a user level application is splitted into 4 frame
 * fragments, then the WebSocket peer will receive 3 fragments with
 * FIN = 0 and the last fragment with FIN = 1.
 *
 * You can use \ref nopoll_msg_is_fragment to know if a particular
 * message was produced due to a fragmentation found at the network
 * level. This happens when the entire frame wasn't sent or it
 * couldn't be read entirely. In the example before, the four frames
 * will be also flagged as fragments too.
 *
 * @param msg The message that is being checked for FIN flag.
 *
 * @return nopoll_true if the message is a final one, otherwise
 * nopoll_false is returned. The function returns nopoll_false when
 * message reference received is NULL.
 *
 */
nopoll_bool  nopoll_msg_is_final (noPollMsg * msg)
{
	if (msg == NULL)
		return nopoll_false;

	return msg->has_fin;
}

/** 
 * @brief Allows to check if the message represents a frame fragment.
 *
 * The function allows to check if the provided noPollMsg is a
 * fragment from a bigger frame or message that was splitted as a
 * consequence of not being able to read the entire frame or because
 * it wasn't sent complete from the other side. See \ref
 * nopoll_msg_is_final for more information.
 *
 * The function also returns that the message is a fragment when the frame has FIN = 0.
 *
 * @param msg The message checked to be a fragment or not.
 *
 * @return nopoll_true if the message is a fragment, otherwise
 * nopoll_false is returned.
 */
nopoll_bool  nopoll_msg_is_fragment (noPollMsg * msg)
{
	if (msg == NULL)
		return nopoll_false;
	return msg->is_fragment || msg->has_fin == 0;
}

/** 
 * @brief Get message OpCode to get the type of message that was
 * received.
 *
 * @param msg The message that is being checked for its OpCode
 *
 * @return The op code or -1 in the case NULL reference is received.
 */
noPollOpCode nopoll_msg_opcode (noPollMsg * msg)
{
	if (msg == NULL)
		return NOPOLL_UNKNOWN_OP_CODE;
	return (noPollOpCode) msg->op_code;
}

/** 
 * @brief Allows to join the provided noPollMsg references to create a
 * newly allocated message (or reusing same reference but increasing reference
 * counting) that contains both content.
 *
 * @param msg The message to be join to the next message. Headers from
 * this message will be used as reference for the headers to be
 * used. In the case this is NULL, the second argument will be used as
 * argument and reference counting will be updated.
 *
 * @param msg2 The message to be join as a second part for the first
 * argument. 
 *
 * Here are some examples showing how the function works. The notation
 * along the argument indicates the reference counting at the end of
 * the function.
 *
 * msgA (2) = nopoll_msg_join (msgA (1), NULL);
 * msgB (2) = nopoll_msg_join (NULL, msgB (1));
 * msgC (1) = nopoll_msg_join (msgA (1), msgB (1));
 * NULL     = nopoll_msg_join (NULL, NULL);
 *
 * @return The function returns the newly allocated or reused
 * reference with increased reference counting or NULL if it fails.
 */
noPollMsg  * nopoll_msg_join (noPollMsg * msg, noPollMsg * msg2)
{
	noPollMsg * result;

	/* check for basic cases */
	if (msg == NULL && msg2 == NULL)
		return NULL;
	if (msg == NULL && msg2) {
		nopoll_msg_ref (msg2);
		return msg2;
	} /* ned if */
	if (msg && msg2 == NULL) {
		nopoll_msg_ref (msg);
		return msg;
	} /* end if */
	
	/* now, join content */
	result            = nopoll_msg_new ();
	result->has_fin   = msg->has_fin;
	result->op_code   = msg->op_code;
	result->is_masked = msg->is_masked;
	if (result->is_masked)
		memcpy (result->mask, msg->mask, 4);

	/* copy payload size and content */
	result->payload_size = msg->payload_size + msg2->payload_size;
	result->payload = nopoll_new (char, result->payload_size + 1);

	/* copy content from first message */
	memcpy (result->payload, msg->payload, msg->payload_size);

	/* copy content from second message */
	memcpy (((unsigned char *) result->payload) + msg->payload_size , msg2->payload, msg2->payload_size);

	/* return joined message */
	return result;
}

/** 
 * @brief Allows to release the reference acquired, finished the
 * object if all references are terminated.
 *
 * @param msg The websocket message to be finished.
 */
void         nopoll_msg_unref (noPollMsg * msg)
{
	if (msg == NULL)
		return;
	
	/* acquire mutex here */
	nopoll_mutex_lock (msg->ref_mutex);

	msg->refs--;
	if (msg->refs != 0) {
		/* release mutex here */
		nopoll_mutex_unlock (msg->ref_mutex);
		return;
	}
	/* release mutex */
	nopoll_mutex_unlock (msg->ref_mutex);
	nopoll_mutex_destroy (msg->ref_mutex);

	/* free websocket message */
	nopoll_free (msg->payload);
	nopoll_free (msg);

	/* release mutex here */
	return;
}


/* @} */
