/**
 * @file c_application.cc
 *
 * @brief Service Placement application for ns-2.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 *
 * $Id: c_application.cc 2686 2008-06-26 10:30:01Z wittenbu $
 */

#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <queue>
#include <vector>

#include "app.h"
#include "packet.h"
#include "rng.h"
#include "scheduler.h"
#include "timer-handler.h"

#include "c_application.h"



/**
 * @brief Group the output of vprintf() by time.
 */
#define VPRINTF_GROUP_OUTPUT_BY_TIME	0



#define STATE_INIT	0x0	///< Application is initializing.
#define STATE_STOPPED	0x1	///< Application is stopped.
#define STATE_RUNNING	0x2	///< Application is running.
#define STATE_TRANSMIT	0x3	///< Application is transmitting a packet.
#define STATE_FINALIZED	0x4	///< Application is finalized.

/**
 * @brief Convert a numeric state represenstation into the corresponding
 * string.
 *
 * @param state Numeric representation of the state.
 * @return String representing the state.
 */
const char* STATE_TO_STRING(int state) {
	switch(state) {
	case STATE_INIT:	return "INIT";
	case STATE_STOPPED:	return "STOPPED";
	case STATE_RUNNING:	return "RUNNING";
	case STATE_TRANSMIT:	return "TRANSMIT";
	case STATE_FINALIZED:	return "FINALIZED";
	default: printf("\t\t\tCApplication: Illegal state (%d), aborting...\n", state); abort();
	}
}



using namespace C;

// Glue code to the Tcl interpreter.
static class CApplicationClass : public TclClass {
public:
	CApplicationClass() : TclClass("Application/C") {}
	TclObject* create(int, const char*const*) {
		return (new CApplication());
	}
} class_c_application;



CApplication* CApplication::instance_ = NULL;
double CApplication::timeOfLastCallToVprintf_ = 0;

/**
 * @brief CApplication constructor.
 */
CApplication::CApplication() : \
	Application()
{
	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Creating new instance...\n");
	instance_ = this;
	timer_ = new CApplicationTimer(this);
	rng_ = new RNG();
	rng_->set_seed(RNG::PREDEF_SEED_SOURCE, 0);
	state_ = STATE_INIT;
	timeOfNextCallback_ = 0;

	// Initialize and export statistics variables.
	transmittedUnicastPackets_ = 0;
	bind("transmittedUnicastPackets_", &transmittedUnicastPackets_);
	receivedUnicastPackets_ = 0;
	bind("receivedUnicastPackets_", &receivedUnicastPackets_);
	transmittedBroadcastPackets_ = 0;
	bind("transmittedBroadcastPackets_", &transmittedBroadcastPackets_);
	receivedBroadcastPackets_ = 0;
	bind("receivedBroadcastPackets_", &receivedBroadcastPackets_);
	transmittedBytes_ = 0;
	bind("transmittedBytes_", &transmittedBytes_);
	receivedBytes_ = 0;
	bind("receivedBytes_", &receivedBytes_);

	// Export C variables.
	bind("directNeighbors_", &C_direct_neighbors);
	bind("longestRoute_", &C_longest_route);
	bind("averageRoute_", &C_average_route);
	bind("DEBUG_ERRORs_", &C_DEBUG_ERRORs);
	bind("DEBUG_WARNs_", &C_DEBUG_WARNs);
	bind("RUDP_totalTransmissions_", &C_RUDP_total_transmissions);
	bind("RUDP_successfulTransmissions_", &C_RUDP_successful_transmissions);
	bind("ICMP_transmittedNops_", &C_ICMP_transmitted_nops);
	bind("ICMP_receivedNops_", &C_ICMP_received_nops);
	bind("SLP_totalQueries_", &C_SLP_total_queries);
	bind("SLP_successfulQueries_", &C_SLP_successful_queries);
	bind("SERVICE_totalDataRequests_", &C_SERVICE_total_data_requests);
	bind("SERVICE_successfulDataRequests_", &C_SERVICE_successful_data_requests);
	bind("SERVICE_totalSuccessfulDataRequestDelay_", &C_SERVICE_total_successful_data_request_delay);
	bind("SERVICE_totalDataUpdates_", &C_SERVICE_total_data_updates);
	bind("SERVICE_successfulDataUpdates_", &C_SERVICE_successful_data_updates);
	bind("SERVICE_totalStreamRequests_", &C_SERVICE_total_stream_requests);
	bind("SERVICE_successfulStreamRequests_", &C_SERVICE_successful_stream_requests);
	bind("SERVICE_expectedStreamPackets_", &C_SERVICE_expected_stream_packets);
	bind("SERVICE_receivedStreamPackets_", &C_SERVICE_received_stream_packets);
	bind("SERVICE_totalReplications_", &C_SERVICE_total_replications);
	bind("SERVICE_successfulReplications_", &C_SERVICE_successful_replications);
	bind("SERVICE_totalMigrations_", &C_SERVICE_total_migrations);
	bind("SERVICE_successfulMigrations_", &C_SERVICE_successful_migrations);

	// Call C code initializer.
	state_ = STATE_STOPPED;
	C_init();
}

/**
 * @brief CApplication destructor.
 */
CApplication::~CApplication() {
	if(state_ != STATE_FINALIZED) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Trying to delete application, but it is not finalized (%s)...\n", STATE_TO_STRING(state_));
		abort();
	}

	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Deleting application...\n");

	// Cancel any remaining callbacks.
	if(timeOfNextCallback_ != 0)
		timer_->cancel();

	delete timer_;
	delete rng_;
}

/*
 * ns-2 entry points.
 */

int CApplication::command(int argc, const char*const* argv) {
	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Processing command \"%s\"...\n", argv[1]);
	instance_ = this;

//	Tcl& tcl = Tcl::instance();
	if(argc == 2) {
		if(strcmp(argv[1], "finalize") == 0) {
			C_finalize();
			state_ = STATE_FINALIZED;
			fflush(stdout);	// Flush standard output streams.
			return TCL_OK;
		}
	} else if(argc == 3) {
		// Intercept "attach-agent" command and initialize the local address.
		if(strcmp(argv[1], "attach-agent") == 0) {
			int ret = Application::command(argc, argv);
			if(ret == TCL_OK)
				C_local_addr = agent_->addr();
			return ret;
		}
	} else if(argc == 4) {
		if(strcmp(argv[1], "seed") == 0) {
			int i = atoi(argv[3]);
			if(strcmp(argv[2], "raw") == 0) {
				rng_->set_seed(RNG::RAW_SEED_SOURCE, i);
			} else if(strcmp(argv[2], "predef") == 0) {
				rng_->set_seed(RNG::PREDEF_SEED_SOURCE, i);
			} else if(strcmp(argv[2], "heuristic") == 0) {
				rng_->set_seed(RNG::HEURISTIC_SEED_SOURCE, 0);
			}
			return TCL_OK;
		}
	}

	if(C_command(argc, argv))
		return TCL_OK;

	return Application::command(argc, argv);
}

void CApplication::start() {
	if(state_ != STATE_STOPPED) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Trying to start application, but it is not stopped (%s)...\n", STATE_TO_STRING(state_));
		abort();
	}

	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Starting...\n");
	instance_ = this;
	state_ = STATE_RUNNING;
	C_start();
}

void CApplication::stop() {
	if(state_ != STATE_RUNNING) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Trying to stop application, but it is not running (%s)...\n", STATE_TO_STRING(state_));
		abort();
	}

	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Stopping...\n");
	instance_ = this;
	C_stop();
	state_ = STATE_STOPPED;
}

// Unused ns-2 entry point.
void CApplication::send(int nbytes) {
	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
		printf("\t\t\tCApplication: Sending %d byte(s) on unused interface.\n", nbytes);
	abort();
}

// Unused ns-2 entry point.
void CApplication::recv(int nbytes) {
	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
		printf("\t\t\tCApplication: Received %d byte(s) on unused interface.\n", nbytes);
	abort();
}

// ns-2 entry point for received packets.
void CApplication::recv(Packet* pkt, Handler*) {
	if(pkt == NULL) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Received a NULL packet.\n");
		abort();
	} else if(pkt->userdata()->size() < ((int) sizeof(packet_header_t))) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Received %d byte(s) but expected at least %d bytes.\n", pkt->userdata()->size(), sizeof(packet_header_t));
		abort();
	}

	unsigned char* p = ((PacketData*) pkt->userdata())->data();
	packet_header_t* header = (packet_header_t*) p;
	header->hops += hdr_cmn::access(pkt)->num_forwards();
	header->last_hop = hdr_cmn::access(pkt)->prev_hop();

	void* payload = NULL;
	if(header->payload_length > 0)
		payload = p + sizeof(packet_header_t);

	if((state_ != STATE_RUNNING) && (state_ != STATE_STOPPED)) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Trying to process %d byte(s) of application data from node #%d, but application is neither running nor stopped...\n", pkt->userdata()->size(), header->from);
		abort();
	}

	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Processing %d byte(s) of application data from node #%d (seqno=%d, hops=%d, last_hop=%d)...\n", pkt->userdata()->size(), header->from, header->seqno, header->hops, header->last_hop);
	instance_ = this;
	C_recv(header, payload);

	// Statistics.
	if(header->to == C_BROADCAST_ADDR)
		receivedBroadcastPackets_++;
	else
		receivedUnicastPackets_++;
	receivedBytes_ += pkt->userdata()->size();
}

// Unused ns-2 entry point.
void CApplication::process_data(int size, AppData* data) {
	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
		printf("\t\t\tCApplication: Received %d byte(s) of application data at %p on unused interface.\n\t\t\tCApplication: Is processPackets_ in Agent/MessagePassing set to 1?\n", size, data);
	abort();
}

// ns-2 entry point upon completed packet transmission.
void CApplication::resume() {
	if(state_ != STATE_TRANSMIT) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Trying to resume application, but it is not transmitting (%s)...\n", STATE_TO_STRING(state_));
		abort();
	}

	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Resuming...\n");
	state_ = STATE_RUNNING;
}

/*
 * C code entry points.
 */

/**
 * @brief ns-2 specific implementation of __C_vprintf().
 */
void CApplication::__C_vprintf(const char *format, va_list ap) {
	if(VPRINTF_GROUP_OUTPUT_BY_TIME && (timeOfLastCallToVprintf_ != Scheduler::instance().clock()))
		printf("\n");
	vprintf(format, ap);
	timeOfLastCallToVprintf_ = Scheduler::instance().clock();
}

/**
 * @brief ns-2 specific implementation of __C_random().
 */
double CApplication::__C_random(double max) {
	if((state_ != STATE_RUNNING) && (state_ != STATE_STOPPED)) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Trying to generate random number between 0 and %f, but application is neither running nor stopped (%s)...\n", max, STATE_TO_STRING(state_));
		abort();
	}

	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Generating random number between 0 and %f...\n", max);
	return rng_->uniform(max);
}

/**
 * @brief ns-2 specific implementation of __C_time().
 */
C_time_t CApplication::__C_time() {
	return Scheduler::instance().clock();
}

/**
 * @brief ns-2 specific implementation of __C_send().
 */
void CApplication::__C_send(const packet_header_t* header, const void* payload) {
	int packet_size = sizeof(packet_header_t) + header->payload_length;

	if((state_ != STATE_RUNNING) && (state_ != STATE_STOPPED)) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Trying to send packet of %d byte(s) with sequence number %d to node #%d, but application is neither running nor stopped (%s)...\n", packet_size, header->seqno, header->to, STATE_TO_STRING(state_));
		abort();
	}

	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Sending packet of %d byte(s) to node #%d (seqno=%d)...\n", packet_size, header->to, header->seqno);
	state_ = STATE_TRANSMIT;

	ns_addr_t dst = {header->to, agent_->port()};
	PacketData* data = new PacketData(packet_size);
	memcpy(data->data(), header, sizeof(packet_header_t));
	if(header->payload_length > 0)
		memcpy(data->data() + sizeof(packet_header_t), payload, header->payload_length);
	agent_->sendto(packet_size, data, NULL, dst);

	// Statistics.
	if(header->to == C_BROADCAST_ADDR)
		transmittedBroadcastPackets_++;
	else
		transmittedUnicastPackets_++;
	transmittedBytes_ += packet_size;

}

/**
 * @brief ns-2 specific implementation of __C_activate_callback().
 */
void CApplication::__C_activate_callback(C_time_t time) {
	if((state_ != STATE_RUNNING) && (state_ != STATE_STOPPED)) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Trying activate callback to be called at %.2fs, but application is neither running nur stopped (%s)...\n", time, STATE_TO_STRING(state_));
		abort();
	}

	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Activating callback to be called at %.2fs...\n", time);

	// Schedule next callback.
	double delay = time - Scheduler::instance().clock();
	if(delay < 0) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Trying to activate callback in the past...\n");
		abort();
	}
	timeOfNextCallback_ = time;
	timer_->resched(delay);
}

/*
 * Timer entry points.
 */

void CApplication::expire() {
	if((state_ != STATE_RUNNING) && (state_ != STATE_STOPPED)) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_WARN)
			printf("\t\t\tCApplication: Trying execute callback, but application is neither running nor stopped (%s)...\n", STATE_TO_STRING(state_));
		return;
	} else if(timeOfNextCallback_ == 0) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Timer expired but none was expected.\n");
		abort();
	} else if(Scheduler::instance().clock() != timeOfNextCallback_) {
		if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_FATAL)
			printf("\t\t\tCApplication: Timer expired at incorrect time (%f != %f).\n", Scheduler::instance().clock(), timeOfNextCallback_);
		abort();
	}

	if(C_DEBUG_LEVEL >= C_DEBUG_LEVEL_VERBOSE)
		printf("\t\t\tCApplication: Timer expired, executing callback...\n");

	// Execute callback.
	instance_ = this;
	timeOfNextCallback_ = 0;
	C_execute_callback();
}



/**
 * CApplicationTimer
 **/

CApplicationTimer::CApplicationTimer(CApplication* app) : \
	TimerHandler(), app_(app)
{}

void CApplicationTimer::expire(Event*) {
	app_->expire();
}



/**
 * Include C code
 **/

// Calls into C code.
#define C_init CApplication::C_init
#define C_start CApplication::C_start
#define C_stop CApplication::C_stop
#define C_finalize CApplication::C_finalize
#define C_recv CApplication::C_recv
#define C_execute_callback CApplication::C_execute_callback
#define C_command CApplication::C_command

// Calls from C code.
#define __C_vprintf CApplication::instance_->__C_vprintf
#define __C_random CApplication::instance_->__C_random
#define __C_time CApplication::instance_->__C_time
#define __C_send CApplication::instance_->__C_send
#define __C_activate_callback CApplication::instance_->__C_activate_callback

// Global C variables.
#define C_check_consistency_on_shutdown CApplication::instance_->C_check_consistency_on_shutdown
#define C_callbacks CApplication::instance_->C_callbacks
#define C_next_callback_id CApplication::instance_->C_next_callback_id
#define C_registered_callbacks CApplication::instance_->C_registered_callbacks
#define C_executed_callbacks CApplication::instance_->C_executed_callbacks
#define C_local_addr CApplication::instance_->C_local_addr
#define C_sequence_number CApplication::instance_->C_sequence_number
#define C_received_packet_cache CApplication::instance_->C_received_packet_cache
#define C_network_map CApplication::instance_->C_network_map
#define C_direct_neighbors CApplication::instance_->C_direct_neighbors
#define C_longest_route CApplication::instance_->C_longest_route
#define C_average_route CApplication::instance_->C_average_route
#define C_addr_to_str_buffer CApplication::instance_->C_addr_to_str_buffer
#define C_addr_to_str_buffer_index CApplication::instance_->C_addr_to_str_buffer_index
#define C_DEBUG_ERRORs CApplication::instance_->C_DEBUG_ERRORs
#define C_DEBUG_WARNs CApplication::instance_->C_DEBUG_WARNs
// ICMP-light
#define C_ICMP_active_echo_requests CApplication::instance_->C_ICMP_active_echo_requests
#define C_ICMP_next_echo_id CApplication::instance_->C_ICMP_next_echo_id
#define C_ICMP_transmitted_nops CApplication::instance_->C_ICMP_transmitted_nops
#define C_ICMP_received_nops CApplication::instance_->C_ICMP_received_nops
// RUDP-light
#define C_RUDP_transmission_queue CApplication::instance_->C_RUDP_transmission_queue
#define C_RUDP_received_packet_cache CApplication::instance_->C_RUDP_received_packet_cache
#define C_RUDP_total_transmissions CApplication::instance_->C_RUDP_total_transmissions
#define C_RUDP_successful_transmissions CApplication::instance_->C_RUDP_successful_transmissions
#define C_RUDP_failed_transmissions CApplication::instance_->C_RUDP_failed_transmissions
// SLP-light
#define C_SLP_active_queries CApplication::instance_->C_SLP_active_queries
#define C_SLP_total_queries CApplication::instance_->C_SLP_total_queries
#define C_SLP_successful_queries CApplication::instance_->C_SLP_successful_queries
#define C_SLP_failed_queries CApplication::instance_->C_SLP_failed_queries
// Service
#define C_SERVICE_service_cache CApplication::instance_->C_SERVICE_service_cache
#define C_SERVICE_total_data_requests CApplication::instance_->C_SERVICE_total_data_requests
#define C_SERVICE_successful_data_requests CApplication::instance_->C_SERVICE_successful_data_requests
#define C_SERVICE_failed_data_requests CApplication::instance_->C_SERVICE_failed_data_requests
#define C_SERVICE_total_successful_data_request_delay CApplication::instance_->C_SERVICE_total_successful_data_request_delay
#define C_SERVICE_total_data_updates CApplication::instance_->C_SERVICE_total_data_updates
#define C_SERVICE_successful_data_updates CApplication::instance_->C_SERVICE_successful_data_updates
#define C_SERVICE_failed_data_updates CApplication::instance_->C_SERVICE_failed_data_updates
#define C_SERVICE_total_stream_requests CApplication::instance_->C_SERVICE_total_stream_requests
#define C_SERVICE_successful_stream_requests CApplication::instance_->C_SERVICE_successful_stream_requests
#define C_SERVICE_failed_stream_requests CApplication::instance_->C_SERVICE_failed_stream_requests
#define C_SERVICE_expected_stream_packets CApplication::instance_->C_SERVICE_expected_stream_packets
#define C_SERVICE_received_stream_packets CApplication::instance_->C_SERVICE_received_stream_packets
#define C_SERVICE_hosted_services CApplication::instance_->C_SERVICE_hosted_services
#define C_SERVICE_served_streams CApplication::instance_->C_SERVICE_served_streams
#define C_SERVICE_incoming_replications CApplication::instance_->C_SERVICE_incoming_replications
#define C_SERVICE_outgoing_replications CApplication::instance_->C_SERVICE_outgoing_replications
#define C_SERVICE_total_replications CApplication::instance_->C_SERVICE_total_replications
#define C_SERVICE_successful_replications CApplication::instance_->C_SERVICE_successful_replications
#define C_SERVICE_failed_replications CApplication::instance_->C_SERVICE_failed_replications
#define C_SERVICE_total_migrations CApplication::instance_->C_SERVICE_total_migrations
#define C_SERVICE_successful_migrations CApplication::instance_->C_SERVICE_successful_migrations
#define C_SERVICE_failed_migrations CApplication::instance_->C_SERVICE_failed_migrations
// Placement
#define C_PLACEMENT_active_algorithm CApplication::instance_->C_PLACEMENT_active_algorithm
// Network graphs.
#define C_GRAPHS_active_constructions CApplication::instance_->C_GRAPHS_active_constructions
#define C_GRAPHS_local_data CApplication::instance_->C_GRAPHS_local_data

// C code files.
#include "../../interface.c"
#include "../../util/debug.c"
#include "../../util/list.c"
#include "../../util/callback.c"
#include "../../util/net.c"
#include "../../icmp-light/icmp-light.c"
#include "../../rudp-light/rudp-light.c"
#include "../../slp-light/slp-light.c"
#include "../../service/service.c"
#include "../../service/service_client.c"
#include "../../service/service_server.c"
#include "../../service/service_management.c"
#include "../../service/service_replication.c"
#include "../../service/service_util.c"
#include "../../placement/placement.c"
#include "../../placement/placement_stats.c"
#include "../../placement/simple_heuristic/simple_heuristic.c"
#include "../../placement/tree-topology_migration/tree-topology_migration.c"
#include "../../placement/linkpull_migration/linkpull_migration.c"
#include "../../placement/peerpull_migration/peerpull_migration.c"
#include "../../graphs/graphs.c"
#include "../../graphs/graphs_local.c"
