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

#include <stdlib.h>

#include "../types.h"
#include "../vars.h"
#include "../interface.h"
#include "../interface_internal.h"
#include "../util/debug.h"
#include "../util/net.h"
#include "service.h"
#include "service_types.h"
#include "service_vars.h"
#include "service_client.h"
#include "service_server.h"
#include "service_management.h"
#include "service_replication.h"
#include "service_util.h"



/*
 * Interface to main module.
 */

/**
 * @brief Initialize the service module.
 */
void SERVICE_init() {
	// Client variables.
	list_alloc(&C_SERVICE_service_cache);
	C_SERVICE_total_data_requests = 0;
	C_SERVICE_successful_data_requests = 0;
	C_SERVICE_failed_data_requests = 0;
	C_SERVICE_total_successful_data_request_delay = 0;
	C_SERVICE_total_data_updates = 0;
	C_SERVICE_successful_data_updates = 0;
	C_SERVICE_failed_data_updates = 0;
	C_SERVICE_total_stream_requests = 0;
	C_SERVICE_successful_stream_requests = 0;
	C_SERVICE_failed_stream_requests = 0;
	C_SERVICE_expected_stream_packets = 0;
	C_SERVICE_received_stream_packets = 0;

	// Server variables.
	list_alloc(&C_SERVICE_hosted_services);
	list_alloc(&C_SERVICE_served_streams);
	list_alloc(&C_SERVICE_incoming_replications);
	list_alloc(&C_SERVICE_outgoing_replications);
	C_SERVICE_total_replications = 0;
	C_SERVICE_successful_replications = 0;
	C_SERVICE_failed_replications = 0;
	C_SERVICE_total_migrations = 0;
	C_SERVICE_successful_migrations = 0;
	C_SERVICE_failed_migrations = 0;
}

/**
 * @brief Start the service module.
 */
void SERVICE_start() {
}

/**
 * @brief Stop the service module.
 */
void SERVICE_stop() {
}

/**
 * @brief Finalize the service module.
 */
void SERVICE_finalize() {
	// Client variables.
	if(C_check_consistency_on_shutdown) {
		// NOTE: Data requests are not acknowledged, hence we can't tell whether they failed.
		/*
		if(C_SERVICE_total_data_requests != (C_SERVICE_successful_data_requests + C_SERVICE_failed_data_requests)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Data request counters don't add up (%d != (%d + %d)).\n", C_SERVICE_total_data_requests, C_SERVICE_successful_data_requests, C_SERVICE_failed_data_requests);
			abort();
		}
		*/
		if(C_SERVICE_total_data_updates != (C_SERVICE_successful_data_updates + C_SERVICE_failed_data_updates)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Data update counters don't add up (%d != (%d + %d)).\n", C_SERVICE_total_data_updates, C_SERVICE_successful_data_updates, C_SERVICE_failed_data_updates);
			abort();
		}
		if(C_SERVICE_total_stream_requests != (C_SERVICE_successful_stream_requests + C_SERVICE_failed_stream_requests)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Stream request counters don't add up (%d != (%d + %d)).\n", C_SERVICE_total_stream_requests, C_SERVICE_successful_stream_requests, C_SERVICE_failed_stream_requests);
			abort();
		}
	}
	list_free(&C_SERVICE_service_cache);

	// Server variables.
	if(C_check_consistency_on_shutdown) {
		if(C_SERVICE_total_replications != (C_SERVICE_successful_replications + C_SERVICE_failed_replications)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Replication counters don't add up (%d != (%d + %d)).\n", C_SERVICE_total_replications, C_SERVICE_successful_replications, C_SERVICE_failed_replications);
			abort();
		}
		if(C_SERVICE_total_migrations != (C_SERVICE_successful_migrations + C_SERVICE_failed_migrations)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Migration counters don't add up (%d != (%d + %d)).\n", C_SERVICE_total_migrations, C_SERVICE_successful_migrations, C_SERVICE_failed_migrations);
			abort();
		}
	}
	list_free(&C_SERVICE_hosted_services);
	list_free(&C_SERVICE_served_streams);
	list_free(&C_SERVICE_incoming_replications);
	list_free(&C_SERVICE_outgoing_replications);
}

/**
 * @brief Service packet classifier.
 *
 * @param header Pointer to header of an incoming packet.
 * @param payload Pointer to payload of an incoming packet.
 */
void SERVICE_recv(const packet_header_t* header, const void* payload) {
	char* data = NULL;
	size_t data_size;

	if(header->app != APP_TYPE_SERVICE) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received non-SERVICE packet.\n");
		abort();
	}

	switch(header->type) {
	case SERVICE_PACKET_TYPE_CLIENT_DATA_REQUEST:
		if(header->payload_length != sizeof(SERVICE_CLIENT_DATA_REQUEST_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received CLIENT DATA REQUEST packet with wrong payload length (%d != %d).\n", header->payload_length, sizeof(SERVICE_CLIENT_DATA_REQUEST_packet_t));
			abort();
		}
		handle_SERVICE_CLIENT_DATA_REQUEST(header, (SERVICE_CLIENT_DATA_REQUEST_packet_t*) payload);
		break;
	case SERVICE_PACKET_TYPE_CLIENT_DATA_UPDATE:
		if(header->payload_length < sizeof(SERVICE_CLIENT_DATA_UPDATE_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received CLIENT DATA UPDATE packet with wrong payload length (%d < %d).\n", header->payload_length, sizeof(SERVICE_CLIENT_DATA_UPDATE_packet_t));
			abort();
		}
		data = (((char*) payload) + sizeof(SERVICE_CLIENT_DATA_UPDATE_packet_t));
		data_size = header->payload_length - sizeof(SERVICE_CLIENT_DATA_UPDATE_packet_t);
		handle_SERVICE_CLIENT_DATA_UPDATE(header, (SERVICE_CLIENT_DATA_UPDATE_packet_t*) payload, data, data_size);
		break;
	case SERVICE_PACKET_TYPE_CLIENT_STREAM_REQUEST:
		if(header->payload_length != sizeof(SERVICE_CLIENT_STREAM_REQUEST_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received CLIENT STREAM REQUST packet with wrong payload length (%d != %d).\n", header->payload_length, sizeof(SERVICE_CLIENT_STREAM_REQUEST_packet_t));
			abort();
		}
		handle_SERVICE_CLIENT_STREAM_REQUEST(header, (SERVICE_CLIENT_STREAM_REQUEST_packet_t*) payload);
		break;
	case SERVICE_PACKET_TYPE_SERVER_DATA_ITEM:
		if(header->payload_length < sizeof(SERVICE_SERVER_DATA_ITEM_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received SERVER DATA ITEM packet with wrong payload length (%d < %d).\n", header->payload_length, sizeof(SERVICE_SERVER_DATA_ITEM_packet_t));
			abort();
		}
		data = (((char*) payload) + sizeof(SERVICE_SERVER_DATA_ITEM_packet_t));
		data_size = header->payload_length - sizeof(SERVICE_SERVER_DATA_ITEM_packet_t);
		handle_SERVICE_SERVER_DATA_ITEM(header, (SERVICE_SERVER_DATA_ITEM_packet_t*) payload, data, data_size);
		break;
	case SERVICE_PACKET_TYPE_SERVER_STREAM_ITEM:
		if(header->payload_length < sizeof(SERVICE_SERVER_STREAM_ITEM_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received SERVER STREAM ITEM packet with wrong payload length (%d < %d).\n", header->payload_length, sizeof(SERVICE_SERVER_STREAM_ITEM_packet_t));
			abort();
		}
		data = (((char*) payload) + sizeof(SERVICE_SERVER_STREAM_ITEM_packet_t));
		data_size = header->payload_length - sizeof(SERVICE_SERVER_STREAM_ITEM_packet_t);
		handle_SERVICE_SERVER_STREAM_ITEM(header, (SERVICE_SERVER_STREAM_ITEM_packet_t*) payload, data, data_size);
		break;
	case SERVICE_PACKET_TYPE_SERVER_NOT_AVAILABLE:
		if(header->payload_length != sizeof(SERVICE_SERVER_NOT_AVAILABLE_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received SERVER NOT AVAILABLE packet with wrong payload length (%d != %d).\n", header->payload_length, sizeof(SERVICE_SERVER_NOT_AVAILABLE_packet_t));
			abort();
		}
		handle_SERVICE_SERVER_NOT_AVAILABLE(header, (SERVICE_SERVER_NOT_AVAILABLE_packet_t*) payload);
		break;
	case SERVICE_PACKET_TYPE_REPLICATION_REQUEST:
		if(header->payload_length != sizeof(SERVICE_REPLICATION_REQUEST_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received REPLICATION REQUEST packet with wrong payload length (%d != %d).\n", header->payload_length, sizeof(SERVICE_REPLICATION_REQUEST_packet_t));
			abort();
		}
		handle_SERVICE_REPLICATION_REQUEST(header, (SERVICE_REPLICATION_REQUEST_packet_t*) payload);
		break;
	case SERVICE_PACKET_TYPE_REPLICATION_DATA:
		if(header->payload_length < sizeof(SERVICE_REPLICATION_DATA_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received REPLICATION DATA packet with wrong payload length (%d < %d).\n", header->payload_length, sizeof(SERVICE_REPLICATION_DATA_packet_t));
			abort();
		}
		data = (((char*) payload) + sizeof(SERVICE_REPLICATION_DATA_packet_t));
		data_size = header->payload_length - sizeof(SERVICE_REPLICATION_DATA_packet_t);
		handle_SERVICE_REPLICATION_DATA(header, (SERVICE_REPLICATION_DATA_packet_t*) payload, data, data_size);
		break;
	case SERVICE_PACKET_TYPE_REPLICATION_ACK:
		if(header->payload_length != sizeof(SERVICE_REPLICATION_ACK_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received REPLICATION ACK packet with wrong payload length (%d != %d).\n", header->payload_length, sizeof(SERVICE_REPLICATION_ACK_packet_t));
			abort();
		}
		handle_SERVICE_REPLICATION_ACK(header, (SERVICE_REPLICATION_ACK_packet_t*) payload);
		break;
	case SERVICE_PACKET_TYPE_REPLICATION_NACK:
		if(header->payload_length != sizeof(SERVICE_REPLICATION_NACK_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received REPLICATION NACK packet with wrong payload length (%d != %d).\n", header->payload_length, sizeof(SERVICE_REPLICATION_NACK_packet_t));
			abort();
		}
		handle_SERVICE_REPLICATION_NACK(header, (SERVICE_REPLICATION_NACK_packet_t*) payload);
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: 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 Request specific service data of a service.
 *
 * @param service_id ID of the service from which to request data.
 * @param offset Offset in service data.
 * @param size Size of requested service data.
 */
void SERVICE_request_data(const SERVICE_id_t* service_id, uint16_t offset, size_t size) {
	if((strlen(service_id) + 1) > SERVICE_ID_LENGTH) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Service ID \"%s\" is too long (%d + 1 > %d).\n", service_id, strlen(service_id), SERVICE_ID_LENGTH);
		return;
	}

	request_data(service_id, offset, size);
}

/**
 * @brief Update specific service data of a service.
 *
 * @param service_id ID of the service of which to update data.
 * @param offset Offset in service data.
 * @param size Size of service data to update.
 * @param data Pointer to new service data.
 */
void SERVICE_update_data(const SERVICE_id_t* service_id, uint16_t offset, size_t size, const char* data) {
	if((strlen(service_id) + 1) > SERVICE_ID_LENGTH) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Service ID \"%s\" is too long (%d + 1 > %d).\n", service_id, strlen(service_id), SERVICE_ID_LENGTH);
		return;
	} else if(data == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: No data provided.\n");
		return;
	}

	update_data(service_id, offset, size, data);
}

/**
 * @brief Request a stream of specific service data of a service.
 *
 * @param service_id ID of the service from which to request data.
 * @param offset Offset in service data.
 * @param size Size of requested service data.
 * @param interval Interval at which to send stream packets in seconds.
 * @param duration Time during which the stream is to be active in seconds.
 */
void SERVICE_request_stream(const SERVICE_id_t* service_id, uint16_t offset, size_t size, C_time_t interval, C_time_t duration) {
	if((strlen(service_id) + 1) > SERVICE_ID_LENGTH) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Service ID \"%s\" is too long (%d + 1 > %d).\n", service_id, strlen(service_id), SERVICE_ID_LENGTH);
		return;
	} else if(interval < 0) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Interval has negative value (%f).\n", interval);
		return;
	} else if(duration < 0) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Duration has negative value (%f).\n", duration);
		return;
	}

	request_stream(service_id, offset, size, interval, duration);
}

/**
 * @brief Create 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.
 */
void SERVICE_create_service(const SERVICE_id_t* service_id, size_t service_data_size) {
	SERVICE_service_t* service = NULL;
#ifdef C_ENABLE_DEBUG
	char* p = NULL;
#endif	// C_ENABLE_DEBUG

	if((strlen(service_id) + 1) > SERVICE_ID_LENGTH) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: New service ID \"%s\" is too long (%d + 1 > %d).\n", service_id, strlen(service_id), SERVICE_ID_LENGTH);
		return;
	}

	service = alloc_service(service_id, service_data_size);
	if(service == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Can't create new service \"%s\".\n", service_id);
		return;
	}
#ifdef C_ENABLE_DEBUG
	MALLOC_ARRAY(p, char, service_data_size);
	memset(p, 0xDA, service_data_size);
	update_service_data(service, 0x00, service_data_size, p);	// Fill service data memory with a known pattern.
#endif	// C_ENABLE_DEBUG
	start_service(service);
}

/**
 * @brief Shutdown a service on this node.
 *
 * @param service_id ID of the service to shutdown.
 */
void SERVICE_shutdown_service(const SERVICE_id_t* service_id) {
	SERVICE_service_t* service = NULL;

	if((strlen(service_id) + 1) > SERVICE_ID_LENGTH) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Service ID \"%s\" is too long (%d + 1 > %d).\n", service_id, strlen(service_id), SERVICE_ID_LENGTH);
		return;
	}

	service = get_service(service_id);
	if(service == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Can't shutdown unknown service \"%s\".\n", service_id);
		return;
	}
	stop_service(service);
	free_service(service);
}

/**
 * @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* SERVICE_get_active_service(const SERVICE_id_t* service_id) {
	if((strlen(service_id) + 1) > SERVICE_ID_LENGTH) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Service ID \"%s\" is too long (%d + 1 > %d).\n", service_id, strlen(service_id), SERVICE_ID_LENGTH);
		return NULL;
	}

	return get_active_service(service_id);
}

/**
 * @brief Replicate a service from this node to another node.
 *
 * @param service_id ID of the service to replicate.
 * @param to Address of the destination node.
 * @param callback Callback function.
 * @param result Pointer to replication result.
 */
void SERVICE_replicate_service(const SERVICE_id_t* service_id, C_addr_t to, callback_function_t callback, SERVICE_replication_result_t* result) {
	SERVICE_service_t* service = NULL;

	if((strlen(service_id) + 1) > SERVICE_ID_LENGTH) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Service ID \"%s\" is too long (%d + 1 > %d).\n", service_id, strlen(service_id), SERVICE_ID_LENGTH);
		return;
	}

	service = get_active_service(service_id);
	if(service == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Can't replicate service \"%s\", service not active.\n", service_id);
		return;
	}

	replicate_service(service, to, callback, result);
}

/**
 * @brief Migrate a service from this node to another node.
 *
 * @param service_id ID of the service to migrate.
 * @param to Address of the destination node.
 * @param callback Callback function.
 * @param result Pointer to migration result.
 */
void SERVICE_migrate_service(const SERVICE_id_t* service_id, C_addr_t to, callback_function_t callback, SERVICE_migration_result_t* result) {
	SERVICE_service_t* service = NULL;

	if((strlen(service_id) + 1) > SERVICE_ID_LENGTH) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Service ID \"%s\" is too long (%d + 1 > %d).\n", service_id, strlen(service_id), SERVICE_ID_LENGTH);
		return;
	}

	service = get_active_service(service_id);
	if(service == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Can't migrate service \"%s\", service not active.\n", service_id);
		return;
	}

	migrate_service(service, to, callback, result);
}

/**
 * @brief Copy a service ID to another.
 *
 * @param dest_service_id Destination service ID.
 * @param src_service_id Source service ID.
 */
void SERVICE_copy_service_id(SERVICE_id_t* dest_service_id, const SERVICE_id_t* src_service_id) {
	copy_service_id(dest_service_id, src_service_id);
}

/**
 * @brief Check whether two service IDs match.
 *
 * @param service_id_1 First service ID.
 * @param service_id_2 Seconds service ID.
 * @return <code>true</code> if the two service IDs are equal,
 * <code>false</code> otherwise.
 */
bool SERVICE_same_service_id(const SERVICE_id_t* service_id_1, const SERVICE_id_t* service_id_2) {
	return same_service_id(service_id_1, service_id_2);
}



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

/**
 * @brief Service command replication callback function.
 *
 * @param parameter Replication result.
 */
static void SERVICE_command_replicate_service_callback(callback_parameter_t parameter) {
	SERVICE_replication_result_t* result = (SERVICE_replication_result_t*) parameter;
	SERVICE_service_t* service = (SERVICE_service_t*) result->parameter;

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

/**
 * @brief Service command migration callback function.
 *
 * @param parameter Migration result.
 */
static void SERVICE_command_migrate_service_callback(callback_parameter_t parameter) {
	SERVICE_migration_result_t* result = (SERVICE_migration_result_t*) parameter;

	switch(result->error_code) {
	case C_ERROR_OK:
		C_DEBUG(C_DEBUG_LEVEL_NOTE, "SERVICE: Migration of service \"%s\" was successful.\n", result->service_id);
		break;
	case C_ERROR_PARAMETER:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: 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, "SERVICE: Migration of service \"%s\" timed out.\n", result->service_id);
		break;
	case C_ERROR_BUSY:
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: 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, "SERVICE: 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, "SERVICE: 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, "SERVICE: Migration of service \"%s\" failed because the remote system refused.\n", result->service_id);
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Migration callback with unknown error code 0x%X.\n", result->error_code);
		abort();
	}
	FREE(result);
}

/**
 * @brief Service 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 SERVICE_command(int argc, const char*const* argv) {
	if(argc == 3) {
		if(strcmp(argv[1], "SERVICE_shutdown_service") == 0) {
			SERVICE_shutdown_service(argv[2]);
			return true;
		} else if(strcmp(argv[1], "SERVICE_is_local_service") == 0) {
			if(SERVICE_get_active_service(argv[2]) != NULL) {
				C_DEBUG(C_DEBUG_LEVEL_NOTE, "SERVICE: Service \"%s\" is hosted on this node.\n", argv[2]);
			} else {
				C_DEBUG(C_DEBUG_LEVEL_NOTE, "SERVICE: Service \"%s\" is not hosted on this node.\n", argv[2]);
			}
			return true;
		}
	} else if(argc == 4) {
		if(strcmp(argv[1], "SERVICE_create_service") == 0) {
			SERVICE_create_service(argv[2], atoi(argv[3]));
			return true;
		} else if(strcmp(argv[1], "SERVICE_replicate_service") == 0) {
			SERVICE_replication_result_t* result = NULL;
			SERVICE_service_t* service = get_active_service(argv[2]);
			if(service == NULL) {
				C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Can't replicate service \"%s\", service not active.\n", argv[2]);
				return false;
			}
			MALLOC(result, SERVICE_replication_result_t);
			result->parameter = service;
			SERVICE_replicate_service(argv[2], atoi(argv[3]), SERVICE_command_replicate_service_callback, result);
			return true;
		} else if(strcmp(argv[1], "SERVICE_migrate_service") == 0) {
			SERVICE_migration_result_t* result = NULL;
			SERVICE_service_t* service = get_active_service(argv[2]);
			if(service == NULL) {
				C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Can't migrate service \"%s\", service not active.\n", argv[2]);
				return false;
			}
			MALLOC(result, SERVICE_migration_result_t);
			result->parameter = service;
			SERVICE_migrate_service(argv[2], atoi(argv[3]), SERVICE_command_migrate_service_callback, result);
			return true;
		}
	} else if(argc == 5) {
		if(strcmp(argv[1], "SERVICE_request_data") == 0) {
			SERVICE_request_data(argv[2], atoi(argv[3]), atoi(argv[4]));
			return true;
		} else if(strcmp(argv[1], "SERVICE_update_data") == 0) {
			size_t size = atoi(argv[4]);
			char* data;
			MALLOC_ARRAY(data, char, size);
			memset(data, 0xDB, size);	// Some byte pattern.
			SERVICE_update_data(argv[2], atoi(argv[3]), size, data);
			return true;
		}
	} else if(argc == 7) {
		if(strcmp(argv[1], "SERVICE_request_stream") == 0) {
			SERVICE_request_stream(argv[2], atoi(argv[3]), atoi(argv[4]), atof(argv[5]), atof(argv[6]));
			return true;
		}
	}
	return false;
}
