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

#ifdef _WIN32
  #define WIN32_LEAN_AND_MEAN
  #include <winsock2.h>
  #include <ws2tcpip.h>
  #pragma comment(lib, "ws2_32.lib")
  typedef SOCKET socket_t;
  #define CLOSESOCKET closesocket
  #define ISVALIDSOCK(s) ((s) != INVALID_SOCKET)
  #define STAT_FUNC _stat
  #define STAT_ST   struct _stat
#else
  #include <unistd.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <fcntl.h>
  typedef int socket_t;
  #define CLOSESOCKET close
  #define ISVALIDSOCK(s) ((s) >= 0)
  #define INVALID_SOCKET (-1)
  #define STAT_FUNC stat
  #define STAT_ST   struct stat
#endif

#define LISTEN_PORT    8080
#define REQ_BUF_SIZE   4096
#define FILE_BUF_SIZE  8192
#define HDR_BUF_SIZE   512
#define PATH_BUF_SIZE  512

/* ------------------------------------------------------------------ */
/*  MIME type lookup                                                    */
/* ------------------------------------------------------------------ */

typedef struct {
    const char *ext;
    const char *mime;
} mime_entry_t;

static const mime_entry_t mime_map[] = {
    { ".html",  "text/html;charset=utf-8" },
    { ".css",   "text/css;charset=utf-8" },
    { ".js",    "application/javascript;charset=utf-8" },
    { ".json",  "application/json" },
    { ".png",   "image/png" },
    { ".jpg",   "image/jpeg" },
    { ".jpeg",  "image/jpeg" },
    { ".gif",   "image/gif" },
    { ".svg",   "image/svg+xml" },
    { ".ico",   "image/x-icon" },
    { ".woff",  "font/woff" },
    { ".woff2", "font/woff2" },
    { ".ttf",   "font/ttf" },
    { ".wasm",  "application/wasm" },
    { ".pdf",   "application/pdf" },
    { ".txt",   "text/plain;charset=utf-8" },
    { ".xml",   "application/xml" },
    { NULL, NULL }
};

static const char *mime_from_path(const char *path)
{
    const char *dot = strrchr(path, '.');
    if (dot) {
        for (const mime_entry_t *e = mime_map; e->ext; ++e) {
            if (strcmp(dot, e->ext) == 0)
                return e->mime;
        }
    }
    return "application/octet-stream";
}

/* ------------------------------------------------------------------ */
/*  Send an HTTP error response                                        */
/* ------------------------------------------------------------------ */

static void send_error(socket_t sock, const char *status)
{
    char hdr[256];
    int len = (int)strlen(status);
    int n = snprintf(hdr, sizeof(hdr),
        "HTTP/1.1 %s\r\n"
        "Content-Type: text/plain\r\n"
        "Content-Length: %d\r\n"
        "Connection: close\r\n"
        "\r\n",
        status, len);
    if (n > 0 && n < (int)sizeof(hdr)) {
        send(sock, hdr, n, 0);
        send(sock, status, len, 0);
    }
}

/* ------------------------------------------------------------------ */
/*  Handle one accepted connection                                     */
/* ------------------------------------------------------------------ */

static void handle_request(socket_t sock)
{
    char req_buf[REQ_BUF_SIZE];
    int n = (int)recv(sock, req_buf, sizeof(req_buf) - 1, 0);
    if (n <= 0) return;
    req_buf[n] = '\0';

    /* ---- Parse "GET /path HTTP/1.x\r\n..." ---- */
    if (strncmp(req_buf, "GET ", 4) != 0) {
        send_error(sock, "405 Method Not Allowed");
        return;
    }

    char *path_start = req_buf + 4;
    char *path_end   = strchr(path_start, ' ');
    if (!path_end) {
        send_error(sock, "400 Bad Request");
        return;
    }
    *path_end = '\0';

    const char *path = path_start;

    /* Default document */
    if (strcmp(path, "/") == 0)
        path = "p2601.html";

    /* Strip leading '/' */
    while (*path == '/')
        ++path;

    /* Reject suspicious paths */
    if (*path == '\0' ||
        strstr(path, "..") != NULL ||
        strchr(path, '\\') != NULL)
    {
        send_error(sock, "403 Forbidden");
        return;
    }

    /* ---- Build filesystem path: out/<rel> ---- */
    char filepath[PATH_BUF_SIZE];
    int pn = snprintf(filepath, sizeof(filepath), "out/%s", path);
    if (pn < 0 || pn >= (int)sizeof(filepath)) {
        send_error(sock, "414 URI Too Long");
        return;
    }

    /* ---- Open and stat the file ---- */
    FILE *fp = fopen(filepath, "rb");
    if (!fp) {
        send_error(sock, "404 Not Found");
        return;
    }

    STAT_ST st;
    if (STAT_FUNC(filepath, &st) != 0) {
        fclose(fp);
        send_error(sock, "500 Internal Server Error");
        return;
    }
    long long file_size = (long long)st.st_size;

    const char *mime = mime_from_path(path);

    /* ---- Send response header ---- */
    char hdr_buf[HDR_BUF_SIZE];
    int hdr_len = snprintf(hdr_buf, sizeof(hdr_buf),
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %lld\r\n"
        "Connection: close\r\n"
        "\r\n",
        mime, file_size);

    if (hdr_len < 0 || hdr_len >= (int)sizeof(hdr_buf)) {
        fclose(fp);
        return;
    }

    if (send(sock, hdr_buf, hdr_len, 0) <= 0) {
        fclose(fp);
        return;
    }

    /* ---- Stream the file body in chunks ---- */
    char file_buf[FILE_BUF_SIZE];
    size_t bytes;
    while ((bytes = fread(file_buf, 1, sizeof(file_buf), fp)) > 0) {
        size_t sent = 0;
        while (sent < bytes) {
            int s = send(sock, file_buf + sent, (int)(bytes - sent), 0);
            if (s <= 0) { fclose(fp); return; }
            sent += (size_t)s;
        }
    }

    fclose(fp);
}

/* ------------------------------------------------------------------ */
/*  main — bind, listen, accept loop                                   */
/* ------------------------------------------------------------------ */

int main(void)
{
#ifdef _WIN32
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        fprintf(stderr, "WSAStartup failed\n");
        return 1;
    }
#endif

    socket_t srv = socket(AF_INET, SOCK_STREAM, 0);
    if (!ISVALIDSOCK(srv)) {
        perror("socket");
        return 1;
    }

    /* SO_REUSEADDR */
    int opt = 1;
#ifdef _WIN32
    setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
#else
    setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family      = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port        = htons(LISTEN_PORT);

    if (bind(srv, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
        perror("bind");
        CLOSESOCKET(srv);
        return 1;
    }

    if (listen(srv, SOMAXCONN) != 0) {
        perror("listen");
        CLOSESOCKET(srv);
        return 1;
    }

    fprintf(stderr, ":: Listening at http://localhost:%d\n", LISTEN_PORT);

    for (;;) {
        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
#ifdef _WIN32
        socket_t client = accept(srv, (struct sockaddr *)&client_addr, (int *)&client_len);
#else
        socket_t client = accept(srv, (struct sockaddr *)&client_addr, &client_len);
#endif
        if (!ISVALIDSOCK(client))
            continue;

        handle_request(client);
        CLOSESOCKET(client);
    }

    /* unreachable, but tidy */
    CLOSESOCKET(srv);
#ifdef _WIN32
    WSACleanup();
#endif
    return 0;
}
