/**
 * @file placement_stats.c
 *
 * @brief Placement statistics functions.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 *
 * $Id: placement_stats.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 "../service/service_types.h"
#include "placement.h"
#include "placement_stats.h"

/**
 * @brief Allocate and initialize statistics for a service.
 *
 * @param service Pointer to service for which to allocate statistics.
 */
void alloc_stats(SERVICE_service_t* service) {
	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "PLACEMENT: Allocating statistics for service \"%s\"...\n", service->id);
	MALLOC(service->placement_data, PLACEMENT_data_t);
	service->placement_data->active = false;
	service->placement_data->callback_id = 0;
	service->placement_data->service_request_count = 0;
	service->placement_data->service_request_traffic = 0;
	list_alloc(&service->placement_data->node_stats);
}

/**
 * @brief Free statistics for a service.
 *
 * @param service Pointer to service whose statistics are to be freed.
 */
void free_stats(SERVICE_service_t* service) {
	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "PLACEMENT: Freeing statistics for service \"%s\"...\n", service->id);
	list_free(&service->placement_data->node_stats);
	FREE(service->placement_data);
}

/**
 * @brief Initialize node statistics.
 *
 * @param node_stats Pointer to node statistics data structure that is to be initialized.
 * @param addr Address of the corresponding node.
 */
static void init_node_stats(PLACEMENT_node_stats_t* node_stats, C_addr_t addr) {
	node_stats->node.address = addr;
	node_stats->node.hops = -1;
	node_stats->service_requests_count = 0;
	node_stats->service_requests_traffic = 0;
	node_stats->last_hop_count = 0;
	node_stats->last_hop_traffic = 0;
	node_stats->refused_migration = false;
}

/**
 * @brief Check whether the node addresses of two node statistics data
 * structures match.
 *
 * @param item Node statistics data structure to check.
 * @param cmp_item Reference node statistics data structure.
 * @return <code>true</code> if the address of the two node statistics data
 * structures matches, <code>false</code> otherwise.
 */
static bool matches_node_stat_addr(const void* item, const void* cmp_item) {
	PLACEMENT_node_stats_t* node_stats = (PLACEMENT_node_stats_t*) item;
	PLACEMENT_node_stats_t* cmp_node_stats = (PLACEMENT_node_stats_t*) cmp_item;
	return (node_stats->node.address == cmp_node_stats->node.address);
}

/**
 * @brief Do statistics for a service request.
 *
 * @param service Pointer to service for which statistics are to be counted.
 * @param from Address of the node that originated the data request.
 * @param last_hop Address of the node that was the last to forward the data request.
 * @param hops Number of times the data request was forwarded.
 * @param size Size of the data request in bytes.
 */
void stat_service_request(SERVICE_service_t* service, C_addr_t from, C_addr_t last_hop, uint8_t hops, size_t size) {
	PLACEMENT_node_stats_t* node_stats;
	PLACEMENT_node_stats_t cmp_node_stats;

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "PLACEMENT: Counting data request over %d hop(s) from %s via %s for %d byte(s) service \"%s\"...\n", hops, addr_to_str(from), addr_to_str(last_hop), size, service->id);

	// Update service statistics.
	service->placement_data->service_request_count++;
	service->placement_data->service_request_traffic += size;

	// Update statistics on the source node.
	cmp_node_stats.node.address = from;
	node_stats = (PLACEMENT_node_stats_t*) list_find(service->placement_data->node_stats, &cmp_node_stats, matches_node_stat_addr);
	if(node_stats == NULL) {
		MALLOC(node_stats, PLACEMENT_node_stats_t);
		init_node_stats(node_stats, from);
		list_add(service->placement_data->node_stats, node_stats);
	}
	node_stats->node.hops = hops;
	node_stats->service_requests_count++;
	node_stats->service_requests_traffic += size;

	// Update statistics on the last forwarding hop.
	cmp_node_stats.node.address = last_hop;
	node_stats = (PLACEMENT_node_stats_t*) list_find(service->placement_data->node_stats, &cmp_node_stats, matches_node_stat_addr);
	if(node_stats == NULL) {
		MALLOC(node_stats, PLACEMENT_node_stats_t);
		init_node_stats(node_stats, last_hop);
		list_add(service->placement_data->node_stats, node_stats);
	}
	node_stats->node.hops = 1;
	node_stats->last_hop_count++;
	node_stats->last_hop_traffic += size;

	// Trigger placement algorithm if service is running.
	if(service->state == SERVICE_STATE_RUNNING)
		PLACEMENT_trigger_algorithm(service);
}
