// -*-	Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*-
/*
# Copyright (c) 2000-2001 by the University of Southern California.
# All rights reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation in source and binary forms for non-commercial purposes and
# without fee is hereby granted, provided that the above copyright notice
# appear in all copies and that both the copyright notice and this permission
# notice appear in supporting documentation, and that any documentation,
# advertising materials, and other materials related to such distribution and
# use acknowledge that the software was developed by the University of
# Southern California, Information Sciences Institute.  The name of the
# University may not be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THE UNIVERSITY OF SOUTHERN CALIFORNIA MAKES NO REPRESENTATIONS ABOUT THE
# SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE.  THIS SOFTWARE IS PROVIDED "AS
# IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
# LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE.
#
# Other copyrights might apply to parts of this software and are so noted when
# applicable.
# ----------------------------------------------------------------------------
#
# Primary author: Purushotham Kamath
#                 January 2001

#This material is based upon work supported by the Defense Advanced Research
#Projects Agency Information Technology Office, (NGI Multicast Applications 
#and Architecture (NMAA-ADDON), ARPA Order No. H645, Program Code No. 9A20, 
#Issued by DARPA/CMD under Contract No. MDA972-99-C-0022.
*/

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <math.h>
#include <float.h>

#include <eventhandler.h>
#include <scheduler.h>
#include <debug.h>


//Function to compare two timestamps (doubles).
inline int areequal(double d1, double d2)
{
//The epsilon is 1 picosecond
	double epsilon = 	1E-12;
	if(fabs(d1 - d2) <= epsilon)
		return 1;
	else
		return 0;
}

int Scheduler:: insert(EventHandler *e1, EventHandler *e2)
{
//Insert e2 after e1
	if (e1 == NULL)  //Insert as head node
	{
		dprintf("scheduler.cc: Schedule::insert(): Event due before head\n");	

		if(head != NULL)  //There may already be a head node
		{
			e2->next = head;	
			head->prev = e2;
		}
		else							//There was no head node
			e2->next  = NULL;

		head = e2;
		e2->prev = NULL;
		return 1;
	}



	e2->next = e1->next;

	if(e1->next != NULL)
	{
		dprintf("scheduler.cc: Schedule::insert(): Event due between other events\n");	
		e1->next->prev = e2;
	}
	else
		dprintf("scheduler.cc: Schedule::insert(): Event due after tail\n");	

	e1->next= e2;
	e2->prev = e1;
	return 1;

}

double Scheduler::start = 0;

Scheduler::Scheduler() : head(NULL)
{
	currenttime = 0;
	halt = 0;
}



Scheduler::~Scheduler()
{


}

//Main scheduler function. Removes the first eventhandler from the 
//eventhandler queue and calls it handle_event() function. Updates 
//the current time.
int Scheduler:: run()
{
	dprintf("scheduler.cc: Schedule::run(): Entered function\n");



	if(!head) 
		return 0;


	//Set current time to the event time.
	currenttime = head->when;	
	

	EventHandler *tmphead = head;	
	//Remove event from queue, if no more events to schedule return FALSE
	if(head->next == NULL)
	{
		head = NULL;
	}
	else
	{
		head = head->next;
		head->prev = NULL;
	}
	
	//Call event handler, user is responsible for deleting head.
	tmphead->handleevent();
	return 1;
}


//Event handling registration function. Event handler's register themselves
//with the scheduler in order to be scheduled. The scheduleevent function 
//adds the eventhandler to the queue in the appropriate place. The queue 
//is maintained in the order of scheduling.
void Scheduler:: scheduleevent(EventHandler *eh)
{

	dprintf("scheduler.cc: Schedule::scheduleevent(): Entered function\n");	
/*	if(halt != 0)
	{
		printf("scheduler.cc: Schedule::scheduleevent(): Scheduler halted. No more new events will be scheduled. Pending events will be scheduled\n");
		return;
	}
*/

//Store times as absolute times.
	eh->when  = currenttime + eh->latency;
	dprintf("scheduler.cc: Schedule::scheduleevent(): Current time: %.12f\n", currenttime);
	dprintf("scheduler.cc: Schedule::scheduleevent(): Scheduling delay: %.12f\n", eh->latency);



	//There are no elements in the queue.
	if(head == NULL)
	{
		dprintf("scheduler.cc: Schedule::scheduleevent(): No elements in the queue\n");	
		dprintf("scheduler.cc: Schedule::scheduleevent(): Scheduling event for: %.12f\n", eh->when);
		insert(head, eh);		
		return;
	}


	EventHandler *e = head;
	dprintf("scheduler.cc: Schedule::scheduleevent(): Scheduling event for (eh): %.12f\n", eh->when);
	dprintf("scheduler.cc: Schedule::scheduleevent(): Scheduling priority (eh): %d\n", eh->priority);
	

	while(e != NULL)
	{
		dprintf("scheduler.cc: Schedule::scheduleevent(): Event scheduled for (e): %.12f\n", e->when);
		dprintf("scheduler.cc: Schedule::scheduleevent(): Event priority (e): %d\n", e->priority);


//Note: If there is already an event scheduled at the same time as the 
//event to be scheduled, then the event to be scheduled is added 
//infront/behind it depending on the relative priorities.

//Check if two events need to be scheduled at the same time
		if(areequal(eh->when, e->when))
		{
			if(eh->priority <= e->priority)
			{
				dprintf("scheduler.cc: Schedule::scheduleevent(): Event due at the same time, different priority\n");
				if(e->next == NULL)
				{
					dprintf("scheduler.cc: Schedule::scheduleevent(): Event due after tail event\n");	
					insert(e, eh);	
					break;
				}
				e=e->next;
				continue;
			}
			else
			{
				dprintf("scheduler.cc: Schedule::scheduleevent(): Event due at the same time, same priority\n");

				EventHandler *first = e;
				EventHandler *etemp = e;
				int ecount = 0;

//Count the number of event of the same priority, 
//scheduled at the same time.
				while ((etemp->priority == first->priority ) &&(etemp->next != NULL))
				{
					etemp = etemp->next;
					ecount++;
				}

				dprintf("scheduler.cc: Schedule::scheduleevent(): Found %d events of the same priority scheduled at the same time\n", ecount);	

#ifdef SCHEDULING_RANDOM
//Select a random number from 0 to ehcount and insert the new event in that position.
				int rnd = (int)(floor((random()/(pow(2, 31) -1)) *ecount));
				dprintf("scheduler.cc: Schedule::scheduleevent(): Inserting event after %d events\n", rnd);

				for(int count=0; count < rnd; count++)
					e=e->next;
				insert (e->prev, eh);
				break;

#else
				insert (e->prev, eh);
				break;
#endif

			}
		}
//End of code for events scheduled at the same time


		if(eh->when < e->when) 
		{
			dprintf("scheduler.cc: Schedule::scheduleevent(): Event due between other events\n");	
			insert(e->prev, eh);
			break;
		}

		if(e->next == NULL)
		{
			dprintf("scheduler.cc: Schedule::scheduleevent(): Event due after tail event\n");	
			insert(e, eh);	
			break;
		}
		e = e->next;
	}
}

void Scheduler::dprintf(char * format, ...)
{
#ifdef DEBUG
	printf("%.12f:", currenttime);
	va_list ap;
	va_start(ap, format);
	vprintf(format, ap);
	fflush(stdout);
#endif
}
