////////////////////////////////
// NOTE(allen): Variables

global U64 w32_ticks_per_second = 1;

global Arena *w32_perm_arena = 0;
global String8 w32_binary_path = {0};
global String8 w32_user_path = {0};
global String8 w32_temp_path = {0};

global String8List w32_cmd_line = {0};

////////////////////////////////
// NOTE(allen): Init

function void
os_main_init(int argc, char **argv){
  ProfBeginFunc();
  
  // setup precision time
  LARGE_INTEGER perf_freq = {0};
  if (QueryPerformanceFrequency(&perf_freq)){
    w32_ticks_per_second = ((U64)perf_freq.HighPart << 32) | perf_freq.LowPart;
  }
  timeBeginPeriod(1);
  
  // arena
  w32_perm_arena = arena_alloc();
  
  // command line arguments
  for (int i = 0; i < argc; i += 1){
    String8 arg = str8_cstring((U8*)argv[i]);
    str8_list_push(w32_perm_arena, &w32_cmd_line, arg);
  }
  
  // paths
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  // binary path
  {
    DWORD cap = 2048;
    U16 *buffer = 0;
    DWORD size = 0;
    for (U64 r = 0; r < 4; r += 1, cap *= 4){
      U16 *try_buffer = push_array(scratch.arena, U16, cap);
      DWORD try_size = GetModuleFileNameW(0, (WCHAR*)try_buffer, cap);
      if (try_size == cap && GetLastError() == ERROR_INSUFFICIENT_BUFFER){
        arena_end_temp(&scratch);
      }
      else{
        buffer = try_buffer;
        size = try_size;
        break;
      }
    }
    
    String8 full_path = str8_from_str16(scratch.arena, str16(buffer, size));
    String8 binary_path = str8_chop_last_slash(full_path);
    w32_binary_path = str8_push_copy(w32_perm_arena, binary_path);
  }
  
  // user data
  {
    HANDLE token = GetCurrentProcessToken();
    DWORD cap = 2048;
    U16 *buffer = push_array(scratch.arena, U16, cap);
    if (!GetUserProfileDirectoryW(token, (WCHAR*)buffer, &cap)){
      arena_end_temp(&scratch);
      buffer = push_array(scratch.arena, U16, cap + 1);
      if (GetUserProfileDirectoryW(token, (WCHAR*)buffer, &cap)){
        buffer = 0;
      }
    }
    
    if (buffer != 0){
      // NOTE(allen): the docs make it sound like we can only count on
      // cap getting the size on failure; so we're just going to cstring
      // this to be safe.
      w32_user_path = str8_from_str16(w32_perm_arena, str16_cstring(buffer));
    }
  }
  
  // temp data
  {
    DWORD cap = 2048;
    U16 *buffer = push_array(scratch.arena, U16, cap);
    DWORD size = GetTempPathW(cap, (WCHAR*)buffer);
    if (size >= cap){
      arena_end_temp(&scratch);
      buffer = push_array(scratch.arena, U16, size + 1);
      size = GetTempPathW(size + 1, (WCHAR*)buffer);
    }
    
    // NOTE(allen): size - 1, because this particular string function
    // in the Win32 API is different from the others and it includes 
    // the trailing backslash. We want consistency, so the "- 1" removes it.
    w32_temp_path = str8_from_str16(w32_perm_arena, str16(buffer, size - 1));
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
}

function String8List
os_command_line_arguments(void){
  String8List result = w32_cmd_line;
  return(result);
}

function void
os_exit_process(U32 code){
  ProfClose();
  ExitProcess(code);
}

////////////////////////////////
// NOTE(allen): WinMain Specialized Init

global HINSTANCE w32_instance = 0;

function void
w32_WinMain_init(HINSTANCE hInstance,
                 HINSTANCE hPrevInstance,
                 LPSTR     lpCmdLine,
                 int       nShowCmd){
  int argc = __argc;
  char **argv = __argv;
  w32_instance = hInstance;
  os_main_init(argc, argv);
}

function HINSTANCE
w32_get_instance(void){
  if (w32_instance == 0){
    w32_instance = GetModuleHandle(0);
  }
  return(w32_instance);
}

////////////////////////////////
// NOTE(allen): Memory Functions

function void*
os_memory_reserve(U64 size){
  void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
  return(result);
}

function B32
os_memory_commit(void *ptr, U64 size){
  B32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0);
  return(result);
}

function void
os_memory_decommit(void *ptr, U64 size){
  VirtualFree(ptr, size, MEM_DECOMMIT);
}

function void
os_memory_release(void *ptr, U64 size){
  VirtualFree(ptr, 0, MEM_RELEASE);
}

////////////////////////////////
// NOTE(allen): Time Helpers

function DateTime
w32_date_time_from_system_time(SYSTEMTIME *in){
  DateTime result = {0};
  result.year = in->wYear;
  result.mon  = (U8)in->wMonth;
  result.day  = in->wDay;
  result.hour = in->wHour;
  result.min  = in->wMinute;
  result.sec  = in->wSecond;
  result.msec = in->wMilliseconds;
  return(result);
}

function SYSTEMTIME
w32_system_time_from_date_time(DateTime *in){
  SYSTEMTIME result = {0};
  result.wYear = in->year;
  result.wMonth = in->mon;
  result.wDay = in->day;
  result.wHour = in->hour;
  result.wMinute = in->min;
  result.wSecond = in->sec;
  result.wMilliseconds = in->msec;
  return(result);
}

function DenseTime
w32_dense_time_from_file_time(FILETIME *file_time){
  SYSTEMTIME system_time = {0};
  FileTimeToSystemTime(file_time, &system_time);
  DateTime date_time = w32_date_time_from_system_time(&system_time);
  DenseTime result = dense_time_from_date_time(&date_time);
  return(result);
}

////////////////////////////////
// NOTE(allen): File Handling

function FilePropertyFlags
w32_prop_flags_from_attribs(DWORD attribs){
  FilePropertyFlags result = 0;
  if (attribs & FILE_ATTRIBUTE_DIRECTORY){
    result |= FilePropertyFlag_IsFolder;
  }
  return(result);
}

function DataAccessFlags
w32_access_from_attributes(DWORD attribs){
  DataAccessFlags result = DataAccessFlag_Read|DataAccessFlag_Execute;
  if (!(attribs & FILE_ATTRIBUTE_READONLY)){
    result |= DataAccessFlag_Write;
  }
  return(result);
}

function String8
os_file_read(Arena *arena, String8 file_name){
  ProfBeginFunc();
  
  // get handle
  ArenaTemp scratch = arena_get_scratch(&arena, 1);
  String16 file_name16 = str16_from_str8(scratch.arena, file_name);
  HANDLE file = CreateFileW((WCHAR*)file_name16.str,
                            GENERIC_READ, 0, 0,
                            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                            0);
  
  String8 result = {0};
  if (file != INVALID_HANDLE_VALUE){
    // get size
    DWORD hi_size = 0;
    DWORD lo_size = GetFileSize(file, &hi_size);
    U64 total_size = (((U64)hi_size) << 32) | (U64)lo_size;
    
    // allocate buffer
    ArenaTemp restore_point = arena_begin_temp(arena);
    U8 *buffer = push_array(arena, U8, total_size);
    
    // read
    U8 *ptr = buffer;
    U8 *opl = buffer + total_size;
    B32 success = 1;
    for (;ptr < opl;){
      U64 total_to_read = (U64)(opl - ptr);
      DWORD to_read = (DWORD)total_to_read;
      if (total_to_read > max_U32){
        to_read = max_U32;
      }
      DWORD actual_read = 0;
      if (!ReadFile(file, ptr, to_read, &actual_read, 0)){
        success = 0;
        break;
      }
      ptr += actual_read;
    }
    
    // set result or reset memory
    if (success){
      result.str = buffer;
      result.size = total_size;
    }
    else{
      arena_end_temp(&restore_point);
    }
    
    CloseHandle(file);
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

function B32
os_file_write_list(String8 file_name, String8Node *first_node){
  ProfBeginFunc();
  
  // get handle
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 file_name16 = str16_from_str8(scratch.arena, file_name);
  HANDLE file = CreateFileW((WCHAR*)file_name16.str,
                            GENERIC_WRITE, 0, 0,
                            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
                            0);
  
  B32 result = 0;
  if (file != INVALID_HANDLE_VALUE){
    result = 1;
    
    for (String8Node *node = first_node;
         node != 0;
         node = node->next){
      U8 *ptr = node->string.str;
      U8 *opl = ptr + node->string.size;
      for (;ptr < opl;){
        U64 total_to_write = (U64)(opl - ptr);
        DWORD to_write = total_to_write;
        if (total_to_write > max_U32){
          to_write = max_U32;
        }
        DWORD actual_write = 0;
        if (!WriteFile(file, ptr, to_write, &actual_write, 0)){
          result = 0;
          goto dblbreak;
        }
        ptr += actual_write;
      }
    }
    dblbreak:;
    
    CloseHandle(file);
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

function FileProperties
os_file_properties(String8 file_name){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 file_name16 = str16_from_str8(scratch.arena, file_name);
  
  // get attribs and convert to properties
  FileProperties result = {0};
  WIN32_FILE_ATTRIBUTE_DATA attribs = {0};
  if (GetFileAttributesExW((WCHAR*)file_name16.str, GetFileExInfoStandard,
                           &attribs)){
    result.size = ((U64)attribs.nFileSizeHigh << 32) | (U64)attribs.nFileSizeLow;
    result.flags = w32_prop_flags_from_attribs(attribs.dwFileAttributes);
    result.create_time = w32_dense_time_from_file_time(&attribs.ftCreationTime);
    result.modify_time = w32_dense_time_from_file_time(&attribs.ftLastWriteTime);
    result.access = w32_access_from_attributes(attribs.dwFileAttributes);
  }
  
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

function B32
os_file_delete(String8 file_name){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 file_name16 = str16_from_str8(scratch.arena, file_name);
  // delete file
  B32 result = DeleteFileW((WCHAR*)file_name16.str);
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

function B32
os_file_rename(String8 og_name, String8 new_name){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 og_name16 = str16_from_str8(scratch.arena, og_name);
  String16 new_name16 = str16_from_str8(scratch.arena, new_name);
  // rename file
  B32 result = MoveFileW((WCHAR*)og_name16.str, (WCHAR*)new_name16.str);
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

function B32
os_file_make_directory(String8 path){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 path16 = str16_from_str8(scratch.arena, path);
  // make directory
  B32 result = CreateDirectoryW((WCHAR*)path16.str, 0);
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

function B32
os_file_delete_directory(String8 path){
  ProfBeginFunc();
  
  // convert name
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 path16 = str16_from_str8(scratch.arena, path);
  // make directory
  B32 result = RemoveDirectoryW((WCHAR*)path16.str);
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}


function OS_FileIter
os_file_iter_init(String8 path){
  ProfBeginFunc();
  
  // convert name
  String8Node nodes[2];
  String8List list = {0};
  str8_list_push_explicit(&list, path, nodes + 0);
  str8_list_push_explicit(&list, str8_lit("\\*"), nodes + 1);
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String8 path_star = str8_join(scratch.arena, &list, 0);
  // TODO(allen): Better unicode conversions here
  String16 path16 = str16_from_str8(scratch.arena, path_star);
  
  // store into iter
  OS_FileIter result = {0};
  W32_FileIter *w32_iter = (W32_FileIter*)&result;
  w32_iter->handle = FindFirstFileW((WCHAR*)path16.str, &w32_iter->find_data);
  arena_release_scratch(&scratch);
  
  ProfEndFunc();
  return(result);
}

function B32
os_file_iter_next(Arena *arena, OS_FileIter *iter,
                  String8 *name, FileProperties *props){
  ProfBeginFunc();
  
  B32 result = 0;
  
  W32_FileIter *w32_iter = (W32_FileIter*)iter;
  if (w32_iter->handle != 0 &&
      w32_iter->handle != INVALID_HANDLE_VALUE){
    for (;!w32_iter->done;){
      // check for . and ..
      WCHAR *file_name = w32_iter->find_data.cFileName;
      B32 is_dot = (file_name[0] == '.' && file_name[1] == 0);
      B32 is_dotdot = (file_name[0] == '.' && file_name[1] == '.' &&
                       file_name[2] == 0);
      
      // setup to emit
      B32 emit = (!is_dot && !is_dotdot);
      WIN32_FIND_DATAW data = {0};
      if (emit){
        MemoryCopyStruct(&data, &w32_iter->find_data);
      }
      
      // increment the iterator
      if (!FindNextFileW(w32_iter->handle, &w32_iter->find_data)){
        w32_iter->done = 1;
      }
      
      // do the emit if we saved one earlier
      if (emit){
        *name = str8_from_str16(arena, str16_cstring((U16*)data.cFileName));
        props->size = ((U64)data.nFileSizeHigh << 32) | (U64)data.nFileSizeLow;
        props->flags = w32_prop_flags_from_attribs(data.dwFileAttributes);
        props->create_time = w32_dense_time_from_file_time(&data.ftCreationTime);
        props->modify_time = w32_dense_time_from_file_time(&data.ftLastWriteTime);
        props->access = w32_access_from_attributes(data.dwFileAttributes);
        result = 1;
        break;
      }
    }
  }
  
  ProfEndFunc();
  return(result);
}

function void
os_file_iter_end(OS_FileIter *iter){
  ProfBeginFunc();
  
  W32_FileIter *w32_iter = (W32_FileIter*)iter;
  if (w32_iter->handle != 0 &&
      w32_iter->handle != INVALID_HANDLE_VALUE){
    FindClose(w32_iter->handle);
  }
  
  ProfEndFunc();
}

function String8
os_file_path(Arena *arena, OS_SystemPath path){
  ProfBeginFunc();
  
  String8 result = {0};
  
  switch (path){
    case OS_SystemPath_CurrentDirectory:
    {
      ArenaTemp scratch = arena_get_scratch(&arena, 1);
      DWORD cap = 2048;
      U16 *buffer = push_array(scratch.arena, U16, cap);
      DWORD size = GetCurrentDirectoryW(cap, (WCHAR*)buffer);
      if (size >= cap){
        arena_end_temp(&scratch);
        buffer = push_array(scratch.arena, U16, size + 1);
        size = GetCurrentDirectoryW(size + 1, (WCHAR*)buffer);
      }
      result = str8_from_str16(arena, str16(buffer, size));
      arena_release_scratch(&scratch);
    }break;
    
    case OS_SystemPath_Binary:
    {
      result = str8_push_copy(arena, w32_binary_path);
    }break;
    
    case OS_SystemPath_UserData:
    {
      result = str8_push_copy(arena, w32_user_path);
    }break;
    
    case OS_SystemPath_TempData:
    {
      result = str8_push_copy(arena, w32_temp_path);
    }break;
  }
  
  ProfEndFunc();
  return(result);
}


////////////////////////////////
// NOTE(allen): Time

function DateTime
os_now_universal_time(void){
  SYSTEMTIME system_time = {0};
  GetSystemTime(&system_time);
  DateTime result = w32_date_time_from_system_time(&system_time);
  return(result);
}

function DateTime
os_local_time_from_universal(DateTime *univ_date_time){
  SYSTEMTIME univ_system_time = w32_system_time_from_date_time(univ_date_time);
  FILETIME univ_file_time = {0};
  SystemTimeToFileTime(&univ_system_time, &univ_file_time);
  FILETIME local_file_time = {0};
  FileTimeToLocalFileTime(&univ_file_time, &local_file_time);
  SYSTEMTIME local_system_time = {0};
  FileTimeToSystemTime(&local_file_time, &local_system_time);
  DateTime result = w32_date_time_from_system_time(&local_system_time);
  return(result);
}

function DateTime
os_universal_time_from_local(DateTime *local_date_time){
  SYSTEMTIME local_system_time = w32_system_time_from_date_time(local_date_time);
  FILETIME local_file_time = {0};
  SystemTimeToFileTime(&local_system_time, &local_file_time);
  FILETIME univ_file_time = {0};
  LocalFileTimeToFileTime(&local_file_time, &univ_file_time);
  SYSTEMTIME univ_system_time = {0};
  FileTimeToSystemTime(&univ_file_time, &univ_system_time);
  DateTime result = w32_date_time_from_system_time(&univ_system_time);
  return(result);
}

function U64
os_now_ticks(void){
  U64 result = 0;
  LARGE_INTEGER perf_counter = {0};
  if (QueryPerformanceCounter(&perf_counter)){
    result = ((U64)perf_counter.HighPart << 32) | perf_counter.LowPart;
  }
  return(result);
}

function void
os_microseconds_over_ticks(U64 *num_out, U64 *denom_out){
  *num_out = Million(1);
  *denom_out = w32_ticks_per_second;
}

function void
os_sleep_milliseconds(U32 t){
  Sleep(t);
}

////////////////////////////////
// NOTE(allen): Libraries

function OS_Library*
os_lib_load(String8 path){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  String16 path16 = str16_from_str8(scratch.arena, path);
  OS_Library *result = (OS_Library*)(LoadLibraryW((WCHAR*)path16.str));
  arena_release_scratch(&scratch);
  return(result);
}

function VoidFunc*
os_lib_get_proc(OS_Library *lib, char *name){
  HMODULE module = (HMODULE)(lib);
  VoidFunc *result = (VoidFunc*)(GetProcAddress(module, name));
  return(result);
}

function void
os_lib_release(OS_Library *lib){
  HMODULE module = (HMODULE)(lib);
  FreeLibrary(module);
}

////////////////////////////////
// NOTE(allen): Entropy

function void
os_get_entropy(void *data, U64 size){
  HCRYPTPROV prov = 0;
  CryptAcquireContextW(&prov, 0, 0, PROV_DSS, CRYPT_VERIFYCONTEXT);
  CryptGenRandom(prov, size, (BYTE*)data);
  CryptReleaseContext(prov, 0);
}

