/*
 * Copyright (c) 2009 Lukas Mejdrech
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - 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.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 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 AUTHOR 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.
 */

/** @addtogroup socket
 *  @{
 */

/** @file
 *  \todo
 */

#include "../err.h"

#include "../include/in.h"
#include "../include/inet.h"

#include "../include/socket_codes.h"
#include "../include/socket_errno.h"

#include "../structures/dynamic_fifo.h"
#include "../structures/int_map.h"
#include "../structures/packet/packet.h"
#include "../structures/packet/packet_client.h"

#include "socket_core.h"

int	socket_bind_insert( socket_ports_ref global_sockets, socket_core_ref socket, int port );

INT_MAP_IMPLEMENT( socket_cores, socket_core_t );

INT_MAP_IMPLEMENT( socket_ports, socket_core_ref );

int socket_bind( socket_cores_ref local_sockets, socket_ports_ref global_sockets, int socket_id, void * addr, size_t addrlen, int free_ports_start, int free_ports_end, int last_used_port ){
	socket_core_ref			socket;
	socket_core_ref	*		socket_pointer;
	struct sockaddr *		address;
	struct sockaddr_in *	address_in;

	if( addrlen < sizeof( struct sockaddr )) return EINVAL;
	address = ( struct sockaddr * ) addr;
	switch( address->sa_family ){
		case AF_INET:
			if( addrlen != sizeof( struct sockaddr_in )) return EINVAL;
			address_in = ( struct sockaddr_in * ) addr;
			// find the socket
			socket = socket_cores_find( local_sockets, socket_id );
			if( ! socket ) return ENOTSOCK;
			// bind a free port?
			if( address_in->sin_port <= 0 ){
				return socket_bind_free_port( global_sockets, socket, free_ports_start, free_ports_end, last_used_port );
			}
			// try to find the port
			socket_pointer = socket_ports_find( global_sockets, address_in->sin_port );
			if( socket_pointer ){
				// already used
				return EADDRINUSE;
			}
			// unbind if bound
			socket_ports_exclude( global_sockets, socket->port );
			socket->port = -1;
			return socket_bind_insert( global_sockets, socket, address_in->sin_port );
			break;
		// TODO IPv6
	}
	return EAFNOSUPPORT;
}

int socket_bind_free_port( socket_ports_ref global_sockets, socket_core_ref socket, int free_ports_start, int free_ports_end, int last_used_port ){
	int	index;

	// from the last used one
	index = last_used_port;
	do{
		++ index;
		// til the range end
		if( index >= free_ports_end ){
			// start from the range beginning
			index = free_ports_start - 1;
			do{
				++ index;
				// til the last used one
				if( index >= last_used_port ){
					// none found
					return ENOTCONN;
				}
			}while( socket_ports_find( global_sockets, index ) != NULL );
			// found, break immediately
			break;
		}
	}while( socket_ports_find( global_sockets, index ) != NULL );
	return socket_bind_insert( global_sockets, socket, index );
}

int socket_bind_insert( socket_ports_ref global_sockets, socket_core_ref socket, int port ){
	ERROR_DECLARE;

	socket_core_ref	*	socket_pointer;

	// create a wrapper
	socket_pointer = ( socket_core_ref * ) malloc( sizeof( socket_core_ref ));
	if( ! socket_pointer ) return ENOMEM;
	* socket_pointer = socket;
	// register the incomming port
	ERROR_CODE = socket_ports_add( global_sockets, port, socket_pointer );
	if( ERROR_CODE < 0 ){
		free( socket_pointer );
		return ERROR_CODE;
	}
	socket->port = port;
	return EOK;
}

int socket_create( socket_cores_ref local_sockets, int app_phone, void * specific_data, int * socket_id ){
	ERROR_DECLARE;

	socket_core_ref	socket;
	int				res;

	if( ! socket_id ) return EBADMEM;
	socket = ( socket_core_ref ) malloc( sizeof( * socket ));
	if( ! socket ) return ENOMEM;
	// initialize
	socket->phone = app_phone;
	socket->port = -1;
	socket->specific_data = specific_data;
	if( ERROR_OCCURRED( dyn_fifo_initialize( & socket->received, SOCKET_INITIAL_RECEIVED_SIZE ))){
		free( socket );
		return ERROR_CODE;
	}
	if( ERROR_OCCURRED( dyn_fifo_initialize( & socket->accepted, SOCKET_INITIAL_ACCEPTED_SIZE ))){
		dyn_fifo_destroy( & socket->received );
		free( socket );
		return ERROR_CODE;
	}
	// get a next free socket number
	socket->socket_id = socket_cores_count( local_sockets ) + 1;
	// store the socket
	res = socket_cores_add( local_sockets, socket->socket_id, socket );
	if( res < 0 ){
		dyn_fifo_destroy( & socket->received );
		dyn_fifo_destroy( & socket->accepted );
		free( socket );
		return res;
	}
	// return the socket identifier
	* socket_id = socket->socket_id;
	return EOK;
}

int socket_destroy( int packet_phone, int socket_id, socket_cores_ref local_sockets, socket_ports_ref global_sockets ){
	socket_core_ref	socket;
	int				accepted_id;
	int				packet_id;

	// find the socket
	socket = socket_cores_find( local_sockets, socket_id );
	if( ! socket ) return ENOTSOCK;
	socket_ports_exclude( global_sockets, socket->port );
	// destroy all accepted sockets
	while(( accepted_id = dyn_fifo_pop( & socket->accepted )) >= 0 ){
		socket_destroy( packet_phone, accepted_id, local_sockets, global_sockets );
	}
	// release all received packets
	while(( packet_id = dyn_fifo_pop( & socket->received )) >= 0 ){
		pq_release( packet_phone, packet_id );
	}
	dyn_fifo_destroy( & socket->received );
	dyn_fifo_destroy( & socket->accepted );
	socket_cores_exclude( local_sockets, socket_id );
	return EOK;
}

/** @}
 */
