/*
	Runtime Engine
		By Craig Kadziolka ( Nov 1999 - Dec 2000 )

	Modifications and copying is permitted providing this header
	is retained
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "ithreads.h"
#include "Types.h"
#include "idle.h"
#include "context.h"
#include "memory.h"
#include "findme.h"
#include <assert.h>
#include "Synchronization.h"
#include "InternalNet.h" /* internal networking header */
#include "events.h"

extern struct Thread_Node * Wait_Queue [ ]; 

struct Thread_Node * current_thread; /* Keeps track of whos running */

bool wait_queue_handler(EventID e)
{
	struct Thread_Node * scan;
	int count;

	printf("Event we are waiting for has occurred\n");
	
	count = 0;
	scan = Wait_Queue[ e ];

	while (scan != NULL) {
		count++;
		scan = scan -> next;
	}

	printf(" There are %u threads waiting for this event\n", count);

	/* FIXME: Dont put the waiting threads back on the default priority,
		put them on their own proper priorities */
	scan = PQH(DEFAULT_PRIORITY);

	/* So we want to concatenate the waiting threads to the tail of the
		thread queue. Then null out this Wait Queue */

	if (scan != NULL) 
		scan -> next = Wait_Queue [ e ];
	else
		PQT(DEFAULT_PRIORITY) = Wait_Queue [ e ];

	Wait_Queue [ e ] -> previous = scan;
	PQH(DEFAULT_PRIORITY) = Wait_Queue [ e ];

}

/* Wait for the specified event to occur */
void Wait ( EventID what )
{
	struct Thread_Node * me;
	struct EventIDNode * theevent;

	printf("Wait called!\n");

//  	me = (TNPTR) (((int) Find_Me()) + SAVE_SIZE);
  	me = (TNPTR) ((int) Find_Me() + 208); /* More Problems! */
	printf("Size of thread node = %d\n", sizeof( struct Thread_Node ));

	/* theevent -> wait_q is the queue where we will stick our
	 * current thread */

	/* Unlink Thread_Node * me from the thread queue */
	if (PQH(me->Priority) == PQT(me->Priority)) 
		PQH(me->Priority) = PQT(me->Priority) = NULL;
	else {

		if (PQH(me->Priority) == me) {
			PQH(me->Priority) = me->next;
		} else {
			me->previous->next = me->next;
		}
	}

	me -> next = Wait_Queue [ what ]; 

	if (Wait_Queue [ what ] != NULL) {
		/* If theres already another thread waiting for the event */
		Wait_Queue [ what ] -> previous = me; 
		/* then keep our backwards links too */
	} else {
		me -> previous = NULL;
	}

	Wait_Queue [ what ]  = me; 

	add_handler ( what, wait_queue_handler );

	Suspend();
	
}

/* Suspends the currently executing thread, allowing other threads
   to have a turn at the cpu */
void Suspend()
{

  disable_interrupts();
  Sync_Save_Context();
  enable_interrupts();

}

void Dump_Thread_Queue()
{
  int p;
  TNPTR scan;
  
  for (p = 0; p < PRIORITY_LEVELS; p++) {

    printf("DUMPING THREADS ON PRIORITY %d\n", p);
    printf("==============================\n");
    
    printf("HEAD: %d   TAIL: %d \n", PQH(p), PQT(p));

    scan = PQH(p);
    
    while (scan != NULL) {

      printf("Thread node %d \n", scan);
      scan = scan -> previous;

    }
    
    printf("End of priority queue\n");
  
  }

  printf("End Of Threads \n\n");

}

void Thread_Closing()
{

  TNPTR me;
  UInt32 priority, loopvar, priority_mark;

  disable_interrupts();

  /* Find the previously executing thread */
  me = (TNPTR) (((int) Find_Me()) + SAVE_SIZE);

  /* Remove the Thread from the thread queue */

  if (me->next != NULL) {
    printf("IMPLEMENT me->next!=NULL in Thread_Closing()\n");
    printf("Where i am %d and my next is %d \n", me, me->next); 
    Dump_Thread_Queue();
    exit(1);
  }

  priority = me->Priority;

  if (me->previous == NULL) {
    PQH(priority) = NULL;
    PQT(priority) = NULL; /*DEBUG CK 15/3/00 10:41pm */
  } else {
    PQH(priority) = PQH(priority)->previous;
    PQH(priority)->next = NULL;
  }
    
  /* Queue the thread for destruction */
 
  /* Note that the disposal queue does not use the next pointer
     it is just a single linked list */
  if (Disposal_Queue != NULL) {
    me -> previous = Disposal_Queue;
    Disposal_Queue = me;
  } else {
    Disposal_Queue = me;
    me -> previous = NULL;
  }    
  
  /* Begin work on the next thread */

  /* NOTE: By doing a full schedule here, on thread loses a turn
     every time a thread dies. This can cause lingering threads */

  for (loopvar = 0; loopvar < PRIORITY_LEVELS; loopvar++) {

    if (PQH(loopvar) != NULL) {
      priority_mark = loopvar;
      loopvar = PRIORITY_LEVELS; /* Get us out of here */
    }
    
  }

  /* Interrupts will be reenabled immediately after the context switch */
  Load_Context(PQH(priority_mark)->save_area_base);
   
}

///////////////////////////////////////////////////////
//  Function:  INVARIANT                             //
// ----------------------                            //
// This function is only included if debugging is    //
// defined.  It ensures that the DQueue that is the  //
// thread queue is valid and properly linked in by   //
// running in both directions and making sure it can //
// reach the head from the tail and visa versa.      //
///////////////////////////////////////////////////////
#ifdef DEBUG

void Invariant()
{

  TNPTR scan;
  int current_priority;

  for (current_priority = 0; current_priority < PRIORITY_LEVELS;
       current_priority ++) {

    scan = PQH(current_priority);

    // Scan down the list to the tail.
    while (scan != PQT(current_priority)) {
      scan = scan -> previous;
      assert(scan != NULL);
    }
    
    // Scan back up the list to the head.
    while (scan != PQH(current_priority)) {
      scan = scan -> next;
      assert (scan != NULL);
    }
    
  }
  
}
#endif // - DEBUG.  Function Invariant is only included if debugging.

/* 

   Destroy()

   This function iterates through the destroy queue, performing
   time consuming housework.
   
   This function is only called from the idle loop and the create
   thread function.  The idle loop, to utilise maximum cpu time possible
   and the create function, as its doing a time consuming function 
   anyway.

   Its possible that this will change, and only be called from create
   thread if create thread runs out of memory....
   
 */
void Destroy()
{

  TN *scan, *last;
  Address location;

  if (Disposal_Queue == NULL) return;

  if (Disposal_Queue->previous == NULL && 
      Disposal_Queue->Handlers_Registered == 0) {
    
    /* perform destruction on the only thread in the queue */
    location = (Address) ((int) Disposal_Queue) - SAVE_SIZE - STACK_SIZE;
    deallocate(location);

    Disposal_Queue = NULL;
    
    return;
  };
  
  last = Disposal_Queue; scan = last->previous;
  
  while ( scan != NULL ) {
    
#ifdef DEBUG
    assert (scan = last->previous);
#endif
    
    if (scan -> Handlers_Registered == 0) {
      
      location = (Address) ((int) scan) - SAVE_SIZE - STACK_SIZE;

      scan = scan -> previous;
      
      if (scan == NULL)
	last -> previous = NULL;
      else
	last -> previous = scan;
      
      /* once we have completely finished with location we free
	 the memory */
      deallocate(location);
    
    } else
      
      last = last -> previous; scan = last -> previous;
    
  }

}

void ShutdownRuntime()
{
  
  /* Perform any necessary shutdown procedures */

  finialize_networking();
  
  /* And finally finish up */
  exit(0);

}
