// Owen Sullivan & Lucia Wang
// CPSC 3600-001 F23
// Assignment 1

#include <netdb.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include "Practical.h"

int main(int argc, char **argv) {
    
    // Test for correct number/usage of arguments
    if (argc < 2 || argc > 3) {
        
        DieWithUserMessage("Parameter(s)",
                           "<Server Address/Name> [<Server Port/Service>]");
        
    }

    // First argument: server address/name
    char *serverAddress = argv[1];
    // Second argument (optional): server port/service
    char *port = (argc == 3) ? argv[2] : "text";

    // Create a connected TCP socket
    int socket = SetupTCPClientSocket(serverAddress, port);
    // Check that socket was created, error message if not
    if (socket < 0) {
        
        DieWithUserMessage("SetupTCPClientSocket() failed",
                           "unable to connect");
    
    }

    // Output message indicating the start of the file name receive action
    fprintf(stdout, "Requesting a list of files from the server %s\n",
            serverAddress);

    // Track the number of file names received from the server
    int numFiles = 0;
    // Create a 2D array to store the list of file names from the server
    char **fileNames = malloc((numFiles + 1) * sizeof(char *));

    // Keep track of whether we should expect to receive more data
    bool receiving = true;

    // Receive a list of available files from the server as long as there are
    // still file names to be received
    while (receiving) {
        
        // I/O buffer
        char buffer[BUFSIZE];
        // File name buffer
        char fileNameBuffer[BUFSIZE];

        // Zero out file name buffer
        memset(fileNameBuffer, 0, sizeof(fileNameBuffer));

        // Receive up to the buffer size (minus 1 to leave space for a null
        // terminator) bytes from the sender
        ssize_t numBytesReceived = recv(socket, buffer, BUFSIZE - 1, 0);

        // If recv() returns an error (-1, less than 0), error out
        if (numBytesReceived < 0) {
            
            DieWithSystemMessage("recv() failed");

            // If recv() returns 0, the socket connection has been
            // closed, error out
        } else if (numBytesReceived == 0) {
            
            DieWithUserMessage("recv()", "connection closed prematurely");
        
        }

        // Loop through all of the bytes in the I/O buffer
        for (ssize_t i = 0; i < numBytesReceived; i++) {
            
            // If the current character is a newline character and the file
            // name buffer is not empty, indicating the end of a file name
            if (buffer[i] == '\n' && strlen(fileNameBuffer) > 0) {
                
                // Expand the 2D fileNames array
                fileNames =
                    realloc(fileNames, (numFiles + 1) * sizeof(char *) + 1);

                // Allocate enough memory in the fileNames array to store the
                // file name
                fileNames[numFiles] = malloc((i + 1) * sizeof(char));

                // Copy the received file name from the buffer to the array
                // space
                strcpy(fileNames[numFiles], fileNameBuffer);

                // Increment numFiles
                numFiles++;

                // Zero out file name buffer
                memset(fileNameBuffer, 0, sizeof(fileNameBuffer));

                // If the current character is a null character and the file
                // name buffer is empty, indicating the end of the list of
                // file names
            } else if (buffer[i] == '\0' && strlen(fileNameBuffer) == 0) {
                
                // Update receiving tracker
                receiving = false;

                // Break loop
                break;

                // Otherwise, add the current character to the file name buffer
            } else {
                
                strncat(fileNameBuffer, &buffer[i], 1);
                
            }
            
        }
        
    }

    // Output message indicating the end of the file name receive action
    fprintf(stdout, "List received.\n");

    // Output the list of file names received from the server
    fprintf(stdout, "User, please select a file:\n");
    for (int i = 0; i < numFiles; i++) {
        
        fprintf(stdout, "%d. %s\n", i + 1, fileNames[i]);
        
    }
    fprintf(stdout, "\n");

    // Get user input for file name selection
    fprintf(stdout, "> ");
    int selection;
    fscanf(stdin, "%d", &selection);

	// Input validation
	while (selection < 1 || selection > numFiles) {

		fprintf(stderr, "Invalid selection, try again\n");

		fprintf(stdout, "> ");
		fscanf(stdin, " %d", &selection);

	}

    // Output message indicating the start of the file receive action
    fprintf(stdout, "Requesting file \"%s\"\n", fileNames[selection - 1]);

    // Send file name as request for file
    ssize_t numBytesSent = send(socket, fileNames[selection - 1],
                                strlen(fileNames[selection - 1]), 0);
    // If send() returns an error (-1, less than 0), error out
    if (numBytesSent < 0) {
        
        DieWithSystemMessage("send() failed");

        // If send() does not return the same number of bytes as the file
        // name length, error out
    } else if (numBytesSent != strlen(fileNames[selection - 1])) {
        
        DieWithUserMessage("send()", "sent unexpected number of bytes");
        
    }

    // Store file contents
    char *fileContents = malloc(sizeof(char));
    // Track number of successful receives
    ssize_t numReceives = 0;

    // Update receiving tracker
    receiving = true;
    // Receive a file from the server as long as there are bytes left to read
    while (receiving) {
        
        // I/O buffer
        char buffer[BUFSIZE];
        // File contents buffer
        char fileContentsBuffer[BUFSIZE];

        // Zero out file contents buffer
        memset(fileContentsBuffer, 0, sizeof(fileContentsBuffer));

        // Receive up to the buffer size bytes from the sender
        ssize_t numBytesReceived = recv(socket, buffer, BUFSIZE, 0);

        // If recv() returns an error (-1, less than 0), error out
        if (numBytesReceived < 0) {
            
            DieWithSystemMessage("recv() failed");

            // If recv() returns 0, the socket connection has been
            // closed, error out
        } else if (numBytesReceived == 0) {
            
            DieWithUserMessage("recv()", "connection closed prematurely");
            
        }

        // Increment numReceives
        numReceives++;

        // Loop through all of the bytes in the I/O buffer
        for (ssize_t i = 0; i < numBytesReceived; i++) {
            
            // If the current character is EOF, indicating the end of the file
            // contents
            if (buffer[i] == '\0') {
                
                // Update receiving tracker
                receiving = false;

                // Break loop
                break;

                // Otherwise, add the current byte to the file name buffer
            } else {
                
                strncat(fileContentsBuffer, &buffer[i], 1);
                
            }
            
        }

        // Expand the file contents array
        fileContents = realloc(fileContents, (numReceives * BUFSIZE));
        // Copy the contents of the file contents buffer into the file
        // contents array
        strcat(fileContents, fileContentsBuffer);
        
    }

    // Output message indicating the end of the file receive action
    fprintf(stdout, "File received:\n\n");

    // Print received file contents
    fprintf(stdout, "%s\n", fileContents);

    // Output goodbye message
    fprintf(stdout, "Goodbye!!!\n");

    // Free fileNames memory
    for (int i = 0; i < numFiles; i++) {
        
        free(fileNames[i]);
        
    }
    free(fileNames);
    // Free fileContents memory
    free(fileContents);

    // Close the socket connection
    close(socket);

    return 0;
    
}