#ifndef SYS_H
#define SYS_H

struct thread_local_store;

/* ========================== *
 * Events
 * ========================== */

enum sys_event_kind {
    SYS_EVENT_KIND_NONE,

    SYS_EVENT_KIND_BUTTON_DOWN,
    SYS_EVENT_KIND_BUTTON_UP,
    SYS_EVENT_KIND_CURSOR_MOVE,
    SYS_EVENT_KIND_MOUSE_MOVE,
    SYS_EVENT_KIND_TEXT,
    SYS_EVENT_KIND_QUIT,

    SYS_EVENT_KIND_COUNT
};

enum sys_btn {
    SYS_BTN_NONE,

    SYS_BTN_M1,
    SYS_BTN_M2,
    SYS_BTN_M3,
    SYS_BTN_M4,
    SYS_BTN_M5,

    SYS_BTN_MWHEELUP,
    SYS_BTN_MWHEELDOWN,

    SYS_BTN_ESC,
    SYS_BTN_F1,
    SYS_BTN_F2,
    SYS_BTN_F3,
    SYS_BTN_F4,
    SYS_BTN_F5,
    SYS_BTN_F6,
    SYS_BTN_F7,
    SYS_BTN_F8,
    SYS_BTN_F9,
    SYS_BTN_F10,
    SYS_BTN_F11,
    SYS_BTN_F12,
    SYS_BTN_F13,
    SYS_BTN_F14,
    SYS_BTN_F15,
    SYS_BTN_F16,
    SYS_BTN_F17,
    SYS_BTN_F18,
    SYS_BTN_F19,
    SYS_BTN_F20,
    SYS_BTN_F21,
    SYS_BTN_F22,
    SYS_BTN_F23,
    SYS_BTN_F24,
    SYS_BTN_GRAVE_ACCENT,
    SYS_BTN_0,
    SYS_BTN_1,
    SYS_BTN_2,
    SYS_BTN_3,
    SYS_BTN_4,
    SYS_BTN_5,
    SYS_BTN_6,
    SYS_BTN_7,
    SYS_BTN_8,
    SYS_BTN_9,
    SYS_BTN_MINUS,
    SYS_BTN_EQUAL,
    SYS_BTN_BACKSPACE,
    SYS_BTN_DELETE,
    SYS_BTN_TAB,
    SYS_BTN_A,
    SYS_BTN_B,
    SYS_BTN_C,
    SYS_BTN_D,
    SYS_BTN_E,
    SYS_BTN_F,
    SYS_BTN_G,
    SYS_BTN_H,
    SYS_BTN_I,
    SYS_BTN_J,
    SYS_BTN_K,
    SYS_BTN_L,
    SYS_BTN_M,
    SYS_BTN_N,
    SYS_BTN_O,
    SYS_BTN_P,
    SYS_BTN_Q,
    SYS_BTN_R,
    SYS_BTN_S,
    SYS_BTN_T,
    SYS_BTN_U,
    SYS_BTN_V,
    SYS_BTN_W,
    SYS_BTN_X,
    SYS_BTN_Y,
    SYS_BTN_Z,
    SYS_BTN_SPACE,
    SYS_BTN_ENTER,
    SYS_BTN_CTRL,
    SYS_BTN_SHIFT,
    SYS_BTN_ALT,
    SYS_BTN_UP,
    SYS_BTN_LEFT,
    SYS_BTN_DOWN,
    SYS_BTN_RIGHT,
    SYS_BTN_PAGE_UP,
    SYS_BTN_PAGE_DOWN,
    SYS_BTN_HOME,
    SYS_BTN_END,
    SYS_BTN_FORWARD_SLASH,
    SYS_BTN_PERIOD,
    SYS_BTN_COMMA,
    SYS_BTN_QUOTE,
    SYS_BTN_LEFT_BRACKET,
    SYS_BTN_RIGHT_BRACKET,
    SYS_BTN_INSERT,
    SYS_BTN_SEMICOLON,

    SYS_BTN_COUNT
};

struct sys_event {
    enum sys_event_kind kind;

    /* SYS_EVENT_KIND_BUTTON_DOWN */
    /* SYS_EVENT_KIND_BUTTON_UP */
    enum sys_btn button;
    b32 is_repeat;

    /* SYS_EVENT_KIND_TEXT */
    u32 text_codepoint;

    /* SYS_EVENT_KIND_CURSOR_MOVE */
    struct v2 cursor_position;

    /* SYS_EVENT_KIND_MOUSE_MOVE */
    struct v2 mouse_delta;
};

struct sys_event_array {
    u64 count;
    struct sys_event *events;
};

/* ========================== *
 * Memory
 * ========================== */

/* Reserve a continuous block of virtual address space. Shouldn't be
 * read / written to until memory is committed.
 * NOTE: Guaranteed to be 16 byte aligned. */
void *sys_memory_reserve(u64 size);

/* Release a continuous block of virtual address space. Before releasing,
 * decommit any committed memory within the reserved space to be safe. */
void sys_memory_release(void *address);

/* Commit a region of reserved address space to make it readable / writable */
void *sys_memory_commit(void *address, u64 size);

/* Decommit a region of committed memory (does not release the reserved
 * address space) */
void sys_memory_decommit(void *address, u64 size);

void sys_memory_set_committed_readonly(void *address, u64 size);

/* ========================== *
 * Time
 * ========================== */

struct sys_datetime {
  u32 year;
  u32 month;
  u32 day_of_week;
  u32 day;
  u32 hour;
  u32 minute;
  u32 second;
  u32 milliseconds;
};

typedef u64 sys_timestamp_t;

sys_timestamp_t sys_timestamp(void);
f64 sys_timestamp_seconds(sys_timestamp_t ts);

struct sys_datetime sys_local_time(void);

/* ========================== *
 * File system
 *
 * NOTE: File paths use forward slash '/' as delimiter
 * ========================== */

struct sys_file {
    u64 handle;
};

struct sys_file_time {
    struct sys_datetime created;
    struct sys_datetime accessed;
    struct sys_datetime modified;
};

struct string sys_get_write_path(struct arena *arena);
b32 sys_is_file(struct string path);
b32 sys_is_dir(struct string path);
void sys_mkdir(struct string path);

struct sys_file sys_file_open_read(struct string path);
struct sys_file sys_file_open_read_wait(struct string path);  /* Waits until file is not being used by another program */
struct sys_file sys_file_open_write(struct string path);
struct sys_file sys_file_open_append(struct string path);
void sys_file_close(struct sys_file file);

struct buffer sys_file_read_all(struct arena *arena, struct sys_file file);
void sys_file_write(struct sys_file file, struct buffer data);
u64 sys_file_get_size(struct sys_file file);
struct sys_file_time sys_file_get_time(struct sys_file file);

/* ========================== *
 * File map
 * ========================== */

struct sys_file_map {
    struct buffer mapped_memory;
    u64 handle;
};

struct sys_file_map sys_file_map_open_read(struct sys_file file);
void sys_file_map_close(struct sys_file_map map);
struct buffer sys_file_map_data(struct sys_file_map map);

/* ========================== *
 * Dir iter
 * ========================== */

struct sys_file_filter_result {
    struct string filename;
    b32 is_dir;
};

struct sys_file_filter {
    struct sys_file_filter_result info;
    u64 handle;
};

/* Iterate all files in a directory */
struct sys_file_filter sys_file_filter_begin(struct arena *arena, struct string pattern);
b32 sys_file_filter_next(struct arena *arena, struct sys_file_filter *iter);
void sys_file_filter_end(struct sys_file_filter *iter);

/* ========================== *
 * Window
 * ========================== */

/* NOTE:
 * A window object can only be interacted with by the thread that created it.
 * This restriction is in place because of how Win32 works, IE you cannot
 * create a Win32 window in one thread and process its messages on another. */

enum sys_window_settings_flags {
    SYS_WINDOW_SETTINGS_FLAG_NONE        = 0x00,
    SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN  = 0x01,

    /* NOTE: Both maximized and minimized can be true at the same time. This
     * means that the window was minimized from a maximized state, and will
     * restore to being maximized once it's un-minimized. */
    SYS_WINDOW_SETTINGS_FLAG_MAXIMIZED   = 0x02,
    SYS_WINDOW_SETTINGS_FLAG_MINIMIZED   = 0x04
};

enum sys_window_flags {
    SYS_WINDOW_FLAG_NONE        = 0x00,
    SYS_WINDOW_FLAG_SHOWING     = 0x02
};


#define SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(name, arg_name) void name(struct sys_event arg_name)
typedef SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(sys_window_event_callback_func, event);

/* sys_window_update_settings should be used when altering settings values */
struct sys_window_settings {
    char title[256];
    u32 flags;

    /* NOTE: Below fields are NOT representative of actual window dimensions.
     * These values represent the window dimensions when the window is not
     * maximized, minimized, or fullscreen. AKA 'floating'. Use
     * `sys_window_get_size` for rendering instead. */
    i32 floating_x;
    i32 floating_y;
    i32 floating_width;
    i32 floating_height;
};

struct sys_window {
    u64 handle;
};

struct sys_window sys_window_alloc(void);
void sys_window_release(struct sys_window *sys_window);

void sys_window_register_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func);
void sys_window_unregister_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func);

void sys_window_update_settings(struct sys_window *sys_window, struct sys_window_settings *settings);
struct sys_window_settings sys_window_get_settings(struct sys_window *sys_window);

void sys_window_show(struct sys_window *sys_window);

struct v2 sys_window_get_size(struct sys_window *sys_window);
struct v2 sys_window_get_monitor_size(struct sys_window *sys_window);

/* Returns a platform specific representation of the window. E.g. `hwnd` on win32. */
u64 sys_window_get_internal_handle(struct sys_window *sys_window);

void sys_window_cursor_set_pos(struct sys_window *sys_window, struct v2 pos);
void sys_window_cursor_show(struct sys_window *sys_window);
void sys_window_cursor_hide(struct sys_window *sys_window);
void sys_window_cursor_enable_clip(struct sys_window *sys_window, struct rect bounds);
void sys_window_cursor_disable_clip(struct sys_window *sys_window);

/* ========================== *
 * Mutex
 * ========================== */

struct sys_mutex {
    u64 handle;
#if RTC
    u64 owner_tid;
#endif
};

struct sys_mutex sys_mutex_alloc(void);
void sys_mutex_release(struct sys_mutex *mutex);
void sys_mutex_lock(struct sys_mutex *mutex);
void sys_mutex_unlock(struct sys_mutex *mutex);

#if RTC
void sys_mutex_assert_locked(struct sys_mutex *mutex);
#else
# define sys_mutex_assert_locked(m)
#endif

/* ========================== *
 * RW Mutex
 * ========================== */

struct sys_rw_mutex {
    u64 handle;
#if RTC
    struct atomic_i64 num_shared;
    u64 owner_tid;
# if _WIN32
    wchar_t *owner_name;
# else
    char *owner_name;
# endif
#endif
};

struct sys_rw_mutex sys_rw_mutex_alloc(void);
void sys_rw_mutex_release(struct sys_rw_mutex *mutex);
void sys_rw_mutex_lock_exclusive(struct sys_rw_mutex *mutex);
void sys_rw_mutex_unlock_exclusive(struct sys_rw_mutex *mutex);
void sys_rw_mutex_lock_shared(struct sys_rw_mutex *mutex);
void sys_rw_mutex_unlock_shared(struct sys_rw_mutex *mutex);

#if RTC
void sys_rw_mutex_assert_locked_exclusive(struct sys_rw_mutex *mutex);
#else
# define sys_rw_mutex_assert_locked_exclusive(m)
#endif

/* ========================== *
 * Condition variable
 * ========================== */

struct sys_condition_variable {
    u64 handle;
#if RTC
    struct atomic_i64 num_waiters;
#endif
};

struct sys_condition_variable sys_condition_variable_alloc(void);
void sys_condition_variable_release(struct sys_condition_variable *cv);
void sys_condition_variable_wait(struct sys_condition_variable *cv, struct sys_mutex *mutex);
void sys_condition_variable_wait_time(struct sys_condition_variable *cv, struct sys_mutex *mutex, f64 seconds);
void sys_condition_variable_signal(struct sys_condition_variable *cv);
void sys_condition_variable_broadcast(struct sys_condition_variable *cv);

/* ========================== *
 * Thread local storage
 * ========================== */

struct thread_local_store *sys_thread_get_thread_local_store(void);

/* ========================== *
 * Threads
 * ========================== */

#define SYS_THREAD_STACK_SIZE MEGABYTE(4)

#define SYS_THREAD_ENTRY_POINT_FUNC_DEF(name, arg_name) void name(void *arg_name)
typedef SYS_THREAD_ENTRY_POINT_FUNC_DEF(sys_thread_entry_point_func, data);

struct sys_thread {
    u64 handle[2];
};

/* Creates a new thread running in the supplied `entry_point` */
struct sys_thread sys_thread_alloc(
    sys_thread_entry_point_func *entry_point,
    void *thread_data,  /* Passed as arg to `entry_point` */
    struct string thread_name
);

/* Halt and wait for a thread to finish */
void sys_thread_wait_release(struct sys_thread *thread);

/* Gets the current executing thread's ID */
u32 sys_thread_id(void);

/* Asserts that the current thread matches the supplied thread id (tid) */
#if RTC
void sys_thread_assert(u32 tid);
#else
# define sys_thread_assert(tid)
#endif

/* ========================== *
 * Message box
 * ========================== */

enum sys_message_box_kind {
    SYS_MESSAGE_BOX_KIND_OK,
    SYS_MESSAGE_BOX_KIND_WARNING,
    SYS_MESSAGE_BOX_KIND_ERROR,
    SYS_MESSAGE_BOX_KIND_FATAL
};

void sys_message_box(enum sys_message_box_kind kind, struct string message);

/* ========================== *
 * Clipboard
 * ========================== */

void sys_set_clipboard_text(struct string str);
struct string sys_get_clipboard_text(struct arena *arena);

/* ========================== *
 * Util
 * ========================== */

u32 sys_num_logical_processors(void);

void sys_exit(void);

u32 sys_rand_u32(void);

void sys_panic(struct string msg);

/* ========================== *
 * Sleep
 * ========================== */

/* Sleep for precisely the amount of time specified (more cpu intensive) */
void sys_sleep_precise(f64 seconds);

/* Sleep for the amount of time specified rounded to the OS scheduler period
 * (less cpu intensive) */
void sys_sleep(f64 seconds);

#endif
