#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

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

#define SERVER_ADDRESS      "127.0.0.1"
#define SERVER_PORT         7780
#define CLIENT_FILE_PATH    "clrepository/file.bin"

void handleTimeout(int signal);
int gClientSocket;

int main(int argc, char** argv)
{
    if (argc < 2)
    {
        fprintf(stderr, "-USAGE- %s <bytes>\n", argv[0]);
        printf("-USAGE- <bytes> represents the number of bytes you want to receive from the server");
        exit(EXIT_FAILURE);
    }
    
    if (atoi(argv[1]) > 65507)
    {
        fprintf(stderr, "-ERROR- The download rate is too high, please reconsider its size!\n");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server;
    socklen_t slen = sizeof server;
    int requested_bytes = atoi(argv[1]), received_bytes;
    int server_file_size;

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

    memset(&server, 0, slen);
    server.sin_family = AF_INET;
    server.sin_port = htons(SERVER_PORT);
    server.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);

    if (inet_aton(SERVER_ADDRESS, &server.sin_addr) == 0)
    {
        perror("inet_aton()");
        exit(EXIT_FAILURE);
    }

    sendto(gClientSocket, &requested_bytes, sizeof(int), 0, (struct sockaddr*) &server, slen);
    
    if (recvfrom(gClientSocket, &server_file_size, sizeof(int), 0, (struct sockaddr*) &server, &slen) != 0)
    {
        //perror("recvfrom()");
        //exit(EXIT_FAILURE);
    }
    
    if (server_file_size > 2048 * 1024)
        printf("-WARNING- The file might be too large! (<2MB)\n");
       
    if (requested_bytes > server_file_size)
    {
        printf("-ERROR- The number of bytes you want exceeds the size of the file!\n");
        close(gClientSocket);
        exit(EXIT_FAILURE);
    }

    FILE *pFile = fopen(CLIENT_FILE_PATH, "wb");
    if (pFile == NULL)
    {
        perror("fopen()");
        close(gClientSocket);
        exit(EXIT_FAILURE);
    }
    int x = 0;
    char* buffer = malloc(requested_bytes * sizeof(char));
    int data_loss = server_file_size % requested_bytes;
    
    printf("Download started. Estimated download time: %.2f seconds\n", 0.025 * (server_file_size / requested_bytes));

    while (x < server_file_size)
    {
        if ((received_bytes = recvfrom(gClientSocket, buffer, requested_bytes, 0, (struct sockaddr*) &server, &slen)) > 0)
        {
            signal(SIGALRM, handleTimeout);
            alarm(3);

            fwrite(buffer, requested_bytes, 1, pFile);
            x += received_bytes;

            if (data_loss == server_file_size - x)
            {
                fwrite(buffer, data_loss, 1, pFile);
                break;
            }
        }
        else
        {
            perror("recvfrom()");
            close(gClientSocket);
            exit(EXIT_FAILURE);
        }
    }

    printf("--------------------------------------------\n");
    printf("The download has finished.\n");
    printf("Download stats:\n- Downloaded/file size - %d/%d\n", x, server_file_size);
    printf("- Data loss for %d bytes per packet: %d bytes\n", requested_bytes, data_loss);
    if (data_loss != 0)
        printf("- Writing the remaining %d bytes to the file...\n", data_loss);
    printf("- All the information has been received from the server!\n");

    // avoid dangling pointers
    if (buffer != NULL)
    {
        free(buffer);
        buffer = NULL;
    }
    fclose(pFile);
    close(gClientSocket);
    exit(EXIT_SUCCESS);
}

void handleTimeout(int signal)
{
    printf("--------------------------------------------\n");
    printf("A timeout has occured!\nPlease check if your download rate\nis the same with the server's upload rate!\n");
    close(gClientSocket);
    exit(EXIT_FAILURE);
}