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

#include <stdlib.h>
#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 "graphs.h"
#include "graphs_types.h"
#include "graphs_vars.h"
#include "graphs_local.h"



/*
 * Interface to main module.
 */

/**
 * @brief Initialize the GRAPHS module.
 */
void GRAPHS_init() {
	list_alloc(&C_GRAPHS_active_constructions);
	C_GRAPHS_local_data = NULL;
}

/**
 * @brief Start the GRAPHS module.
 */
void GRAPHS_start() {
}

/**
 * @brief Stop the GRAPHS module.
 */
void GRAPHS_stop() {
}

/**
 * @brief Finalize the GRAPHS module.
 */
void GRAPHS_finalize() {
}



/*
 * Packet handling functions.
 */

/**
 * @brief Check whether two global data nodes have the same address.
 *
 * @param item Data node to check.
 * @param cmp_item Reference data node.
 * @return <code>true</code> if the data nodes have the same address,
 * <code>false</code> otherwise.
 */
static bool cmp_global_data_node_addr(const void* item, const void* cmp_item) {
	GRAPHS_global_data_node_t* data_node = (GRAPHS_global_data_node_t*) item;
	GRAPHS_global_data_node_t* cmp_data_node = (GRAPHS_global_data_node_t*) cmp_item;
	return (data_node->node.address == cmp_data_node->node.address);
}

static void construct_iterate(GRAPHS_construction_t* graph_construction);

/**
 * @brief RUDP callback for sending a GRAPHS REQUEST packet.
 *
 * @param parameter RUDP transmission result.
 */
static void send_GRAPHS_REQUEST_callback(callback_parameter_t parameter) {
	RUDP_transmission_result_t* result = (RUDP_transmission_result_t*) parameter;

	if(result->error_code == C_ERROR_OK) {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "GRAPHS: GRAPHS REQUEST to node %s was successful.\n", addr_to_str(result->to));
	} else {
		GRAPHS_construction_t* graph_construction = NULL;
		GRAPHS_global_data_node_t* data_node = NULL;
		GRAPHS_global_data_node_t cmp_data_node;

		// This timeout might trigger after the graph construction is
		// already complete, hence we have to check. Note that there is
		// a slight chance that we actually getting a reference to the
		// NEXT graph construction process, but in reality this
		// shouldn't happen as graph construction is only done once.
		if(list_empty(C_GRAPHS_active_constructions)) {
			C_DEBUG(C_DEBUG_LEVEL_DEBUG, "GRAPHS: GRAPHS REQUEST to node %s timed out, but graph construction has already been completed...\n", addr_to_str(result->to));
			return;
		}

		// NOTE: Since only one graph construction process can be
		// active, we can just get the head of the list of active
		// constructions.
		graph_construction = (GRAPHS_construction_t*) list_head(C_GRAPHS_active_constructions);
		cmp_data_node.node.address = result->to;
		data_node = (GRAPHS_global_data_node_t*) list_find(graph_construction->result->data_nodes, &cmp_data_node, cmp_global_data_node_addr);
		if(data_node == NULL) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "GRAPHS: GRAPHS REQUEST to node %s timed out, but it is not in network list...\n", addr_to_str(result->to));
			abort();
		} else if(data_node->local_data_list != NULL) {
			C_DEBUG(C_DEBUG_LEVEL_DEBUG, "GRAPHS: GRAPHS REQUEST to node %s timed out, but node has already received data...\n", addr_to_str(result->to));
			return;
		} else if(result->to != graph_construction->active_node) {
			C_DEBUG(C_DEBUG_LEVEL_ERROR, "GRAPHS: GRAPHS REQUEST to node %s timed out, but active node is %s...\n", addr_to_str(result->to), addr_to_str(graph_construction->active_node));
			return;
		}

		C_DEBUG(C_DEBUG_LEVEL_WARN, "GRAPHS: GRAPHS REQUEST failed (%d), skipping node %s...\n", result->error_code, addr_to_str(result->to));
		callback_cancel(graph_construction->callback_id);
		list_alloc(&data_node->local_data_list);

		// Continue iterating over all nodes.
		construct_iterate(graph_construction);
	}

	FREE(result);
}



/**
 * @brief Send a GRAPHS REQUEST packet.
 *
 * @param to Address of the destination node.
 * @param graph_construction Pointer to graph construction data structure.
 */
static void send_GRAPHS_REQUEST(C_addr_t to, GRAPHS_construction_t* graph_construction) {
	node_t* node_array = NULL;
	RUDP_transmission_result_t* result = NULL;

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "GRAPHS: Sending GRAPHS REQUEST to node #%d...\n", to);
	MALLOC_ARRAY(node_array, node_t, list_size(graph_construction->nodes));
	list_to_array(graph_construction->nodes, node_array, sizeof(node_t));
	MALLOC(result, RUDP_transmission_result_t);
	RUDP_sendto(to, APP_TYPE_GRAPHS, GRAPHS_PACKET_TYPE_REQUEST, node_array, (list_size(graph_construction->nodes) * sizeof(node_t)), send_GRAPHS_REQUEST_callback, result);
	FREE(node_array);
}

/**
 * @brief Handle an incoming GRAPHS REPLY packet.
 *
 * Add the data collected by the remote node to the global data and start the
 * next iteration of the construction process. If a reply is received from a
 * node other than the node that is currantly active, a rollback is scheduled.
 *
 * @param header Pointer to header of the incoming packet.
 * @param payload Pointer to the payload that contains a list of nodes with
 * data on network conditions and connectivty.
 */
static void handle_GRAPHS_REPLY(const packet_header_t* header, const void* payload) {
	GRAPHS_construction_t* graph_construction = NULL;
	GRAPHS_global_data_node_t* data_node = NULL;
	GRAPHS_global_data_node_t cmp_data_node;

	// NOTE: Since only one graph construction process can be active, we
	// can just get the head of the list of active constructions.
	if(list_empty(C_GRAPHS_active_constructions)) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "GRAPHS: Received GRAPHS REPLY from node %s, but no graph construction is active.\n", addr_to_str(header->from));
		return;
	}

	graph_construction = (GRAPHS_construction_t*) list_head(C_GRAPHS_active_constructions);
	if(header->from != graph_construction->active_node) {
		if(graph_construction->rollback_necessary) {
			C_DEBUG(C_DEBUG_LEVEL_ERROR, "GRAPHS: Received GRAPHS REPLY from node %s, but active node is %s and a rollback has already been scheduled previously to node %s.\n", addr_to_str(header->from), addr_to_str(graph_construction->active_node), addr_to_str(graph_construction->rollback_node_address));

			// TODO: When dealing with multiple rollbacks we have
			// to rollback to the node that is first in the list of
			// all nodes in the network. The implementation of this
			// a bit nasty. The error has a very low probability of
			// ever occuring, so we just report it for now.

			return;
		}

		C_DEBUG(C_DEBUG_LEVEL_WARN, "GRAPHS: Received GRAPHS REPLY from node %s, but active node is %s. Scheduling rollback to node %s (inclusive)...\n", addr_to_str(header->from), addr_to_str(graph_construction->active_node), addr_to_str(header->from));
		graph_construction->rollback_necessary = true;
		graph_construction->rollback_node_address = header->from;
		return;
	}

	cmp_data_node.node.address = header->from;
	data_node = (GRAPHS_global_data_node_t*) list_find(graph_construction->result->data_nodes, &cmp_data_node, cmp_global_data_node_addr);
	if(data_node == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "GRAPHS: Received GRAPHS REPLY from node %s, which is not in network list...\n", addr_to_str(header->from));
		return;
	}

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "GRAPHS: Received GRAPHS REPLY from node %s...\n", addr_to_str(header->from));
	callback_cancel(graph_construction->callback_id);
	list_alloc(&data_node->local_data_list);
	list_from_array(data_node->local_data_list, payload, sizeof(GRAPHS_local_data_node_t), header->payload_length);

	// Continue iterating over all nodes.
	construct_iterate(graph_construction);
}

/**
 * @brief Handle an incoming GRAPHS RESET packet.
 *
 * Add the newly discovered nodes to the global list of all nodes in the
 * network, discard the data collected so far and restart the collection
 * process.
 *
 * @param header Pointer to header of the incoming packet.
 * @param payload Pointer to the payload that contains a list of newly
 * discovered nodes.
 */
static void handle_GRAPHS_RESET(const packet_header_t* header, const void* payload) {
	GRAPHS_construction_t* graph_construction = NULL;
	list_t* newly_discovered_nodes = NULL;
	unsigned int i;

	// NOTE: Since only one graph construction process can be active, we
	// can just get the head of the list of active constructions.
	if(list_empty(C_GRAPHS_active_constructions)) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "GRAPHS: Received GRAPHS RESET from node %s, but no graph construction is active.\n", addr_to_str(header->from));
		return;
	}

	graph_construction = (GRAPHS_construction_t*) list_head(C_GRAPHS_active_constructions);
	if(header->from != graph_construction->active_node) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "GRAPHS: Received GRAPHS RESET from node %s, but active node is %s.\n", addr_to_str(header->from), addr_to_str(graph_construction->active_node));

		// TODO: The correct way of handling this case is similar to
		// dealing with multiple REPLY packets: We have to wait for the
		// other node to report back and then clean up. The
		// implementation of this a bit nasty, since theoretically this
		// can happen between several nodes, not only two, and we have
		// to add the newly discovered only once to the global list of
		// nodes. The error has a very low probability of ever
		// occuring, so we just report it for now.

		return;
	}

	graph_construction->rollback_necessary = false;
	graph_construction->rollback_node_address = C_BROADCAST_ADDR;
	callback_cancel(graph_construction->callback_id);

	list_alloc(&newly_discovered_nodes);
	list_from_array(newly_discovered_nodes, payload, sizeof(node_t), header->payload_length);
	C_DEBUG(C_DEBUG_LEVEL_NOTE, "GRAPHS: Received GRAPHS RESET from node %s, adding %d node(s) to node list: ", addr_to_str(header->from), list_size(newly_discovered_nodes));
	list_print(newly_discovered_nodes, print_node_addr);
	C_DEBUG(C_DEBUG_LEVEL_NONE, "\n");

	// Discard previous results.
	while(!list_empty(graph_construction->result->data_nodes)) {
		GRAPHS_global_data_node_t* data_node = (GRAPHS_global_data_node_t*) list_head(graph_construction->result->data_nodes);
		if(data_node->local_data_list != NULL)
			list_free(&data_node->local_data_list);
		list_remove(graph_construction->result->data_nodes, data_node);
		FREE(data_node);
	}

	// Add newly discovered nodes to the global list of nodes.
	while(!list_empty(newly_discovered_nodes)) {
		node_t* node = (node_t*) list_head(newly_discovered_nodes);
		list_add(graph_construction->nodes, node);
		list_remove(newly_discovered_nodes, node);
	}
	list_free(&newly_discovered_nodes);
	list_sort(graph_construction->nodes, node_addr_less_than);

	// Re-add all nodes to the list of data nodes.
	for(i = 0; i < list_size(graph_construction->nodes); i++) {
		GRAPHS_global_data_node_t* data_node = NULL;
		MALLOC(data_node, GRAPHS_global_data_node_t);
		memcpy(&data_node->node, list_get(graph_construction->nodes, i), sizeof(node_t));
		data_node->local_data_list = NULL;
		list_add(graph_construction->result->data_nodes, data_node);
	}

	// Print list of nodes for debugging.
	C_DEBUG(C_DEBUG_LEVEL_INFO, "GRAPHS: %d node(s) in network: ", list_size(graph_construction->nodes));
	list_print(graph_construction->nodes, print_node_addr_and_distance);
	C_DEBUG(C_DEBUG_LEVEL_NONE, ".\n");

	// Restart iterating over all nodes.
	construct_iterate(graph_construction);
}

/**
 * @brief GRAPHS packet classifier.
 *
 * @param header Pointer to header of an incoming packet.
 * @param payload Pointer to payload of an incoming packet.
 */
void GRAPHS_recv(const packet_header_t* header, const void* payload) {
	if(header->app != APP_TYPE_GRAPHS) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "GRAPHS: Received non-GRAPHS packet.\n");
		abort();
	}

	switch(header->type) {
	case GRAPHS_PACKET_TYPE_REQUEST:
		handle_GRAPHS_REQUEST(header, payload);
		break;
	case GRAPHS_PACKET_TYPE_REPLY:
		handle_GRAPHS_REPLY(header, payload);
		break;
	case GRAPHS_PACKET_TYPE_RESET:
		handle_GRAPHS_RESET(header, payload);
		break;
	case GRAPHS_PACKET_TYPE_TEST_REQUEST:
		if(header->payload_length != sizeof(GRAPHS_TEST_REQUEST_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "GRAPHS: Received TEST REQUEST packet with wrong payload length (%d != %d).\n", header->payload_length, sizeof(GRAPHS_TEST_REQUEST_packet_t));
			abort();
		}
		handle_GRAPHS_TEST_REQUEST(header, (GRAPHS_TEST_REQUEST_packet_t*) payload);
		break;
	case GRAPHS_PACKET_TYPE_TEST_DATA:
		if(header->payload_length != GRAPHS_INTERFERENCE_PAYLOAD_SIZE) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "GRAPHS: Received TEST DATA packet with wrong payload length (%d != %d).\n", header->payload_length, GRAPHS_INTERFERENCE_PAYLOAD_SIZE);
			abort();
		}
		// NOTE: We're ignoring that the payload also contains dummy
		// data for testing.
		handle_GRAPHS_TEST_DATA(header, (GRAPHS_TEST_DATA_packet_t*) payload);
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "GRAPHS: Received packet of unknown type 0x%X from node %s.\n", header->type, addr_to_str(header->from));
		abort();
	}
}



/*
 * Interface provided by this module.
 */

/**
 * @brief Timeout for request for local information.
 *
 * This timeout is triggered if a node hasn't replied after a certain delay.
 *
 * @param parameter Graph construction data structure.
 */
static void construct_iterate_timeout(callback_parameter_t parameter) {
	GRAPHS_construction_t* graph_construction = (GRAPHS_construction_t*) parameter;
	GRAPHS_global_data_node_t* data_node = NULL;
	GRAPHS_global_data_node_t cmp_data_node;
	cmp_data_node.node.address = graph_construction->active_node;
	data_node = (GRAPHS_global_data_node_t*) list_find(graph_construction->result->data_nodes, &cmp_data_node, cmp_global_data_node_addr);
	if(data_node == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "GRAPHS: Requesting data from node %s timed out, but it is not in network list...\n", addr_to_str(graph_construction->active_node));
		abort();
	} else if(data_node->local_data_list != NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "GRAPHS: Requesting data from node %s timed out, but node has already received data...\n", addr_to_str(graph_construction->active_node));
		return;
	}

	C_DEBUG(C_DEBUG_LEVEL_WARN, "GRAPHS: Requesting data from node %s has timed out, proceeding with next node...\n", addr_to_str(graph_construction->active_node));
	list_alloc(&data_node->local_data_list);

	// Continue iterating over all nodes.
	construct_iterate(graph_construction);
}

/**
 * @brief Delayed sending of the request for local information.
 *
 * The intention of this delay is to allow the network to settle down between
 * requests and to make sure that queues on all layers are empty when starting
 * the next request.
 *
 * @param parameter Graph construction data structure.
 */
static void construct_iterate_delay(callback_parameter_t parameter) {
	GRAPHS_construction_t* graph_construction = (GRAPHS_construction_t*) parameter;
	C_DEBUG(C_DEBUG_LEVEL_NOTE, "GRAPHS: Requesting data from node %s...\n", addr_to_str(graph_construction->active_node));

	// The data request timeout depends on how many nodes need to be
	// queried locally by each node.
	graph_construction->callback_id = callback_add(construct_iterate_timeout, graph_construction, GRAPHS_PER_NODE_TIMEOUT);
	send_GRAPHS_REQUEST(graph_construction->active_node, graph_construction);
}

/**
 * @brief Check whether a global data node has no local data list.
 *
 * @param item Node to check.
 * @param cmp_item Reference node (not used).
 * @return <code>true</code> if the node has no local data list,
 * <code>false</code> otherwise.
 */
static bool node_without_local_data_list(const void* item, const void* cmp_item) {
	GRAPHS_global_data_node_t* data_node = (GRAPHS_global_data_node_t*) item;
	return (data_node->local_data_list == NULL);
}

/**
 * @brief Iterate over all nodes in the network and request each node to
 * construct its connectivity and interference lists.
 *
 * In case a rollback is scheduled, it is performed in this function prior to
 * the next iteration.
 *
 * @param graph_construction Graph construction data structure.
 */
static void construct_iterate(GRAPHS_construction_t* graph_construction) {
	GRAPHS_global_data_node_t* data_node = NULL;

	// Perform any scheduled rollback.
	if(graph_construction->rollback_necessary) {
		unsigned int i = 0;
		bool clear_results = false;

		C_DEBUG(C_DEBUG_LEVEL_NOTE, "GRAPHS: Rolling back to node %s (inclusive)...\n", addr_to_str(graph_construction->rollback_node_address));
		while(i < list_size(graph_construction->result->data_nodes)) {
			GRAPHS_global_data_node_t* data_node = (GRAPHS_global_data_node_t*) list_get(graph_construction->result->data_nodes, i);
			if(clear_results)
				list_free(&data_node->local_data_list);
			if(data_node->node.address == graph_construction->rollback_node_address)
				clear_results = true;
			if(data_node->node.address == graph_construction->active_node)
				break;
			i++;
		}
		graph_construction->rollback_necessary = false;
		graph_construction->rollback_node_address = C_BROADCAST_ADDR;
	}

	// Request data from the next node.
	data_node = (GRAPHS_global_data_node_t*) list_find(graph_construction->result->data_nodes, NULL, node_without_local_data_list);
	if(data_node != NULL) {
		graph_construction->active_node = data_node->node.address;
		callback_add(construct_iterate_delay, graph_construction, C_NETWORK_FLUSH_TIME);
	} else {
		// All nodes have constructed their graphs, we're done.
		C_DEBUG(C_DEBUG_LEVEL_NOTE, "GRAPHS: Received data from all nodes.\n");
		graph_construction->result->error_code = C_ERROR_OK;
		graph_construction->function(graph_construction->result);
		list_free(&graph_construction->nodes);
		list_remove(C_GRAPHS_active_constructions, graph_construction);
		FREE(graph_construction);
	}
}

/**
 * @brief Callback for echo request of the construct function.
 *
 * @param parameter The results of the echo request.
 */
static void construct_echo_request_callback(callback_parameter_t parameter) {
	ICMP_echo_request_result_t* echo_request_result = (ICMP_echo_request_result_t*) parameter;
	GRAPHS_construction_t* graph_construction = (GRAPHS_construction_t*) echo_request_result->parameter;
	node_t* node = NULL;
	unsigned int i;

	switch(echo_request_result->error_code) {
	case C_ERROR_OK:
		// Add local node to the list of nodes.
		MALLOC(node, node_t);
		init_node(node, C_LOCAL_ADDR);
		node->hops = 0;
		list_add(graph_construction->nodes, node);
		list_sort(graph_construction->nodes, node_addr_less_than);

		// Print list of nodes for debugging.
		C_DEBUG(C_DEBUG_LEVEL_INFO, "GRAPHS: %d node(s) in network: ", list_size(graph_construction->nodes));
		list_print(graph_construction->nodes, print_node_addr_and_distance);
		C_DEBUG(C_DEBUG_LEVEL_NONE, ".\n");

		// Add all nodes to the list of data nodes.
		for(i = 0; i < list_size(graph_construction->nodes); i++) {
			GRAPHS_global_data_node_t* data_node = NULL;
			MALLOC(data_node, GRAPHS_global_data_node_t);
			memcpy(&data_node->node, list_get(graph_construction->nodes, i), sizeof(node_t));
			data_node->local_data_list = NULL;
			list_add(graph_construction->result->data_nodes, data_node);
		}

		// Start collecting data from all nodes.
		construct_iterate(graph_construction);

		break;
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_WARN, "GRAPHS: No other nodes have been found.\n");
		graph_construction->result->error_code = C_ERROR_TIMEOUT;
		graph_construction->function(graph_construction->result);
		list_free(&graph_construction->nodes);
		list_remove(C_GRAPHS_active_constructions, graph_construction);
		FREE(graph_construction);
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "GRAPHS: Callback with unknown error code 0x%X.\n", echo_request_result->error_code);
		abort();
	}
	FREE(echo_request_result);
}

/**
 * @brief Construct network graphs.
 *
 * @param function Pointer to function to call upon completion.
 * @param construction_result Data structure for the resulting network graphs.
 */
void GRAPHS_construct(callback_function_t function, GRAPHS_construction_result_t* construction_result) {
	GRAPHS_construction_t* graph_construction = NULL;
	ICMP_echo_request_result_t* echo_request_result = NULL;

	if((function == NULL) || (construction_result == NULL)) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "GRAPHS: Callback function or parameter not given (%p, %p).\n", function, construction_result);
		return;
	} else if(!list_empty(C_GRAPHS_active_constructions)) {
		// Allow only one graph construction to be active.
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "GRAPHS: Trying to construct network graphs, but a construction is in progress...\n");
		construction_result->error_code = C_ERROR_BUSY;
		function(construction_result);
		return;
	}

	C_DEBUG(C_DEBUG_LEVEL_NOTE, "GRAPHS: Starting to construct network graphs...\n");
	MALLOC(graph_construction, GRAPHS_construction_t);
	list_alloc(&graph_construction->nodes);
	graph_construction->active_node = C_BROADCAST_ADDR;
	graph_construction->rollback_necessary = false;
	graph_construction->rollback_node_address = C_BROADCAST_ADDR;
	graph_construction->callback_id = 0;
	graph_construction->function = function;
	graph_construction->result = construction_result;
	list_add(C_GRAPHS_active_constructions, graph_construction);

	MALLOC(echo_request_result, ICMP_echo_request_result_t);
	echo_request_result->nodes = graph_construction->nodes;
	echo_request_result->parameter = graph_construction;
	ICMP_send_echo_request(C_BROADCAST_ADDR, C_DEFAULT_TTL, construct_echo_request_callback, echo_request_result);
}



/*
 * Interface provided by this module for direct commands.
 */

/**
 * @brief Print the address of a global data node structure.
 *
 * @param item Data node whose address is to be printed.
 */
static void print_data_node_address(const void* item) {
	GRAPHS_global_data_node_t* data_node = (GRAPHS_global_data_node_t*) item;
	C_DEBUG(C_DEBUG_LEVEL_NONE, "%s", addr_to_str(data_node->node.address));
}

/**
 * @brief Print the connectivity field of a local data node structure.
 *
 * @param item Data node whose connectivity is to be printed.
 */
static void print_data_node_connectivity(const void* item) {
	GRAPHS_local_data_node_t* data_node = (GRAPHS_local_data_node_t*) item;
	C_DEBUG(C_DEBUG_LEVEL_NONE, "%.0f%%", (((float) data_node->connectivity_count) / GRAPHS_CONNECTIVITY_SAMPLES * 100));
}

/**
 * @brief Print connectivity data in local data lists of a global node data structure.
 *
 * @param item Node whose graph list is to be processed.
 */
static void print_connectivity_in_local_data_list(const void* item) {
	GRAPHS_global_data_node_t* data_node = (GRAPHS_global_data_node_t*) item;
	C_DEBUG(C_DEBUG_LEVEL_INFO, "GRAPHS: %s:\t", addr_to_str(data_node->node.address));
	list_print(data_node->local_data_list, print_data_node_connectivity);
	C_DEBUG(C_DEBUG_LEVEL_NONE, "\n");
}

/**
 * @brief Print the interference field of a local data node structure.
 *
 * @param item Data node whose interference is to be printed.
 */
static void print_data_node_interference(const void* item) {
	GRAPHS_local_data_node_t* data_node = (GRAPHS_local_data_node_t*) item;
	C_DEBUG(C_DEBUG_LEVEL_NONE, "%.0f%%", (data_node->interference * 100));
}

/**
 * @brief Print interference data in local data lists of a global node data structure.
 *
 * @param item Node whose graph list is to be processed.
 */
static void print_interference_in_local_data_list(const void* item) {
	GRAPHS_global_data_node_t* data_node = (GRAPHS_global_data_node_t*) item;
	C_DEBUG(C_DEBUG_LEVEL_INFO, "GRAPHS: %s:\t", addr_to_str(data_node->node.address));
	list_print(data_node->local_data_list, print_data_node_interference);
	C_DEBUG(C_DEBUG_LEVEL_NONE, "\n");
}

/**
 * @brief Free local data lists of a global node data structure.
 *
 * @param item Node whose graph lists are to be freed.
 */
static void free_local_data_list(const void* item) {
	GRAPHS_global_data_node_t* data_node = (GRAPHS_global_data_node_t*) item;
	list_free(&data_node->local_data_list);
}

/**
 * @brief Callback for ECHO REQUESTs of the ICMP command function.
 *
 * @param parameter The results of the echo request.
 */
static void GRAPHS_command_construct_callback(callback_parameter_t parameter) {
	GRAPHS_construction_result_t* construction_result = (GRAPHS_construction_result_t*) parameter;

	switch(construction_result->error_code) {
	case C_ERROR_OK:
		C_DEBUG(C_DEBUG_LEVEL_NOTE, "GRAPHS: Network graph construction complete, %d node(s) in network.\n", list_size(construction_result->data_nodes));
		C_DEBUG(C_DEBUG_LEVEL_INFO, "GRAPHS: Connectivity graph:\n");
		C_DEBUG(C_DEBUG_LEVEL_INFO, "GRAPHS: \t");
		list_print(construction_result->data_nodes, print_data_node_address);
		C_DEBUG(C_DEBUG_LEVEL_NONE, "\n");
		list_map(construction_result->data_nodes, print_connectivity_in_local_data_list);
		C_DEBUG(C_DEBUG_LEVEL_INFO, "GRAPHS: Interference graph:\n");
		C_DEBUG(C_DEBUG_LEVEL_INFO, "GRAPHS: \t");
		list_print(construction_result->data_nodes, print_data_node_address);
		C_DEBUG(C_DEBUG_LEVEL_NONE, "\n");
		list_map(construction_result->data_nodes, print_interference_in_local_data_list);
		list_map(construction_result->data_nodes, free_local_data_list);
		break;
	case C_ERROR_BUSY:
		C_DEBUG(C_DEBUG_LEVEL_WARN, "GRAPHS: Network graph construction in progress.\n");
		break;
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_WARN, "GRAPHS: Network graph construction aborted because no other nodes are reachable.\n");
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "GRAPHS: Callback with unknown error code 0x%X.\n", construction_result->error_code);
		abort();
	}

	list_free(&construction_result->data_nodes);
	FREE(construction_result);
}

/**
 * @brief GRAPHS command function.
 *
 * @param argc Number of command parameters.
 * @param argv Array of strings used as command parameters.
 * @return <code>true</code> if the command was executed successfully,
 * <code>false</code> otherwise.
 */
bool GRAPHS_command(int argc, const char*const* argv) {
	if(argc == 2) {
		if(strcmp(argv[1], "GRAPHS_construct") == 0) {
			GRAPHS_construction_result_t* construction_result;
			MALLOC(construction_result, GRAPHS_construction_result_t);
			list_alloc(&construction_result->data_nodes);
			GRAPHS_construct(GRAPHS_command_construct_callback, construction_result);
			return true;
		}
	}
	return false;
}
