/*	##############################################################################################
	Advanced RTI System, ARTÌS			http://pads.cs.unibo.it

	Description:	
		Very simple wireless example. It simulates a set of wireless devices moving in a
		toroidal 2D space. The interactions between them are based on proximity.

	Output:
		the output can be found in the following files:
		<LP_ID>.out	model output
		<LP_ID>.err	middleware output

	Changelog:
		06/10/09
			Basic event rate management	

	Authors:
		First version by Michele Bracuto <bracuto@cs.unibo.it>
		Modified by Gabriele D'Angelo <gda@cs.unibo.it>

	############################################################################################## */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <math.h>
#include <assert.h>
#include <ini.h>
#include <ts.h>
#include <rnd.h>

/*-------------------------- D E B U G --------------------------------------*/
// Uncomment to activate debug
#define DEBUG
/*---------------------------------------------------------------------------*/

#define	UNUSED			__attribute__ ((__unused__))
#define ASSERT( _cond, _act )   { if( !(_cond) ) { printf _act; printf("\n"); assert( _cond ); } }

#define TIMER_NOW(_t) 		gettimeofday(&_t,NULL)
#define TIMER_SECONDS(_t) 	((double)(_t).tv_sec + (_t).tv_usec*1e-6)
#define TIMER_DIFF(_t1, _t2) 	(TIMER_SECONDS(_t1)-TIMER_SECONDS(_t2))

#define PI			3.1415926535897932384626433832795

#define MIN(X, Y)  		((X) < (Y) ? (X) : (Y))
#define MAX(X, Y)  		((X) > (Y) ? (X) : (Y))
#define	YES			'1'
#define	NO			'0'

/*---------------------------------------------------------------------------*/

// Proximity detection mechanism (all pairs algorithm)
//	to use squares optimization uncomment the define
//	for more details: http://doi.acm.org/10.1145/1164717.1164727
#define	USE_SQUARE

// Simulation length (final clock value)
#define END_CLOCK		1000.0

static int      NSIMULATE, 		// Number of simulated hosts per LP
		NLP, 			// Number of Logical Processes
		LPID;			// Local Logical Process Identifier

// Random-Waypoint mobility model, motion speed
static int	SPEED;

// Playground definition (X and Y size)
static float	MAX_X=0.0, MAX_Y=0.0;

// Communication range of wireless devices
static float	RADIUS, RADIUS_INT;

// SImulation MAnager information and localhost identifier
static char	LP_HOST[64];
static char	SIMA_HOST[64];
static int	SIMA_PORT;

// Time management variables
static double   step, clock=0.0;
static int	end_reached=0;

// Total number of processed events in the LP
static long int	total_processed_events = 0;

// Seed used for the random generator
TSeed		Seed, *S=&Seed;

// Time measurement
struct timeval 	t1,t2;
/*---------------------------------------------------------------------------*/

// Model messages definition
typedef struct _move_msg    MoveMsg;
typedef struct _ping_msg    PingMsg;
typedef union   msg         Msg;

//	Movement message
struct _move_msg {
	char    type;
	int     from;
	float	x;
	float	y;
};

//	Ping message
struct _ping_msg {
	char   type;
	int    from;
	int    to;
};

//	Union structure for messages
union msg {
	char	type;
    	MoveMsg gen;
    	MoveMsg move;
    	PingMsg ping;
};
/*---------------------------------------------------------------------------*/

// Hash tables
enum HASH_TYPE {
	SMH,		/* Global hash table of simulated mobile hosts */
	SIM,		/* Hash table of locally simulated mobile hosts */
};

typedef struct hash_data_t {
	int	key;
	int 	lp;
	float	posX;
	float	posY;
	float	targX;
	float	targY;
	char	state;
	char	changed;
} hash_data_t;

typedef struct hash_node_t {
	struct hash_data_t *data;
   	struct hash_node_t *next;
} hash_node_t;


typedef struct hash_t {
   	struct hash_node_t **bucket;
	int count;
   	int size;
} hash_t;

hash_t		hash_table,  *table=&hash_table;	/* Global hash table of simulated mobile hosts */
hash_t		sim_table,   *stable=&sim_table;	/* Hash table of locally simulated mobile hosts */
/*---------------------------------------------------------------------------*/


/* ************************************************************************* */
/* 	         D A T A    S T R U C T U R E S    M A N A G E M E N T       */
/* ************************************************************************* */

static int hash(hash_t *tptr, int x)
{
	return  (x % tptr->size);
}
/*---------------------------------------------------------------------------*/


/*
	Hash table initialization
*/
static void hash_init(hash_t *tptr, int size)
{
   	tptr->size	= size;
	tptr->count	= 0;
   	tptr->bucket	= (hash_node_t **) calloc(tptr->size, sizeof(hash_node_t *));

   	return;
}

/*
	Lookup of a node
*/
static hash_node_t *hash_lookup(hash_t *tptr, int key)
{
   	int 		h;
	hash_node_t 	*node;


   	h = hash(tptr, key);
   	for (node=tptr->bucket[h]; node!=NULL; node=node->next) {
     		if (node->data->key == key)
      			break;
   	}

   	return(node);
}

/*
	Insertion of a new node
*/
static hash_node_t * hash_insert(enum HASH_TYPE type, hash_t *tptr, struct hash_data_t *data, int key, int lp)
{
   	hash_node_t 	*node, *tmp;
   	int 		h;



	if ( (tmp=hash_lookup(tptr, key)) )
		return(tmp);
	
	h		 = hash(tptr, key);

	node		 = (struct hash_node_t *) malloc(sizeof(hash_node_t));
	ASSERT ((node != NULL), ("hash_insert: malloc error"));

	if(type == SMH) {
		node->data	 = (struct hash_data_t *) malloc(sizeof(hash_data_t));
		ASSERT ((node->data != NULL), ("hash_insert: malloc error"));

		node->data->key	   	= key;
		node->data->lp	   	= lp;
		node->data->posX	= 0.0;
		node->data->posY	= 0.0;
	}
	else if(type == SIM) {
		node->data 		= data;
		node->data->lp	   	= lp;
	}


   	node->next	= tptr->bucket[h];
   	tptr->bucket[h]	= node;
        tptr->count	+= 1;
		
   	return node;
}
/*---------------------------------------------------------------------------*/


/* ************************************************************************ */
/* 		      M O D E L    D E F I N I T I O N			    */
/* ************************************************************************ */

/*
	RANDOM generation of coordinates
*/
static void	rwp_coordinate (float *posX, float *posY)
{
	float	tmpx, tmpy;


	tmpx  	= (float)RND_Interval (S, 0, MAX_X); 
	tmpy  	= (float)RND_Interval (S, 0, MAX_Y); 

	*posX = tmpx;
	*posY = tmpy;
}
/*---------------------------------------------------------------------------*/



/*
	RANDOM WAY POINT movement
*/
static void UNUSED	 rwp_move(hash_node_t *smh_node, float *posX, float *posY)
{
	float	tmpX,tmpY;
        double 	angle;


	// Check if the destination has been reached
 	if ( (fabs(smh_node->data->posX - smh_node->data->targX) <= SPEED) && (fabs(smh_node->data->posX - smh_node->data->targX) <= SPEED) ) {
		rwp_coordinate(&(smh_node->data->targX), &(smh_node->data->targY));
	}

	tmpX = smh_node->data->targX;
	tmpY = smh_node->data->targY;

	// Shortest path determination
	if (fabs(smh_node->data->targX - smh_node->data->posX) > MAX_X/2) {
		if (smh_node->data->targX > smh_node->data->posX)
			tmpX	-= MAX_X;
		else
			tmpX	+= MAX_X;
	}

	if (fabs(smh_node->data->targY - smh_node->data->posY) > MAX_X/2) {
		if (smh_node->data->targY > smh_node->data->posY)
			tmpY	-= MAX_X;
		else
			tmpY	+= MAX_X;
	}

	angle	= atan((tmpY - smh_node->data->posY)/(tmpX - smh_node->data->posX) );
	if ( (tmpX - smh_node->data->posX < 0) ){
			angle += PI;
	}

	tmpX	= smh_node->data->posX + (SPEED * cos(angle));
	tmpY	= smh_node->data->posY + (SPEED * sin(angle));

	if( tmpX >= MAX_X)	{tmpX -= MAX_X;}
	if( tmpY >= MAX_Y)	{tmpY -= MAX_Y;}
	if( tmpX < 0 )		{tmpX += MAX_X;}
	if( tmpY < 0 )		{tmpY += MAX_Y;}

	*posX	= tmpX;
	*posY	= tmpY;
}
/*---------------------------------------------------------------------------*/



/*
	SMH Movement
*/
static void	move_event (double ts, hash_node_t *node)
{
	float	posX, posY;
	MoveMsg	msg;


	/* random way point  */
	rwp_move(node, &posX, &posY);

	msg.type	= 'M';
	msg.from	= node->data->key;
	msg.x		= posX;
	msg.y		= posY;

	TS_Broadcast(ts, (void *)&msg, sizeof(MoveMsg) );
}
/*---------------------------------------------------------------------------*/


/*
	Movements generation
*/
static void	ScanActivity ()
{
	int		h;
	hash_node_t 	*node;

	
   	for (h=0; h<stable->size; h++)
	{
		for (node=stable->bucket[h]; node; node=node->next)
			if( RND_Integer (S, 0, 1) ) 
				move_event (clock+step, node);
	}
}
/*---------------------------------------------------------------------------*/


/*
 * 	Ping another SMH, creating a 'P' type message and sending it to the correct LP
 */
static void	ping (double ts, hash_node_t *src, hash_node_t *dest) 
{
	int	lp_dest;
	PingMsg	msg;


	msg.type	= 'P';
	msg.from	= src->data->key;
	msg.to		= dest->data->key;
	lp_dest		= dest->data->lp;

	TS_Send(lp_dest, ts, (void *)&msg, sizeof(PingMsg) );
}
/*---------------------------------------------------------------------------*/


/*
	Euclidean Distance
*/
static float UNUSED distance (float diff_x, float diff_y)
{
	return ( sqrtf( (diff_x*diff_x) + (diff_y*diff_y) ) );
}
/*---------------------------------------------------------------------------*/



/*
	Send a Ping to all SMHs within the communication range
*/
static void UNUSED signal_to_range (hash_t *tptr, hash_node_t *smh_node)
{
	int	h;
	float 	dist;
	#ifdef USE_SQUARE
	float	distX, distY;
	#endif
	hash_node_t *peer;


	for (h=0; h<tptr->size; h++)
	{
		for (peer=tptr->bucket[h]; peer; peer=peer->next)
		{	
			if( peer->data->key == smh_node->data->key  ) 
				continue;
		
			// Proximity detection mechanism
			#ifdef USE_SQUARE
			distX = fabsf(peer->data->posX-smh_node->data->posX);
			distY = fabsf(peer->data->posY-smh_node->data->posY);

			if( (distX <= RADIUS) && (distY <= RADIUS) )
			{
				if( (distX <= RADIUS_INT) && (distY <= RADIUS_INT) )
				{
					ping (clock+step, smh_node, peer);

				}
				else {
			#endif
					dist = distance(peer->data->posX - smh_node->data->posX, peer->data->posY - smh_node->data->posY);
		
					if( (dist <= RADIUS) ) {
						ping (clock+step, smh_node, peer);
					}
			#ifdef USE_SQUARE
				}
			}
			#endif
		}
		
	}
}
/*---------------------------------------------------------------------------*/



/*
 	Sends a Ping to all SMHs that are inside the action range
*/
static void UNUSED BroadcastSignal ()
{
	int		h;
	hash_node_t 	*node;


   	for (h=0; h<stable->size; h++)
	{
		for (node=stable->bucket[h]; node; node=node->next)
		{
			signal_to_range(table, node);
		}
	}

}
/*---------------------------------------------------------------------------*/


/*
	SMH generation, called once when global variables have been initialized.
*/
static void	Generate ()
{
	int		i;
	float		posX, posY;
	MoveMsg		msg;


	for(i=(LPID * NSIMULATE); i< (LPID * NSIMULATE)+NSIMULATE; i++) {

		/* random way point */
		rwp_coordinate (&posX, &posY);

		msg.type	= 'G';
		msg.from	= i;
		msg.x		= posX;
		msg.y		= posY;
	
		TS_Broadcast(0.0+step, (void *)&msg, sizeof(MoveMsg) );
	}
}
/*---------------------------------------------------------------------------*/




/* ************************************************************************ */
/* 			 E V E N T   H A N D L E R S			    */
/* ************************************************************************ */

/*
	Upon arrival of an echo request do "nothing"
*/
void	ping_event_handler (Msg *msg)
{
	/* ... */
}
/*---------------------------------------------------------------------------*/



/*
	Update the SMH position
*/
void	move_event_handler (Msg *msg)
{
	hash_node_t *node;

	if( (node = hash_lookup ( table, msg->move.from)) ) {
		node->data->changed	= YES;

		node->data->posX = msg->move.x;
		node->data->posY = msg->move.y;
	}
}
/*---------------------------------------------------------------------------*/



/*
 	A new SMH has been created in an external LP, we have to insert
 	the SMH into the local hashtable at the position given by sender's ID
*/
void	generation_event_handler (int lp, Msg *msg)
{
 	hash_node_t 	*node;

	
	node = hash_insert(SMH, table, NULL, msg->move.from, lp);
	
	if(node) {
		node->data->posX	= msg->move.x;
		node->data->posY	= msg->move.y;
		node->data->changed	= YES;
									
		if(lp == LPID) { 
			// Inserting it in the table of local SMHs
			hash_insert(SIM, stable, node->data, node->data->key, 0);

			// Random Way Point
			rwp_coordinate(&(node->data->targX), &(node->data->targY));
		}
	}
}
/*---------------------------------------------------------------------------*/



/* ************************************************************************ */
/* 			   	  U T I L S				    */
/* ************************************************************************ */

/*
	Loading the configuration file of the simulation
*/
static void UNUSED LoadINI(char *ini_name)
{
	int	ret;	
	char	data[64];
	

	ret = INI_Load(ini_name);
	ASSERT( ret == INI_OK, ("Error loading ini file \"%s\"", ini_name) );
	
	/* SIMA */
        ret = INI_Read( "SIMA", "HOST", data);
	if (ret == INI_OK && strlen(data))
		strcpy(SIMA_HOST, data);

        ret = INI_Read( "SIMA", "PORT", data);
	if (ret == INI_OK && strlen(data))
		SIMA_PORT = atoi(data);

	/* MODEL */
        ret = INI_Read( "MODEL", "SPEED", data);
	if (ret == INI_OK && strlen(data))
		SPEED = atoi(data);

        ret = INI_Read( "MODEL", "RADIUS", data);
	if (ret == INI_OK && strlen(data))
		RADIUS = atof(data);

        ret = INI_Read( "MODEL", "MAX_X", data);
	if (ret == INI_OK && strlen(data))
		MAX_X = atof(data);

        ret = INI_Read( "MODEL", "MAX_Y", data);
	if (ret == INI_OK && strlen(data))
		MAX_Y = atof(data);

	INI_Free(); 
}
/*---------------------------------------------------------------------------*/


/*
	Simulation Trace
*/
static void UNUSED PrintTrace ()
{
	int	h;
	hash_node_t *node;



	fprintf(stdout, "<BEGIN %6.4f>\n",  clock);

	for (h=0; h<table->size; h++)
	{
		for (node=table->bucket[h]; node; node=node->next)
		{
			if(node->data->changed == YES) {
				fprintf(stdout, "%d\t%d\t%10.2f\t%10.2f\n", node->data->key, node->data->lp, node->data->posX, node->data->posY);
				node->data->changed = NO;
			}
		}
	}
	
	fprintf(stdout, "<END>\n");
	fflush(stdout);
}
/*---------------------------------------------------------------------------*/



/* ******************************************************** */
/* 			    M A I N			    */
/* ******************************************************** */

int main(int argc, char* argv[])
{
	char 	data[1024], *rnd_file="Rand.seed";
	int	from;
	long 	size;
	double  Ts;
	Msg	*msg;


	// Loading the input parameters from the configuration file
	LoadINI( "wireless.ini" );

	// Setup of the TIMESTEPPED synchronization algorithm 
	LPID = TS_Init( NULL, SIMA_HOST, SIMA_PORT);
	// The size of the step is given by the "GLOBAL_LA" variable set in "channels.txt"
	step = TS_GetStep();

	// Command-line input parameters
	NLP		= atoi(argv[1]);
	NSIMULATE	= atoi(argv[2]);	
	RADIUS_INT	= (RADIUS * sqrtf(2.0));

	// Local hashtable initialization
	hash_init(table,  NSIMULATE*NLP);
	hash_init(stable, NSIMULATE);
	
	// Initialization of the random numbers generator
	RND_Init (S, rnd_file, LPID);

	gethostname(LP_HOST, 64);
	fprintf(stdout, "#LP [%d] HOSTNAME [%s]\n", LPID, LP_HOST);

	/* Starting Timer */
	TIMER_NOW(t1);

	/* Generation of the locally manager simulated entities (SMHs) */
	Generate();

	fprintf(stdout, "<PARAMETERS: TRANSMISSION RAGE=%.0f AREA_SIZE_X=%.0f AREA_SIZE_Y=%.0f>\n",  RADIUS, MAX_X, MAX_Y);
	fflush(stdout);
	

	/* Main simulation loop, receives messages and calls the handler associated with them */
	while (!end_reached) {

		// Looking for a new incoming message
		size = TS_Receive(&from, &Ts, (void *)data, 1024);

		if(size > 0) {
			// A message has been received, process it (calling appropriate handler)
			msg 	= (Msg *)data;

			// Total number of processed events in the LP
			total_processed_events++;

			// Each message type has a different handler function
			if (msg->type == 'G' )	generation_event_handler (from, msg);
			if (msg->type == 'M' )	move_event_handler (msg);
			if (msg->type == 'P' )	ping_event_handler (msg);
		}
		else {
			// no new incoming messages

			// if DEBUG then LP0 shows the tracelog
			#ifdef DEBUG
				if(LPID == 0)	PrintTrace();
			#endif
			
			if ( clock < END_CLOCK )  {
				// The simulation is not finished

				// Wireless model: broadcast messages
				BroadcastSignal();
				
				// Simulated entities (SMHs) movement
				if ( clock > 0.0 )	
					ScanActivity();

				// End of step
				fprintf(stdout,"#[%12.2f]\n", clock); fflush(stdout);
				clock = TS_TimeAdvance();
			}
			else {
				// The simulation is finished
				end_reached = 1;

				/* Stop the timer */
				TIMER_NOW(t2);
				
				// Output final statistics
				//
				fprintf(stdout,"\n");
				// Human-readable string format
				fprintf(stdout,"-- Termination condition reached\n");
				fprintf(stdout,"-- Clock               %12.2f\n", clock);
				fprintf(stdout,"-- Elapsed Time        %11.2fs\n",TIMER_DIFF(t2, t1));
				//	Total number of processed events in the LP
				fprintf(stdout,"-- Processed events     %11ld\n",total_processed_events);
				// Script-parsable string format
				//	Events per second processed in the LP
				fprintf(stdout,"++ Events per second:%.3f\n",(float)(total_processed_events / TIMER_DIFF(t2,t1)));
				fflush(stdout);

				// Finalize the synchronization mechanism
				TS_Finalize();
			}

		}

	}

	return 0;
}



