/**
 * @file interface.c
 *
 * @brief Service Placement interface implementation.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 *
 * $Id: interface.c 2685 2008-06-25 16:38:56Z wittenbu $
 */

#include <string.h>

#include "types.h"
#include "vars.h"
#include "interface.h"
#include "interface_internal.h"
#include "util/debug.h"
#include "util/list.h"
#include "util/callback.h"
#include "util/net.h"
#include "icmp-light/icmp-light.h"
#include "rudp-light/rudp-light.h"
#include "slp-light/slp-light.h"
#include "service/service.h"
#include "placement/placement.h"
#include "graphs/graphs.h"



/**
 * @brief Global initialization function called by the underlying system.
 */
void C_init() {
	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Initializing... \n");

	if(C_LOCAL_ADDR == C_BROADCAST_ADDR) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Local node is using the broadcast address (%s == %s).\n", addr_to_str(C_LOCAL_ADDR), addr_to_str(C_BROADCAST_ADDR));
		abort();
	}

	// Initialize global variables.
	C_check_consistency_on_shutdown = true;
	list_alloc(&C_callbacks);
	C_next_callback_id = 0;
	C_registered_callbacks = 0;
	C_executed_callbacks = 0;
	C_sequence_number = 0;
	memset(C_received_packet_cache, 0, C_RECEIVED_PACKET_CACHE_SIZE * sizeof(packet_fingerprint_t));
	list_alloc(&C_network_map);
	C_direct_neighbors = 0;
	C_longest_route = 0;
	C_average_route = 0;
	C_addr_to_str_buffer_index = 0;

	C_DEBUG_ERRORs = 0;
	C_DEBUG_WARNs = 0;

	// Initialize components.
	ICMP_init();
	RUDP_init();
	SLP_init();
	SERVICE_init();
	PLACEMENT_init();
	GRAPHS_init();
}

/**
 * @brief Global start function called by the underlying system.
 */
void C_start() {
	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Starting... \n");

	// Start components.
	ICMP_start();
	RUDP_start();
	SLP_start();
	SERVICE_start();
	PLACEMENT_start();
	GRAPHS_start();
}

/**
 * @brief Global stop function called by the underlying system.
 */
void C_stop() {
	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Stopping...\n");

	// Stop components.
	ICMP_stop();
	RUDP_stop();
	SLP_stop();
	SERVICE_stop();
	PLACEMENT_stop();
	GRAPHS_stop();
}

/**
 * @brief Global finalization function called by the underlying system.
 */
void C_finalize() {
	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Destroying...\n");

	calculate_network_statistics();

	if(C_check_consistency_on_shutdown) {
		if(!list_empty(C_callbacks)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "There are still %d callback(s) in queue, last one due in %.2f seconds.\n", list_size(C_callbacks), (((callback_t*) list_tail(C_callbacks))->time - __C_time()));
			abort();
		} else if(C_executed_callbacks != C_registered_callbacks) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "Callback counters don't add up (%d != %d).\n", C_executed_callbacks, C_registered_callbacks);
			abort();
		}
	}

	list_free(&C_callbacks);
	list_free(&C_network_map);

	// Destroy components.
	ICMP_finalize();
	RUDP_finalize();
	SLP_finalize();
	SERVICE_finalize();
	PLACEMENT_finalize();
	GRAPHS_finalize();
}

/**
 * @brief Internal packet reception function.
 *
 * This function may be called either when packets arrive from the underlying
 * system or if a (possibly encapsulated) packet needs to be processed again.
 *
 * @param header Pointer to the header of the received packet.
 * @param payload Pointer to the payload of the received packet.
 * @param maybe_duplicate Set to true if this packet is a potential duplicate
 * that requires filtering.
 */
void C_handle_packet(const packet_header_t* header, const void* payload, bool maybe_duplicate) {
	if(header->ttl == 0) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "Dropping packet with expired TTL from node %s...\n", addr_to_str(header->from));
		return;
	} else if(maybe_duplicate && check_for_duplicate(C_received_packet_cache, C_RECEIVED_PACKET_CACHE_SIZE, header)) {
		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Dropping duplicate packet from node %s with sequence number %d...\n", addr_to_str(header->from), header->seqno);
		return;
	}

	switch(header->app) {
	case APP_TYPE_ICMP:
		ICMP_recv(header, payload);
		break;
	case APP_TYPE_RUDP:
		RUDP_recv(header, payload);
		break;
	case APP_TYPE_SLP:
		SLP_recv(header, payload);
		break;
	case APP_TYPE_SERVICE:
		SERVICE_recv(header, payload);
		break;
	case APP_TYPE_PLACEMENT:
		PLACEMENT_recv(header, payload);
		break;
	case APP_TYPE_GRAPHS:
		GRAPHS_recv(header, payload);
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "Received packet for unknown application 0x%X from %s.\n", header->app, addr_to_str(header->from));
	}
}

/**
 * @brief Global packet reception function called by the underlying system.
 *
 * @param header Pointer to the header of the received packet.
 * @param payload Pointer to the payload of the received packet.
 */
void C_recv(const packet_header_t* header, const void* payload) {
	add_node_to_network_map(header->from, header->hops, header->last_hop);
	C_handle_packet(header, payload, (header->to == C_BROADCAST_ADDR));
}

/**
 * @brief Global callback trigger function called by the underlying system.
 */
void C_execute_callback() {
	callback_expire();
}

/**
 * @brief Global command function called by the underlying system.
 *
 * @param argc Length of the argument array.
 * @param argv Array of argument strings.
 * @return <code>true</code> if the command was processed successfully,
 * <code>false</code> otherwise.
 */
bool C_command(int argc, const char*const* argv) {
	if(argc >= 2) {
		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Received command \"%s\"...\n", argv[1]);
	} else {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "Received command without command string...\n");
	}

	// Call into components.
	if(ICMP_command(argc, argv))
		return true;
	else if(RUDP_command(argc, argv))
		return true;
	else if(SLP_command(argc, argv))
		return true;
	else if(SERVICE_command(argc, argv))
		return true;
	else if(PLACEMENT_command(argc, argv))
		return true;
	else if(GRAPHS_command(argc, argv))
		return true;
	else
		return false;
}

/**
 * @brief Calculate the maximal payload size.
 *
 * @return Maximal payload size in bytes.
 */
size_t C_max_payload_size() {
	return (C_MTU - sizeof(packet_header_t));
}



/**
 * @brief Wrapper function around low-level output function.
 *
 * @param format Format string.
 * @param ... Variable number of arguments matching the format string.
 */
void C_printf(const char *format, ...) {
	va_list argp;
	va_start(argp, format);
	__C_vprintf(format, argp);
	va_end(argp);
}

/**
 * @brief Wrapper function around low-level output function.
 *
 * @param format Format string.
 * @param ap Variable length argument structure.
 */
void C_vprintf(const char *format, va_list ap) {
	__C_vprintf(format, ap);
}

/**
 * @brief Wrapper function around low-level random function.
 *
 * @param max Maximal value.
 * @return Random value between 0 and max.
 */
double C_random(double max) {
	return __C_random(max);
}

/**
 * @brief Wrapper function around low-level time function.
 *
 * @return Current time.
 */
C_time_t C_time() {
	return __C_time();
}

/**
 * @brief Wrapper function around low-level send function.
 *
 * @param header Header of packet to send.
 * @param payload Payload of packet to send.
 */
void C_send(const packet_header_t* header, const void* payload) {
	if(header == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to send packet without header.\n");
		abort();
	} else if((header->payload_length > 0) && (payload == NULL)) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to send non-empty packet without payload.\n");
		abort();
	} else if((sizeof(packet_header_t) + header->payload_length) > C_MTU) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to send packet exceeding the MTU (%d > %d).\n", (sizeof(packet_header_t) + header->payload_length), C_MTU);
		abort();
	}

	if(header->to != C_LOCAL_ADDR)
		__C_send(header, payload);
	else
		C_handle_packet(header, payload, false);
}

/**
 * @brief Wrapper function around low-level callback activation function.
 *
 * @param time Time at which the next callback needs to be activated.
 */
void C_activate_callback(C_time_t time) {
	if(time < C_time()) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "Trying to activate callback in the past (%f < %f).\n", time, C_time());
		return;
	}
	__C_activate_callback(time);
}
