#pragma comment(lib, "user32")
#pragma comment(lib, "winmm")
#pragma comment(lib, "shell32")

//~ Memory
root_function U64   
OS_page_size(void) {
    SYSTEM_INFO info;
    GetSystemInfo(&info);
    return info.dwPageSize;
}

root_function void 
*OS_reserve(U64 size) {
    U64 gb_snapped_size = size;
    // Align the reserved memory to nearest gigabyte?
    gb_snapped_size += M_DEFAULT_RESERVE_SIZE - 1;
    gb_snapped_size -= gb_snapped_size % M_DEFAULT_RESERVE_SIZE;
    void *ptr = VirtualAlloc(0, gb_snapped_size, MEM_RESERVE, PAGE_NOACCESS);
    return ptr;
}

root_function void  
OS_release(void *ptr, U64 size) {
    VirtualFree(ptr, 0, MEM_RELEASE);
}

root_function void  
OS_commit(void *ptr, U64 size) {
    U64 page_snapped_size = size;
    page_snapped_size += OS_page_size() - 1;
    page_snapped_size -= page_snapped_size%OS_page_size();
    VirtualAlloc(ptr, page_snapped_size, MEM_COMMIT, PAGE_READWRITE);
}

root_function void  
OS_decommit(void *ptr, U64 size){
    VirtualFree(ptr, size, MEM_DECOMMIT);
}

//~ Thread
root_function OS_InitReceipt OS_init(void) 
{
  if (is_main_thread() && os_g_w32_state == 0)
  {
    Arena *arena = m_make_arena_reserve(Gigabytes(1));
    os_g_w32_state = PushArray(arena, OS_W32_State, 1);
    os_g_w32_state->arena = arena;
    
    os_g_w32_state->thread_arena = m_make_arena_reserve(Kilobytes(256));

  }

  OS_InitReceipt out;
  out.u64[0] = 1;
  return out;
}


root_function void OS_thread_context_set(void *ptr) {
    TlsSetValue(os_g_w32_state->thread_context_index, ptr);
}

root_function void* OS_thread_context_get(void) {
    void *result = TlsGetValue(os_g_w32_state->thread_context_index);
    return result;
}

/* // TODO(anton): This is an interesting function to protect virtual allocs, but it doesn't look like it's
used in the app template right now. I'll add it when I need it.
root_function void  
OS_set_memory_access_flags(void *ptr, U64 size, OS_AccessFlags flags) {
    
    U64 page_snapped_size = size;
    page_snapped_size += OS_page_size() - 1;
    page_snapped_size -= page_snapped_size%OS_page_size();
    
    
    DWORD new_flags = 0;
    {
        switch(flags)
        {
            default: 
            {
                new_flags = PAGE_NOACCESS;
            }break;
#define Map(win32_code, bitflags) case bitflags:{new_flags = win32_code;}break
            Map(PAGE_EXECUTE,           OS_AccessFlag_Execute);
            Map(PAGE_EXECUTE_READ,      OS_AccessFlag_Execute|OS_AccessFlag_Read);
            Map(PAGE_EXECUTE_READWRITE, OS_AccessFlag_Execute|OS_AccessFlag_Read|OS_AccessFlag_Write);
            Map(PAGE_EXECUTE_WRITECOPY, OS_AccessFlag_Execute|OS_AccessFlag_Write);
            Map(PAGE_READONLY,          OS_AccessFlag_Read);
            Map(PAGE_READWRITE,         OS_AccessFlag_Read|OS_AccessFlag_Write);
#undef Map
        }
    }
    
    
    DWORD old_flags = 0;
    VirtualProtect(ptr, page_snapped_size, new_flags, &old_flags);
}
*/



//~ @os_per_backend File System
root_function OS_Handle 
OS_file_open(OS_AccessFlags access_flags, String8 path) {
    ArenaTemp scratch = scratch_get(0, 0);
    String16 path16 = str16_from8(scratch.arena, path);
    
    // Map to win32 access flags
    DWORD desired_access = 0;
    if(access_flags & OS_AccessFlag_Read) { desired_access |= GENERIC_READ; }
    if(access_flags & OS_AccessFlag_Write) { desired_access |= GENERIC_WRITE; }
    
    DWORD share_mode = 0;
    
    SECURITY_ATTRIBUTES security_attributes = {
        (DWORD)sizeof(SECURITY_ATTRIBUTES),
        0,
        0,
    };
    
    // Map to win32 creation disposition
    DWORD creation_disposition = 0;
    if(!(access_flags & OS_AccessFlag_CreateNew)) {
        creation_disposition = OPEN_EXISTING;
    }
    
    DWORD flags_and_attribs = 0;
    HANDLE template_file = 0;
    
    HANDLE file = CreateFileW((WCHAR*)path16.str,
                              desired_access, 
                              share_mode,
                              &security_attributes,
                              creation_disposition,
                              flags_and_attribs,
                              template_file);
    
    if(file == INVALID_HANDLE_VALUE) {
        // TODO(anton): Append to errors
        break_debugger();
    }
    
    // Map to abstract handle
    OS_Handle handle = {0};
    handle.u64[0] = (U64)file;
    
    scratch_release(scratch);
    return handle;
}

root_function void     
OS_file_close(OS_Handle file) {
    HANDLE handle = (HANDLE)file.u64[0];
    if(handle != INVALID_HANDLE_VALUE) {
        CloseHandle(handle);
    }
}

root_function String8
OS_file_read(Arena *arena, OS_Handle file, U64 min, U64 max) {
    String8 result = {0};
    
    HANDLE handle = (HANDLE)file.u64[0];
    if(handle == INVALID_HANDLE_VALUE) {
      // TODO(anton): accumulate errors
    } else {
        U64 bytes_to_read = AbsoluteValueU64(max - min);
        U64 bytes_actually_read = 0;
        result.str = PushArray(arena, U8, bytes_to_read);
        result.size = 0;
        U8 *ptr = result.str;
        U8 *one_past_last = result.str + bytes_to_read;
        
        for(;;) {
            U64 unread = (U64)(one_past_last - ptr);
            DWORD to_read = (DWORD)(ClampTop(unread, U32Max));
            DWORD did_read = 0;
            // TODO(anton): Understand WINAPI
            if(!ReadFile(handle, ptr, to_read, &did_read, 0)) {
                break;
            }
            ptr += did_read;
            result.size += did_read;
            if(ptr >= one_past_last) {
                break;
            }
                                    
        }
    }
    return result;
}

root_function OS_FileAttributes
OS_attributes_from_file(OS_Handle file) 
{
  HANDLE handle = (HANDLE)file.u64[0];
  OS_FileAttributes attrs = {0};
  U32 high_bits = 0;
  U32 low_bits = GetFileSize(handle, (DWORD *)&high_bits);
  FILETIME last_write_time = {0};
  GetFileTime(handle, 0, 0, &last_write_time);
  attrs.size = (U64)low_bits | (((U64)high_bits) << 32);
  attrs.last_modified = ((U64)last_write_time.dwLowDateTime) | 
    (((U64)last_write_time.dwHighDateTime) << 32);
  return attrs;
}

root_function void
OS_file_write(Arena *arena, OS_Handle file, U64 off, String8List data, OS_ErrorList *out_errors) {
    HANDLE handle = (HANDLE)file.u64[0];
    if(handle == 0 || handle == INVALID_HANDLE_VALUE)
    {
        // TODO(anton): accumulate errors
    }
    else for(String8Node *node = data.first; node != 0; node = node->next)
    {
        U8 *ptr = node->string.str;
        U8 *opl = ptr + node->string.size;
        for(;;)
        {
            U64 unwritten = (U64)(opl - ptr);
            DWORD to_write = (DWORD)(ClampTop(unwritten, U32Max));
            DWORD did_write = 0;
            // TODO(anton): understand winapi
            if(!WriteFile(handle, ptr, to_write, &did_write, 0))
            {
                goto fail_out;
            }
            ptr += did_write;
            if(ptr >= opl)
            {
                break;
            }
        }
    }
    fail_out:;
}

root_function Rng2_F32 
OS_client_rect_from_window(OS_Handle window_handle)
{
  Rng2_F32 rect = {0};
  OS_W32_Window *window = (OS_W32_Window *)window_handle.u64[0];
  if(window != 0)
  {
    RECT w32_rect = {0};
    if(GetClientRect(window->hwnd, &w32_rect))
    {
      rect.x0 = (F32)w32_rect.left;
      rect.y0 = (F32)w32_rect.top;
      rect.x1 = (F32)w32_rect.right;
      rect.y1 = (F32)w32_rect.bottom;
    }
  }
  return rect;
}