#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

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

#define SERVER_PORT         7780
#define SERVER_FILE_PATH    "svrepository/file2.bin"

void handleClientConnection(struct sockaddr_in client);

int gServerUpload;
int gServerSocket;
int gServerFileSize;

int main(int argc, char** argv)
{
    if (argc < 2)
    {
        fprintf(stderr, "-USAGE- %s <upload>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if (atoi(argv[1]) > 65507)
    {
        printf("The upload rate is too high, please reconsider its size!\n");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server, client;
    socklen_t clen = sizeof(client);
    int cl_bytes;
    gServerUpload = atoi(argv[1]);

    if ((gServerSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket()");
        exit(EXIT_FAILURE);
    }

    memset(&server, 0, sizeof(server));
    server.sin_family =         AF_INET;
    server.sin_addr.s_addr =    htonl(INADDR_ANY);
    server.sin_port =           htons(SERVER_PORT);

    if (bind(gServerSocket, (struct sockaddr*) &server, sizeof(server)) < 0)
    {
        perror("bind()");
        close(gServerSocket);
        exit(EXIT_FAILURE);
    }

    printf("Server started using port %d...\n", SERVER_PORT);

    // get the size in bytes of the file we want to send
    FILE* pFile = fopen(SERVER_FILE_PATH, "rb");
    struct stat st;
    stat(SERVER_FILE_PATH, &st);
    gServerFileSize = st.st_size;
    fclose(pFile);

    printf("SERVER FILE INFO: PATH - \"%s\" | SIZE - %d bytes\n", SERVER_FILE_PATH, gServerFileSize);

    for(;;)
    {
        // retrieve the client's upload rate
        if (recvfrom(gServerSocket, &cl_bytes, sizeof(int), 0, (struct sockaddr*) &client, &clen) > 0)
        { 
            printf("Client (%s:%d) has connected!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
            printf("The client downloads %d packets (each %d bytes)\n", gServerFileSize / cl_bytes, cl_bytes);
            printf("The client will%sreceive all the information at %d bytes per packet!\n\n", gServerFileSize % cl_bytes == 0 ? (" ") : (" not "), cl_bytes);

            // handle the client inside the child process
            int pid = fork();
            if (pid == 0)
            {
                handleClientConnection(client);
            }
            else if (pid < 0)
            {
                perror("fork()");
                close(gServerSocket);
                exit(EXIT_FAILURE);
            }
        }
        else
        {
            printf("A user disconnected!\n");
        }
    }
}

void handleClientConnection(struct sockaddr_in client)
{   
    // send to the client the server's file size in bytes
    sendto(gServerSocket, &gServerFileSize, sizeof(int), 0, (struct sockaddr*) &client, sizeof(client));
    
    // allocate memory for the buffer and open the file in read-mode (binary)
    char* buffer = malloc(gServerUpload * sizeof(char));
    FILE* pFile = fopen(SERVER_FILE_PATH, "rb");

    // read the whole file
    while (fread(buffer, gServerUpload, 1, pFile) == 1)
    {
        // send to the client gServerUpload bytes at a time
        if (sendto(gServerSocket, buffer, gServerUpload, 0, (struct sockaddr*) &client, sizeof(client)) > 0)
        {
            // wait 0.025s between the messages
            usleep(25000);
        }
        else
        {
            perror("sendto()");
            close(gServerSocket);
            exit(EXIT_FAILURE);
        }
    }
    // free the buffer, close everything and exit
    // avoid dangling pointers
    if (buffer != NULL)
    {
        free(buffer);
        buffer = NULL;
    }
    fclose(pFile);
    close(gServerSocket);
    exit(EXIT_SUCCESS);
}