#include <cnet.h>
#include <stdlib.h>

#define	MAXHOPS		4
#define	ALL_LINKS	(-1)

/*
    This file implements a better flooding algorithm which exhibits a bit
    more "intellegence" than the naive algorithm in flooding1.c .
    These additions, implemented with selective_flood(), include:

    1) data packets are initially sent on all links.
    2) packets are forwarded on all links except the one on which they arrived.
    3) acknowledgement packets are initially sent on the link on which their
       data packet arrived.

    selective_flood() accepts a bitmap indicating which links should be used.

    In addition, some simple routing statistics are maintained - the
    average (actually minimal) round-trip number of hops and the average
    round-trip time in milliseconds.

    This flooding algorithm exhibits a better efficiency than flooding1.c .
    With the 7 nodes in the AUSTRALIA.MAP file, the efficiency is typically
    about 7%.

    Execute this protocol with the command:	cnet FLOODING2
 */


typedef enum    	{ NL_DATA, NL_ACK }   NL_PACKETKIND;

typedef struct {
    CnetAddr		src;
    CnetAddr		dest;
    NL_PACKETKIND	kind;      	/* only ever NL_DATA or NL_ACK */
    int			seqno;		/* 0, 1, 2, ... */
    int			hopsleft;	/* time to live */
    int			hopstaken;
    int			timesent;	/* in milliseconds */
    int			length;       	/* the length of the msg portion only */
    char		msg[MAX_MESSAGE_SIZE];
} NL_PACKET;

#define PACKET_HEADER_SIZE  \
		    (2*sizeof(CnetAddr)+sizeof(NL_PACKETKIND)+5*sizeof(int))
#define PACKET_SIZE(p)	    (PACKET_HEADER_SIZE + p.length)


/* -------- A SIMPLE NETWORK LAYER TABLE AS AN ABSTRACT DATA TYPE -------- */


typedef struct {
    CnetAddr	address;
    int		ackexpected;
    int		nextframetosend;
    int		frameexpected;
    long	totalhops;
    long	totaltime;
} NL_TABLE;

static	NL_TABLE	*table;
static	int		table_size;


static void init_NL()
{
    table	= (NL_TABLE *)malloc(sizeof(NL_TABLE));
    table_size	= 0;
}


static int find_address(CnetAddr address)
{
    int	t;

    for(t=0 ; t<table_size ; ++t)
	if(table[t].address == address)
	    return(t);

    table	= (NL_TABLE *)realloc((char *)table,
					(table_size+1)*sizeof(NL_TABLE));

    table[table_size].address		= address;
    table[table_size].ackexpected	= 0;
    table[table_size].nextframetosend	= 0;
    table[table_size].frameexpected	= 0;
    table[table_size].totalhops		= 0;
    table[table_size].totaltime		= 0;
    return(table_size++);
}

static int ackexpected(CnetAddr address) {
    return(table[ find_address(address) ].ackexpected);
}

static void inc_ackexpected(CnetAddr address) {
    table[ find_address(address) ].ackexpected++;
}

static int nextframetosend(CnetAddr address) {
    return(table[ find_address(address) ].nextframetosend++);
}

static int frameexpected(CnetAddr address) {
    return(table[ find_address(address) ].frameexpected);
}

static void inc_frameexpected(CnetAddr address) {
    table[ find_address(address) ].frameexpected++;
}

static void NL_stats(CnetAddr address, int hops, int ms)
{
    int	t;

    t = find_address(address);
    table[t].totalhops	+= hops;
    table[t].totaltime	+= ms;
}
			 
static void show_table(CnetEvent ev, CnetTimer timer, CnetData data)
{
    int	t, n;

    CNET_clear();
    printf("\n%16s %16s %16s %16s\n",
	    "destination","frames-ack'ed","round-trip-hops","round-trip-time");
    for(t=0 ; t<table_size ; ++t)
	if((n = table[t].ackexpected) > 0)
	    printf("%16d %16d %16.1f %16ld\n",
		    (int)table[t].address, table[t].ackexpected,
		    (float)table[t].totalhops / n,
		    table[t].totaltime / n);
}


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

static int  down_to_datalink 	_PARAMS((int, char *, int));

static void selective_flood(char *packet, int length, int links_wanted)
{
    int	   link;

    for(link=1 ; link<=nodeinfo.nlinks ; ++link)
	if( links_wanted & (1<<link) )
	    CHECK(down_to_datalink(link, packet, length));
}
			
static void down_to_network(CnetEvent ev, CnetTimer timer, CnetData data)
{
    NL_PACKET	p;

    p.length	= sizeof(p.msg);
    CHECK(CNET_read_application(&p.dest, p.msg, &p.length));
    CNET_disable_application(p.dest);

    p.src	= nodeinfo.address;
    p.kind	= NL_DATA;
    p.hopsleft	= MAXHOPS;
    p.hopstaken	= 1;
    p.timesent	= nodeinfo.time_in_ms;
    p.seqno	= nextframetosend(p.dest);

    selective_flood((char *)&p, PACKET_SIZE(p), ALL_LINKS);
}


static int up_to_network(char *packet, int length, int arrived_on)
{
    NL_PACKET	*p;
    CnetAddr	addr;

    p	= (NL_PACKET *)packet;

    if(p->dest == nodeinfo.address) {		/*  THIS PACKET IS FOR ME */
	if(p->kind == NL_DATA && p->seqno == frameexpected(p->src)) {
	    length	= p->length;
	    CHECK(CNET_write_application(p->msg, &length));
	    inc_frameexpected(p->src);

	    addr	= p->src;	/* transform NL_DATA into NL_ACK */
	    p->src	= p->dest;
	    p->dest	= addr;

	    p->kind	= NL_ACK;
	    p->hopsleft	= MAXHOPS;
	    p->length	= 0;
	    p->hopstaken++;
	    selective_flood(packet, PACKET_HEADER_SIZE, (1<<arrived_on) );
	}
	else if(p->kind == NL_ACK && p->seqno == ackexpected(p->src)) {
	    CNET_enable_application(p->src);
	    inc_ackexpected(p->src);
	    NL_stats(p->src,p->hopstaken,nodeinfo.time_in_ms-p->timesent);
	}
    }
    else {				/* THIS PACKET IS FOR SOMEONE ELSE */
	if(--p->hopsleft > 0) {		/* send it back out again */
	    p->hopstaken++;
	    selective_flood(packet, length, ALL_LINKS & ~(1<<arrived_on) );
	}
    }
    return(0);
}


/* ------------- A MINIMAL RELIABLE DATALINK LAYER FOLLOWS -------------- */


typedef struct {
    /* as we use a reliable datalink, we don't need any other fields */
    char        packet[sizeof(NL_PACKET)];
} DLL_FRAME;


static int down_to_datalink(int link, char *packet, int length)
{
    CHECK(CNET_write_physical_reliable(link, (char *)packet, &length));
    return(0);
}

static void up_to_datalink(CnetEvent ev, CnetTimer timer, CnetData data)
{
    DLL_FRAME	f;
    int		link, length;

    length	= sizeof(DLL_FRAME);
    CHECK(CNET_read_physical(&link, (char *)&f, &length));

    CHECK(up_to_network(f.packet, length, link));
}

static void init_DLL()
{
    /* nothing to do! */
}


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

void reboot_node(CnetEvent ev, CnetTimer timer, CnetData data)
{
    if(nodeinfo.nlinks > 32) {
	fprintf(stderr,"selective flooding will not work here\n");
	exit(1);
    }

    CHECK(CNET_set_handler(EV_APPLICATIONREADY, down_to_network, 0));
    CHECK(CNET_set_handler(EV_PHYSICALREADY,    up_to_datalink, 0));
    CHECK(CNET_set_handler(EV_DEBUG1,           show_table, 0));

    CHECK(CNET_set_debug_string( EV_DEBUG1, "NL info"));

    init_DLL();
    init_NL();

    CNET_enable_application(ALLNODES);
}
