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



/**
 * @brief Maximal number of hosted services.
 */
#define SERVICE_MAX_HOSTED_SERVICES	4



/**
 * @brief Allocate and initialize a new service on this node.
 *
 * @param service_id ID of the new service.
 * @param service_data_size Size of the service data in bytes.
 * @return Pointer to the new service.
 */
SERVICE_service_t* alloc_service(const SERVICE_id_t* service_id, size_t service_data_size) {
	SERVICE_service_t* service = NULL;

	if(get_service(service_id) != NULL) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Trying to allocate service \"%s\", but a service with that ID is already hosted on this node...\n", service_id);
		return NULL;
	}
	if(list_size(C_SERVICE_hosted_services) >= SERVICE_MAX_HOSTED_SERVICES) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: No space to host additional service.\n");
		return NULL;
	}

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Allocating service \"%s\" with %d byte(s) of data...\n", service_id, service_data_size);

	MALLOC(service, SERVICE_service_t);
	copy_service_id(service->id, service_id);
	service->state = SERVICE_STATE_INIT;
	MALLOC_ARRAY(service->data, char, service_data_size);
	service->data_size = service_data_size;
	service->outgoing_replication = NULL;
	service->incoming_replication = NULL;
	service->redirect_addr = C_BROADCAST_ADDR;
	PLACEMENT_alloc_stats(service);
	list_add(C_SERVICE_hosted_services, service);
	return service;
}

/**
 * @brief Start a service on this node.
 *
 * @param service Pointer to the service that is to be started.
 */
void start_service(SERVICE_service_t* service) {
	if(service->state != SERVICE_STATE_INIT) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Trying to start service \"%s\" service that is not initializing (%d)...\n", service->id, service->state);
		abort();
	}
	C_DEBUG(C_DEBUG_LEVEL_NOTE, "SERVICE: Starting service \"%s\"...\n", service->id);
	service->state = SERVICE_STATE_RUNNING;
	PLACEMENT_start_algorithm(service);
}

/**
 * @brief Stop service on this node.
 *
 * @param service Pointer to the service that is to be stopped.
 */
void stop_service(SERVICE_service_t* service) {
	if((service->state != SERVICE_STATE_RUNNING) && (service->state != SERVICE_STATE_REDIRECTING)) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Trying to stop service \"%s\" service that is neither running nor redirecting (%d)...\n", service->id, service->state);
		abort();
	}

	C_DEBUG(C_DEBUG_LEVEL_NOTE, "SERVICE: Stopping service \"%s\"...\n", service->id);
	PLACEMENT_stop_algorithm(service);
	service->state = SERVICE_STATE_STOPPED;

	// TODO: Possibly shutdown streams being served (including cancelling their current callbacks) and notify clients.
}

/**
 * @brief Free a service on this node.
 *
 * @param service Pointer to the service that is to be freed.
 */
void free_service(SERVICE_service_t* service) {
	if((service->state != SERVICE_STATE_INIT) && (service->state != SERVICE_STATE_STOPPED)) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Trying to free service \"%s\" that has not been stopped (%d)...\n", service->id, service->state);
		abort();
	}
	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Freeing service \"%s\"...\n", service->id);
	list_remove(C_SERVICE_hosted_services, service);
	PLACEMENT_free_stats(service);
	FREE(service->data);
	FREE(service);
}

/**
 * @brief Update service data of a service hosted on this node.
 *
 * @param service Pointer to the service.
 * @param offset Offset in service data.
 * @param size Size of the new service data in bytes.
 * @param data Pointer to new service data.
 */
void update_service_data(SERVICE_service_t* service, uint16_t offset, size_t size, const char* data) {
	if((offset + size) > service->data_size) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Trying to update %d byte(s) at offset 0x%X of service \"%s\", but service data size is only %d...\n", size, offset, service->id, service->data_size);
		return;
	}

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Updating %d byte(s) at offset 0x%X of service \"%s\"...\n", size, offset, service->id);
	memcpy(service->data + offset, data, size);
}

/**
 * @brief Check whether two services have the same service ID.
 *
 * @param item Service to check.
 * @param cmp_item Reference service.
 * @return <code>true</code> if the service IDs of the two services are equal,
 * <code>false</code> otherwise.
 */
static bool matches_service_id(const void* item, const void* cmp_item) {
	SERVICE_service_t* service = (SERVICE_service_t*) item;
	SERVICE_service_t* cmp_service = (SERVICE_service_t*) cmp_item;
	return same_service_id(service->id, cmp_service->id);
}

/**
 * @brief Retrieve a service by service ID.
 *
 * @param service_id ID of the service to retrieve.
 * @return Pointer to the service if it exists, NULL otherwise.
 */
SERVICE_service_t* get_service(const SERVICE_id_t* service_id) {
	SERVICE_service_t cmp_service;
	copy_service_id(cmp_service.id, service_id);
	return (SERVICE_service_t*) list_find(C_SERVICE_hosted_services, &cmp_service, matches_service_id);
}

/**
 * @brief Check whether two active services have the same service ID.
 *
 * @param item Service to check.
 * @param cmp_item Reference service.
 * @return <code>true</code> if the service is active and the service IDs of
 * the two services are equal, <code>false</code> otherwise.
 */
static bool matches_active_service_id(const void* item, const void* cmp_item) {
	SERVICE_service_t* service = (SERVICE_service_t*) item;
	SERVICE_service_t* cmp_service = (SERVICE_service_t*) cmp_item;
	return (
		(
			(service->state == SERVICE_STATE_RUNNING)
			|| (service->state == SERVICE_STATE_REPLICATING)
		)
		&& same_service_id(service->id, cmp_service->id)
	);
}

/**
 * @brief Retrieve a pointer to an active service on this node.
 *
 * @param service_id ID of the service to be retrieved.
 * @return Pointer to the service if it is active on this node, NULL otherwise.
 */
SERVICE_service_t* get_active_service(const SERVICE_id_t* service_id) {
	SERVICE_service_t cmp_service;
	copy_service_id(cmp_service.id, service_id);
	return (SERVICE_service_t*) list_find(C_SERVICE_hosted_services, &cmp_service, matches_active_service_id);
}
