/***********************************************************
 * loadserver                                              *
 * calculate distributed load                              *
 * Verteilte Systeme II Kapitel 10                         *
 ***********************************************************
 * 1999 by Frank Kargl (frank.kargl@informatik.uni-ulm.de) *
 ***********************************************************
 * Usage: loadserver <port> (default port = 1099)          *
 ***********************************************************/

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <arpa/inet.h>

#include "crypt.h"

/* debug flag */
#undef DEBUG

/* secret key */
#define KEY 'P'

/* default port to use */
#define DEFPORT 1099
/* max num of connections */
#define MAXCON FD_SETSIZE
/* max length of line */
#define MAXLINE 128
/* request string */
#define REQUEST_STRING "getload\n"
/* reply pattern */
#define REPLY_PATTERN "myload %lf"
/* time to sleep between polls (in seconds) */
#define SLEEPTIME 5

/* status of whole server */
enum status_t { SENDING_REQUESTS, RECEIVING_REPLIES, SLEEPING };

/* status of each client connection */
enum jobstatus_t { FREE, IDLE, REQUEST, REPLY };

/* struct describung each client connection */
struct jobentry_t {
    int socket;			/* client socket */
    enum jobstatus_t status;	/* status of job */
    double load;		/* load value */
    char client[64];		/* ip address string of client */
};

/* global variables section */
struct jobentry_t jobs[MAXCON];	/* array of job entries */
enum status_t status;		/* request or reply status */
int numclients;			/* number of clients connected */

/***********************************************************
 *                                                         *
 * Function:                                               *
 * void usage(char* name)                                  *
 * print usage message                                     *
 * Parameters:                                             * 
 *  name - name of executable                              *
 * Return:                                                 *
 *  -                                                      *
 *                                                         *
 ***********************************************************/

void usage(char* name) {
    printf("%s - a load server\n", name);
    printf("Usage: %s <port> (default port = %d)\n", name, DEFPORT);
    exit(1);
}

/***********************************************************
 *                                                         *
 * Function:                                               *
 * void accept_con(int lsocket)                            *
 * accept new connection on socket                         *
 * Parameters:                                             * 
 *  sock - socket to accept()                              *
 * Return:                                                 *
 *  -                                                      *
 *                                                         *
 ***********************************************************/

void accept_sock(int lsocket) {

    int asocket;			/* accept socket */
    struct sockaddr_in cliaddr;		/* client address */
    int cliaddr_len;			/* length of client address */
    int ret;				/* generic return value */
    int i;				/* loop counter */

    cliaddr_len = sizeof(cliaddr);
    asocket=accept(lsocket, (struct sockaddr*) &cliaddr, &cliaddr_len);
    if (ret == -1) {
	perror("Can't accept on lsocket");
	exit(1);
    }

    /* find new job entry */
    for (i=0; i<MAXCON; i++) {
	if (jobs[i].status == FREE) {
	    break;
	}
    }
    /* no free fd ready */
    if (i == MAXCON) {
	fprintf(stderr, "Out of connections\n");
	close(asocket);
	return;
    }
    /* write job entry */
    jobs[i].socket = asocket;
    jobs[i].status = IDLE;
    strcpy(jobs[i].client, inet_ntoa(cliaddr.sin_addr));
    numclients++;
    
    /* "log" connection */
    printf("New connection from %s, port %d\n",
	    inet_ntoa(cliaddr.sin_addr),
	    ntohs(cliaddr.sin_port));
}

/***********************************************************
 *                                                         *
 * Function:                                               *
 * void send_requests(void)                                *
 * send requests to all relevant clients                   *
 * Parameters:                                             * 
 *  -                                                      *
 * Return:                                                 *
 *  -                                                      *
 *                                                         *
 ***********************************************************/

void send_requests(void) {

    int i;			/* loop counter */
    int sentflag = 0;		/* did we sent at least one request ? */
    char cipher[MAXLINE];	/* line buffer */


    for (i=0; i < MAXCON; i++) {
	if (jobs[i].status == IDLE) {
#ifdef DEBUG
	    fprintf(stderr, "Sending request to %s\n",
		    jobs[i].client);
#endif

	    encrypt( KEY, REQUEST_STRING, cipher );
	    write(jobs[i].socket, cipher, sizeof(cipher));
	    jobs[i].status = REQUEST;
	    sentflag = 1;
	}
    }
    if (sentflag) {
	status = RECEIVING_REPLIES;
    } else {
	status = SLEEPING;
    }

}

/***********************************************************
 *                                                         *
 * Function:                                               *
 * void receive_replies(fd_set rset)                       *
 * receive replies from clients                            *
 * Parameters:                                             * 
 *  rset - file descritor set of descriptors ready for     *
 *         reading                                         *
 * Return:                                                 *
 *  -                                                      *
 *                                                         *
 ***********************************************************/

void receive_replies(fd_set rset) {

    int i;				/* loop counter */
    char plain[MAXLINE];		/* line buffer */
    char cipher[MAXLINE];		/* line buffer */
    int numreplies;			/* number of replies received */
    double sumload;			/* load summary */
    double newload;			/* new load read in */
    int flag;				/* exit flag */

    for (i=0; i<MAXCON; i++) {
	if (jobs[i].status == REQUEST &&
	    FD_ISSET(jobs[i].socket, &rset)) {
#ifdef DEBUG
	    fprintf(stderr, "Reply from %s\n", jobs[i].client);
#endif
	    /* read one line of input */
	    if (read(jobs[i].socket, cipher, MAXLINE) == 0) {
		close(jobs[i].socket);
		jobs[i].socket = -1;
		jobs[i].status = FREE;
		numclients--;
		continue;
	    }
	    
#ifdef DEBUG
	    fprintf(stderr, "Read %s from %s\n", cipher, jobs[i].client);
#endif

	    decrypt( KEY, cipher, plain );

#ifdef DEBUG
	    fprintf(stderr, "%s after decode\n", plain);
#endif


	    /* get load */
	    if (sscanf(plain, REPLY_PATTERN, &newload) == 1) {
		jobs[i].load = newload;
	    } else {
		fprintf(stderr, "Reply format error : %s from %s\n",
			plain, jobs[i].client);
		/* kill this client */
		close(jobs[i].socket);
		jobs[i].status = FREE;
		continue;
	    }
	    
	    /* set status to reply received */
	    jobs[i].status = REPLY;

	}
    }

    /* check if all replies were received */
    flag = 0;
    numreplies = 0;
    sumload = 0;
    for (i=0; i < MAXCON; i++) {
	if (jobs[i].status == REQUEST) {
	    flag = 1;
	    break;
	}
	if (jobs[i].status == REPLY) {
	    flag = 1;
	    numreplies++;
	    sumload += jobs[i].load;
	}
    }
    if (!flag) {
	status = SLEEPING;
	return;
    }
    if (i == MAXCON && numreplies != 0) {
	printf("Machine loads:\n");
	for (i=0; i < MAXCON; i++) {
	    if (jobs[i].status == REPLY) {
		printf("%s\t%4.2f\n", jobs[i].client, jobs[i].load);
				jobs[i].status = IDLE;
	    }
	}
	printf("Average load: %4.2f\n", sumload/numreplies);
	status = SLEEPING;
    }
}

/***********************************************************
 *                                                         *
 * Function:                                               *
 * void sleep_while(void)                                  *
 * sleep for a while                                       *
 * Parameters:                                             * 
 *  -                                                      *
 * Return:                                                 *
 *  -                                                      *
 *                                                         *
 ***********************************************************/

void sleep_while(void) {

    static int sleeptimer=0;

#ifdef DEBUG
    fprintf(stderr, "Sleeping\n");
#endif
    sleep(1);
    sleeptimer++;
    if (sleeptimer > SLEEPTIME) {
	status = SENDING_REQUESTS;
	sleeptimer = 0;
    }
}

int main(int argc, char** argv) {
    
    int lsocket;			/* listen socket */
    int port = DEFPORT;			/* port to use */
    struct sockaddr_in servaddr;	/* server address */
    int ret;				/* generic return value */
    fd_set rset;			/* fd set for select */
    int maxfd = -1;			/* maxfd used */
    int sleeptimer;			/* lets sleep */
    struct timeval timeout = { 0, 5000 };		/* short timeout */
    int i;				/* loop counter */

    /* check for port */
    if (argc == 2) {
	port = atoi(argv[1]);
    } else if (argc != 1) {
	usage(argv[0]);
    }
    
#ifdef DEBUG
    fprintf(stderr, "Starting using port %d\n", port);
#endif

    /* init data */
    for (i=0; i<MAXCON; i++) {
	jobs[i].socket    = -1;
	jobs[i].status    = FREE;
	jobs[i].load      = 0;
	jobs[i].client[0] = '\0';
    }
    numclients = 0;
    status=SENDING_REQUESTS;

    /* open listen socket */
    lsocket = socket(AF_INET, SOCK_STREAM, 0);
    if (lsocket == -1) {
	perror("Can't open lsocket");
	exit(1);
    }
    
    /* bind socket to port */
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);
    ret = bind(lsocket, (struct sockaddr*) &servaddr, sizeof(servaddr));
    if (ret == -1) {
	perror("Can't bind lsocket");
	exit(1);
    }
    
    /* listen to socket */
    ret = listen(lsocket, 10);
    if (ret == -1) {
	perror("Can't listen to lsocket");
	exit(1);
    }

    /* multiplex server loop */
    while (1) {

	/* if there are no more clients left, we sleep */
	if (numclients == 0) {
	    status = SLEEPING;
	}

	/* prepare rset and select sockets for reading */
	FD_ZERO(&rset);
	FD_SET(lsocket, &rset);
	maxfd = lsocket;
	for (i=0; i< MAXCON; i++) {
	    if (jobs[i].status == REQUEST && jobs[i].socket != -1) {
		FD_SET(jobs[i].socket, &rset);
		maxfd=(maxfd>jobs[i].socket?maxfd:jobs[i].socket);
	    }
	}
	ret = select(maxfd+1, &rset, NULL, NULL, &timeout);

	/* check for new connections */
	if (FD_ISSET(lsocket, &rset)) {
	    /* accept new connection */
	    accept_sock(lsocket);
	}
	
	/* send requests */
	if (status == SENDING_REQUESTS) {
	    send_requests();
	} /* REQUEST */

	/* receive replies */
	if (status == RECEIVING_REPLIES) {
	    receive_replies(rset);
	} /* REPLY */
    
	/* sleep a while */
	if (status == SLEEPING) {
	    sleep_while();
	} /* SLEEP */

    } /* main loop */

    /* when do we close lsocket ? */
    /* propably should catch signal or so */

}
