#if defined(WIN32)
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_NONSTDC_NO_DEPRECATE
#endif

#include "DOOM.h"

#include "d_main.h"
#include "doomdef.h"
#include "doomtype.h"
#include "i_system.h"
#include "m_argv.h"
#include "m_misc.h"


extern byte* screens[5];
extern unsigned char screen_palette[256 * 3];
extern doom_boolean is_wiping_screen;
extern default_t defaults[];
extern int numdefaults;
extern signed short mixbuffer[2048];


static unsigned char* screen_buffer = 0;
static unsigned char* final_screen_buffer = 0;
static int last_update_time = 0;
static int button_states[3] = { 0 };
static char itoa_buf[20];

void *malloc_wrapper(int sz) {
    return malloc(sz);
}

void free_wrapper(void *p) {
    free(p);
}

static void* doom_malloc_impl(int size)
{
    return malloc((size_t)size);
}
static void doom_free_impl(void* ptr) {
    free(ptr);
}

char error_buf[260];
int doom_flags = 0;
doom_print_fn doom_print = 0;
doom_malloc_fn doom_malloc = malloc_wrapper;
doom_free_fn doom_free = free_wrapper;
doom_open_fn doom_open = 0;
doom_close_fn doom_close = 0;
doom_read_fn doom_read = 0;
doom_write_fn doom_write = 0;
doom_seek_fn doom_seek = 0;
doom_tell_fn doom_tell = 0;
doom_eof_fn doom_eof = 0;
doom_gettime_fn doom_gettime = 0;
doom_exit_fn doom_exit = 0;
doom_getenv_fn doom_getenv = 0;


void D_DoomLoop(void);
void D_UpdateWipe(void);
void I_UpdateSound();
unsigned long I_TickSong();


#if defined(DOOM_IMPLEMENT_PRINT)
#include <stdio.h>
static void doom_print_impl(const char* str)
{
    printf("%s", str);
}
#else
extern void doom_print_impl(const char* str);
#endif

#if defined(DOOM_IMPLEMENT_MALLOC)
#include <stdlib.h>
static void* doom_malloc_impl(int size)
{
    return malloc((size_t)size);
}
static void doom_free_impl(void* ptr)
{
    free(ptr);
}
#else
extern void* doom_malloc_impl(int size);
extern void doom_free_impl(void* ptr);
#endif


#if defined(DOOM_IMPLEMENT_FILE_IO)
#include <stdio.h>
void* doom_open_impl(const char* filename, const char* mode)
{
    return fopen(filename, mode);
}
void doom_close_impl(void* handle)
{
    fclose(handle);
}
int doom_read_impl(void* handle, void *buf, int count)
{
    return (int)fread(buf, 1, count, handle);
}
int doom_write_impl(void* handle, const void *buf, int count)
{
    return (int)fwrite(buf, 1, count, handle);
}
int doom_seek_impl(void* handle, int offset, doom_seek_t origin)
{
    return fseek(handle, offset, origin);
}
int doom_tell_impl(void* handle)
{
    return (int)ftell(handle);
}
int doom_eof_impl(void* handle)
{
    return feof(handle);
}
#else
extern void* doom_open_impl(const char* filename, const char* mode);
extern void doom_close_impl(void* handle);
extern int doom_read_impl(void* handle, void *buf, int count);
extern int doom_write_impl(void* handle, const void *buf, int count);
extern int doom_seek_impl(void* handle, int offset, doom_seek_t origin);
extern int doom_tell_impl(void* handle);
extern int doom_eof_impl(void* handle);
#endif


#if defined(DOOM_IMPLEMENT_GETTIME)
#if defined(WIN32)
#include <winsock.h>
#else
#include <sys/time.h>
#endif
void doom_gettime_impl(int* sec, int* usec)
{
#if defined(WIN32)
    static const unsigned long long EPOCH = ((unsigned long long)116444736000000000ULL);
    SYSTEMTIME system_time;
    FILETIME file_time;
    unsigned long long time;
    GetSystemTime(&system_time);
    SystemTimeToFileTime(&system_time, &file_time);
    time = ((unsigned long long)file_time.dwLowDateTime);
    time += ((unsigned long long)file_time.dwHighDateTime) << 32;
    *sec = (int)((time - EPOCH) / 10000000L);
    *usec = (int)(system_time.wMilliseconds * 1000);
#else
    struct timeval tp;
    struct timezone tzp;
    gettimeofday(&tp, &tzp);
    *sec = tp.tv_sec;
    *usec = tp.tv_usec;
#endif
}
#else
extern void doom_gettime_impl(int* sec, int* usec);
#endif


#if defined(DOOM_IMPLEMENT_EXIT)
#include <stdlib.h>
void doom_exit_impl(int code)
{
    exit(code);
}
#else
extern void doom_exit_impl(int code);
#endif


#if defined(DOOM_IMPLEMENT_GETENV)
#include <stdlib.h>
char* doom_getenv_impl(const char* var)
{
    return getenv(var);
}
#else
//extern char* doom_getenv_impl(const char* var);
char* doom_getenv_impl(const char* var) {
    return "";
}
#endif

void doom_memset(void* ptr, int value, int num)
{
    unsigned char* p = ptr;
    for (int i = 0; i < num; ++i, ++p)
    {
        *p = (unsigned char)value;
    }
}


void* doom_memcpy(void* destination, const void* source, int num)
{
    unsigned char* dst = destination;
    const unsigned char* src = source;

    for (int i = 0; i < num; ++i, ++dst, ++src)
    {
        *dst = *src;
    }

    return destination;
}


int doom_strlen(const char* str)
{
    int len = 0;
    while (*str++) ++len;
    return len;
}


char* doom_concat(char* dst, const char* src)
{
    char* ret = dst;
    dst += doom_strlen(dst);

    while (*src) *dst++ = *src++;
    *dst = *src; // \0

    return ret;
}


char* doom_strcpy(char* dst, const char* src)
{
    char* ret = dst;

    while (*src) *dst++ = *src++;
    *dst = *src; // \0

    return ret;
}


char* doom_strncpy(char* dst, const char* src, int num)
{
    int i = 0;

    for (; i < num; ++i)
    {
        if (!src[i]) break;
        dst[i] = src[i];
    }

    while (i < num) dst[i++] = '\0';

    return dst;
}


int doom_strcmp(const char* str1, const char* str2)
{
    int ret = 0;

    while (!(ret = *(unsigned char*)str1 - *(unsigned char*) str2) && *str1)
        ++str1, ++str2;

    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;

    return (ret);
}


int doom_strncmp(const char* str1, const char* str2, int n)
{
    int ret = 0;
    int count = 1;

    while (!(ret = *(unsigned char*)str1 - *(unsigned char*) str2) && *str1 && count++ < n)
        ++str1, ++str2;

    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;

    return (ret);
}


int doom_toupper(int c)
{
    if (c >= 'a' && c <= 'z') return c - 'a' + 'A';
    return c;
}


int doom_strcasecmp(const char* str1, const char* str2)
{
    int ret = 0;

    while (!(ret = doom_toupper(*(unsigned char*)str1) - doom_toupper(*(unsigned char*)str2)) && *str1)
        ++str1, ++str2;

    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;

    return (ret);
}


int doom_strncasecmp(const char* str1, const char* str2, int n)
{
    int ret = 0;
    int count = 1;

    while (!(ret = doom_toupper(*(unsigned char*)str1) - doom_toupper(*(unsigned char*)str2)) && *str1 && count++ < n)
        ++str1, ++str2;

    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;

    return (ret);
}


int doom_atoi(const char* str)
{
    int i = 0;
    int c;

    while ((c = *str++) != 0)
    {
        i *= 10;
        i += c - '0';
    }

    return i;
}


int doom_atox(const char* str)
{
    int i = 0;
    int c;

    while ((c = *str++) != 0)
    {
        i *= 16;
        if (c >= '0' && c <= '9')
            i += c - '0';
        else
            i += c - 'A' + 10;
    }

    return i;
}


const char* doom_itoa(int k, int radix)
{
    int i = k < 0 ? -k : k;
    if (i == 0)
    {
        itoa_buf[0] = '0';
        itoa_buf[1] = '\0';
        return itoa_buf;
    }

    int idx = k < 0 ? 1 : 0;
    int j = i;
    while (j)
    {
        j /= radix;
        idx++;
    }
    itoa_buf[idx] = '\0';

    if (radix == 10)
    {
        while (i)
        {
            itoa_buf[--idx] = '0' + (i % 10);
            i /= 10;
        }
    }
    else
    {
        while (i)
        {
            int k = (i & 0xF);
            if (k >= 10)
                itoa_buf[--idx] = 'A' + ((i & 0xF) - 10);
            else
                itoa_buf[--idx] = '0' + (i & 0xF);
            i >>= 4;
        }
    }

    if (k < 0) itoa_buf[0] = '-';

    return itoa_buf;
}


const char* doom_ctoa(char c)
{
    itoa_buf[0] = c;
    itoa_buf[1] = '\0';
    return itoa_buf;
}


const char* doom_ptoa(void* p)
{
    int idx = 0;
    unsigned long long i = (unsigned long long)p;

    itoa_buf[idx++] = '0';
    itoa_buf[idx++] = 'x';

    while (i)
    {
        int k = (i & 0xF);
        if (k >= 10)
            itoa_buf[idx++] = 'A' + ((i & 0xF) - 10);
        else
            itoa_buf[idx++] = '0' + (i & 0xF);
        i >>= 4;
    }

    itoa_buf[idx] = '\0';
    return itoa_buf;
}


int doom_fprint(void* handle, const char* str)
{
    return doom_write(handle, str, doom_strlen(str));
}


static default_t* get_default(const char* name)
{
    for (int i = 0; i < numdefaults; ++i)
    {
        if (doom_strcmp(defaults[i].name, name) == 0) return &defaults[i];
    }
    return 0;
}


void doom_set_resolution(int width, int height)
{
    if (width <= 0 || height <= 0) return;
    // SCREENWIDTH = width;
    // SCREENHEIGHT = height;
}


void doom_set_default_int(const char* name, int value)
{
    default_t* def = get_default(name);
    if (!def) return;
    def->defaultvalue = value;
}


void doom_set_default_string(const char* name, const char* value)
{
    default_t* def = get_default(name);
    if (!def) return;
    def->default_text_value = (char*)value;
}


void doom_set_print(doom_print_fn print_fn)
{
    doom_print = print_fn;
}


void doom_set_malloc(doom_malloc_fn malloc_fn, doom_free_fn free_fn)
{
    doom_malloc = malloc_fn;
    doom_free = free_fn;
}


void doom_set_file_io(doom_open_fn open_fn,
                      doom_close_fn close_fn,
                      doom_read_fn read_fn,
                      doom_write_fn write_fn,
                      doom_seek_fn seek_fn,
                      doom_tell_fn tell_fn,
                      doom_eof_fn eof_fn)
{
    doom_open = open_fn;
    doom_close = close_fn;
    doom_read = read_fn;
    doom_write = write_fn;
    doom_seek = seek_fn;
    doom_tell = tell_fn;
    doom_eof = eof_fn;
}


void doom_set_gettime(doom_gettime_fn gettime_fn)
{
    doom_gettime = gettime_fn;
}


void doom_set_exit(doom_exit_fn exit_fn)
{
    doom_exit = exit_fn;
}


void doom_set_getenv(doom_getenv_fn getenv_fn)
{
    doom_getenv = getenv_fn;
}


void doom_init(int argc, char** argv, int flags)
{
    if (!doom_print) doom_print = doom_print_impl;
    if (!doom_malloc) doom_malloc = doom_malloc_impl;
    if (!doom_free) doom_free = doom_free_impl;
    if (!doom_open) doom_open = doom_open_impl;
    if (!doom_close) doom_close = doom_close_impl;
    if (!doom_read) doom_read = doom_read_impl;
    if (!doom_write) doom_write = doom_write_impl;
    if (!doom_seek) doom_seek = doom_seek_impl;
    if (!doom_tell) doom_tell = doom_tell_impl;
    if (!doom_eof) doom_eof = doom_eof_impl;
    if (!doom_gettime) doom_gettime = doom_gettime_impl;
    if (!doom_exit) doom_exit = doom_exit_impl;
    if (!doom_getenv) doom_getenv = doom_getenv_impl;

    screen_buffer = doom_malloc(SCREENWIDTH * SCREENHEIGHT);

    final_screen_buffer = doom_malloc(SCREENWIDTH * SCREENHEIGHT * 4);
    last_update_time = I_GetTime();

    myargc = argc;
    myargv = argv;
    doom_flags = flags;

    D_DoomMain();
}


void doom_update(void)
{
    int now = I_GetTime();
    int delta_time = now - last_update_time;

    while (delta_time-- > 0)
    {
        if (is_wiping_screen)
            D_UpdateWipe();
        else
            D_DoomLoop();
    }

    last_update_time = now;
}


const unsigned char* doom_get_framebuffer(int channels)
{
    int i, len;

    doom_memcpy(screen_buffer, screens[0], SCREENWIDTH * SCREENHEIGHT);

    extern doom_boolean menuactive;
    extern gamestate_t gamestate; 
    extern doom_boolean automapactive;
    extern int crosshair;

    // Draw crosshair
    if (crosshair && 
        !menuactive &&
        gamestate == GS_LEVEL &&
        !automapactive)
    {
        int y;
        extern int setblocks;
        if (setblocks == 11) y = SCREENHEIGHT / 2 + 8;
        else y = SCREENHEIGHT / 2 - 8;
        for (i = 0; i < 2; ++i)
        {
            screen_buffer[SCREENWIDTH / 2 - 2 - i + y * SCREENWIDTH] = 4;
            screen_buffer[SCREENWIDTH / 2 + 2 + i + y * SCREENWIDTH] = 4;
        }
        for (i = 0; i < 2; ++i)
        {
            screen_buffer[SCREENWIDTH / 2 + (y - 2 - i) * SCREENWIDTH] = 4;
            screen_buffer[SCREENWIDTH / 2 + (y + 2 + i) * SCREENWIDTH] = 4;
        }
    }

    if (channels == 1)
    {
        return screen_buffer;
    }
    else if (channels == 3)
    {
        for (i = 0, len = SCREENWIDTH * SCREENHEIGHT; i < len; ++i)
        {
            int k = i * 3;
            int kpal = screen_buffer[i] * 3;
            final_screen_buffer[k + 0] = screen_palette[kpal + 0];
            final_screen_buffer[k + 1] = screen_palette[kpal + 1];
            final_screen_buffer[k + 2] = screen_palette[kpal + 2];
        }
        return final_screen_buffer;
    }
    else if (channels == 4)
    {
        for (i = 0, len = SCREENWIDTH * SCREENHEIGHT; i < len; ++i)
        {
            int k = i * 4;
            int kpal = screen_buffer[i] * 3;
            final_screen_buffer[k + 0] = screen_palette[kpal + 0];
            final_screen_buffer[k + 1] = screen_palette[kpal + 1];
            final_screen_buffer[k + 2] = screen_palette[kpal + 2];
            final_screen_buffer[k + 3] = 255;
        }
        return final_screen_buffer;
    }
    else
    {
        return 0;
    }
}


unsigned long doom_tick_midi(void)
{
    return I_TickSong();
}


short* doom_get_sound_buffer(void)
{
    I_UpdateSound();
    return mixbuffer;
}


void doom_key_down(doom_key_t key)
{
    event_t event;
    event.type = ev_keydown;
    event.data1 = (int)key;
    D_PostEvent(&event);
}


void doom_key_up(doom_key_t key)
{
    event_t event;
    event.type = ev_keyup;
    event.data1 = (int)key;
    D_PostEvent(&event);
}


void doom_button_down(doom_button_t button)
{
    button_states[button] = 1;

    event_t event;
    event.type = ev_mouse;
    event.data1 =
        (button_states[0]) |
        (button_states[1] ? 2 : 0) |
        (button_states[2] ? 4 : 0);
    event.data2 = event.data3 = 0;
    D_PostEvent(&event);
}


void doom_button_up(doom_button_t button)
{
    button_states[button] = 0;

    event_t event;
    event.type = ev_mouse;
    event.data1 =
        (button_states[0]) |
        (button_states[1] ? 2 : 0) |
        (button_states[2] ? 4 : 0);

    event.data1 =
        event.data1
        ^ (button_states[0] ? 1 : 0)
        ^ (button_states[1] ? 2 : 0)
        ^ (button_states[2] ? 4 : 0);

    event.data2 = event.data3 = 0;
    D_PostEvent(&event);
}


void doom_mouse_move(int delta_x, int delta_y)
{
    event_t event;

    event.type = ev_mouse;
    event.data1 =
        (button_states[0]) |
        (button_states[1] ? 2 : 0) |
        (button_states[2] ? 4 : 0);
    event.data2 = delta_x;
    event.data3 = -delta_y;

    if (event.data2 || event.data3)
    {
        D_PostEvent(&event);
    }
}
