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

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

#include "Practical.h"

#define MAX_LEN 256
#define NUM_FILES 3

// Maximum outstanding connection requests
static const int MAXPENDING = 5;
char FILES[NUM_FILES][MAX_LEN] = {"song.txt", "poem.txt", "quote.txt"};

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) {
            
            // Socket creation failed; try next address
            continue;
            
        }

        // 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;
    
}

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");
        
    }

    fputs("Received file list request from ", stdout);
    PrintSocketAddress((struct sockaddr *)&clntAddr, stdout);
    fputc('\n', stdout);

    return clntSock;
    
}

// Sends back the contents of the file requested
void SendFileContents(char *fileName, int clientSoc) {
    
    // Opens file for reading and initializes buffer to be sent
    FILE *file = fopen(fileName, "r");
    
    // Returns error if the file could not be opened
    if (file == NULL) {
        
        DieWithSystemMessage("fopen() failed");
        
    }

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

    char buffer[fileSize];
    memset(buffer, 0, fileSize);

    // Reads the file into the buffer
    fread(buffer, fileSize, 1, file);
    fclose(file);

    // Sends the file contents
    ssize_t numBytesSent = send(clientSoc, buffer, fileSize, 0);
    // Send terminating null character
    send(clientSoc, "\0", 1, 0);

    // Checks if the message was sent
    if (numBytesSent < 0) {
        
        DieWithSystemMessage("send() failed");
        
    }
    
}

void HandleTCPClient(int clntSocket) {
    
    char buffer[BUFSIZE];  // Buffer for echo string

    fputs("Sending list of files", stdout);
    fputc('\n', stdout);

    // Builds the message of filenames and sends it to the client
    for (int i = 0; i < NUM_FILES; i++) {
        
        char message[MAX_LEN];

        strcpy(message, FILES[i]);
        strncat(message, "\n", strlen(message) - 1);

        send(clntSocket, message, strlen(message), 0);
        
    }

    // Send terminating null character
    send(clntSocket, "\0", 1, 0);

    // Recieves a filename from the client
    ssize_t numBytesRcvd = recv(clntSocket, buffer, BUFSIZE, 0);
    if (numBytesRcvd < 0) {
        
        DieWithSystemMessage("recv() failed");
        
    }

    buffer[numBytesRcvd] = '\0';
    printf("Received request for file \"%s\"\n", buffer);
    
    // Send file contents
    printf("Sending file to the client\n");
    SendFileContents(buffer, clntSocket);

    // Quits after sending file contents
    printf("File sent\n\n");
    printf("Goodbye!!!\n");

    close(clntSocket); // Close client socket
    
}