/*
 * Mr. 4th Dimention - Allen Webster
 *
 * 12.12.2014
 *
 * Win32 layer for project codename "4ed"
 *
 */

// TOP
// TODO(allen):
//
// Fix the OwnDC thing.
// 

#ifdef FRED_NOT_PACKAGE

#define FRED_INTERNAL 1
#define FRED_SLOW 1

#define FRED_PRINT_DEBUG 1
#define FRED_PRINT_DEBUG_FILE_LINE 0
#define FRED_PROFILING 1
#define FRED_PROFILING_OS 0
#define FRED_FULL_ERRORS 0

#else

#define FRED_SLOW 0
#define FRED_INTERNAL 0

#define FRED_PRINT_DEBUG 0
#define FRED_PRINT_DEBUG_FILE_LINE 0
#define FRED_PROFILING 0
#define FRED_PROFILING_OS 0
#define FRED_FULL_ERRORS 0

#endif

#define SOFTWARE_RENDER 0

#if FRED_INTERNAL == 0
#undef FRED_PRINT_DEBUG
#define FRED_PRINT_DEBUG 0
#undef FRED_PROFILING
#define FRED_PROFILING 0
#undef FRED_PROFILING_OS
#define FRED_PROFILING_OS 0
#endif

#if FRED_PRINT_DEBUG == 0
#undef FRED_PRINT_DEBUG_FILE_LINE
#define FRED_PRINT_DEBUG_FILE_LINE 0
#undef FRED_PRINT_DEBUG_FILE_LINE
#define FRED_PROFILING_OS 0
#endif

#define FPS 30
#define FRAME_TIME (1000000 / FPS)

#define BUFFER_EXPERIMENT_SCALPEL 3

#include "4ed_meta.h"

#define FCPP_FORBID_MALLOC

#include "4cpp_types.h"
#define FCPP_STRING_IMPLEMENTATION
#include "4cpp_string.h"
#define FCPP_LEXER_IMPLEMENTATION
#include "4cpp_lexer.h"
#include "4ed_math.cpp"
#include "4coder_custom.h"
#include "4ed.h"
#include "4ed_system.h"
#include "4ed_rendering.h"

struct TEMP_BACKDOOR{
    Get_Binding_Data_Function *get_bindings;
    Set_Extra_Font_Function *set_extra_font;
} TEMP;

#if FRED_INTERNAL

struct Sys_Bubble : public Bubble{
    i32 line_number;
    char *file_name;
};

#endif

#include <windows.h>
#include <GL/gl.h>

#include "4ed_internal.h"
#include "4ed_rendering.cpp"
#include "4ed_command.cpp"
#include "4ed_layout.cpp"
#include "4ed_style.cpp"
#include "4ed_file_view.cpp"
#include "4ed_color_view.cpp"
#include "4ed_interactive_view.cpp"
#include "4ed_menu_view.cpp"
#include "4ed_debug_view.cpp"
#include "4ed.cpp"
#include "4ed_keyboard.cpp"

struct Full_Job_Data{
    Job_Data job;
    
    u32 job_memory_index;
    u32 running_thread;
    bool32 finished;
    u32 id;
};

struct Work_Queue{
    u32 volatile write_position;
    u32 volatile read_position;
    Full_Job_Data jobs[256];
    
    HANDLE semaphore;
};

struct Thread_Context{
    u32 job_id;
    bool32 running;
    
    Work_Queue *queue;
    u32 id;
    u32 windows_id;
    HANDLE handle;
};

struct Thread_Group{
    Thread_Context *threads;
    i32 count;
};

struct Win32_Vars{
	HWND window_handle;
	Key_Codes key_codes, loose_codes;
	Key_Input_Data input_data, previous_data;
    
#if SOFTWARE_RENDER
	BITMAPINFO bmp_info;
	union{
		struct{
			void *pixel_data;
			i32 width, height, pitch;
		};
		Render_Target target;
	};
	i32 true_pixel_size;
#else
    Render_Target target;
#endif
    
    u32 volatile force_redraw;
    
	Mouse_State mouse;
	bool32 focus;
	bool32 keep_playing;
	HCURSOR cursor_ibeam;
	HCURSOR cursor_arrow;
	HCURSOR cursor_leftright;
	HCURSOR cursor_updown;
	Application_Mouse_Cursor prev_mouse_cursor;
	Clipboard_Contents clipboard_contents;
	bool32 next_clipboard_is_self;
	DWORD clipboard_sequence;
    
	Thread_Context main_thread;
    
    Thread_Group groups[THREAD_GROUP_COUNT];
    Work_Queue queues[THREAD_GROUP_COUNT];
    HANDLE locks[LOCK_COUNT];
    HANDLE DEBUG_sysmem_lock;
    Thread_Memory *thread_memory;

    HMODULE custom;
    
    i64 performance_frequency;
    i64 start_pcount;
};

globalvar Win32_Vars win32vars;
globalvar Application_Memory win32memory;

internal void
_OutDbgStr(u8 *msg){
	OutputDebugString((char*)msg);
}

internal void
system_fatal_error(u8 *message){
	MessageBox(0, (char*)message, "4ed Error", MB_OK|MB_ICONERROR);
}

internal File_Data
system_load_file(u8 *filename){
    File_Data result = {};
    HANDLE file;
    file = CreateFile((char*)filename, GENERIC_READ, 0, 0,
                      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if (!file){
        return result;
    }
    
    DWORD lo, hi;
    lo = GetFileSize(file, &hi);
    
    if (hi != 0){
        CloseHandle(file);
        return result;
    }
    
    result.size = (lo) + (((u64)hi) << 32);
    result.data = system_get_memory(result.size);
    
    if (!result.data){
        CloseHandle(file);
        result = {};
        return result;
    }
    
    DWORD read_size;
    BOOL read_result = ReadFile(file, result.data, result.size,
                                &read_size, 0);
    if (!read_result || read_size != result.size){
        CloseHandle(file);
        system_free_memory(result.data);
        result = {};
        return result;
    }
    
    CloseHandle(file);
    return result;
}

internal bool32
system_save_file(u8 *filename, void *data, i32 size){
	HANDLE file;
	file = CreateFile((char*)filename, GENERIC_WRITE, 0, 0,
					  CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
	
	if (!file){
		return 0;
	}
	
	BOOL write_result;
	DWORD bytes_written;
	write_result = WriteFile(file, data, size, &bytes_written, 0);
	
	CloseHandle(file);
	
	if (!write_result || bytes_written != (u32)size){
		return 0;
	}
	
	return 1;
}

internal Time_Stamp
system_file_time_stamp(u8 *filename){
    Time_Stamp result;
    result = {};
    
    FILETIME last_write;
    WIN32_FILE_ATTRIBUTE_DATA data;
    if (GetFileAttributesEx((char*)filename, GetFileExInfoStandard, &data)){
        last_write = data.ftLastWriteTime;
        
        result.time = ((u64)last_write.dwHighDateTime << 32) | last_write.dwLowDateTime;
        result.success = 1;
    }
    
    return result;
}

internal u64
system_get_now(){
    u64 result;
    SYSTEMTIME sys_now;
    FILETIME file_now;
    GetSystemTime(&sys_now);
    SystemTimeToFileTime(&sys_now, &file_now);
    result = ((u64)file_now.dwHighDateTime << 32) | file_now.dwLowDateTime;
    return result;
}

internal void
system_free_file(File_Data data){
    system_free_memory(data.data);
}

internal i32
system_get_working_directory(u8 *destination, i32 max_size){
	DWORD required = GetCurrentDirectory(0, 0);
	if ((i32) required > max_size){
		// TODO(allen): WHAT NOW? Not enough space in destination for
		// current directory. Two step approach perhaps?
		return 0;
	}
	DWORD written = GetCurrentDirectory(max_size, (char*)destination);
	return (i32)written;
}

internal i32
system_get_easy_directory(u8 *destination){
	persist char easydir[] = "C:\\";
	for (i32 i = 0; i < ArrayCount(easydir); ++i){
		destination[i] = easydir[i];
	}
	return ArrayCount(easydir)-1;
}

internal File_List
system_get_files(String directory){
    File_List result = {};
    
    if (directory.size > 0){
        char dir_space[MAX_PATH + 32];
        String dir = make_string(dir_space, 0, MAX_PATH + 32);
        append(&dir, directory);
        char trail_str[] = "\\*";
        append(&dir, trail_str);
        
        char *c_str_dir = make_c_str(dir);
        
        WIN32_FIND_DATA find_data;
        HANDLE search;
        search = FindFirstFileA(c_str_dir, &find_data);
        
        if (search != INVALID_HANDLE_VALUE){            
            i32 count = 0;
            i32 file_count = 0;
            BOOL more_files = 1;
            do{
                if (!match(find_data.cFileName, ".") &&
                    !match(find_data.cFileName, "..")){
                    ++file_count;
                    i32 size = 0;
                    for(;find_data.cFileName[size];++size);
                    count += size + 1;
                }
                more_files = FindNextFile(search, &find_data);
            }while(more_files);
            FindClose(search);
            
            result.block = system_get_memory(count + file_count * sizeof(File_Info));
            result.infos = (File_Info*)result.block;
            char *name = (char*)(result.infos + file_count);
            if (result.block){
                search = FindFirstFileA(c_str_dir, &find_data);
                
                if (search != INVALID_HANDLE_VALUE){
                    File_Info *info = result.infos;
                    more_files = 1;
                    do{
                        if (!match(find_data.cFileName, ".") &&
                            !match(find_data.cFileName, "..")){
                            info->folder = (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
                            info->filename.str = name;
                            
                            char *name_base = name;
                            i32 i = 0;
                            for(;find_data.cFileName[i];++i) *name++ = find_data.cFileName[i];
                            info->filename.size = (i32)(name - name_base);
                            info->filename.memory_size = info->filename.size + 1;
                            *name++ = 0;
                            ++info;
                        }
                        more_files = FindNextFile(search, &find_data);
                    }while(more_files);
                    FindClose(search);
                    
                    result.count = file_count;
                    
                }else{
                    system_free_memory(result.block);
                    result = {};
                }
            }
        }
    }
    
	return result;
}

internal void
system_free_file_list(File_List list){
    system_free_memory(list.block);
}

#if FRED_INTERNAL
Sys_Bubble INTERNAL_sentinel;

internal Bubble*
INTERNAL_system_sentinel(){
    return &INTERNAL_sentinel;
}
#endif

internal void*
system_get_memory_(i32 size, i32 line_number, char *file_name){
	void *ptr = 0;
    
#if FRED_INTERNAL
    ptr = VirtualAlloc(0, size + sizeof(Sys_Bubble), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    Sys_Bubble *bubble = (Sys_Bubble*)ptr;
    bubble->flags = MEM_BUBBLE_SYS_DEBUG;
    bubble->line_number = line_number;
    bubble->file_name = file_name;
    bubble->size = size;
    WaitForSingleObject(win32vars.DEBUG_sysmem_lock, INFINITE);
    insert_bubble(&INTERNAL_sentinel, bubble);
    ReleaseSemaphore(win32vars.DEBUG_sysmem_lock, 1, 0);
    ptr = bubble + 1;
#else
    ptr = VirtualAlloc(0, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#endif
    
	return ptr;
}

internal void
system_free_memory(void *block){
    if (block){
#if FRED_INTERNAL
        Sys_Bubble *bubble = ((Sys_Bubble*)block) - 1;
        Assert((bubble->flags & MEM_BUBBLE_DEBUG_MASK) == MEM_BUBBLE_SYS_DEBUG);
        WaitForSingleObject(win32vars.DEBUG_sysmem_lock, INFINITE);
        remove_bubble(bubble);
        ReleaseSemaphore(win32vars.DEBUG_sysmem_lock, 1, 0);
        VirtualFree(bubble, 0, MEM_RELEASE);
#else
        VirtualFree(block, 0, MEM_RELEASE);
#endif
    }
}

internal i64
system_time(){
	i64 result = 0;
	LARGE_INTEGER time;
	if (QueryPerformanceCounter(&time)){
		result = (i64)(time.QuadPart - win32vars.start_pcount) * 1000000 / win32vars.performance_frequency;
	}
	return result;
}

// TODO(allen): Probably best to just drop all system functions here again.
internal void
system_post_clipboard(String str){
	if (OpenClipboard(win32vars.window_handle)){
		EmptyClipboard();
		HANDLE memory_handle;
		memory_handle = GlobalAlloc(GMEM_MOVEABLE, str.size+1);
		if (memory_handle){
			char *dest = (char*)GlobalLock(memory_handle);
            copy_fast_unsafe(dest, str);
			GlobalUnlock(memory_handle);
			SetClipboardData(CF_TEXT, memory_handle);
			win32vars.next_clipboard_is_self = 1;
		}
		CloseClipboard();
	}
}

#if SOFTWARE_RENDER
internal void
Win32RedrawScreen(HDC hdc){
	win32vars.bmp_info.bmiHeader.biHeight =
		-win32vars.bmp_info.bmiHeader.biHeight;
	SetDIBitsToDevice(hdc,
					  0, 0,
					  win32vars.width, win32vars.height,
					  0, 0,
					  0, win32vars.height,
					  win32vars.pixel_data,
					  &win32vars.bmp_info,
					  DIB_RGB_COLORS);
	win32vars.bmp_info.bmiHeader.biHeight =
		-win32vars.bmp_info.bmiHeader.biHeight;
}
#else
internal void
Win32RedrawScreen(HDC hdc){
    glFlush();
    SwapBuffers(hdc);
}
#endif

internal void
Win32Resize(i32 width, i32 height){
    if (width > 0 && height > 0){
        glViewport(0, 0, width, height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, width, height, 0, -1, 1);
        glScissor(0, 0, width, height);
        
        win32vars.target.width = width;
        win32vars.target.height = height;
    }
}

internal void
Win32KeyboardHandle(bool8 current_state, bool8 previous_state, WPARAM wParam){
    u16 key = keycode_lookup((u8)wParam);
    if (key != -1){
        if (current_state & !previous_state){
            i32 count = win32vars.input_data.press_count;
            if (count < KEY_INPUT_BUFFER_SIZE){
                win32vars.input_data.press[count].keycode = key;
                win32vars.input_data.press[count].loose_keycode = loose_keycode_lookup((u8)wParam);
                ++win32vars.input_data.press_count;
            }
        }
        else if (current_state){
            i32 count = win32vars.input_data.hold_count;
            if (count < KEY_INPUT_BUFFER_SIZE){
                win32vars.input_data.hold[count].keycode = key;
                win32vars.input_data.hold[count].loose_keycode = loose_keycode_lookup((u8)wParam);
                ++win32vars.input_data.hold_count;
            }
        }
    }
}

#define HOTKEY_ALT_ID 0

internal LRESULT
Win32Callback(HWND hwnd, UINT uMsg,
              WPARAM wParam, LPARAM lParam){
    
    LRESULT result = {};
    switch (uMsg){
    case WM_MENUCHAR:
    case WM_SYSCHAR:break;
    
    case WM_SYSKEYDOWN:
    case WM_SYSKEYUP:
    case WM_KEYDOWN:
    case WM_KEYUP:
    {
        switch (wParam){
        case VK_CONTROL:case VK_LCONTROL:case VK_RCONTROL:
        case VK_MENU:case VK_LMENU:case VK_RMENU:
        case VK_SHIFT:case VK_LSHIFT:case VK_RSHIFT: break;
            
        default:
            bool8 previous_state, current_state;
            previous_state = ((lParam & Bit_30)?(1):(0));
            current_state = ((lParam & Bit_31)?(0):(1));
            Win32KeyboardHandle(current_state, previous_state, wParam);
            result = DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
    }break;
    
    case WM_MOUSEMOVE:
    {
        win32vars.mouse.x = LOWORD(lParam);
        win32vars.mouse.y = HIWORD(lParam);
    }break;
    
    case WM_MOUSEWHEEL:
    {
        i16 rotation = GET_WHEEL_DELTA_WPARAM(wParam);
        if (rotation > 0){
            win32vars.mouse.wheel = 1;
        }
        else{
            win32vars.mouse.wheel = -1;
        }
    }break;
    
    case WM_LBUTTONDOWN:
    {
        win32vars.mouse.left_button = true;
    }break;
    
    case WM_RBUTTONDOWN:
    {
        win32vars.mouse.right_button = true;
    }break;
    
    case WM_LBUTTONUP:
    {
        win32vars.mouse.left_button = false;
    }break;
    
    case WM_RBUTTONUP:
    {
        win32vars.mouse.right_button = false;
    }break;
    
    case WM_KILLFOCUS:
    {
        win32vars.focus = 0;
        win32vars.mouse.left_button = false;
        win32vars.mouse.right_button = false;
        for (int i = 0; i < CONTROL_KEY_COUNT; ++i){
            win32vars.input_data.control_keys[i] = 0;
        }
    }break;
    
    case WM_SETFOCUS:
    {
        win32vars.focus = 1;
    }break;
    
    case WM_SIZE:
    {
#if SOFTWARE_RENDER
        i32 new_width = LOWORD(lParam);
        i32 new_height = HIWORD(lParam);
        i32 new_pitch = new_width * 4;
        
        if (new_height*new_pitch > win32vars.true_pixel_size){
            system_free_memory(win32vars.pixel_data);
            
            win32vars.pixel_data = system_get_memory(new_height*new_pitch);
            win32vars.true_pixel_size = new_height*new_pitch;
            
            if (!win32vars.pixel_data){
                FatalError("Failure allocating new screen memory");
                win32vars.keep_playing = 0;
            }
        }
        
        win32vars.width = new_width;
        win32vars.height = new_height;
        win32vars.pitch = new_pitch;
        
        win32vars.bmp_info.bmiHeader.biWidth = win32vars.width;
        win32vars.bmp_info.bmiHeader.biHeight = win32vars.height;
#else
        if (win32vars.target.handle){
            i32 new_width = LOWORD(lParam);
            i32 new_height = HIWORD(lParam);
            
            Win32Resize(new_width, new_height);
        }
#endif
    }break;
    
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        
        Clipboard_Contents empty_contents = {};
#if FRED_INTERNAL
        INTERNAL_collecting_events = 0;
#endif
        app_step(&win32vars.main_thread,
                 &win32vars.key_codes,
                 &win32vars.previous_data, &win32vars.mouse,
                 0, &win32vars.target, &win32memory, empty_contents, 0, 1);
#if FRED_INTERNAL
        INTERNAL_collecting_events = 1;
#endif
        Win32RedrawScreen(hdc);
        
        EndPaint(hwnd, &ps);
    }break;
    
    case WM_CLOSE: // NOTE(allen): I expect WM_CLOSE not WM_DESTROY
    case WM_DESTROY:
    {
        win32vars.keep_playing = 0;
    }break;
    
    default:
    {
        result = DefWindowProc(hwnd, uMsg, wParam, lParam);
    }break;
	}
	return result;
}

#define THREAD_NOT_ASSIGNED 0xFFFFFFFF

#define JOB_ID_WRAP (ArrayCount(queue->jobs) * 4)
#define QUEUE_WRAP (ArrayCount(queue->jobs))

internal DWORD WINAPI
ThreadProc(LPVOID lpParameter){
    Thread_Context *thread = (Thread_Context*)lpParameter;
    Work_Queue *queue = thread->queue;
    
    for (;;){
        u32 read_index = queue->read_position;
        u32 write_index = queue->write_position;
        
        if (read_index != write_index){
            u32 next_read_index = (read_index + 1) % JOB_ID_WRAP;
            u32 safe_read_index =
                InterlockedCompareExchange(&queue->read_position,
                                           next_read_index, read_index);
            
            if (safe_read_index == read_index){
                Full_Job_Data *full_job = queue->jobs + (safe_read_index % QUEUE_WRAP);
                // NOTE(allen): This is interlocked so that it plays nice
                // with the cancel job routine, which may try to cancel this job
                // at the same time that we try to run it
                
                i32 safe_running_thread =
                    InterlockedCompareExchange(&full_job->running_thread,
                                               thread->id, THREAD_NOT_ASSIGNED);
                
                if (safe_running_thread == THREAD_NOT_ASSIGNED){
                    thread->job_id = full_job->id;
                    thread->running = 1;
                    Thread_Memory *thread_memory = 0;
                    if (full_job->job.memory_request != 0){
                        thread_memory = win32vars.thread_memory + thread->id - 1;
                        if (thread_memory->size < full_job->job.memory_request){
                            if (thread_memory->data){
                                system_free_memory(thread_memory->data);
                            }
                            i32 new_size = LargeRoundUp(full_job->job.memory_request, Kbytes(4));
                            thread_memory->data = system_get_memory(new_size);
                            thread_memory->size = new_size;
                        }
                    }
                    full_job->job.callback(thread, thread_memory, full_job->job.data);
                    full_job->running_thread = 0;
                    thread->running = 0;
                }
            }
        }
        else{
            WaitForSingleObject(queue->semaphore, INFINITE);
        }
    }
}

internal bool32
Win32JobIsPending(Work_Queue *queue, u32 job_id){
    bool32 result;
    u32 job_index;
    Full_Job_Data *full_job;
    
    job_index = job_id % QUEUE_WRAP;
    full_job = queue->jobs + job_index;
    
    Assert(full_job->id == job_id);
    
    result = 0;
    if (full_job->running_thread != 0){
        result = 1;
    }
    
    return result;
}

internal u32
system_thread_get_id(Thread_Context *thread){
    return thread->id;
}

internal u32
system_thread_current_job_id(Thread_Context *thread){
    return thread->job_id;
}

internal u32
system_post_job(Thread_Group_ID group_id, Job_Data job){
    Work_Queue *queue = win32vars.queues + group_id;
    
    Assert((queue->write_position + 1) % QUEUE_WRAP != queue->read_position % QUEUE_WRAP);
    
    bool32 success = 0;
    u32 result = 0;
    while (!success){
        u32 write_index = queue->write_position;
        u32 next_write_index = (write_index + 1) % JOB_ID_WRAP;
        u32 safe_write_index =
            InterlockedCompareExchange(&queue->write_position,
                                       next_write_index, write_index);
        if (safe_write_index  == write_index){
            result = write_index;
            write_index = write_index % QUEUE_WRAP;
            queue->jobs[write_index].job = job;
            queue->jobs[write_index].running_thread = THREAD_NOT_ASSIGNED;
            queue->jobs[write_index].id = result;
            success = 1;
        }
    }
    
    ReleaseSemaphore(queue->semaphore, 1, 0);
    
    return result;
}

internal void
system_cancel_job(Thread_Group_ID group_id, u32 job_id){
    Work_Queue *queue = win32vars.queues + group_id;
    Thread_Group *group = win32vars.groups + group_id;
    
    u32 job_index;
    u32 thread_id;
    Full_Job_Data *full_job;
    Thread_Context *thread;
    
    job_index = job_id % QUEUE_WRAP;
    full_job = queue->jobs + job_index;
    
    Assert(full_job->id == job_id);
    thread_id =
        InterlockedCompareExchange(&full_job->running_thread,
                                   0, THREAD_NOT_ASSIGNED);
    
    if (thread_id != THREAD_NOT_ASSIGNED){
        system_aquire_lock(CANCEL_LOCK0 + thread_id - 1);
        thread = group->threads + thread_id - 1;
        TerminateThread(thread->handle, 0);
        u32 creation_flag = 0;
        thread->handle = CreateThread(0, 0, ThreadProc, thread, creation_flag, (LPDWORD)&thread->windows_id);
        system_release_lock(CANCEL_LOCK0 + thread_id - 1);
        thread->running = 0;
    }
}

internal bool32
system_job_is_pending(Thread_Group_ID group_id, u32 job_id){
    Work_Queue *queue = win32vars.queues + group_id;;
    return Win32JobIsPending(queue, job_id);
}

internal void
system_aquire_lock(Lock_ID id){
    WaitForSingleObject(win32vars.locks[id], INFINITE);
}

internal void
system_release_lock(Lock_ID id){
    ReleaseSemaphore(win32vars.locks[id], 1, 0);
}

internal void
system_aquire_lock(i32 id){
    WaitForSingleObject(win32vars.locks[id], INFINITE);
}

internal void
system_release_lock(i32 id){
    ReleaseSemaphore(win32vars.locks[id], 1, 0);
}

internal void
system_grow_thread_memory(Thread_Memory *memory){
    system_aquire_lock(CANCEL_LOCK0 + memory->id - 1);
    void *old_data = memory->data;
    i32 old_size = memory->size;
    i32 new_size = LargeRoundUp(memory->size*2, Kbytes(4));
    memory->data = system_get_memory(new_size);
    memory->size = new_size;
    if (old_data){
        memcpy(memory->data, old_data, old_size);
        system_free_memory(old_data);
    }
    system_release_lock(CANCEL_LOCK0 + memory->id - 1);
}

internal void
system_force_redraw(){
    InterlockedExchange(&win32vars.force_redraw, 1);
}

#if FRED_INTERNAL
internal void
INTERNAL_get_thread_states(Thread_Group_ID id, bool8 *running, i32 *pending){
    Work_Queue *queue = win32vars.queues + id;
    u32 write = queue->write_position;
    u32 read = queue->read_position;
    if (write < read) write += JOB_ID_WRAP;
    *pending = (i32)(write - read);
    
    Thread_Group *group = win32vars.groups + id;
    for (i32 i = 0; i < group->count; ++i){
        running[i] = (group->threads[i].running != 0);
    }
}
#endif

internal b32
system_cli_call(char *path, char *script_name, CLI_Handles *cli_out){
    char cmd[] = "c:\\windows\\system32\\cmd.exe";
    char *env_variables = 0;
    char command_line[2048];
    
    b32 success = 1;
    String s = make_fixed_width_string(command_line);
    copy(&s, make_lit_string("/C "));
    append_partial(&s, script_name);
    append_partial(&s, make_lit_string(".bat "));
    success = terminate_with_null(&s);
    
    if (success){
        success = 0;
        
        SECURITY_ATTRIBUTES sec_attributes;
        HANDLE out_read;
        HANDLE out_write;
        
        sec_attributes = {};
        sec_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
        sec_attributes.bInheritHandle = TRUE;
        
        if (CreatePipe(&out_read, &out_write, &sec_attributes, 0)){
            if (SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)){
                STARTUPINFO startup = {};
                startup.cb = sizeof(STARTUPINFO);
                startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
                startup.hStdError = out_write;
                startup.hStdOutput = out_write;
                startup.wShowWindow = SW_HIDE;
    
                PROCESS_INFORMATION info = {};

                Assert(sizeof(Plat_Handle) >= sizeof(HANDLE));
                if (CreateProcess(cmd, command_line,
                                  0, 0, TRUE, 0,
                                  env_variables, path,
                                  &startup, &info)){
                    success = 1;
                    CloseHandle(info.hThread);
                    *(HANDLE*)&cli_out->proc = info.hProcess;
                    *(HANDLE*)&cli_out->out_read = out_read;
                    *(HANDLE*)&cli_out->out_write = out_write;
                }
                else{
                    CloseHandle(out_read);
                    CloseHandle(out_write);
                    *(HANDLE*)&cli_out->proc = INVALID_HANDLE_VALUE;
                    *(HANDLE*)&cli_out->out_read = INVALID_HANDLE_VALUE;
                    *(HANDLE*)&cli_out->out_write = INVALID_HANDLE_VALUE;
                }
            }
            else{
                // TODO(allen): failed SetHandleInformation
            }
        }
        else{
            // TODO(allen): failed CreatePipe
        }
    }
    
    return success;
}

struct CLI_Loop_Control{
    u32 remaining_amount;
};

internal void
system_cli_begin_update(CLI_Handles *cli){
    Assert(sizeof(cli->scratch_space) >= sizeof(CLI_Loop_Control));
    CLI_Loop_Control *loop = (CLI_Loop_Control*)cli->scratch_space;
    loop->remaining_amount = 0;
}

internal b32
system_cli_update_step(CLI_Handles *cli, char *dest, u32 max, u32 *amount){
    HANDLE handle = *(HANDLE*)&cli->out_read;
    CLI_Loop_Control *loop = (CLI_Loop_Control*)cli->scratch_space;
    b32 has_more = 0;
    DWORD remaining = loop->remaining_amount;
    u32 pos = 0;
    DWORD read_amount = 0;
    
    for (;;){
        if (remaining == 0){
            if (!PeekNamedPipe(handle, 0, 0, 0, &remaining, 0)) break;
            if (remaining == 0) break;
        }
        
        if (remaining + pos < max){
            has_more = 1;
            ReadFile(handle, dest + pos, remaining, &read_amount, 0);
            TentativeAssert(remaining == read_amount);
            pos += remaining;
            remaining = 0;
        }
        else{
            has_more = 1;
            ReadFile(handle, dest + pos, max - pos, &read_amount, 0);
            TentativeAssert(max - pos == read_amount);
            loop->remaining_amount = remaining - (max - pos);
            pos = max;
            break;
        }
    }
    *amount = pos;
    
    return has_more;
}

internal b32
system_cli_end_update(CLI_Handles *cli){
    b32 close_me = 0;
    if (WaitForSingleObject(*(HANDLE*)&cli->proc, 0) == WAIT_OBJECT_0){
        close_me = 1;
        CloseHandle(*(HANDLE*)&cli->proc);
        CloseHandle(*(HANDLE*)&cli->out_read);
        CloseHandle(*(HANDLE*)&cli->out_write);
    }
    return close_me;
}

int
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,
        int nCmdShow){
    win32vars = {};
    TEMP = {};
    LARGE_INTEGER lpf;
    QueryPerformanceFrequency(&lpf);
    win32vars.performance_frequency = lpf.QuadPart;
    QueryPerformanceCounter(&lpf);
    win32vars.start_pcount = lpf.QuadPart;
    
#if FRED_INTERNAL
    memset(INTERNAL_event_hits, 0, INTERNAL_event_index_count * sizeof(u32));
    INTERNAL_frame_index = 0;
    INTERNAL_updating_profile = 1;
    INTERNAL_collecting_events = 1;
    
    INTERNAL_sentinel.next = &INTERNAL_sentinel;
    INTERNAL_sentinel.prev = &INTERNAL_sentinel;
    INTERNAL_sentinel.flags = MEM_BUBBLE_SYS_DEBUG;
#endif
    
    keycode_init(&win32vars.key_codes, &win32vars.loose_codes);
    
#ifdef FRED_SUPER
    win32vars.custom = LoadLibraryA("4coder_custom.dll");
    if (win32vars.custom){
        TEMP.get_bindings = (Get_Binding_Data_Function*)
            GetProcAddress(win32vars.custom, "get_bindings");
        
        TEMP.set_extra_font = (Set_Extra_Font_Function*)
            GetProcAddress(win32vars.custom, "set_extra_font");
    }
#endif
    
    Thread_Context background[4];
    memset(background, 0, sizeof(background));
    win32vars.groups[BACKGROUND_THREADS].threads = background;
    win32vars.groups[BACKGROUND_THREADS].count = ArrayCount(background);
    
    Thread_Memory thread_memory[ArrayCount(background)];
    win32vars.thread_memory = thread_memory;
    
    win32vars.queues[BACKGROUND_THREADS].semaphore =
        CreateSemaphore(0, 0, win32vars.groups[BACKGROUND_THREADS].count, 0);
    
    u32 creation_flag = 0;
    for (i32 i = 0; i < win32vars.groups[BACKGROUND_THREADS].count; ++i){
        Thread_Context *thread = win32vars.groups[BACKGROUND_THREADS].threads + i;
        thread->id = i + 1;
        
        Thread_Memory *memory = win32vars.thread_memory + i;
        *memory = {};
        memory->id = thread->id;
        
        thread->queue = &win32vars.queues[BACKGROUND_THREADS];
        thread->handle = CreateThread(0, 0, ThreadProc, thread, creation_flag, (LPDWORD)&thread->windows_id);
    }
    
    Assert(win32vars.locks);
    for (i32 i = 0; i < LOCK_COUNT; ++i){
        win32vars.locks[i] = CreateSemaphore(0, 1, 1, 0);
    }
    win32vars.DEBUG_sysmem_lock = CreateSemaphore(0, 1, 1, 0);
    
    win32vars.cursor_ibeam = LoadCursor(NULL, IDC_IBEAM);
    win32vars.cursor_arrow = LoadCursor(NULL, IDC_ARROW);
    win32vars.cursor_leftright = LoadCursor(NULL, IDC_SIZEWE);
    win32vars.cursor_updown = LoadCursor(NULL, IDC_SIZENS);
    win32vars.prev_mouse_cursor = APP_MOUSE_CURSOR_ARROW;
    
	WNDCLASS window_class = {};
	window_class.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC;
	window_class.lpfnWndProc = Win32Callback;
	window_class.hInstance = hInstance;
	window_class.lpszClassName = "4coder-win32-wndclass";
    
	if (!RegisterClass(&window_class)){
		// TODO(allen): diagnostics
		FatalError("Failed to create window class");
		return 1;
	}
    
    RECT window_rect = {};
    window_rect.right = 800;
    window_rect.bottom = 600;
    
    if (!AdjustWindowRect(&window_rect, WS_OVERLAPPEDWINDOW, false)){
        // TODO(allen): non-fatal diagnostics
    }
    
#if SOFTWARE_RENDER
#define WINDOW_NAME "4coder-softrender-window"
#else
#define WINDOW_NAME "4coder-window"
#endif
    
    HWND window_handle = {};
    window_handle = CreateWindowA(
        window_class.lpszClassName,
        WINDOW_NAME,
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT/*x*/, CW_USEDEFAULT/*y*/,
        window_rect.right - window_rect.left,
        window_rect.bottom - window_rect.top,
        0, 0, hInstance, 0);
    
    if (!window_handle){
        // TODO(allen): diagnostics
        FatalError("Failed to create window");
        return 2;
    }
    
    // TODO(allen): errors?
    win32vars.window_handle = window_handle;
    HDC hdc = GetDC(window_handle);
    
    GetClientRect(window_handle, &window_rect);
    
#if SOFTWARE_RENDER
    win32vars.width = window_rect.right - window_rect.left;
    win32vars.height = window_rect.bottom - window_rect.top;
    
    win32vars.pitch = win32vars.width*4;
#define bmi_header win32vars.bmp_info.bmiHeader
    bmi_header = {};
    bmi_header.biSize = sizeof(BITMAPINFOHEADER);
    bmi_header.biWidth = win32vars.width;
    bmi_header.biHeight = win32vars.height;
    bmi_header.biPlanes = 1;
    bmi_header.biBitCount = 32;
    bmi_header.biCompression = BI_RGB;
#undef bmi_header
    
	win32vars.true_pixel_size = win32vars.height*win32vars.pitch;
	win32vars.pixel_data = system_get_memory(win32vars.true_pixel_size);
    
	if (!win32vars.pixel_data){
		FatalError("Failure allocating screen memory");
		return 3;
	}
#else
    static PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,
        PFD_DRAW_TO_WINDOW |
        PFD_SUPPORT_OPENGL |
        PFD_DOUBLEBUFFER,
        PFD_TYPE_RGBA,
        32,
        0, 0, 0, 0, 0, 0,
        0,
        0,
        0,
        0, 0, 0, 0,
        16,
        0,
        0,
        PFD_MAIN_PLANE,
        0,
        0, 0, 0 };
    
    i32 pixel_format;
    pixel_format = ChoosePixelFormat(hdc, &pfd);
    SetPixelFormat(hdc, pixel_format, &pfd);
    
    win32vars.target.handle = hdc;
    win32vars.target.context = wglCreateContext(hdc);
    wglMakeCurrent(hdc, (HGLRC)win32vars.target.context);
    
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_SCISSOR_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    Win32Resize(window_rect.right - window_rect.left, window_rect.bottom - window_rect.top);
#endif
    
    LPVOID base;
#if FRED_INTERNAL
    base = (LPVOID)Tbytes(1);
#else
    base = (LPVOID)0;
#endif
    
	win32memory.vars_memory_size = Mbytes(2);
    win32memory.vars_memory = VirtualAlloc(base, win32memory.vars_memory_size,
                                           MEM_COMMIT | MEM_RESERVE,
                                           PAGE_READWRITE);
    
#if FRED_INTERNAL
    base = (LPVOID)Tbytes(2);
#else
    base = (LPVOID)0;
#endif
    win32memory.target_memory_size = Mbytes(512);
    win32memory.target_memory = VirtualAlloc(base, win32memory.target_memory_size,
                                             MEM_COMMIT | MEM_RESERVE,
                                             PAGE_READWRITE);
    
    if (!win32memory.vars_memory){
        FatalError("Failure allocating application memory");
        return 4;
    }
    
    win32vars.clipboard_sequence = GetClipboardSequenceNumber();
    
    if (win32vars.clipboard_sequence == 0){
        system_post_clipboard(make_lit_string(""));
        
        win32vars.clipboard_sequence = GetClipboardSequenceNumber();
        win32vars.next_clipboard_is_self = 0;
        
        if (win32vars.clipboard_sequence == 0){
            FatalError("Failure to access platform's clipboard");
        }
	}
    else{
        if (IsClipboardFormatAvailable(CF_TEXT)){
            if (OpenClipboard(win32vars.window_handle)){
                HANDLE clip_data;
                clip_data = GetClipboardData(CF_TEXT);
                if (clip_data){
                    win32vars.clipboard_contents.str = (u8*)GlobalLock(clip_data);
                    if (win32vars.clipboard_contents.str){
                        win32vars.clipboard_contents.size = str_size((char*)win32vars.clipboard_contents.str);
                        GlobalUnlock(clip_data);
                    }
                }
                CloseClipboard();
            }
        }
    }
    
	if (!app_init(&win32vars.main_thread,
				  &win32memory, &win32vars.key_codes,
                  win32vars.clipboard_contents)){
		return 5;
	}
	
	win32vars.keep_playing = 1;
	timeBeginPeriod(1);
	
    system_aquire_lock(FRAME_LOCK);
    Thread_Context *thread = &win32vars.main_thread;
    AllowLocal(thread);
	bool32 first = 1;
	i64 timer_start = system_time();
    
	while (win32vars.keep_playing){
#if FRED_INTERNAL
        i64 dbg_procing_start = system_time();
        if (!first){
            if (INTERNAL_updating_profile){
                i32 j = (INTERNAL_frame_index % 30);
                Profile_Frame *frame = past_frames + j;
                
                sort(&profile_frame.events);
                
                frame->events.count = profile_frame.events.count;
                memcpy(frame->events.e, profile_frame.events.e, sizeof(Debug_Event)*profile_frame.events.count);
                
                past_frames[j].dbg_procing_start = profile_frame.dbg_procing_start;
                past_frames[j].dbg_procing_end = profile_frame.dbg_procing_end;
                past_frames[j].index = profile_frame.index;
                past_frames[j].first_key = profile_frame.first_key;
                
                ++INTERNAL_frame_index;
                if (INTERNAL_frame_index < 0){
                    INTERNAL_frame_index = ((INTERNAL_frame_index - 1) % 30) + 1;
                }
                memset(INTERNAL_event_hits, 0, INTERNAL_event_index_count * sizeof(u32));
            }
        }
        profile_frame.events.count = 0;
        profile_frame.first_key = -1;
        profile_frame.index = INTERNAL_frame_index;
        INTERNAL_frame_start_time = timer_start;
        profile_frame.dbg_procing_start = (i32)(dbg_procing_start - INTERNAL_frame_start_time);
        profile_frame.dbg_procing_end = (i32)(system_time() - INTERNAL_frame_start_time);
#endif
        
        ProfileStart(OS_input);
		win32vars.previous_data = win32vars.input_data;
		win32vars.input_data.press_count = 0;
		win32vars.input_data.hold_count = 0;
		win32vars.input_data.caps_lock = GetKeyState(VK_CAPITAL) & 0x1;
        
        win32vars.input_data.control_keys[CONTROL_KEY_SHIFT] = (GetKeyState(VK_SHIFT) & 0x0100) >> 8;
        win32vars.input_data.control_keys[CONTROL_KEY_CONTROL] = (GetKeyState(VK_CONTROL) & 0x0100) >> 8;
        win32vars.input_data.control_keys[CONTROL_KEY_ALT] = (GetKeyState(VK_MENU) & 0x0100) >> 8;
        
		win32vars.mouse.left_button_prev = win32vars.mouse.left_button;
		win32vars.mouse.right_button_prev = win32vars.mouse.right_button;
		win32vars.mouse.wheel = 0;
		
		MSG msg;
		while (PeekMessage(&msg, window_handle, 0, 0, PM_REMOVE) && win32vars.keep_playing){
			if (msg.message == WM_QUIT){
				win32vars.keep_playing = 0;
			}else{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
		
		if (!win32vars.keep_playing){
			break;
		}
		
		win32vars.mouse.out_of_window = 0;
		POINT mouse_point;
		if (GetCursorPos(&mouse_point) &&
			ScreenToClient(window_handle, &mouse_point)){
			
			if (mouse_point.x < 0 || mouse_point.x >= win32vars.target.width ||
				mouse_point.y < 0 || mouse_point.y >= win32vars.target.height){
				win32vars.mouse.out_of_window = 1;
			}
		}
		else{
			win32vars.mouse.out_of_window = 1;
		}
		
		b32 shift = win32vars.input_data.control_keys[CONTROL_KEY_SHIFT];
		b32 caps_lock = win32vars.input_data.caps_lock;
        for (i32 i = 0; i < win32vars.input_data.press_count; ++i){
            i16 keycode = win32vars.input_data.press[i].keycode;
			win32vars.input_data.press[i].character =
				keycode_to_character_ascii(&win32vars.key_codes, keycode,
										   shift, caps_lock);
            
			win32vars.input_data.press[i].character_no_caps_lock =
				keycode_to_character_ascii(&win32vars.key_codes, keycode,
										   shift, 0);
		}
        
        for (i32 i = 0; i < win32vars.input_data.hold_count; ++i){
            i16 keycode = win32vars.input_data.hold[i].keycode;
			win32vars.input_data.hold[i].character =
				keycode_to_character_ascii(&win32vars.key_codes, keycode,
										   shift, caps_lock);
			
			win32vars.input_data.hold[i].character_no_caps_lock =
				keycode_to_character_ascii(&win32vars.key_codes, keycode,
										   shift, 0);
		}
		
		win32vars.clipboard_contents = {};
		if (win32vars.clipboard_sequence != 0){
			DWORD new_number = GetClipboardSequenceNumber();
			if (new_number != win32vars.clipboard_sequence){
				win32vars.clipboard_sequence = new_number;
				if (win32vars.next_clipboard_is_self){
					win32vars.next_clipboard_is_self = 0;
				}
				else if (IsClipboardFormatAvailable(CF_TEXT)){
					if (OpenClipboard(win32vars.window_handle)){
						HANDLE clip_data;
						clip_data = GetClipboardData(CF_TEXT);
						if (clip_data){
							win32vars.clipboard_contents.str = (u8*)GlobalLock(clip_data);
							if (win32vars.clipboard_contents.str){
								win32vars.clipboard_contents.size = str_size((char*)win32vars.clipboard_contents.str);
								GlobalUnlock(clip_data);
							}
						}
						CloseClipboard();
					}
				}
			}
		}
        
        i32 redraw = InterlockedExchange(&win32vars.force_redraw, 0);
        ProfileEnd(OS_input);
        
		Application_Step_Result result =
			app_step(&win32vars.main_thread,
					 &win32vars.key_codes,
					 &win32vars.input_data, &win32vars.mouse,
					 1, &win32vars.target,
					 &win32memory,
					 win32vars.clipboard_contents,
					 first, redraw);
        
        ProfileStart(OS_frame_out);
		first = 0;
		switch (result.mouse_cursor_type){
			case APP_MOUSE_CURSOR_ARROW:
				SetCursor(win32vars.cursor_arrow); break;
                
			case APP_MOUSE_CURSOR_IBEAM:
				SetCursor(win32vars.cursor_ibeam); break;
                
			case APP_MOUSE_CURSOR_LEFTRIGHT:
				SetCursor(win32vars.cursor_leftright); break;
                
			case APP_MOUSE_CURSOR_UPDOWN:
                SetCursor(win32vars.cursor_updown); break;
		}
		
		if (result.redraw) Win32RedrawScreen(hdc);
        ProfileEnd(OS_frame_out);
        
        ProfileStart(frame_sleep);
		i64 timer_end = system_time();
		i64 end_target = (timer_start + FRAME_TIME);
        
        system_release_lock(FRAME_LOCK);
		while (timer_end < end_target){
            DWORD samount = (DWORD)((end_target - timer_end) / 1000);
            if (samount > 0) Sleep(samount);
            timer_end = system_time();
		}
        system_aquire_lock(FRAME_LOCK);
		timer_start = system_time();
        ProfileEnd(frame_sleep);
	}
	
	return 0;
}

#if FRED_INTERNAL
const i32 INTERNAL_event_index_count = __COUNTER__;
u32 INTERNAL_event_hits[__COUNTER__];
#endif

// BOTTOM