/*
 * 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 net
 *  @{
 */

/** @file
 *  Generic module functions implementation.
 */

#include <async.h>
#include <malloc.h>

#include <ipc/ipc.h>
#include <ipc/services.h>

#include <sys/time.h>

#include "err.h"
#include "modules.h"

/** The time between connect requests in microseconds.
 */
#define MODULE_WAIT_TIME	(10 * 1000)

void answer_call(ipc_callid_t callid, int result, ipc_call_t * answer, int answer_count){
	// choose the most efficient answer function
	if(answer || (! answer_count)){
		switch(answer_count){
			case 0:
				ipc_answer_0(callid, (ipcarg_t) result);
				break;
			case 1:
				ipc_answer_1(callid, (ipcarg_t) result, IPC_GET_ARG1(*answer));
				break;
			case 2:
				ipc_answer_2(callid, (ipcarg_t) result, IPC_GET_ARG1(*answer), IPC_GET_ARG2(*answer));
				break;
			case 3:
				ipc_answer_3(callid, (ipcarg_t) result, IPC_GET_ARG1(*answer), IPC_GET_ARG2(*answer), IPC_GET_ARG3(*answer));
				break;
			case 4:
				ipc_answer_4(callid, (ipcarg_t) result, IPC_GET_ARG1(*answer), IPC_GET_ARG2(*answer), IPC_GET_ARG3(*answer), IPC_GET_ARG4(*answer));
				break;
			case 5:
			default:
				ipc_answer_5(callid, (ipcarg_t) result, IPC_GET_ARG1(*answer), IPC_GET_ARG2(*answer), IPC_GET_ARG3(*answer), IPC_GET_ARG4(*answer), IPC_GET_ARG5(*answer));
				break;
		}
	}
}

int bind_service(services_t need, ipcarg_t arg1, ipcarg_t arg2, ipcarg_t arg3, async_client_conn_t client_receiver){
	return bind_service_timeout(need, arg1, arg2, arg3, client_receiver, 0);
}

int bind_service_timeout(services_t need, ipcarg_t arg1, ipcarg_t arg2, ipcarg_t arg3, async_client_conn_t client_receiver, suseconds_t timeout){
	ERROR_DECLARE;

	int phone;
	ipcarg_t phonehash;

	// connect to the needed service
	phone = connect_to_service_timeout(need, timeout);
	// if connected
	if(phone >= 0){
		// request the bidirectional connection
		if(ERROR_OCCURRED(ipc_connect_to_me(phone, arg1, arg2, arg3, &phonehash))){
			ipc_hangup(phone);
			return ERROR_CODE;
		}
		async_new_connection(phonehash, 0, NULL, client_receiver);
	}
	return phone;
}

int connect_to_service(services_t need){
	return connect_to_service_timeout(need, 0);
}

int connect_to_service_timeout(services_t need, suseconds_t timeout){
	int phone;

	// if no timeout is set
	if (timeout <= 0){
		return async_connect_me_to_blocking(PHONE_NS, need, 0, 0);
	}

	while(true){
		phone = async_connect_me_to(PHONE_NS, need, 0, 0);
		if((phone >= 0) || (phone != ENOENT)){
			return phone;
		}

		// end if no time is left
		if(timeout <= 0){
			return ETIMEOUT;
		}

		// wait the minimum of the module wait time and the timeout
		usleep((timeout <= MODULE_WAIT_TIME) ? timeout : MODULE_WAIT_TIME);
		timeout -= MODULE_WAIT_TIME;
	}
}

int data_receive(void ** data, size_t * length){
	ERROR_DECLARE;

	ipc_callid_t callid;

	if(!(data && length)){
		return EBADMEM;
	}

	// fetch the request
	if(! async_data_write_receive(&callid, length)){
		return EINVAL;
	}

	// allocate the buffer
	*data = malloc(*length);
	if(!(*data)){
		return ENOMEM;
	}

	// fetch the data
	if(ERROR_OCCURRED(async_data_write_finalize(callid, * data, * length))){
		free(data);
		return ERROR_CODE;
	}
	return EOK;
}

int data_reply(void * data, size_t data_length){
	size_t length;
	ipc_callid_t callid;

	// fetch the request
	if(! async_data_read_receive(&callid, &length)){
		return EINVAL;
	}

	// check the requested data size
	if(length < data_length){
		async_data_read_finalize(callid, data, length);
		return EOVERFLOW;
	}

	// send the data
	return async_data_read_finalize(callid, data, data_length);
}

void refresh_answer(ipc_call_t * answer, int * answer_count){

	if(answer_count){
		*answer_count = 0;
	}

	if(answer){
		IPC_SET_RETVAL(*answer, 0);
		// just to be precize
		IPC_SET_METHOD(*answer, 0);
		IPC_SET_ARG1(*answer, 0);
		IPC_SET_ARG2(*answer, 0);
		IPC_SET_ARG3(*answer, 0);
		IPC_SET_ARG4(*answer, 0);
		IPC_SET_ARG5(*answer, 0);
	}
}

/** @}
 */
