/* these are needed for socket calls */
#include <iostream.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdarg.h>
#include "UCTPacketMgmt.h"
#include "UCTGlobal.h"

extern void LocalProcessMsg(Tuple*);

int SD;  //stores the server socket descriptor

/* UCTConnectToTM() is called by each worker from within UCTInit().
   This will simply establish a connection between the worker and the
   UCTuple manager. */

void UCTConnectToTM() {
   struct sockaddr_in Addr;
   struct hostent *HostPtr;

   /* open a socket */
   if ((SD = socket(AF_INET,SOCK_STREAM,0)) < 0)
     Error("Failure Opening server socket from client.");

   /* set up for an Internet connection to the host whose 
      name was specified by the user on the command line */
   Addr.sin_family = AF_INET;
   Addr.sin_port = UCTPortNum;
   /* get IP address of host */
   if (!(HostPtr = gethostbyname(UCTTMName)))
     Error("Failure getting server's host info.");

   memcpy(&Addr.sin_addr.s_addr,
      HostPtr->h_addr_list[0],HostPtr->h_length);

   /* OK, now connect */
   while(connect(SD,(struct sockaddr *) &Addr,sizeof(Addr)) < 0) {
     close(SD);
     if ((SD = socket(AF_INET, SOCK_STREAM, 0)) == -1)
       Error("Error opening manager socket from client.\n");
   }
}

/* out() is a function called by the user application.  This will
   accept a variable number of arguments, representing a UCTuple to
   be written to the UCTuple space.  This is achieved by calling
   the function EncodePacketClient() to build a packet describing
   the UCTuple, which is then delivered to the UCTuple manager. We
   then loop until we receive the packet receipt confirmation from
   the server. */

void out (char* key, ...) {
  va_list ap1, ap2;	            //set up variable argument structures
  int keysize = strlen(key);	//stores the size of the UCTuple key
  char dummy[5];	            //dummy buffer for receipt confirmation
  Tuple * t;

  va_start(ap1, key);  // initialize var arguments
  va_start(ap2, key);

  /* Encode the UCTuple and deliver to the UCTuple manager. */
  t = EncodePacketClnt(OUT, key, keysize, ap1, ap2);

  if (key[0] == 'r') {
    /* Wait for confirmation of packet receipt from UCTuple manager. */
    ReadData(SD, dummy, 3);
  }
  else LocalProcessMsg(t);

  va_end(ap1);
  va_end(ap2);
}

/* in() is a function called by the user application.  This will
   accept a variable number of arguments, representing a UCTuple to
   be requested from the UCTuple space.  This is achieved by calling
   the function EncodePacketClient() to build a packet describing
   the UCTuple, which is then delivered to the UCTuple manager. The
   manager will search the UCTuple space to locate a match on this
   UCTuple. If such a match does not yet exist, the server will
   block the transaction until such a match is available.  Once found,
   the server transmits only the data requested by the worker to be
   read.  This data is then copied to the addresses specified by "p"
   elements in the initial UCTuple sent by the worker. */

void in (char* key, ...) {
  va_list ap1, ap2;
  int keysize = strlen(key);
  Tuple * t;

  va_start(ap1, key);  // initialize var arguments
  va_start(ap2, key);

  /* Build the packet to be sent to the manager */
  t = EncodePacketClnt(IN, key, keysize, ap1, ap2);

  if (key[0] == 'r') {
    /* Packet has now been sent and decoded by manager, wait for response */
    ReadData(SD, HeaderBuffer, headersize);

    /* We have the header, decode it, receive the data packet */
    DecodePacketClnt();
  }
  else {
    LocalProcessMsg(t);
  }

  va_end(ap1);
  va_end(ap2);
}

/* rd() is identical to in() in functionality.  The only difference
   is that it is non-destructive, and will not remove the requested
   UCTuple from the UCTuple space. */

void rd (char* key, ...) {
  va_list ap1, ap2;
  int keysize = strlen(key);
  Tuple * t;

  va_start(ap1, key);  // initialize var args
  va_start(ap2, key);

  /* Build the packet to be sent to the manager */
  t = EncodePacketClnt(RD, key, keysize, ap1, ap2);

  if (key[0] == 'r') {
    /* Packet has been sent and decoded by manager, wait for response */
    ReadData(SD, HeaderBuffer, headersize);
  
    /* We have the header, decode it, receive the data packet */
    DecodePacketClnt();
  }
  else LocalProcessMsg(t);

  va_end(ap1);
  va_end(ap2);
}

/* tmexec() is called by the worker to execute a remote function.  This
   function will be executed locally at the manager, without initiating
   communications with the worker except for the return data. */

void tmexec (void* func, char* inArgs, int inLen, char* outArgs, int* outLen) {
  char *buffer;  //buffer for the packet to send
  char *index;   //index to current write point in buffer
  int datasize, op; 

  //calculate the length of data to send
  datasize = inLen + addrsize;
   
  //set the opcode
  op = EXEC; 

  //allocate a buffer and init index 
  index = buffer = new char[headersize + datasize];
   
  //copy the size into header
  memcpy(index, &datasize, intsize);
   
  //adjust the index pointer
  index += intsize;

  //copy the opcode into the header
  memcpy(index, &op, intsize);
  
  //adjust the index pointer
  index += intsize;

  //copy function pointer into data buffer
  memcpy(index, &func, addrsize);
   
  //adjust the index pointer
  index += addrsize;

  //copy input arguments to data buffer
  memcpy(index, inArgs, inLen);

  //send to manager
  WriteData(SD, buffer, (headersize + datasize));
   
  //wait for response
  ReadData(SD, buffer, intsize);
   
  //copy length of output arguments into output argument
  memcpy(outLen, buffer, intsize);

  //deallocate our buffer
  delete[] buffer;

  //read return argument into the user allocated buffer 
  ReadData(SD, outArgs, *outLen);
}
