#define DOOM_IMPLEMENTATION 
#include "PureDOOM.h"
#include <syscall.h>
#include <stdint.h>

//We use a separate heap because malloc is not yet available in userspace.
#define DOOM_HEAP_START ((unsigned char*)0x00500000)
#define DOOM_HEAP_SIZE  (24 * 1024 * 1024) //24mb

static unsigned char* doom_heap_curr = DOOM_HEAP_START;
static unsigned char* doom_heap_end = DOOM_HEAP_START + DOOM_HEAP_SIZE;

// The following functions are wrappers for system calls made for
// compatibility with puredoom's function signatures
static int doom_cstr_len(const char* str)
{
    int len = 0;
    while (str && str[len]) len++;
    return len;
}

static void doom_print_cb(const char* str)
{
    int len = doom_cstr_len(str);
    if (len > 0) {
        write(1, str, len);
    }
}

static void* doom_open_cb(const char* filename, const char* mode)
{
    (void)mode; // open doesn't support flags/mode (yet)
    int fd = open(filename, 0);
    if (fd < 0) return 0;
    return (void*)(intptr_t)(fd);
}

static void doom_close_cb(void* handle)
{
    int fd = (int)(intptr_t)handle;
    if (fd >= 0) {
        close(fd);
    }
}

static int doom_read_cb(void* handle, void* buf, int count)
{
    int fd = (int)(intptr_t)handle;
    if (fd < 0) return -1;
    return read(fd, (char*)buf, count);
}

static int doom_write_cb(void* handle, const void* buf, int count)
{
    int fd = (int)(intptr_t)handle;
    if (fd < 0) return -1;
    return (int)write(fd, (const char*)buf, count);
}

static int doom_seek_cb(void* handle, int offset, doom_seek_t origin)
{
    int fd = (int)(intptr_t)handle;
    if (fd < 0) return -1;
    return seek(fd, offset, (int)origin);
}

static int doom_tell_cb(void* handle)
{
    int fd = (int)(intptr_t)handle;
    if (fd < 0) return -1;
    return tell(fd);
}

static int doom_eof_cb(void* handle)
{
    int fd = (int)(intptr_t)handle;
    if (fd < 0) return 1;
    return eof(fd);
}

// Bump allocator (bump ptr until we're out of heap memory)
static void* doom_malloc_cb(int size)
{
    if (size <= 0) return 0;

    uintptr_t curr = (uintptr_t)doom_heap_curr;

    // 16-byte align allocated blocks
    curr = (curr + 15ULL) & ~15ULL;

    if (curr + (uintptr_t)size > (uintptr_t)doom_heap_end) {
        return 0;
    }

    doom_heap_curr = (unsigned char*)(curr + (uintptr_t)size);
    return (void*)curr;
}

// No free
static void doom_free_cb(void* ptr)
{
    (void)ptr;
}

static void doom_exit_cb(int code)
{
    exit(code);
}

// To get path for the WAD file
static char* doom_getenv_cb(const char* var)
{
    static char home[] = "/";
    static char waddir[] = "/";

    if (!var) return 0;
    if (doom_strcmp(var, "HOME") == 0) return home;
    if (doom_strcmp(var, "DOOMWADDIR") == 0) return waddir;
    return 0;
}

int main()
{
    char* argv[2] = {"doom", 0};

    // Override default implementations
    doom_set_print(doom_print_cb);
    doom_set_malloc(doom_malloc_cb, doom_free_cb);
    doom_set_file_io(doom_open_cb,
                     doom_close_cb,
                     doom_read_cb,
                     doom_write_cb,
                     doom_seek_cb,
                     doom_tell_cb,
                     doom_eof_cb);
    doom_set_exit(doom_exit_cb);
    doom_set_getenv(doom_getenv_cb);

    doom_init(1, argv, 0);

    while (true)
    {
        doom_force_update();
        const uint8_t* framebuffer = doom_get_framebuffer(4);
        draw(framebuffer, 320, 200, 4);
    }
}