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

#include "../../types.h"
#include "../../vars.h"
#include "../../interface.h"
#include "../../interface_internal.h"
#include "../../util/debug.h"
#include "../../util/list.h"
#include "../../util/net.h"
#include "../../service/service.h"
#include "../../service/service_types.h"
#include "../placement.h"
#include "../placement_types.h"
#include "../placement_vars.h"
#include "../placement_types.h"
#include "simple_heuristic.h"

/**
 * @brief Minimal number of data points to collect before running the algorithm.
 */
#define PLACEMENT_MIN_DATA_POINTS		100

/**
 * @brief Purely heuristic factor to adjust the threshold traffic metric that
 * controls whether to migrate or not.
 */
#define PLACEMENT_THRESHOLD_TRAFFIC_FACTOR	2.0

/**
 * @brief Service migration callback.
 *
 * @param parameter Migration result.
 */
static void simple_heuristic_callback(callback_parameter_t parameter) {
	SERVICE_migration_result_t* result = (SERVICE_migration_result_t*) parameter;
	PLACEMENT_node_stats_t* node_stats_of_migration_target = (PLACEMENT_node_stats_t*) result->parameter;

	switch(result->error_code) {
	case C_ERROR_OK:
		C_DEBUG(C_DEBUG_LEVEL_NOTE, "PLACEMENT: Migration of service \"%s\" was successful.\n", result->service_id);
		break;
	case C_ERROR_PARAMETER:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" failed due to an invalid parameter.\n", result->service_id);
		break;
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" timed out.\n", result->service_id);
		break;
	case C_ERROR_BUSY:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" failed because system is busy.\n", result->service_id);
		break;
	case C_ERROR_IN_PROGRESS:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" failed due to an invalid parameter.\n", result->service_id);
		break;
	case C_ERROR_COMM_ERROR:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" failed due to communication errors.\n", result->service_id);
		break;
	case C_ERROR_REMOTE_BUSY:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "PLACEMENT: Migration of service \"%s\" failed because the remote system refused.\n", result->service_id);
		node_stats_of_migration_target->refused_migration = true;
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "PLACEMENT: Migration callback with unknown error code 0x%X.\n", result->error_code);
		abort();
	}
	FREE(result);
}

/**
 * @brief Simple heuristic algorithm.
 *
 * @param service Pointer to service for which to run this algorithm.
 */
static void simple_heuristic_run(SERVICE_service_t* service) {
	// Initialize aggregate statistics.
	unsigned int direct_neighbors = 0;
	unsigned int max_last_hop_traffic = 0;
	PLACEMENT_node_stats_t* node_stats_with_max_last_hop_traffic = NULL;
	unsigned int threshold_traffic;

	// Calculate aggregate statistics.
	unsigned int i;
	for(i = 0; i < list_size(service->placement_data->node_stats); i++) {
		PLACEMENT_node_stats_t* node_stats = (PLACEMENT_node_stats_t*) list_get(service->placement_data->node_stats, i);

		if(node_stats->node.hops == 1) {
			C_DEBUG(C_DEBUG_LEVEL_DEBUG, "PLACEMENT: %d data request(s) for service \"%s\" via %s at %d hop(s).\n", node_stats->last_hop_count, service->id, addr_to_str(node_stats->node.address), node_stats->node.hops);
			direct_neighbors++;
		}

		// Only consider nodes that have not refused a migration in the past.
		if(!node_stats->refused_migration && (node_stats->last_hop_traffic > max_last_hop_traffic)) {
			max_last_hop_traffic = node_stats->last_hop_traffic;
			node_stats_with_max_last_hop_traffic = node_stats;
		}
	}

	// This basically can't happen but it can't hurt to check.
	if(direct_neighbors == 0) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "PLACEMENT: %d service request(s) for service \"%s\" but no direct neighbors.\n", service->placement_data->service_request_count, service->id);
		abort();
	} else if(node_stats_with_max_last_hop_traffic == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "PLACEMENT: %d service request(s) for service \"%s\" but no traffic.\n", service->placement_data->service_request_count, service->id);
		abort();
	}

	// Make a decision whether to migrate or not.
	// NOTE: This is just a simple heuristic. Nothing more, nothing less.
	threshold_traffic = (unsigned int) (((float) service->placement_data->service_request_traffic) / ((float) direct_neighbors) * PLACEMENT_THRESHOLD_TRAFFIC_FACTOR);
	if(
		(max_last_hop_traffic > threshold_traffic)
		&& (node_stats_with_max_last_hop_traffic->node.address != C_LOCAL_ADDR)
	) {
		SERVICE_migration_result_t* result = NULL;

		C_DEBUG(C_DEBUG_LEVEL_NOTE, "PLACEMENT: Migrating service \"%s\" to node %s (%d bytes after %d total requests, %d neighbors, %d bytes threshold traffic)...\n", service->id, addr_to_str(node_stats_with_max_last_hop_traffic->node.address), max_last_hop_traffic, service->placement_data->service_request_count, direct_neighbors, threshold_traffic);

		MALLOC(result, SERVICE_migration_result_t);
		result->parameter = node_stats_with_max_last_hop_traffic;
		SERVICE_migrate_service(service->id, node_stats_with_max_last_hop_traffic->node.address, simple_heuristic_callback, result);
	} else if(node_stats_with_max_last_hop_traffic->node.address == C_LOCAL_ADDR) {
		C_DEBUG(C_DEBUG_LEVEL_NOTE, "PLACEMENT: Service \"%s\" is placed optimally on the current node (%d bytes after %d total requests, %d neighbors, %d bytes threshold traffic)...\n", service->id, max_last_hop_traffic, service->placement_data->service_request_count, direct_neighbors, threshold_traffic);
	} else {
		C_DEBUG(C_DEBUG_LEVEL_INFO, "PLACEMENT: Migration of service \"%s\" is not necessary (%d bytes after %d total requests, %d neighbors, %d bytes threshold traffic)...\n", service->id, max_last_hop_traffic, service->placement_data->service_request_count, direct_neighbors, threshold_traffic);
	}
}

/**
 * @brief Start simple heuristic algorithm.
 *
 * @param service Pointer to service for which to start this algorithm.
 */
void simple_heuristic_start(SERVICE_service_t* service) {
	// Nothing to do.
}

/**
 * @brief Stop simple heuristic algorithm.
 *
 * @param service Pointer to service for which to stop this algorithm.
 */
void simple_heuristic_stop(SERVICE_service_t* service) {
	// Nothing to do.
}

/**
 * @brief Trigger simple heuristic algorithm.
 *
 * @param service Pointer to service for which to trigger this algorithm.
 */
void simple_heuristic_trigger(SERVICE_service_t* service) {
	// Don't do anything if we don't have enough data points.
	if(service->placement_data->service_request_count < PLACEMENT_MIN_DATA_POINTS) {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "PLACEMENT: Only %d of %d required data points in statistics of service \"%s\", not running algorithm...\n", service->placement_data->service_request_count, PLACEMENT_MIN_DATA_POINTS, service->id);
		return;
	}

	simple_heuristic_run(service);
}
