#ifndef MR4TH_GFX_H
#define MR4TH_GFX_H

/* TODO(allen) - mr4th_gfx.h
**
** Unknown: What should a program do when there are multiple monitors with
**  different refresh rates? Should it constantly check the monitor for the
**  window(s) and dynamically apply different refresh rates? Should it pick
**  a refresh rate of it's own and just let whatever happens happen? What
**  even happens when a window straddles between two monitors?
**
** [ ] Design a solution for the "overlap" problem with
**      key events and text insert events
**
*/

////////////////////////////////
// GFX Configuration

#if !defined(GFX_MAX_WINDOW_COUNT)
# define GFX_MAX_WINDOW_COUNT 64
#endif

////////////////////////////////
// GFX Types

// types for all

typedef void GFX_Window;

typedef void GFX_ResizingFunc(GFX_Window *window, U32 width, U32 height);

typedef enum GFX_CursorAppearance{
  GFX_CursorAppearance_Null,
  
  GFX_CursorAppearance_Hidden,
  GFX_CursorAppearance_Pointer,
  GFX_CursorAppearance_HandPointer,
  GFX_CursorAppearance_TextInsert,
  GFX_CursorAppearance_Loading,
  GFX_CursorAppearance_ResizeLeftRight,
  GFX_CursorAppearance_ResizeUpDown,
  
  GFX_CursorAppearance_COUNT
} GFX_CursorAppearance;

// types for graphics system implementors

typedef void GFX_WindowReleaseFunc(GFX_Window *window);

// types for system dialogue boxes

typedef struct GFX_OpenFileResult{
  String8 file_name;
} GFX_OpenFileResult;

// event kind

#define GFX_EventKindXList(X)\
X(Null)\
X(WindowClose)\
X(WindowLoseFocus)\
X(Key)\
X(TextInsert)\
X(MouseButton)\
X(MouseWheel)\
X(FileDrop)

typedef enum GFX_EventKind{
#define X(N) GFX_EventKind_##N,
  GFX_EventKindXList(X)
#undef X
  GFX_EventKind_COUNT
} GFX_EventKind;

// event flags

typedef U32 GFX_EventFlags;
enum{
  GFX_EventFlag_Press   = (1 << 0),
  GFX_EventFlag_Release = (1 << 1),
  GFX_EventFlag_Repeat  = (1 << 2),
#define GFX_EventFlag_TransitionShift 0
#define GFX_EventFlag_TransitionMask  0x7
  
  GFX_EventFlag_Shift   = (1 << 3),
  GFX_EventFlag_Control = (1 << 4),
  GFX_EventFlag_Alt     = (1 << 5),
  GFX_EventFlag_Command = (1 << 6),
#define GFX_EventFlag_ModifierShift 3
#define GFX_EventFlag_ModifierMask  0xF
};

// key kinds

#define GFX_KeyXList(XX)\
XX(Null)\
XX(Escape)\
XX(F1)   XX(F2)  XX(F3)  XX(F4)\
XX(F5)   XX(F6)  XX(F7)  XX(F8)\
XX(F9)  XX(F10) XX(F11) XX(F12)\
XX(F13) XX(F14) XX(F15) XX(F16)\
XX(F17) XX(F18) XX(F19) XX(F20)\
XX(F21) XX(F22) XX(F23) XX(F24)\
XX(F25) XX(F26) XX(F27) XX(F28)\
XX(F29) XX(F30) XX(F31) XX(F32)\
XX(Backspace)\
XX(Tab)\
XX(CapsLock)\
XX(Return)\
XX(Shift)\
XX(Control)\
XX(Alt)\
XX(Command)\
XX(Context)\
XX(Space)\
XX(PrintScreen)\
XX(ScrollLock)\
XX(PauseBreak)\
XX(Insert)\
XX(Delete)\
XX(Home)\
XX(End)\
XX(PageUp)\
XX(PageDown)\
XX(Up) XX(Left) XX(Down) XX(Right)\
XX(NumLock)\
XX(A) XX(B) XX(C) XX(D) XX(E) XX(F) XX(G) XX(H) XX(I) XX(J) XX(K) XX(L) XX(M)\
XX(N) XX(O) XX(P) XX(Q) XX(R) XX(S) XX(T) XX(U) XX(V) XX(W) XX(X) XX(Y) XX(Z)\
XX(0) XX(1) XX(2) XX(3) XX(4) XX(5) XX(6) XX(7) XX(8) XX(9)\
XX(Tick) XX(Minus) XX(Equals) XX(LeftBracket) XX(RightBracket) XX(BackSlash)\
XX(Semicolon) XX(Quote) XX(Comma) XX(Period) XX(Slash)\
XX(Num0) XX(Num1) XX(Num2) XX(Num3) XX(Num4)\
XX(Num5) XX(Num6) XX(Num7) XX(Num8) XX(Num9)\
XX(NumMultiply)\
XX(NumAdd)\
XX(NumSubtract)\
XX(NumDecimal)\
XX(NumDivide)

typedef enum GFX_Key{
#define X(N) GFX_Key_##N,
  GFX_KeyXList(X)
#undef X
  GFX_Key_COUNT
} GFX_Key;

// mouse

#define GFX_MouseButtonXList(X)\
X(Null)\
X(Left)\
X(Right)\
X(Middle)

typedef enum GFX_MouseButton{
#define X(N) GFX_MouseButton_##N,
  GFX_MouseButtonXList(X)
#undef X
  GFX_MouseButton_COUNT
} GFX_MouseButton;

// events

typedef struct GFX_FileDropData{
  String8List files;
  S32 point[2];
} GFX_FileDropData;

typedef struct GFX_Event{
  struct GFX_Event *next;
  GFX_EventKind kind;
  GFX_EventFlags flags;
  GFX_Window *window;
  U32 code;
  F32 delta;
  union{
    GFX_FileDropData *file_drop_data;
  };
} GFX_Event;

typedef struct GFX_EventList{
  GFX_Event *first;
  GFX_Event *last;
  U32 count;
} GFX_EventList;


////////////////////////////////
// GFX Implementable Functions

// apis for all

MR4TH_SYMBOL void gfx_message_box(String8 title, String8 message);

MR4TH_SYMBOL GFX_OpenFileResult* gfx_open_file_box(Arena *arena, GFX_Window *window, String8 title);

// apis for applications

MR4TH_SYMBOL B32 gfx_is_inited(void);
MR4TH_SYMBOL B32 gfx_init(void);

MR4TH_SYMBOL void gfx_set_cursor(GFX_CursorAppearance appearance);

// TODO(allen): reconsider
MR4TH_SYMBOL void              gfx_set_resizing_hook(GFX_ResizingFunc *func);
MR4TH_SYMBOL GFX_ResizingFunc* gfx_get_resizing_hook(void);

/// (window handling)

MR4TH_SYMBOL GFX_Window* gfx_window_create(void);
MR4TH_SYMBOL void        gfx_window_destroy(GFX_Window *window);
MR4TH_SYMBOL B32         gfx_window_is_valid(GFX_Window *window);

MR4TH_SYMBOL B32 gfx_window_set_visible(GFX_Window *window, B32 visible);
MR4TH_SYMBOL B32 gfx_window_set_title(GFX_Window *window, String8 title);
MR4TH_SYMBOL B32 gfx_window_set_inner_rect(GFX_Window *window, S32 *rect);
MR4TH_SYMBOL B32 gfx_window_get_inner_rect(GFX_Window *window, S32 *rect_out);
MR4TH_SYMBOL B32 gfx_window_get_outer_rect(GFX_Window *window, S32 *rect_out);
MR4TH_SYMBOL B32 gfx_window_set_resizable(GFX_Window *window, B32 resizable);
MR4TH_SYMBOL B32 gfx_window_set_full_screen(GFX_Window *window, B32 full_screen);
MR4TH_SYMBOL B32 gfx_window_is_full_screen(GFX_Window *window);
MR4TH_SYMBOL B32 gfx_window_is_minimized(GFX_Window *window);
MR4TH_SYMBOL B32 gfx_window_is_maximized(GFX_Window *window);
MR4TH_SYMBOL B32 gfx_window_set_drops_allowed(GFX_Window *window, B32 allowed);

MR4TH_SYMBOL GFX_Window* gfx_get_window_with_keyboard_focus(void);

MR4TH_SYMBOL B32  gfx_window_set_user_data(GFX_Window *window, void *ptr);
MR4TH_SYMBOL void*gfx_window_get_user_data(GFX_Window *window);

MR4TH_SYMBOL void*gfx_window_get_equipped_data(GFX_Window *window);

// TODO(allen): would probably want a full monitor handling API but
// no idea how that would abstract yet - just throwing this in there
// for now.
MR4TH_SYMBOL U64  gfx_window_refresh_rate(GFX_Window *window);

/// (input)

MR4TH_SYMBOL void           gfx_wait_for_input(void);
MR4TH_SYMBOL GFX_EventList* gfx_get_input(Arena *arena);

MR4TH_SYMBOL void gfx_get_absolute_mouse_position(S32 *out);

// apis for system implementors

// TODO(allen): rethink this
MR4TH_SYMBOL B32 gfx_window_set_equipped_data(GFX_Window *window, void *ptr, GFX_WindowReleaseFunc *release_func);


////////////////////////////////
// GFX Helper Functions

// event construction

// NOTE(allen): This push function is safe to call with a null arena or
// null input structure. In either case the return will be a pointer to
// a static memory block for an input event that can be safely mutated
// with no effects. This way complicated code paths for decoding OS
// event data and encoding GFX layer input data don't have to be
// littered with null checks.

MR4TH_SYMBOL GFX_Event* gfx_event_push(Arena *arena, GFX_EventList *list);


/*******************************
**      Begin GFX Win32       **
*******************************/

#if OS_WINDOWS

////////////////////////////////
// GFX Win32 Types

typedef struct W32_Window{
  struct W32_Window *next;
  HWND wnd;
  WINDOWPLACEMENT place;
  B32 supports_resizing;
  
  // applications
  void *user_data;
  
  // graphics system implementors
  void *equipped_data;
  GFX_WindowReleaseFunc *release_func;
} W32_Window;

#define W32_GRAPHICS_WINDOW_CLASS_NAME L"mr4th-graphics"

////////////////////////////////
// GFX Win32 Functions

MR4TH_SYMBOL LRESULT     gfx_w32_window_proc(HWND, UINT, WPARAM, LPARAM);

MR4TH_SYMBOL W32_Window* gfx_w32_window_from_handle(GFX_Window *window);
MR4TH_SYMBOL GFX_Window* gfx_w32_handle_from_hwnd(HWND wnd);

MR4TH_SYMBOL GFX_Key     gfx_w32_key_code_from_vk_code(WPARAM vk);

#endif

/*******************************
**       End GFX Win32        **
*******************************/

#endif //MR4TH_GFX_H
