#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>

#include "http_response.h"

/**
 * Converts a supported status code into a human-readable brief. The brief is a
 * literal or global borrowed by the caller.
 * 
 * Supported codes are those in the `response_code_t` enum.
 * 
 * Will crash via assert, abort, or similar on unsupported status codes.
 */
static const char *status_brief(response_code_t code);
/**
 * Converts a supported type code into a HTTP mime type string.
 * 
 * Will crash via assert, abort, or similar on unsupported status codes.
 */
static const char *mime_string(mime_type_t type);

/**
 * Gets the length as a string of a number when serialized in base 10.
 */
static size_t base_ten_repr_len(size_t n);

static const char *status_brief(response_code_t code) {
    switch (code) {
        case HTTP_OK:
            return "OK";
        case HTTP_BAD_REQUEST:
            return "Bad Request";
        case HTTP_FORBIDDEN:
            return "Forbidden";
        case HTTP_NOT_FOUND:
            return "Not Found";
        default:
            fprintf(stderr, "status_brief: Unsupported response status code: `%d`\n", code);
            abort();
    }
}

static const char *mime_string(mime_type_t type) {
    switch (type) {
        case MIME_PLAIN:        return "text/plain";
        case MIME_HTML:         return "text/html";
        case MIME_JS:           return "text/javascript";
        case MIME_JSON:         return "text/json";
        case MIME_PNG:          return "image/png";
        case MIME_WASM:         return "application/wasm";
        case MIME_OCTET_STREAM: return "application/octet-stream";
        default:
            fprintf(stderr, "response_type_format: Unsupported mime type: `%d`\n", type);
            abort();
    }
}

static size_t base_ten_repr_len(size_t n) {
    if (n == 0) {
        return 1;
    }
    size_t len = 0;
    while (n > 0) {
        len += 1;
        n /= 10;
    }
    return len;
}

bytes_t *bytes_init(size_t len, char *data) {
    bytes_t *init = malloc(sizeof(bytes_t));
    assert(init);
    init->len = len;
    init->data = data;
    return init;
}

void bytes_free(bytes_t *bytes) {
    free(bytes->data);
    free(bytes);
}

bytes_t *response_type_format(response_code_t code, mime_type_t type, bytes_t *_body) {
    const char *body;
    size_t body_len;
    if (_body == NULL) {
        body = "";
        body_len = 0;
    } else {
        body = ((bytes_t *) _body)->data;
        body_len = ((bytes_t *) _body)->len;
    }
    const char *brief = status_brief(code);
    size_t brief_len = strlen(brief);
    const char *mime = mime_string(type);
    size_t mime_len = strlen(mime);
    const char FORMAT[] = 
        "HTTP/1.1 %d %s\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %zu\r\n"
        "\r\n";
    // length of the template in the formatted string. Subtracts off format strings.
    const size_t TEMPLATE_LEN = strlen(FORMAT) - strlen("%d") - 2 * strlen("%s") - strlen("%zu");
    const size_t STATUS_CODE_LEN = 3;
    size_t content_len_len = base_ten_repr_len(body_len);
    size_t resp_len = TEMPLATE_LEN + STATUS_CODE_LEN + brief_len + mime_len + content_len_len + body_len;
    if (type == MIME_HTML || type == MIME_PLAIN) {
        resp_len += 1;
    }
    char *resp = calloc(resp_len, sizeof(char));
    assert(resp);
    size_t end = snprintf(resp, resp_len, FORMAT, code, brief, mime, body_len);
    memcpy(resp + end, body, body_len);
    return bytes_init(resp_len, resp);
}