/**
 * @file main.c
 *
 * @brief Service Placement main file.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 *
 * $Id: main.c 2688 2008-06-26 11:59:33Z wittenbu $
 */

/**
 * @mainpage
 *
 * The problem of service placement in ad hoc networks can be stated as follows:
 * Given a physical network topology and service demands of client nodes, adapt
 * the number and location of services in the network over time such that the
 * service demands are met at a minimal cost. The cost function may include
 * metrics such as network traffic, energy expenditure or service-dependant
 * Quality of Service (QoS) metrics (availability, latency, etc.).
 *
 * The purpose of this framework is to evaluate different approaches to service
 * placement in ad hoc networks. It is designed to run on different platforms
 * ranging from network simulators to major operating systems and embedded
 * devices.
 *
 * For more information, please have a look at the project webpage at
 * http://cst.mi.fu-berlin.de/projects/ServicePlacement/.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 */



#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef ARCH_LINUX
#include <errno.h>
#include <error.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#elif defined(ARCH_WIN32)
#include <winsock2.h>
#include <windows.h>
#endif	// ARCH_*

#include "types.h"
#include "vars.h"
#include "interface.h"
#include "util/debug.h"
#include "util/list.h"
#include "util/callback.h"
#include "util/net.h"
#include "service/service.h"



#ifdef ARCH_WIN32

/**
 * @brief Redefine errno to function of the Winsock API.
 */
#undef errno
#define errno WSAGetLastError()

/**
 * @brief Define error INTR error code of the Winsock API.
 */
#define EINTR WSAEINTR

#endif	// ARCH_WIN32



/**
 * @brief Status of the application.
 */
bool running;

/**
 * @brief Size of buffer for received packets in bytes.
 */
#define RECV_BUFFER_SIZE	1500

/**
 * @brief Buffer for received packets in bytes.
 */
unsigned char recv_buffer[RECV_BUFFER_SIZE];

/**
 * @brief Time interval at which to insert a mark into logfile in seconds.
 */
#define LOG_MARK_INTERVAL	10

/**
 * @brief Time at which the last mark was inserted into logfile.
 */
C_time_t last_mark_in_log_time;



/**
 * @brief Initialization of the application.
 *
 * Seed the random number generator, setup and configure a socket, establish
 * the local address and initial
 */
static void init() {
	unsigned int seed;
	struct protoent *udp = NULL;
	struct sockaddr_in addr;
#ifdef ARCH_LINUX
	int opt = 1;
#elif defined(ARCH_WIN32)
	BOOL opt = TRUE;
	WSADATA wsa;
	int wsastartup_error;
#endif	// ARCH_*
	int i = 1;

	last_mark_in_log_time = C_time();

#ifdef ARCH_WIN32
	// Start Windows Sockets API.
	wsastartup_error = WSAStartup(MAKEWORD(2,0), &wsa);
	if(wsastartup_error != 0) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Error while starting Windows Sockets API (%d)...\n", wsastartup_error);
		abort();
	}
#endif	// ARCH_WIN32

	// Seed random number generator.
	seed = (unsigned int) (C_time() * 1000000);
	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Setting random seed to %d...\n", seed);
	srand(seed);

	// Setup socket.
	udp = getprotobyname("UDP");
	if(udp == NULL) {
		int errsv = errno;
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Error while retrieving protocol information for UDP (%d)...\n", errsv);
#ifdef ARCH_LINUX
		error(1, errsv, NULL);
#elif defined(ARCH_WIN32)
		abort();
#endif	// ARCH_*
	}
	if((C_udp_socket = socket(PF_INET, SOCK_DGRAM, udp->p_proto)) < 0) {
		int errsv = errno;
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Error while creating UDP socket (%d)...\n", errsv);
#ifdef ARCH_LINUX
		error(1, errsv, NULL);
#elif defined(ARCH_WIN32)
		abort();
#endif	// ARCH_*
	}
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(C_DEFAULT_UDP_PORT);
	if(bind(C_udp_socket, (struct sockaddr*) &addr, sizeof(struct sockaddr_in)) < 0) {
		int errsv = errno;
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Error while binding socket (%d)...\n", errsv);
#ifdef ARCH_LINUX
		error(1, errsv, NULL);
#elif defined(ARCH_WIN32)
		abort();
#endif	// ARCH_*
	}

	// Enable support for broadcasting.
#ifdef ARCH_LINUX
	if(setsockopt(C_udp_socket, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(int)) < 0) {
#elif defined(ARCH_WIN32)
	if(setsockopt(C_udp_socket, SOL_SOCKET, SO_BROADCAST, (LPSTR) &opt, sizeof(BOOL)) < 0) {
#endif	// ARCH_*
		int errsv = errno;
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Error while setting socket options (%d)...\n", errsv);
#ifdef ARCH_LINUX
		error(1, errsv, NULL);
#elif defined(ARCH_WIN32)
		abort();
#endif	// ARCH_*
	}

	// Establish local IP address.
#ifdef ARCH_LINUX
	// Adapted from http://www.hungry.com/~alves/local-ip-in-C.html (no license).
	C_local_addr = 0;
	while(true) {
		struct ifreq ifr;
		struct sockaddr_in *sin = (struct sockaddr_in*) &ifr.ifr_addr;
		ifr.ifr_ifindex = i++;
		if(ioctl(C_udp_socket, SIOCGIFNAME, &ifr) < 0)
			break;
		if(
			(ioctl(C_udp_socket, SIOCGIFADDR, &ifr) < 0)
			|| (strncmp(inet_ntoa(sin->sin_addr), "127.0.0.1", 10) == 0)
		)
			continue;
		C_local_addr = sin->sin_addr.s_addr;
	}
	if(C_local_addr == 0) {
		int errsv = errno;
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Cannot establish local IP address (%d).\n", errsv);
		error(1, errsv, NULL);
	}
#elif defined(ARCH_WIN32)
	// Adapted from http://tangentsoft.net/wskfaq/examples/ipaddr.html (public domain).
	{
		char hostname[256];
		struct hostent *host = NULL;
		int i;

		if(gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
			int errsv = errno;
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "Cannot establish local hostname (%d).\n", errsv);
			abort();
		}
		host = gethostbyname(hostname);
		if(host == NULL) {
			int errsv = errno;
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "Cannot establish local host (%d).\n", errsv);
			abort();
		}
		for(i = 0; host->h_addr_list[i] != NULL; ++i) {
			struct in_addr addr;
			memcpy(&addr, host->h_addr_list[i], sizeof(struct in_addr));
			C_local_addr = addr.s_addr;
		}
	}
#endif	// ARCH_*
}

/**
 * @brief Handle incoming packets.
 *
 * Retrieve meta-information and hand packet up to components.
 *
 */
static void handle_packet() {
	struct sockaddr_in addr;
#ifdef ARCH_LINUX
	socklen_t addrlen = sizeof(struct sockaddr_in);
	ssize_t size = recvfrom(C_udp_socket, recv_buffer, RECV_BUFFER_SIZE, 0, (struct sockaddr*) &addr, &addrlen);
#elif defined(ARCH_WIN32)
	int addrlen = sizeof(struct sockaddr_in);
	int size = recvfrom(C_udp_socket, recv_buffer, RECV_BUFFER_SIZE, 0, (struct sockaddr*) &addr, &addrlen);
#endif	// ARCH_*
	if(size > 0) {
		packet_header_t* header = NULL;
		void* payload = NULL;

		if(size < ((int) sizeof(packet_header_t))) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "Received %d byte(s) but expected at least %d bytes.\n", size, sizeof(packet_header_t));
			abort();
		}

		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Received a packet of size %d from %s:%d.\n", size, addr_to_str(addr.sin_addr.s_addr), ntohs(addr.sin_port));
		header = (packet_header_t*) recv_buffer;

		// TODO: At this point we need to gather information from the
		// routing protocol.
		header->hops = 1;
		header->last_hop = addr.sin_addr.s_addr;

		if(header->payload_length > 0)
			payload = recv_buffer + sizeof(packet_header_t);
		C_recv(header, payload);

		addrlen = sizeof(struct sockaddr_in);	// Reset size of addr variable.
	} else if(size == 0) {	// Can't happen.
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Non-existant connection has been shutdown by peer (recvfrom() returned 0).\n");
		abort();
	} else {		// size is -1.
		int errsv = errno;
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Error while receiving packet (%d).\n", errsv);
#ifdef ARCH_LINUX
		error(1, errsv, NULL);
#elif defined(ARCH_WIN32)
		abort();
#endif	// ARCH_*
	}
}

/**
 * @brief Main loop iteration.
 *
 * Wait for either the next callback or for a packet to arrive.
 */
static void loop_iteration() {
	fd_set incoming_sockets;
	struct timeval timeout;
	C_time_t time_until_next_callback = -1;
	C_time_t time_until_next_mark = -1;
	bool waiting_for_callback;
	int ready;

	waiting_for_callback = false;
	time_until_next_mark = last_mark_in_log_time + LOG_MARK_INTERVAL - C_time();
#ifdef ARCH_LINUX
	timeout.tv_sec = time_until_next_mark;
	timeout.tv_usec = (time_until_next_mark - ((C_time_t) timeout.tv_sec)) * 1000000;
#elif defined(ARCH_WIN32)
	timeout.tv_sec = (long) time_until_next_mark;
	timeout.tv_usec = (long) ((time_until_next_mark - ((C_time_t) timeout.tv_sec)) * 1000000);
#endif	// ARCH_*

	// Check whether callbacks are scheduled.
	if(!list_empty(C_callbacks)) {
		time_until_next_callback = ((callback_t*) list_head(C_callbacks))->time - C_time();
		if(time_until_next_callback <= time_until_next_mark) {
			waiting_for_callback = true;
#ifdef ARCH_LINUX
			timeout.tv_sec = time_until_next_callback;
			timeout.tv_usec = (time_until_next_callback - ((C_time_t) timeout.tv_sec)) * 1000000;
#elif defined(ARCH_WIN32)
			timeout.tv_sec = (long) time_until_next_callback;
			timeout.tv_usec = (long) ((time_until_next_callback - ((C_time_t) timeout.tv_sec)) * 1000000);
#endif	// ARCH_*
		}
	}

	// Wait for either a packet to arrive or for the timeout to expire.
	FD_ZERO(&incoming_sockets);
	FD_SET(C_udp_socket, &incoming_sockets);
	ready = select(C_udp_socket + 1, &incoming_sockets, NULL, NULL, &timeout);

	// Return if aborted.
	if(!running)
		return;

	// Periodically insert mark into log file.
	if(C_time() >= (last_mark_in_log_time + LOG_MARK_INTERVAL)) {
		if(time_until_next_callback == -1)
			C_DEBUG(C_DEBUG_LEVEL_DEBUG, "-- MARK --\n");
		else
			C_DEBUG(C_DEBUG_LEVEL_DEBUG, "-- MARK -- (next callback in %.2fs)\n", time_until_next_callback);
		last_mark_in_log_time = C_time();
	}

	if((ready == 1) && FD_ISSET(C_udp_socket, &incoming_sockets)) {
		handle_packet();
	} else if(ready == 0) {
		if(waiting_for_callback)
			callback_expire(NULL);
	} else {	// ready is -1, or 1 but C_udp_socket was not set.
		int errsv = errno;
		if(errsv != EINTR) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "Error while waiting for packet (%d).\n", errsv);
#ifdef ARCH_LINUX
			error(1, errsv, NULL);
#elif defined(ARCH_WIN32)
			abort();
#endif	// ARCH_*
		}
	}
}

/**
 * @brief Process command line arguments.
 *
 * @param argc Number of command line arguments.
 * @param argv Array of command line arguments
 */
static bool process_args(int argc, char *argv[]) {
	int i;

	for(i = 1; i < argc; i++) {
		if(strcmp(argv[i], "-nop") == 0) {
			ICMP_send_nop(C_BROADCAST_ADDR, C_DEFAULT_TTL);
		} else if(strcmp(argv[i], "-srv") == 0) {
			if((i + 1) < argc) {
				SERVICE_create_service(argv[i + 1], 1024);
				i += 1;
			} else {
				fprintf(stderr, "Not enough parameters for option \"%s\".\n", argv[i]);
				return false;
			}
		} else if(strcmp(argv[i], "-req") == 0) {
			if((i + 1) < argc) {
				SERVICE_request_data(argv[i + 1], 0x0000, 1024);
				i += 1;
			} else {
				fprintf(stderr, "Not enough parameters for option \"%s\".\n", argv[i]);
				return false;
			}
		} else {
			fprintf(stderr, "Unknown argument \"%s\".\n", argv[i]);
			return false;
		}
	}
	return true;
}

/**
 * @brief Print usage information.
 *
 * @param cmd Command name of this program.
 */
static void print_usage(char* cmd) {
	fprintf(stderr, "Usage: %s [-nop|-srv service_id|-req service_id]\n", cmd);
}

/**
 * @brief Stop the main loop.
 *
 * This function is called in response to a SIGINT signal.
 */
#ifdef ARCH_LINUX
static void stop() {
#elif defined(ARCH_WIN32)
static void stop(int UNUSED) {
#endif	// ARCH_*
	C_DEBUG(C_DEBUG_LEVEL_INFO, "Interrupted by user.\n");
	C_check_consistency_on_shutdown = false;
	running = false;
}

/**
 * @brief The main function.
 *
 * Initialize and start all components, run the main loop and on shutdown stop
 * and finalize all components.
 *
 * @param argc Number of command line arguments.
 * @param argv Array of command line arguments
 */
int main(int argc, char *argv[]) {
	init();
	signal(SIGINT, stop);

	// Initialize and start components.
	C_init();
	C_start();

	// Process command line arguments.
	if(!process_args(argc, argv)) {
		print_usage(argv[0]);
		exit(1);
	}

	// Main loop.
	running = true;
	while(running)
		loop_iteration();

	// Stop and finalize components.
	C_stop();
	C_finalize();

	// Close socket.
#ifdef ARCH_LINUX
	close(C_udp_socket);
#elif defined(ARCH_WIN32)
	closesocket(C_udp_socket);
#endif	// ARCH_*

	return 0;
}
