/*
 *  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_loop.h>
#include <nopoll_private.h>

/** 
 * \defgroup nopoll_loop noPoll Loop: basic support to create a watching loop for WebSocket listeners
 */

/** 
 * \addtogroup nopoll_loop
 * @{
 */

/** 
 * @internal Function used by nopoll_loop_wait to register all
 * connections into the io waiting object.
 */
nopoll_bool nopoll_loop_register (noPollCtx * ctx, noPollConn * conn, noPollPtr user_data)
{
	/* do not add connections that aren't working */
	if (! nopoll_conn_is_ok (conn)) {
		
		/* remove this connection from registry */
		nopoll_ctx_unregister_conn (ctx, conn);

		return nopoll_false; /* keep foreach, don't stop */
	}

	/* register the connection socket */
	/* nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Adding socket id: %d", conn->session);*/
	if (! ctx->io_engine->addto (conn->session, ctx, conn, ctx->io_engine->io_object)) {

		/* remove this connection from registry */
		nopoll_ctx_unregister_conn (ctx, conn);
		nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "Failed to add socket %d to the watching set", conn->session);

	}

	return nopoll_false; /* keep foreach, don't stop */
}

/** 
 * @internal Function used to handle incoming data from from the
 * connection and to notify this data on the connection.
 */
void nopoll_loop_process_data (noPollCtx * ctx, noPollConn * conn)
{
	noPollMsg * msg;

	/* call to get messages from the connection */
	msg = nopoll_conn_get_msg (conn);
	if (msg == NULL)
		return;

	/* found message, notify it */
	if (conn->on_msg) 
		conn->on_msg (ctx, conn, msg, conn->on_msg_data);
	else if (ctx->on_msg)
		ctx->on_msg (ctx, conn, msg, ctx->on_msg_data);

	/* release message */
	nopoll_msg_unref (msg);
	return;
}

/** 
 * @internal Function used to detected which connections has something
 * interesting to be notified.
 *
 */
nopoll_bool nopoll_loop_process (noPollCtx * ctx, noPollConn * conn, noPollPtr user_data)
{
	int        * conn_changed = (int *) user_data;

	/* check if the connection have something to notify */
	if (ctx->io_engine->isset (ctx, conn->session, ctx->io_engine->io_object)) {

		/* call to notify action according to role */
		switch (conn->role) {
		case NOPOLL_ROLE_CLIENT:
		case NOPOLL_ROLE_LISTENER:
			/* received data, notify */
			nopoll_loop_process_data (ctx, conn);
			break;
		case NOPOLL_ROLE_MAIN_LISTENER:
			/* call to handle */
			nopoll_conn_accept (ctx, conn);
			break;
		default:
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Found connection with unknown role, closing and dropping");
			nopoll_conn_shutdown (conn);
			break;
		}
		
		/* reduce connection changed */
		(*conn_changed)--;
	} /* end if */
	
	return (*conn_changed) == 0;
}

/** 
 * @internal Function used to init internal io wait mechanism...
 *
 * @param ctx The noPoll context to be initialized if it wasn't
 */
void nopoll_loop_init (noPollCtx * ctx) 
{
	if (ctx == NULL)
		return;

	/* grab the mutex for the following check */
	if (ctx->io_engine == NULL) {
		ctx->io_engine = nopoll_io_get_engine (ctx, NOPOLL_IO_ENGINE_DEFAULT);
		if (ctx->io_engine == NULL) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to create IO wait engine, unable to implement wait call");
			return;
		} 
	} /* end if */
	/* release the mutex */

	return;
}

/** 
 * @brief Flag to stop the current loop implemented (if any) on the provided context.
 *
 * @param ctx The context where the loop is being done, and wanted to
 * be stopped.
 *
 */
void nopoll_loop_stop (noPollCtx * ctx)
{
	if (! ctx)
		return;
	ctx->keep_looping = nopoll_false;
	return;
} /* end if */

/** 
 * @brief Allows to implement a wait over all connections registered
 * under the provided context during the provided timeout until
 * something is detected meaningful to the user, calling to the action
 * handler defined, optionally receving the user data pointer.
 *
 * @param ctx The context object where the wait will be implemented.
 *
 * @param timeout The timeout to wait for changes. If no changes
 * happens, the function returns. The function will block the caller
 * until a call to \ref nopoll_loop_stop is done in the case timeout
 * passed is 0.
 *
 * @return The function returns 0 when finished or -2 in the case ctx
 * is NULL or timeout is negative.
 */
int nopoll_loop_wait (noPollCtx * ctx, long timeout)
{
	struct timeval start;
	struct timeval stop;
	struct timeval diff;
	long           ellapsed;
	int            wait_status;

	nopoll_return_val_if_fail (ctx, ctx, -2);
	nopoll_return_val_if_fail (ctx, timeout >= 0, -2);
	
	/* call to init io engine */
	nopoll_loop_init (ctx);

	/* get as reference current time */
	if (timeout > 0)
#if defined(NOPOLL_OS_WIN32)
		nopoll_win32_gettimeofday (&start, NULL);
#else
		gettimeofday (&start, NULL);
#endif
	
	/* set to keep looping everything this function is called */
	ctx->keep_looping = nopoll_true;

	while (ctx->keep_looping) {
		/* ok, now implement wait operation */
		ctx->io_engine->clear (ctx, ctx->io_engine->io_object);
		
		/* add all connections */
		/* nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Adding connections to watch: %d", ctx->conn_num);  */
		nopoll_ctx_foreach_conn (ctx, nopoll_loop_register, NULL);

		/* if (errno == EBADF) { */
			/* detected some descriptor not properly
			 * working, try to check them */
			/* nopoll_ctx_foreach_conn (ctx, nopoll_loop_clean_descriptors, NULL); */
		/* nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Found some descriptor is not valid (errno==%d)", errno);
		   continue; */ 
		/* } */ /* end if */
		
		/* implement wait operation */
		/* nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Waiting for changes into %d connections", ctx->conn_num); */
		wait_status = ctx->io_engine->wait (ctx, ctx->io_engine->io_object);
		/* nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Waiting finished with result %d", wait_status);  */
		if (wait_status == -1) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received error from wait operation, error code was: %d", errno);
			break;
		} /* end if */

		/* check how many connections changed and restart */
		if (wait_status > 0) {
			/* check and call for connections with something
			 * interesting */
			nopoll_ctx_foreach_conn (ctx, nopoll_loop_process, &wait_status);
		}

		/* check to stop wait operation */
		if (timeout > 0) {
#if defined(NOPOLL_OS_WIN32)
			nopoll_win32_gettimeofday (&stop, NULL);
#else
			gettimeofday (&stop, NULL);
#endif
			nopoll_timeval_substract (&stop, &start, &diff);
			ellapsed = (diff.tv_sec * 1000000) + diff.tv_usec;
			if (ellapsed > timeout)
				break;
		} /* end if */
	} /* end while */

	/* release engine */
	nopoll_io_release_engine (ctx->io_engine);
	ctx->io_engine = NULL;

	return 0;
}

/* @} */


