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

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

#include "InternalNet.h"

/* Common Interfaces */
#include "Network.h"
#include "events.h"

/* Low level threading header */
#include "ithreads.h"

/* Other Required Interfaces */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

/* Stores the information about the local host computer. */
char * hostname;
int hostnamelen;
int hostpid;

/* The linux given handles to each socket. */
int send_socket;
int recv_socket;

/* Because htons(...) is costly these are caches for it */
unsigned short int send_port; 
unsigned short int recv_port;

/* Counts which local_id we are up to */
int lookup_counter = 0;

bool program_ready_to_finish;

/* The handle which corresponds to the network manager thread */ 
Handle network_manager;

#define MAX_MESSAGE_SIZE 1024
#define MESSAGE_HEADER_SIZE 28
#define MAX_CONFIG_FILE_SIZE 1024

#if MAX_MESSAGE_SIZE <= MESSAGE_HEADER_SIZE
#error "MAX_MESSAGE_SIZE LOWER THAN HEADER SIZE!"
#endif

struct LowLevel_Message {
  
  char  Dest_IP[4];
  int   Dest_PID;
  int   Dest_TID;
  char  Src_IP[4];
  int   Src_PID;
  int   Src_TID;
  int   Msg_Len;
  char  Message[MAX_MESSAGE_SIZE - MESSAGE_HEADER_SIZE];

};

Object o;

void dump_remote_table()
{
  int loop;

  printf("--------------------\nDumping remote table\n--------------------\n");
 
  for (loop = 1; loop <= lookup_counter; loop++) {

    printf("Host named %s\n", lookup[loop].ip_addr.address);
    printf(" On PID %d\n", lookup[loop].pid);
    printf("  On TID %d\n", lookup[loop].tid);

  }
 
}

void send_message(Local_Handle dest, struct Message *sendme)
{
  
  struct hostent * hp;
  struct LowLevel_Message * msg;

  msg = (struct LowLevel_Message *) allocate (sizeof(struct LowLevel_Message));

#ifdef DEBUG
  printf("Sending a message\n");
#endif  

  memcpy(msg->Dest_IP, &lookup[dest].hardware_addr.sin_addr, 4);
  msg->Dest_PID = lookup[dest].pid;
  msg->Dest_TID = lookup[dest].tid; 
  
  /* Now the destinations filled in, its time to fill in the source */

  hp = gethostbyname(hostname);
  memcpy(msg->Src_IP, hp->h_addr_list[0], 4);
  assert (hp->h_length == 4);
  
  msg->Src_PID = hostpid;

  if (lookup[dest].tid == 0) {
    msg->Src_TID = network_manager;   
  } else {
    msg->Src_TID = Find_Me();
  }
  
  /* Fill in the actual message length */
  msg->Msg_Len = sendme->length;

  /* And now the actual message */
  memcpy (msg->Message,
  	  sendme->message,
  	  sendme->length);
 
  /* Now send a message to this rte, telling them that we are
     ready for action */

  sendto(send_socket, msg, 1024, 0,  
	  (struct sockaddr *)&lookup[dest].hardware_addr, 
	  sizeof(lookup[dest].hardware_addr));

   deallocate(msg);
}

void listen_thread()
{

  /* Should probably put this stuff into a structure */
  int src_length;
  char *msg; int len;
  struct sockaddr_in *from; int fromlen;
  int loop; bool found; int src_handle;
  struct Message * recvd;
  struct LowLevel_Message *lmsg;
  
 start:

  len = 1500;
  src_length = 0;
  found = false;
 
  msg = (char *) allocate (len);

  from = (struct sockaddr_in *) allocate ( 1024 );
  memset(from, 0, 1024);
  fromlen = 1024;

#ifdef DEBUG
  printf("Waiting for a message\n");
#endif

  while (recvfrom(recv_socket, msg, len, 0, (struct sockaddr *) from, 
		&fromlen) == -1) {

    /* Until we have data we suspend our thread */
    Suspend();
    
  }

  /* Now we have recieved a message on the socket we... */

#ifdef DEBUG
  printf("Have received a message....\n");
#endif  

  lmsg = (struct LowLevel_Message *) msg;

#ifdef DEBUG
  printf("Dest PID %d\n", lmsg->Dest_PID);
  printf("Dest TID %d\n", lmsg->Dest_TID);

  printf("Src PID %d\n", lmsg->Src_PID);
  printf("Src TID %d\n", lmsg->Src_TID);
#endif

  /* 1.  check to see if the computer exists in our computer list. 
     If not then we add it to the list. */
  
  for (loop = 1; loop <= lookup_counter; loop++) {
    
#ifdef DEBUG
    printf("Iterating a lookup loop: loop = %d, lookup_counter = %d \n",
	   loop, lookup_counter);
#endif    

    //    if (lookup[loop].ip_addr.length == src_length) {
      
    //    printf("Found an entry with a matching ip_addr length!\n");
      
    if (memcmp(&lookup[loop].hardware_addr.sin_addr, 
	       lmsg->Src_IP, 4) == 0) {
#ifdef DEBUG
      printf("Found an entry with a matching ip_addr!\n");
#endif

      if (lookup[loop].pid == lmsg->Src_PID) {
	
	if (lookup[loop].tid == lmsg->Src_TID) {
	  
	  found = true;
	  src_handle = loop;
	  
	}
	
      }      
    } 
    
  }

  recvd = (struct Message *) allocate (sizeof (struct Message));
  
  /* FIXME:
     should probably make the table into a table of pointers,
     so its only one instruction to add an entry and doesnt
     need to disable interrupts 
  */
  /* If we didnt find it, its time to add it to our table */
  if (!found) {

    struct hostent * hp;

    lookup_counter ++ ;
    
    /* Check that the lookup_counter < MAX_REMOTE_RTES */

    hp = gethostbyaddr(lmsg->Src_IP, 4, AF_INET);

    /* 
       Copy the text address into the slots in the lookup structure
       in case someone asks for the runtime by name.    
     */
    lookup[lookup_counter].ip_addr.length = strlen(hp->h_name);
    lookup[lookup_counter].ip_addr.address = 
      (char *) allocate (strlen(hp->h_name));
    memcpy(lookup[lookup_counter].ip_addr.address,
	   hp->h_name, strlen(hp->h_name));

    /* FIXME: This might not be efficient.  much changes here */
    
    lookup[lookup_counter].hardware_addr.sin_family = AF_INET; 
    lookup[lookup_counter].hardware_addr.sin_port = recv_port;
    bcopy(hp->h_addr_list[0],(char *)&lookup[lookup_counter].hardware_addr.sin_addr,hp->h_length);

    lookup[lookup_counter].tid = lmsg->Src_TID;
    lookup[lookup_counter].pid = lmsg->Src_PID;
    
    recvd -> source = lookup_counter;
          
  } else recvd -> source = src_handle;

  /* 2.  lookup the handle that the message is destined for.  
     generate the appropriate event. */

  /* Dispatch to the thread that the message is destined. */
  
  /* With some shrewd reorganizing of the Message structure
     and the LowLevel_Message structure it may become possible
     to fill in the event passed message with a single 
     memcpy and set, with only one allocate call, and only
     one set call.  saving an allocate and a set */
  
  recvd -> length = lmsg->Msg_Len;
  recvd -> message = (char *) allocate (recvd -> length);
  memcpy (recvd->message, 
	  lmsg->Message,
	  recvd -> length);
  
  if (lmsg->Dest_TID != 0) {
#ifdef DEBUG
    printf("Dest threadid : %d\n", lmsg->Dest_TID);
#endif
    o = (Object) recvd;
    cause_event(((struct Thread_Node *) (((int) lmsg->Dest_TID) + SAVE_SIZE))->Message_Arrived);

  } else {

    cause_event( (EventID) recvd);

  }

  Suspend();
  goto start; /* BIG FIXME: This doesnt really need to be a goto :-) */
    
}

int open_socket(int port)
{

  int udp_socket;
  struct sockaddr_in myself;
  struct hostent * hp;

  /* set up a udp socket */
#ifdef __BEOS__
	/* Must fill out the beos equilivilant here */

#else
  udp_socket = socket(PF_INET, SOCK_DGRAM, 0);
#endif

  /* Set the socket to be non-blocking so we can allow other
     threads to execute while we wait for a message */
  fcntl(udp_socket, F_SETFL, O_NONBLOCK);

  hp = gethostbyname(hostname);

	if (!hp) {
		printf("Unable to open socket!\n");
		return -1; /* Propogate error condition to caller */
	} 
 
  /* set up the myself data structure */
  myself.sin_family = AF_INET; 
  myself.sin_port = port;

  bcopy(hp->h_addr_list[0],(char *)&myself.sin_addr,hp->h_length);	

  /* FIXME:

     Quite possible that bind is no longer needed, as its ok for the
     ports to be holes in the wall. Now the message itself contains the 
     source and destination details its no longer required to strip
     the source off the UDP packet (which doesnt work properly in
     ip-masquerading anyhow...)
     
   */
  if (bind(udp_socket, (struct sockaddr *)&myself, sizeof(myself))==-1) {

    printf("Error occurred trying to bind to socket. \n");
    return -1; /* Error condition propagates to the caller */
    
  }

  return udp_socket;
  
}

/*

  This is the handler for packets destined for the actual
  network manager.  Things to do with initial booting of
  computers and that sort of thing.


	Origionally this was :
bool nm_handler(Object o, Handle h, EventID e, int i, void ** a)
	but this does not agree with the new event mechanisim system.
	events may not have parameters!
*/
bool nm_handler(EventID e)
{
  struct Message * ptr;

#ifdef DEBUG
  printf("nm_handler called\n");
#endif

  ptr = (struct Message *) o;

#ifdef DEBUG
  printf("Message length = %d\n", ptr -> length);
#endif  

  if (ptr -> length != 1) {
    printf("Invalid message caught by the network manager\n Discarding...");
    return true; /* could possibly return false here and let the default
		    handler make any error condition setting... */
  }

  if (ptr -> message[0] == 1) {
#ifdef DEBUG
    printf("Someones registering...\n");
#endif
    /* Someones registering a computer */
    ptr -> message[0] = 2;
    send_message(ptr->source, ptr);
  } else if (ptr -> message[0] == 2) {
#ifdef DEBUG
    printf("Registration sucessful.\n");
#endif

  } else {
    printf("Error!. uncertian format  got a :%d:\n", ptr->message[0]);
  }

  return true;
  
}

void register_msg_handler(Handle thread, Handler newhandler)
{
  
  struct Thread_Node * info_block;
  
  info_block = (struct Thread_Node *) (((int) network_manager) + SAVE_SIZE);  

  info_block->Message_Arrived = add_event();

  /* There is no real object at the moment.... maybe added later */
  add_handler(info_block->Message_Arrived, (Handler) nm_handler);

}     

/*

  initialize_networking
  
 */
void initialize_networking(bool boot_cpu)
{

  send_port = htons(SEND_PORT); /* For performance this is never done again */
  recv_port = htons(LISTEN_PORT);

  /* Setup the computers hostname values */
  hostnamelen = 254;
  hostname = (char *) allocate (hostnamelen);
  gethostname(hostname, hostnamelen);
  hostnamelen = strlen(hostname);

  send_socket = open_socket(send_port);
  recv_socket = open_socket(recv_port);
  if (send_socket == -1 || recv_socket == -1) {
	  /* We cannot initialize the networking system */
#ifdef DEBUG
	  printf("Not initializing distributed extensions, could not open a required socket\n");
#endif
	  return;
  }
  
  hostpid = getpid();

#ifdef DEBUG
  printf("Initializing runtime on PID : %d\n", hostpid);
#endif

  /* We will need to initialize a handler for network events here,
     because registration events can be expected straight away
     and will be destined for the network manager */
  
  network_manager = Create_Thread(DEFAULT_PRIORITY, 0, listen_thread);
  
  register_msg_handler(network_manager, nm_handler);

  /* Now we have the network managers TID we can display it, so
     other computers can send to it */
#ifdef DEBUG
  printf("Network Manager on thread %d\n", network_manager);
#endif

  if (boot_cpu) {
    
    FILE * config_file;
    char config_buffer [MAX_CONFIG_FILE_SIZE];
    int actual_size;
    char computer[MAX_CONFIG_FILE_SIZE], binary_location[MAX_CONFIG_FILE_SIZE];
    int i=0, j=0;
    char * argv[5];
    
    config_file = fopen("rteconf", "r");

    if (!config_file) {
#ifdef DEBUG
      printf("Could not open config file.  Not starting additional runtimes\n");
#endif
      return;
    }
    
    actual_size = fread(config_buffer, sizeof(char), MAX_CONFIG_FILE_SIZE, config_file);
    
#ifdef DEBUG
    printf("The size of the config file is %d characters\n", actual_size);
#endif

    while (config_buffer[i] != ' ' && config_buffer[i] != '\t' && config_buffer[i] != '\n') {
      computer[i] = config_buffer[i];
      i++;      
    }
    computer[i++] = '\0'; 

    /* FIXME: the binary location is not mandatory so this should be optional */
    while (config_buffer[i] != ' ' && config_buffer[i] != '\t' && config_buffer[i] != '\n') {
      binary_location[j] = config_buffer[i];
      i++; j++;     
    }
    binary_location[j++] = '\0';
    
#ifdef DEBUG
    if (++i != actual_size) 
      printf("IMPLEMENT ME: Multiple hosts i = %d, size = %d\n", i, actual_size);
    
    printf("The computer is : %s\n", computer);
    printf("The binary path is : %s\n", binary_location);

#endif

    argv[0] = computer;
    argv[1] = binary_location;
    argv[2] = "-nostart";
    argv[3] = hostname;
    argv[4] = (char *) allocate (10);
    sprintf(argv[4], "%d", hostpid);
    printf("PID After sprintf is %s\n", argv[4]);
    argv[5] = "0";
    argv[6] = NULL;
    
    /* Now rsh off a process on the specifiec computer with the remote program
      residing at the specified location */
    
    if (fork() == 0) {
      
      if (execve("/usr/bin/rsh", argv, NULL) == -1)
	printf("Error executing rsh!\n");
      
    }

  }
  
}

/*

  Register_with_boot_processor

  This function is used to tell the boot processor (currently
  specified on the command line after the -nostart option.

  There are three distinct parts to this process.  

  1. Since we dont yet know the address of the network manager 
  thread we send a dummy message to thread 0 which is picked up
  in the listen thread and sent to the network manager.

  2. We then set up the message which will be interpreted in the
  network manager as a registration request.

  3. We then move the lookup counter back one, and send to the 
  "dummy" entry, which exists but will be overwritten after the
  function returns.

  Its fairly important that no messages be recieved for the duration
  of this function, and some safeguard should be added.  But in 
  reality noone knows about this node until this function finishes
  the send function at the end of this one anyhow.

 */
void register_with_boot_processor(int argc, char ** argv)
{

  struct hostent * hp;
  register int address_length_cache;
  struct Message reg_comp;
  
  /* Register ourselves with the boot processor */
  
  if (argc < 3) {
    /*
      If someones putting in options into the command line we should
      stop and tell them if they are doing it wrong.
     */
    printf("Invalid use of the nostart option\n");
    exit(1);
  }
  
  /* cache this strlen call .... */
  address_length_cache = strlen(argv[2]);
  
  /* Add these details to the mapping table as if we had received a message 
     from the computer. */

  lookup_counter ++;
  
  hp = gethostbyname(argv[2]);
  
  /* set up the myself data structure */
  lookup[lookup_counter].ip_addr.length = address_length_cache;
  lookup[lookup_counter].ip_addr.address =
    (char *) allocate (address_length_cache);
  memcpy(lookup[lookup_counter].ip_addr.address, 
	 argv[2],
	 lookup[lookup_counter].ip_addr.length);
  
  lookup[lookup_counter].pid = atoi(argv[3]);
  lookup[lookup_counter].tid = 0;

  lookup[lookup_counter].hardware_addr.sin_family = AF_INET; 
  lookup[lookup_counter].hardware_addr.sin_port = recv_port;
  bcopy(hp->h_addr_list[0],(char *)&lookup[lookup_counter].hardware_addr.sin_addr,hp->h_length);

  reg_comp.length = 1;
  reg_comp.message = (char *) allocate (1);
  reg_comp.message[0] = 1; /* FIXME: make an enum.  for now 1 is register*/
  
  send_message(lookup_counter, &reg_comp);
  
  lookup_counter--;  /* Remove the tempory entry. */
  
}

void finialize_networking()
{

  close(send_socket);
  close(recv_socket);

  return;

}

