/****************************************************************/
/*                                                              */
/*      CS 484 Programming Assignment 2                         */
/*      Matt Michie mmichie@cs.nmsu.edu                         */
/*                                                              */
/*      Simple server, implements program 2                     */
/*      I tried testing in character mode but was unable.  It   */
/*      should work as I deal with input character by character */
/*      even when I am in line mode.
/*                                                              */
/*      FILE:       server.c                                    */
/*                                                              */
/*      USAGE:      server port                                 */
/*                                                              */
/*          port            integer of port to bind server to   */
/*                                                              */
/****************************************************************/
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>

// Fixed size string length
#define BUFFER_SIZE 1024

/*--------------------------------------------------------------*/
/*  Function Prototypes                                         */
/*--------------------------------------------------------------*/
void setupSocket(int server_port);
int parsePacket(char *buff, char *tokens[]);
int computeChecksum(char *data);
void readFrame(char *buffer);
int sendNAK(char *frame_id, char *error_code);
int sendACK(char *frame_id, char *message);
char *rtrim(char *str);
void clearTokens(char **tokens, int args);

/*--------------------------------------------------------------*/
/*  Global Variables                                            */
/*--------------------------------------------------------------*/
struct sockaddr_in server;
struct sockaddr_in client;
int sfd, afd;


FILE *fp;

/*--------------------------------------------------------------*/
/*  main                Loop to accept clients                  */
/*--------------------------------------------------------------*/
int main(int argc, char *argv[])
{
 
    char *buffer, *tmp;
    char c;

    int i, sin_size, sof, eof;

    if (argc != 2) {
	fprintf(stderr, "Error: You must specify which port to bind to!\n");
	exit(1);
    }
	
    // Allocate space for buffer string
    buffer = (char *) malloc(BUFFER_SIZE * sizeof(char));
    if (buffer == NULL) {
        fprintf(stderr, "Malloc failed to allocate for buffer string!\n");
        exit(1);
    }
    
    // Setup the socket with the port specified on command line
    setupSocket(atoi(argv[1]));

    // Continue to Accept connections after the client disconnects
    while (1) {
        sof = 0;
	eof = 0;

	sin_size = sizeof(struct sockaddr_in);
	afd = accept(sfd, (struct sockaddr *) &client, &sin_size);

	fprintf(stderr, "Client Accepted...\n");

	fp = fdopen(afd, "r");

	while ((c = fgetc(fp)) != EOF) {
            // Check for Start Of Frame 
            if (c == 'S' && !sof) {
                c = fgetc(fp);
                if (c == 'O') {
                    c = fgetc(fp);
		    if (c == 'F') {
                        fprintf(stderr, "Frame started...\n");
                        sof = 1;
                    }
                }
            }
            
            if (sof) {
		fprintf(stderr, "Attempting to read frame...\n");
		readFrame(buffer);
		sof = 0;
	    }
	}
	
    }

    // Close the socket
    fprintf(stderr, "Closing socket...");
    close(sfd);

    free(buffer);

    return 0;
}

/*--------------------------------------------------------------*/
/*  sendNAK             Attempts to send a NAK error message    */
/*                      with the given frame_id and error code. */
/*--------------------------------------------------------------*/
int sendNAK(char *frame_id, char *error_code)
{
    char *tmp;

    // Allocate space for buffer string
    tmp = (char *) malloc(BUFFER_SIZE * sizeof(char));
    if (tmp == NULL) {
	fprintf(stderr, "Malloc failed to allocate for tmp string!\n");
	exit(1);
    }

    //rtrim(frame_id);
    fprintf(stderr, "Length of frame_id: %d\n", strlen(frame_id));
    
    if (strlen(frame_id) < 5) {		  
	fprintf(stderr, "Frame ID invalid\n");
	frame_id = "00000";
    }

    sprintf(tmp, "NAK %s %s\n", frame_id, error_code);
    if (send(afd, tmp, strlen(tmp), 0) == -1) {
	perror("ACK");
	return (-1);
    }		

    fprintf(stderr, "NACK Sent!\n");
    
    return 0;
}

/*--------------------------------------------------------------*/
/*  sendAck             Attempts to send an ACK with the given  */
/*                      frame_id and message.                   */
/*--------------------------------------------------------------*/
int sendACK(char *frame_id, char *message)
{
    char *tmp;

    // Allocate space for buffer string
    tmp = (char *) malloc(BUFFER_SIZE * sizeof(char));
    if (tmp == NULL) {
	fprintf(stderr, "Malloc failed to allocate for tmp string!\n");
	exit(1);
    }

    sprintf(tmp, "ACK %s %d\n", frame_id, computeChecksum(message));
    
    if (send(afd, tmp, strlen(tmp), 0) == -1) {
	perror("ACK");
	return(-1);
    }		

    fprintf(stderr, "ACK Sent!\n");

    return 0;
}

/*--------------------------------------------------------------*/
/*  readFrame           Attempts to read a complete frame from  */
/*                      the client.  If it can it sends an ACK  */
/*                      otherwise it sends a NAK with the error */
/*                      field set.
/*--------------------------------------------------------------*/
void readFrame(char *buffer)
{
   
    int length, args, endframe;
    char c;
    char *tokens[100];
   
    length = 0;
    endframe = 0;

    while (!endframe) {
	c = fgetc(fp);
	buffer[length] = c;
	length++;
	
	// Check for End Of Frame
	if (c == 'E') {
	    c = fgetc(fp);
	    //                buffer[length] = c;
	    if (c == 'O') {
		c = fgetc(fp);
		//buffer[length] = c;
		if (c == 'F') {
		    fprintf(stderr, "Frame ended...\n");
		    endframe = 1;
		}
	    }
	}
	
	if (c == '\n' || endframe) {	    
	    args = parsePacket(buffer, tokens);

	    // Check for EOL w/o EOF
	    if (c == '\n' && !endframe) {
		fprintf(stderr, "Missing EOF\n");
		sendNAK(tokens[0], "01");

		return;
	    }

	    // Check for Packet Size
	    if (args != 3) {
		fprintf(stderr, "Packet wrong size : %d\n", args);	       
		sendNAK(tokens[0], "02");

		return;
	    }

	    // Check for Malformed frame_id
	    if (strlen(tokens[0]) < 5) {		  
		fprintf(stderr, "Frame ID invalid\n");
		sendNAK("00000", "03");

		return;
	    }

	    // Check here for Malformed data byte count
	    if (atoi(tokens[1]) != strlen(tokens[2])) {
		fprintf(stderr, "Malformed data byte count\n");
		sendNAK(tokens[0], "04");
		
		return;
	    }

	    // No errors so ACK it
	    sendACK(tokens[0], tokens[2]);
	    clearTokens(tokens, args);
	    length = 0;
	    return;
	}
    }
}
 
void clearTokens(char **tokens, int args) 
{
    int i;
    for (i = 0; i < args; i++)
	tokens[args][0] = '\0';

}

/*--------------------------------------------------------------*/
/*  computeChecksum     Given a string computers and returns    */
/*                      an integer from 0 to 99.                */
/*--------------------------------------------------------------*/
int computeChecksum(char *data)
{
    int len, i, sum;
    char c;

    sum = 0;

    len = strlen(data);
    for (i = 0; i < len; i++) {
	c = data[i];
	sum += (unsigned int) c;
    }

    return (sum % 100);
}

/*--------------------------------------------------------------*/
/*  setupSocket         Sets up and binds the socket to the     */
/*                      correct port and does error handling.   */
/*--------------------------------------------------------------*/
void setupSocket(int server_port)
{
    // Setup the socket for Internet
    if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
	perror("Socket SFD");
	exit(1);
    }

    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);	// Bind to port specified on command line
    server.sin_addr.s_addr = INADDR_ANY;

    // Bind the socket
    if (bind(sfd, (struct sockaddr *) &server, sizeof(struct sockaddr)) ==
	-1) {
	perror("bind");
	exit(1);
    }
    // Listen to that socket with 10 possible pending connections
    if (listen(sfd, 10) == -1) {
	perror("listen");
	exit(1);
    }
}

/*--------------------------------------------------------------*/
/*  parsePacket         Parses the input buffer into distinct   */
/*                      tokens and returns number of args.      */
/*--------------------------------------------------------------*/
int parsePacket(char *buff, char *tokens[])
{
    int i, j;
    char *point;

    j = -1;

    point = (char *) strtok(buff, "  ");

    while (point != NULL) {
	j++;
	tokens[j] = point;
	point = (char *) strtok(NULL, "     ");
    }
    
    tokens[j + 1] = '\0';

    return (j);
}

/*--------------------------------------------------------------*/
/*  rtrim               Trims all whitespace from the end of    */
/*                      a string.                               */
/*--------------------------------------------------------------*/
char *rtrim(char *str) {
   char *s, *p; int c;
   s = p = str;
   while ((c = *s++)) if (c > ' ') p = s;
   *p = '\0';
   return str;
}
