#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>/* set time to 0 for each process */

#include <time.h>
#include <signal.h>

#include "config.h"
#include "list.h"

#include <search.h>				/* for btree routines */

/* item structure */
struct dbentry {
    char    key[DL_NS_KEYLENGTH];		/* key */
    char    data[DL_NS_DATALENGTH];		/* data*/
};

/**** Maximum number of servers ****/
#define SERV_NUM  2

static struct dbentry *dl_ns_root = NULL;   /* root entry of data tree */
static struct dbentry *name_root = NULL;    /* root entry of name tree */

char other_ns_addr[16];
int other_ns_port = 0;
int vtime;                 /* vector time stamp of servers */
int sigFlag = 0;
Element* List = NULL;          /* list of received changes */
Element* ListIt = NULL;        /* needed for walking through the list */


/***
 *** usage()
 *** Prints a usage message
 ***/
void usage() {
  fprintf( stderr, "\nusage: server <port> [-s ip-address port interval]\n\
\twhen -s is given the program acts as a primary nameserver\n\
\tand contacts the backup server after interval seconds\n\
\trunning at ip-address on port for synchronisation\n\n" );
  exit( 1 );
}

/***
 *** int dl_ns_dbcompare(void *entry1, void *entry2)
 ***
 *** Function: compare two key tree entries
 *** Return: 0  if equal
 ***         >0 if greater
 ***         <0 if smaller
 ***/

int dl_ns_dbcompare(const void *entry1, const void *entry2) {
    return strcmp(((struct dbentry *)entry1)->key,
		    ((struct dbentry *)entry2)->key);
}

/***
 *** int name_compare( void *rntry1, void *entry2 )
 ***
 *** Function: compare two data tree entries using strcmp
 *** Return: 0  if equal
 ***         >0 if greater
 ***         <0 if smaller
 ***/

int name_compare(const void *entry1, const void *entry2 ) {
  return strcmp( ((struct dbentry *)entry1)->data,
		 ((struct dbentry *)entry2)->data );
}

/***
 *** char *parsereq(char *mesg);
 ***
 *** Function: parse a request to the nameserver
 ***           and generate an according response
 *** Return: response string
 *** 
 *** Nameserver protocol:
 *** Requests:
 *** I <key> <data><CR><LF>      insert data into nameserver
 *** D <key><CR><LF>             delete data from nameserver
 *** R <key><CR><LF>             retrieve data from nameserver
 *** P<CR><LF>                   print the namespace
 *** S<CR><LF>                   start synchronisation of namespace
 *** T <time stamp><CR><LF>      time stamp request from other server
 *** TD<CR><LF>                  transmit next entry
 *** N <data><CR><LF>            retrieve key from nameserver
 *** Z<CR><LF>                   reset local time stamp
 *** Q                           quit nameserver
 *** Replies:
 *** ERROR<CR><LF>               general error
 *** OK<CR><LF>                  OK reply
 *** R <key> <data><CR><LF>		 reply to request
 *** N <data> <key><CR><LF>      reply to request
 *** NOTFOUND<CR><LF>            key not found
 *** E<CR><LF>                   last entry transmitted
 ***/

char *parsereq(char *mesg) {

  struct dbentry *tmpptr=NULL,*tmpptr2=NULL;	/* temporary pointer */
  char    **anyptr=NULL;		/* I hate all this conversion ;-) */
  struct dbentry dummydb;		/* dummy for inserts */
  static char returnmsg[DL_MAXLINE];	/* return string */
  char arg1[DL_NS_KEYLENGTH];		/* the key */
  char arg2[DL_NS_DATALENGTH];	/* the data */
  void dl_ns_printit();		/* for twalk */
  int otime;                 /* vector time of other server */  

  /* what command is it ? */
  switch(mesg[0]) {
    
    /*** Insert 'I key data' ***/
  case 'I':
    
    /* parse input line */
    if (sscanf(mesg,"I %s %s\r\n",arg1,arg2) != 2) {
      strcpy(returnmsg,ERRORMSG);
      break;
    }

    Insert( &List, mesg );
    vtime++;
    
    /* insert data into tree */
    tmpptr=(struct dbentry *)malloc(sizeof(struct dbentry));
    if (tmpptr==NULL) {
      strcpy(returnmsg,ERRORMSG);
      break;
    }
    
    strcpy(tmpptr->key,arg1);
    strcpy(tmpptr->data,arg2);
    
    /* find the right place */
    anyptr=tsearch((void *)tmpptr,(void **) &dl_ns_root, \
		   dl_ns_dbcompare);
    
    tmpptr2=(struct dbentry *)(*anyptr);
    
    /* key already in table ? */
    if(tmpptr2 != tmpptr) {
      /* yes, copy new data over old one */
      strcpy(tmpptr2->data,tmpptr->data);
    }
    
    /* find the right place in name tree */
    anyptr = tsearch( (void *)tmpptr, (void **) &name_root, \
		      name_compare );
    
    
    tmpptr2=(struct dbentry *)(*anyptr);
    
    /* name already in table ? */
    if(tmpptr2 != tmpptr) {
      /* yes, copy new data over old one */
      strcpy(tmpptr2->key,tmpptr->key);
      free(tmpptr);
    }
    
    strcpy(returnmsg,OKMSG);
    break;
    
    /*** Delete 'D key' ***/
    
  case 'D':
    
    /* parse input line */
    if (sscanf(mesg,"D %s\r\n", arg1) != 1) {
      strcpy(returnmsg,ERRORMSG);
      break;
    }
    
    if( !dl_ns_root ) {
      printf( "Trying to delete from an empty tree\n" );
      strcpy( returnmsg, ERRORMSG );
      strcat( returnmsg, "Cannot delete from an empty table\r\n" );
      break;
    }

    Insert( &List, mesg );
    vtime++;

    strcpy(dummydb.key, arg1 );
    // find name for key which should be deleted
    anyptr = tfind( (void *)&dummydb, (void **) &dl_ns_root, \
		    dl_ns_dbcompare );

    tmpptr = (struct dbentry *)(*anyptr);
    strcpy( dummydb.data, tmpptr->data );

    anyptr = tdelete((void *)arg1,(void **) &dl_ns_root, \
		   dl_ns_dbcompare);
    
    if( anyptr == NULL ) {
      strcpy(returnmsg,NOTFOUNDMSG);
      break;
    }

    /* delete entry from name table */
    anyptr = tdelete( (void *)&dummydb,(void **) &name_root, \
		      name_compare );
    
    if( anyptr == NULL ) {
      strcpy(returnmsg, NOTFOUNDMSG );
      break;
    }
    
    strcpy(returnmsg,OKMSG);
    break;
    
    /*** Retrieve 'N data' ***/
  case 'N':
    /* parse input line */
    if (sscanf(mesg,"N %s\r\n", arg1) != 1) {
      strcpy(returnmsg,ERRORMSG);
      break;
    }
    
    /* create db entry for request */
    strcpy(dummydb.data,arg1);
    
    anyptr=tfind((void *)&dummydb,(void **) &name_root, \
		 name_compare );
    
    /* we didn't find the entry */
    if (anyptr==NULL) {
      strcpy(returnmsg,NOTFOUNDMSG);
      break;
    }
    
    /* now some little conversion tricks */
    tmpptr=(struct dbentry *)(*anyptr);
    
    sprintf(returnmsg,NAMEMSG,tmpptr->data,tmpptr->key);
    break;
    
    /*** Retrieve 'R key' ***/
    
  case 'R':
    
    /* parse input line */
    if (sscanf(mesg,"R %s\r\n", arg1) != 1) {
      strcpy(returnmsg,ERRORMSG);
      break;
    }
    
    /* create db entry for request */
    strcpy(dummydb.key,arg1);
    
    anyptr=tfind((void *)&dummydb,(void **) &dl_ns_root, \
		 dl_ns_dbcompare);
    
    /* we didn't find the entry */
    if (anyptr==NULL) {
      strcpy(returnmsg,NOTFOUNDMSG);
      break;
    }
    
    /* now some little conversion tricks */
    tmpptr=(struct dbentry *)(*anyptr);
    
    sprintf(returnmsg,REPLYMSG,tmpptr->key,tmpptr->data);
    break;
    
    /*** Synchronisation process ***/
  case 'S':

    sscanf( mesg, "S %d\r\n", &otime );
    if( otime ) {
      // return "SEND DATA" to other server
      strcpy( returnmsg, DATAMSG );
      break;
    }
    strcpy( returnmsg, OKMSG );
    break;

    /*** send time stamp 'T' and 'TD' send list data to other server ***/    
  case 'T':
    if( mesg[1] == 'D' ) {
      // send next entry in list
      if( ListIt ) {
	strcpy( returnmsg, ListIt->data );
	ListIt = ListIt->next;
      } else {
	strcpy( returnmsg, EODMSG );
      }
    } else {
      // other server is asking for my time stamp
      ListIt = List;
      // reply my time stamp
      sprintf( returnmsg, "T %d", vtime );
    }  
    break;

    /*** Print namespace 'P' ***/
  case 'P':
    
    fprintf(stderr,"server: dumping nametable\n");
    twalk((void *) dl_ns_root, dl_ns_printit);
    
    fprintf( stderr, "\n\n-- Printing the name table --\n" );
    twalk( (void *) name_root, dl_ns_printit );
    
    strcpy(returnmsg,OKMSG);
    break;
    
    /*** Quit 'Q' ***/
  case 'Q':
    
    strcpy( returnmsg, "CONNECTION CLOSED" );
    break;
        
    /*** 'Z' reset time stamp on local server ***/
  case 'Z':
    vtime = 0;

    // Remove entries from list
    Clear( &List );
    strcpy( returnmsg, OKMSG );
    break;
    
    /*** Default -> Protocol error */
  default:
    
    strcpy(returnmsg,ERRORMSG);
    break;
  }
  
  return returnmsg;
}

/***
 *** void sigAlarm( int );
 *** Function: initiate synchronisation of data trees
 *** Return: - (reset the alarm timer)
 ***/

static void sigAlarm( int interval ) {
  // connect other server

  int commSock, otherLen;
  int otime = 0, ret;
  char buffer[DL_MAXLINE], response[DL_MAXLINE];
  Element* ptr;

  struct sockaddr_in otherAddr;


  // reinstall the signal handler
  signal( SIGALRM, sigAlarm );
  sigFlag = 1;

  // create communication socket to connect other server
  if( (commSock = socket( AF_INET, SOCK_STREAM, DL_SCK_TCP )) == DL_ERROR ) {
    fprintf( stderr, "sigAlarm: error creating comm socket" );
    exit( 2 );
  }

  // init memor with constant zero
  memset( (void *)&otherAddr, 0, sizeof(otherAddr) );

  // connection informationn for other server
  otherAddr.sin_family = AF_INET;
  otherAddr.sin_addr.s_addr = htonl(inet_addr(other_ns_addr));
  otherAddr.sin_port = htons(other_ns_port);
  otherLen = sizeof( otherAddr );

  // connect other server
  if( connect(commSock, (struct sockaddr *) &otherAddr, \
	      sizeof(otherAddr)) == DL_ERROR ) {
    fprintf( stderr, "sigAlarm: error connecting 2nd server");
		exit( 2 );
  }
	  
  // send synchronisation request
  printf( "\n--> synchronisation process initiated on alarm " \
	  "interrupt <--\n" \
	  "Sending my time stamp to other server to determine " \
	  "if I\'ll have to send my events\n" );
  sprintf( buffer, "S %d\r\n", vtime );
  write( commSock, buffer, strlen(buffer) );
  
  ret = read( commSock, (void *)&buffer, sizeof(buffer) );
  buffer[ret] = '\0';

  printf( "REPLY: '%s'", buffer );
  if( !strcmp( buffer, "SEND DATA\r\n" ) ) {
    printf( "initiating transfer of local events to other server\n" );

    // transmit list of changes
    ptr = List;
    while( ptr != NULL ) {
      strcpy( buffer, ptr->data );
      printf( "\nSending %s", buffer );
      write( commSock, buffer, strlen(buffer) );

      ret = read( commSock, (void *)&buffer, sizeof(buffer) );
      buffer[ret] = '\0';
      printf( "REPLY: '%s", buffer );
      ptr = ptr->next;
    }
  } else {
    printf( "The other server at %s on %d needs no updates\n", \
	     other_ns_addr, other_ns_port );
  }

  // get synchronisation data from other server
  printf( "Asking for time stamp of other server if I\'ll have to do any" \
	  " updates\n" );
  strcpy( buffer, "T\r\n" ); // aks for time stamp of other server

  write( commSock, (void *) &buffer, sizeof(buffer) );

  ret = read( commSock, (void *) &buffer, sizeof(buffer) );
  buffer[ret] = '\0';
  sscanf( buffer, "T %d\r\n", &otime );
  printf( "REPLY: got time stamp %d\n", otime );

  if( otime ) {
    printf( "Need data from other server\n" );
    // Need data from other server
    while( 1 ) {
      strcpy( buffer, "TD\r\n" );
      write( commSock, (void *) &buffer, sizeof(buffer) );

      ret = read( commSock, (void *) &response, sizeof(response) );
      response[ret] = '\0';

      printf( "Received: %s", response );

      // END OF DATA reply terminates this endless loop
      if( !strcmp( response, EODMSG ) ) {
	break;
      } else {
	parsereq( response );
      }
    }
  }

  // reset time stamp on both server
  printf( "Reset time stamps on both servers\n" );
  vtime = 0;
  strcpy( buffer, "Z\r\n" ); // send reset to other server
  printf( "Sending: %s", buffer );
  write( commSock, (void *) &buffer, sizeof(buffer) );

  ret = read( commSock, (void *) &response, sizeof(response) );
  response[ret] = '\0';

  if( strcmp( response, OKMSG) ) {
    perror( "error resetting time stamp on other server" );
  } else {
    printf( "REPLY : %sReset of other server successful\n", response );
  }

  // clear list
  Clear( &List );
  vtime = 0;

  // close connection
  printf( "\n==> Closing update connection to other server <==\n" );
  strcpy( buffer, "Q\r\n" );
  write( commSock, &buffer, strlen(buffer) );
  close( commSock );

  // reset alarm timer
  alarm( interval );
}

/***
 *** int main();
 ***
 *** Function: act as a nameserver on TCP basis
 *** Return: ­ (endless loop)
 ***/

int main(int argc, char *argv[]) {

  int servsock, cli_sock;		/* serversocket */
  struct sockaddr_in client;		/* the Internet endpoint address
					   (IEA) of the client */
  struct sockaddr_in servaddr;        /* the IEA of the server */
  int client_len;			/* client address length */

  char buffer[DL_MAXLINE];		/* the request buffer */
  int bufferlen;                      /* length of received request */
  int optval = 1;			/* set socket option */
  char *response;			/* the reply */
  
  vtime = 0;                          /* init time stamp */

  // command line switch -s, server initiates synchronisation fo data trees
  if( (argc > 2) && !strcmp( argv[2], "-s" ) ) {
    strcpy( other_ns_addr, argv[3] );
    other_ns_port = atoi(argv[4]);
    signal( SIGALRM, sigAlarm );
    alarm( atoi(argv[5]) );
  }
  else if( argc <= 1 ) usage();

  /* create the server socket */
  if( (servsock=socket( AF_INET, SOCK_STREAM, DL_SCK_TCP )) == DL_ERROR ) {
    fprintf( stderr, "server: error creating socket" );
    exit( 1 );
  }

  /* reuse port */
  if( setsockopt( servsock, SOL_SOCKET, SO_REUSEADDR, (void *)&optval,\
	sizeof( optval ) ) < 0 ) {
    perror( "server: error setting socket option" );
    exit( 1 );
  }

  // init memory area
  memset( (void *)&servaddr, 0, sizeof(servaddr) );
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(atoi(argv[1]));
  
  if( bind(servsock, (struct sockaddr *) &servaddr, \
	   sizeof(servaddr) ) == DL_ERROR ) {
    fprintf( stderr, "server: error binding server socket\n" );
    exit( 1 );
  }
  
  /* mark connection-mode socket as accepting connection and limit 
  ** the number of outstanding connections in the socket's listen queue
  ** to 5 entries.
  */
  if( listen(servsock, 5) == DL_ERROR ) {
    perror( "server: error listen on server socket" );
    exit( 1 );
  }
  
  /* endless loop for accepting connections */
  while(1) {
    /* get request */
    client_len=sizeof(client);
    
    /* This loop is needed as follows:
    ** When the server is started it is waiting for any clients which
    ** transmit requests.
    ** After a certain amount of time the alarm timer causes a SIGALRM
    ** interrupt and the flow of control reaches the sigAlarm function.
    ** After sigAlarm is executed control goes back after the accept
    ** instruction below. accept throws an error because it was
    ** interrupted by SIGALRM signal and returns -1 (i.e. DL_ERROR) then
    ** our server program would terminate with an error message.
    ** Therefor in the sigAlarm function the sigFlag is set to tell the
    ** following endless loop to recall accept.
    */
    do {
      if( (cli_sock=accept(servsock, (struct sockaddr *)&client, \
			   &client_len)) == DL_ERROR) {
	/* error reading ? */
	if( !sigFlag ) {
	  perror( "server: error reading in nameserver: ");
	  exit(1);
	}
	else
	  sigFlag = 0; // reset the sigAlarm flag and retry accept
      } else
	break;      // accept succeeded, answer the requests
    } while( 1 );
    
    printf( "\n## connection from %s, port %d ##\n", \
	    inet_ntoa(client.sin_addr), ntohs(client.sin_port) );
    
    /* A client is able to send requests to the server until it
    ** sends a "Q\r\n" message.
    ** Therefor the server remains in this endless loop.
    */
    while( 1 ) {
      bufferlen = read(cli_sock, (void *) &buffer, sizeof(buffer) );
      buffer[bufferlen] = '\0';
      
      printf( "Server got %s", buffer );
      
      /* parse request */
      response = parsereq(buffer);
      
      /* send reply */
      write( cli_sock, response, strlen(response) );
      
      if( !strcmp( response, "CONNECTION CLOSED" ) ) {
	/* leave endless loop */
	break;
      }
    }
    printf( "\n## connection from %s, port %d closed ##\n", \
	    inet_ntoa(client.sin_addr), ntohs(client.sin_port) );
    close( cli_sock ); // Socket wird nicht mehr benoetigt
  }
}

/***
 *** void dl_ns_printit(void **node, VISIT order, int level)
 ***
 *** Function: print out the namespace
 *** Return  : -
 ***/

void dl_ns_printit(void **node, VISIT order, int level) {

  char *key,*data;
 
  if (order == postorder || order == leaf) {
    key = (*((struct dbentry **)node))->key;
    data = (*((struct dbentry **)node))->data;
    fprintf(stderr,"%s\t%s\n",key,data);
  }
}
