/* Verteilte Systeme - Programmieraufgabe Uebungsblatt 6
   Author: Stefan Schonger
   Date  : 06/01/99

   "News/Chat"-Server.
   Several Clients can connect and join groups, leave groups and
   send messages to all members in a group.
   groups are created automatically once a member joins.

   If a client joins multiple times, it may get multiple messages and
   not get any messages after the first leave.

   The server port can be specified on the command line. Default is 12345

   Message format:
   1) join #Groupname
   2) leave #Groupname
   3) message #Groupname<CRLF>
      MESSAGE-BODY

   since messages are sent by udp, the size is restricted to 64k-header
   and generally unreliable (no ACKs).
*/

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

/* maximum packet size */
#define MAX 65537
#define DEFAULT_PORT 12345

typedef unsigned long inetAddrType;
typedef unsigned int  inetPortType;

/* Member and Group maintainance */

/* Group members */
typedef struct MemberT {
  inetAddrType ip;
  inetPortType port;

  struct MemberT *next;
} Member;

/* Groups */
typedef struct GroupT {
  char *name;
  Member *members;

  struct GroupT *next;
} Group;

Group *groups;


/* join a group

   Parameters:
   groupName : Name of the group to be joined
   ip        : ip of the joiner
   port      : port of the joiner
*/
void joinGroup(char *groupName, inetAddrType ip, inetPortType port) {
  Group *last, *g;
  Member *m;

  last=g=groups;
  while( g != 0 ) {
    if( strcmp(g->name, groupName) == 0) { /* found the group */
      m = g->members;
      g->members = malloc( sizeof(Member));
      g->members->ip = ip;
      g->members->port = port;
      g->members->next = m;
      
      return;
    }
    last = g;
    g = g->next;
  }
  /* not found => new group */
  g = malloc( sizeof(Group) );
  g->name = strdup((char *)groupName);
  g->members = malloc( sizeof(Member));
  g->members->ip = ip;
  g->members->port = port;
  g->members->next = 0;
  g->next = 0;
  if(last == 0) { /* first group */
    groups = g;
    puts("first group");
    printf("test: %s#%ld\n", groups->name, groups->members->ip);
  } else {
    last->next = g;
  }   
}

/* leave a group

   Parameters:
   groupName : Name of the group to be left
   ip        : ip of the sender
   port      : ip of the sender
*/
void leaveGroup(char *groupName, inetAddrType ip, inetPortType port) {
  Group *p = groups;
  Member *last, *m;

  while( p != 0 ) {
    if( strcmp(p->name, groupName) == 0 ) {
      last = m = p->members;
      while( m != 0 ) {
	if( m->ip == ip && m-> port == port ) {
	  if( m == p->members ) { /* delete first element */
	    p->members = m->next;
	  } else {
	    last->next = m->next;
	  }
	}
	
	last = m;
	m = m->next;
      }
    }
    p = p->next;
  }
}

Member *getMembersGroup( char* groupName ) {
  Group *p = groups;
  while( p != 0 ) {
    /*    printf("serching (%s#%s)\n", p->name, groupName);*/
    if( strcmp(p->name, groupName) == 0) {
      return p->members;
    }

    p = p->next;
  }
  return 0; /* nothing found => return null */
}

/* the actual server functionality 

   Parameters:
   sockfd    : the server socket
*/	    
void serve(int sockfd) {
  struct sockaddr_in cliAddr;
  size_t cliAddrLen;
  char mesgBuf[MAX];
  char reqType[MAX], reqGroup[MAX];
  int mesgSize;
  Member *m;
  char *tmp;


  cliAddrLen = sizeof(cliAddr);
  for(;;) { /* Do this forever (iterative server) */

    /* get message */
    mesgSize = recvfrom(sockfd, mesgBuf, MAX, 0, (struct sockaddr *) &cliAddr, (size_t *) &cliAddrLen);

    mesgBuf[mesgSize] = '\0'; /* Zero-Terminate String */
    printf("Received packet from %s,%d (%s)\n", inet_ntoa(cliAddr.sin_addr), ntohs(cliAddr.sin_port), mesgBuf);
    sscanf(mesgBuf, "%s %s\r\n", reqType, reqGroup);
    printf("Incoming Request: %s#%s\n", reqType, reqGroup);

    /* decide what to do */
    if( strcmp(reqType, "join") == 0) {
      puts("adding client...");
      joinGroup( reqGroup, cliAddr.sin_addr.s_addr, ntohs(cliAddr.sin_port) );
    } else if( strcmp(reqType, "leave") == 0) {
      puts("deleting client");
      leaveGroup( reqGroup, cliAddr.sin_addr.s_addr, ntohs(cliAddr.sin_port) );
    } else if( strcmp(reqType, "message") == 0) {
      puts("sending message...");

      m = getMembersGroup( reqGroup );
      while( m != 0 ) {
	printf("sending to (%ld, %d)\n", m->ip, m->port);

	/* setup client address */
	memset((char *) &cliAddr, 0, sizeof(struct sockaddr_in)); 
	cliAddr.sin_family = AF_INET;
	cliAddr.sin_addr.s_addr = m->ip;
	cliAddr.sin_port = htons( m->port );

	cliAddrLen = sizeof( cliAddr );

	/* Send Message back (without the command line) */
	if ( (tmp=strstr(mesgBuf,"\r\n")) != 0) { /* see if we find \r\n */
	  /*	  printf("Sending: \"%s\"\n", tmp+2);*/
	  if( sendto(sockfd, tmp+2, mesgSize-(mesgBuf-tmp)-2, 0, (struct sockaddr *) &cliAddr, cliAddrLen) == -1 ) {
	    perror("sendto error!");
	    exit(-1);
	  }
	} else {
	  puts("wrong format... aborting");
	}

	m = m->next;
      }
    }  else { /* discard */
      puts("ignoring...");
    }
  }
}

int main(int argc, char *argv[]) {
  int sockfd;
  struct sockaddr_in servAddr;

  int servPort;
  int ret;

  if( argc > 1 ) { /* Take Port from command line */
    servPort = atoi(argv[1]);
  } else {
    servPort = DEFAULT_PORT;
  }

  /* Open a UDP socket */
  if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0 )) < 0){
    perror("Can't open socket");
    exit(-1);
  }
 
  /* setup server address */
  memset((char *) &servAddr, 0, sizeof(struct sockaddr_in)); 
  servAddr.sin_family = AF_INET;
  servAddr.sin_addr.s_addr = htonl( INADDR_ANY );
  servAddr.sin_port = htons( servPort );

  /* bind server */
  ret = bind(sockfd, (struct sockaddr *) &servAddr, sizeof(servAddr));
  if (ret == -1) {
    perror("bind() error");
    exit(1);
  }    

  serve( sockfd );

  /* not reached */
  return 0;
}
