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

typedef struct _noPollSelect {
	noPollCtx          * ctx;
	fd_set               set;
	int                  length;
	int                  max_fds;
} noPollSelect;

/** 
 * @internal nopoll implementation to create a compatible "select" IO
 * call fd set reference.
 *
 * @return A newly allocated fd_set reference.
 */
noPollPtr nopoll_io_wait_select_create (noPollCtx * ctx) 
{
	noPollSelect * select = nopoll_new (noPollSelect, 1);

	/* set default behaviour expected for the set */
	select->ctx           = ctx;
	
	/* clear the set */
	FD_ZERO (&(select->set));

	return select;
}

/** 
 * @internal noPoll implementation to destroy the "select" IO call
 * created by the default create.
 * 
 * @param fd_group The fd group to be deallocated.
 */
void    nopoll_io_wait_select_destroy (noPollCtx * ctx, noPollPtr fd_group)
{
	fd_set * __fd_set = (fd_set *) fd_group;

	/* release memory allocated */
	nopoll_free (__fd_set);
	
	/* nothing more to do */
	return;
}

/** 
 * @internal noPoll implementation to clear the "select" IO call
 * created by the default create.
 * 
 * @param fd_group The fd group to be deallocated.
 */
void    nopoll_io_wait_select_clear (noPollCtx * ctx, noPollPtr __fd_group)
{
	noPollSelect * select = (noPollSelect *) __fd_group;

	/* clear the fd set */
	select->length = 0;
	FD_ZERO (&(select->set));

	/* nothing more to do */
	return;
}

/** 
 * @internal Default internal implementation for the wait operation to
 * change its status at least one socket description inside the fd set
 * provided.
 * 
 * @param __fd_group The fd set having all sockets to be watched.
 * @param wait_to The operation requested.
 * 
 * @return Number of connections that changed or -1 if something wailed
 */
int nopoll_io_wait_select_wait (noPollCtx * ctx, noPollPtr __fd_group)
{
	int                 result = -1;
	struct timeval      tv;
	noPollSelect     * _select = (noPollSelect *) __fd_group;

	/* init wait */
	tv.tv_sec    = 0;
	tv.tv_usec   = 500000;
	result       = select (_select->max_fds + 1, &(_select->set), NULL,   NULL, &tv);

	/* check result */
	if ((result == NOPOLL_SOCKET_ERROR) && (errno == NOPOLL_EINTR))
		return -1;
	
	return result;
}

/** 
 * @internal noPoll select implementation for the "add to" on fd set
 * operation.
 * 
 * @param fds The socket descriptor to be added.
 *
 * @param fd_set The fd set where the socket descriptor will be added.
 */
nopoll_bool  nopoll_io_wait_select_add_to (int               fds, 
					   noPollCtx       * ctx,
					   noPollConn      * conn,
					   noPollPtr         __fd_set)
{
	noPollSelect * select = (noPollSelect *) __fd_set;

	if (fds < 0) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL,
			    "received a non valid socket (%d), unable to add to the set", fds);
		return nopoll_false;
	}

	/* set the value */
	FD_SET (fds, &(select->set));

	/* update length */
	select->length++;

	/* update max fds */
	if (fds > select->max_fds)
		select->max_fds = fds;

	return nopoll_true;
}

/** 
 * @internal
 *
 * @brief Default noPoll implementation for the "is set" on fd
 * set operation.
 * 
 * @param fds The socket descriptor to be checked to be active on the
 * given fd group.
 *
 * @param fd_set The fd set where the socket descriptor will be checked.
 */
nopoll_bool      nopoll_io_wait_select_is_set (noPollCtx   * ctx,
					       int           fds, 
					       noPollPtr      __fd_set)
{
	noPollSelect * select = (noPollSelect *) __fd_set;
	
	return FD_ISSET (fds, &(select->set));
}


/** 
 * @brief Creates an object that represents the best IO wait mechanism
 * found on the current system.
 *
 * @param ctx The context where the engine will be created/associated.
 *
 * @param engine Use \ref NOPOLL_IO_ENGINE_DEFAULT or the engine you
 * want to use.
 *
 * @return The selected IO wait mechanism or NULL if it fails.
 */ 
noPollIoEngine * nopoll_io_get_engine (noPollCtx * ctx, noPollIoEngineType engine_type)
{
	noPollIoEngine * engine = nopoll_new (noPollIoEngine, 1);
	if (engine == NULL)
		return NULL;

	/* configure default implementation */
	engine->create  = nopoll_io_wait_select_create;
	engine->destroy = nopoll_io_wait_select_destroy;
	engine->clear   = nopoll_io_wait_select_clear;
	engine->wait    = nopoll_io_wait_select_wait;
	engine->addto   = nopoll_io_wait_select_add_to;
	engine->isset   = nopoll_io_wait_select_is_set;

	/* call to create the object */
	engine->ctx       = ctx;
	engine->io_object = engine->create (ctx);
	
	/* return the engine that was created */
	return engine;
}

/** 
 * @brief Release the io engine created by \ref nopoll_io_get_engine.
 *
 * @param engine The engine to be released.
 */
void             nopoll_io_release_engine (noPollIoEngine * engine)
{
	if (engine == NULL)
		return;
	engine->destroy (engine->ctx, engine->io_object);
	nopoll_free (engine);
	return;
}


