// Owen Sullivan
// CPSC 3600-001 F23
// Assignment 1

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

#include "Practical.h"

static const int MAXPENDING = 5;  // Maximum outstanding connection requests

// Function modified not to print "Binding..." text from sample
int SetupTCPServerSocket(const char *service) {
    
    // Construct the server address structure
    struct addrinfo addrCriteria;  // Criteria for address match
    memset(&addrCriteria, 0, sizeof(addrCriteria));  // Zero out structure
    addrCriteria.ai_family = AF_UNSPEC;              // Any address family
    addrCriteria.ai_flags = AI_PASSIVE;      // Accept on any address/port
    addrCriteria.ai_socktype = SOCK_STREAM;  // Only stream sockets
    addrCriteria.ai_protocol = IPPROTO_TCP;  // Only TCP protocol

    struct addrinfo *servAddr;  // List of server addresses
    int rtnVal = getaddrinfo(NULL, service, &addrCriteria, &servAddr);
    if (rtnVal != 0) {
        
        DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnVal));
        
    }

    int servSock = -1;
    for (struct addrinfo *addr = servAddr; addr != NULL; addr = addr->ai_next) {
        // Create a TCP socket
        servSock =
            socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
        if (servSock < 0) {
            
            continue;  // Socket creation failed; try next address
            
        }

        // Bind to the local address and set socket to listen
        if ((bind(servSock, addr->ai_addr, addr->ai_addrlen) == 0) &&
            (listen(servSock, MAXPENDING) == 0)) {
                
            // Print local address of socket
            struct sockaddr_storage localAddr;
            socklen_t addrSize = sizeof(localAddr);
            if (getsockname(servSock, (struct sockaddr *)&localAddr,
                            &addrSize) < 0) {
                                
                DieWithSystemMessage("getsockname() failed");
                
            }

            break;  // Bind and listen successful
            
        }

        close(servSock);  // Close and try again
        servSock = -1;
        
    }

    // Free address list allocated by getaddrinfo()
    freeaddrinfo(servAddr);

    return servSock;
    
}

// Function modified to print the correct output for Assignment 1
int AcceptTCPConnection(int servSock) {
    
    struct sockaddr_storage clntAddr;  // Client address
    // Set length of client address structure (in-out parameter)
    socklen_t clntAddrLen = sizeof(clntAddr);

    // Wait for a client to connect
    int clntSock = accept(servSock, (struct sockaddr *)&clntAddr, &clntAddrLen);
    if (clntSock < 0) {
        
        DieWithSystemMessage("accept() failed");
        
    }

    // Output message indicating that the client has requested the list of
    // file names
    fprintf(stdout, "Received file list request from ");
    PrintSocketAddress((struct sockaddr *)&clntAddr, stdout);
    fprintf(stdout, "\n");

    return clntSock;
    
}

// Function modified to handle server logic
void HandleTCPClient(int clientSocket) {
    
    // Create a 2D array to store the list of file names
    char **fileNames = malloc(3 * sizeof(char *));
    // Allocate memory for each index, using BUFSIZE for wiggle room
    for (int i = 0; i < 3; i++) {
        
        fileNames[i] = malloc(BUFSIZE * sizeof(char));
        
    }

    // Copy the known file names into the indexes of the 2D file name array
    // File names are terminated with newline characters
    strcpy(fileNames[0], "song.txt\n");
    strcpy(fileNames[1], "poem.txt\n");
    strcpy(fileNames[2], "quote.txt\n");

    // Output message indicating the start of the file name list send action
    fprintf(stdout, "Sending list of files\n");

    // Loop through the known file names
    for (int i = 0; i < 3; i++) {
        
        // Send the file name bytes to the client
        ssize_t numBytesSent =
            send(clientSocket, fileNames[i], strlen(fileNames[i]), 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[i])) {
            
            DieWithUserMessage("send()", "sent unexpected number of bytes");
            
        }
        
    }

    // Send a null character to the client to indicate the end of
    // the file name list
    send(clientSocket, "\0", 1, 0);

    // Receive file request by file name
    // Buffer for receiving file name
    char buffer[BUFSIZE];

    // Zero out the I/O buffer
    memset(buffer, 0, sizeof(buffer));

    // Receive message from client (minus 1 to leave space for a null
    // terminator)
    ssize_t numBytesReceived = recv(clientSocket, buffer, BUFSIZE - 1, 0);
    // If recv() returns an error (-1, less than 0), error out
    if (numBytesReceived < 0) {
        
        DieWithSystemMessage("recv() failed");
        
    }

    // Output message indicating which file the client requested
    fprintf(stdout, "Received request for file \"%s\"\n", buffer);

    // Open the requested file (name stored in buffer after receiving from
    // client)
    FILE *fileToSend = fopen(buffer, "rb");

    if (fileToSend == NULL) {

        DieWithSystemMessage("fopen() failed");

    }

    // Get file size (in bytes)
    fseek(fileToSend, 0, SEEK_END);
    ssize_t fileSize = ftell(fileToSend);
    rewind(fileToSend);

    // Output message indicating the start of the file send action
    fprintf(stdout, "Sending file to the client\n");

    // Read file contents and store in a character array for passing as a
    // message
    char *fileContents = malloc(fileSize + 1);
    fread(fileContents, fileSize, 1, fileToSend);
    fileContents[fileSize] = '\0';

    // Send file to client
    ssize_t numBytesSent = send(clientSocket, fileContents, fileSize + 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 plus null character, error out
    } else if (numBytesSent != fileSize + 1) {
        
        DieWithUserMessage("send()", "sent unexpected number of bytes");
        
    }

    // Close file
    fclose(fileToSend);

    // Output message saying it's done
    fprintf(stdout, "File sent\n\n");

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

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

    // Close client socket
    close(clientSocket);
    
}