#define EVENT_KINDS \
    X(EVENT_NONE) \
    X(EVENT_UPDATE) \
    X(EVENT_QUIT) \
    X(EVENT_MOUSE_LEFT) \
    X(EVENT_MOUSE_RIGHT) \
    X(EVENT_MOUSE_MIDDLE) \
    X(EVENT_MOUSE_X1) \
    X(EVENT_MOUSE_X2) \
    X(EVENT_MOUSE_LEFT_UP) \
    X(EVENT_MOUSE_RIGHT_UP) \
    X(EVENT_MOUSE_MIDDLE_UP) \
    X(EVENT_MOUSE_X1_UP) \
    X(EVENT_MOUSE_X2_UP) \
    X(EVENT_MOUSE_WHEEL) \
    X(EVENT_KEY_PRESS) \
    X(EVENT_TEXT_INPUT) \
    X(EVENT_DROP_FILE)

enum EventKind {
#define X(TYPE) TYPE,
    EVENT_KINDS
#undef X
    EVENT_KIND_COUNT,
    EVENT_KIND_INVALID = 111,
};

const char *EventKindStrings[] = {
#define X(TYPE) #TYPE,
    EVENT_KINDS
#undef X
};

#define EVENT_FIELDS         \
    X(EventKind, Int, kind) \
    X(SDL_Keycode, Int, key) \
    X(int16_t, Int, xwindow) \
    X(int16_t, Int, ywindow) \
    X(int16_t, Int, xmouse)  \
    X(int16_t, Int, ymouse)  \
    X(uint8_t, Int, clicks)  \
    X(uint8_t, Int, shift)   \
    X(uint8_t, Int, ctrl)    \
    X(uint8_t, Int, alt)     \
    X(uint8_t, Int, super)   \
    X(float, Float, xwheel)  \
    X(float, Float, ywheel)  \
    X(char *, String, text)
#define EVENT_FIELD_COUNT 14

struct Event {
#define X(TYPE, KIND, NAME) TYPE NAME;
    EVENT_FIELDS
#undef X
};

struct BSet {
    struct Window *window;
    View   *view;
    Buffer *buffer;
};
BSet GetBSet(struct Window *window);
BSet GetBSet(WindowID window_id);


// @WARNING: be careful about using this, should only be used for debugging
// the problem with this is that we want events to be reproducible.
// We eat as many events as we can in a frame, we abstract the frame and so on.
// Dont use it
Int FrameID;

Allocator SysAllocator = {SystemAllocatorProc};
String ConfigDir;
float  DPIScale = 1.0f;

void     AfterEdit(View *view, Array<Edit> edits);
Scroller ComputeScrollerRect(Window *window);
BSet     Open(String path, String meta = "");
BSet     Open(String16 path, String meta = "");
void     UpdateScroll(Window *window, bool update_caret_scrolling);
String   GetMainDir();

void        SelectEntireBuffer(View *view);
void        Replace(View *view, String16 string);
void        SelectRange(View *view, Range range);
void        Append(View *view, String16 string, bool scroll_to_end_if_cursor_on_last_line);
void        Append(View *view, String string, bool scroll_to_end_if_cursor_on_last_line);
Array<Edit> ReplaceEx(Allocator scratch, View *view, String16 string);
void        Eval(String string);
void        Eval(String16 string);
void ReportDebugf(const char *fmt, ...);

void ReplaceWithoutMovingCarets(Buffer *buffer, Range range, String16 string);
void ClipboardCopy(View *view);
void ClipboardPaste(View *view);

void ReportConsolef(const char *fmt, ...);
void ReportErrorf(const char *fmt, ...);
void ReportWarningf(const char *fmt, ...);
void Appendf(View *view, const char *fmt, ...);

Buffer *CreateBuffer(Allocator allocator, String name, Int size = 4096);
View   *CreateView(BufferID active_buffer);
void ReopenBuffer(Buffer *buffer);
bool ProcessIsActive(ViewID view);
void EvalCommand(String command);
void EvalCommand(String16 command);

inline bool operator==(BufferID a, BufferID b) { return a.id == b.id; }
inline bool operator==(ViewID a, ViewID b) { return a.id == b.id; }
inline bool operator!=(BufferID a, BufferID b) { return a.id != b.id; }
inline bool operator!=(ViewID a, ViewID b) { return a.id != b.id; }

// This function as name suggests tries to open a buffer,
// there is no name resolution here, path should already be resolved etc.
//
// 1. It tries to find an already open buffer
// 2. Returns a buffer if the file doesn't exist (even if a directory doesn't exist)
//      - This is a worry for later time, also we want to handle weird names and so on
// 3. If file exists we read it, convert to utf16, tabs to spaces etc.
//
// We don't really care about opening buffers that don't have proper paths
Buffer *BufferOpenFile(String path);

typedef void PFunction(void *param);
struct PFunctionData { String name; PFunction *function; };

struct Register_Function {
    Register_Function(Array<FunctionData> *functions, String name, Function *f) {
        int64_t pos = 0;
        if (Seek(name, "_", &pos, 0)) {
            name = Skip(name, pos + 1);
        }
        Add(functions, {name, f});
    }
};
#define RegisterFunction(functions, name) Register_Function RF__##name(functions, #name, name)

struct Register_Command { Register_Command(Array<CommandData> *fucs, Function *function, String name, String binding) { if (StartsWith(name, "Command_")) name = Skip(name, 8); Add(fucs, {name, binding, function}); } };
#define RegisterCommand(name, binding) Register_Command RC__##name(&CommandFunctions, name, #name, binding)

const int DIR_RIGHT = 0;
const int DIR_LEFT = 1;
const int DIR_DOWN = 2;
const int DIR_UP = 3;
const int DIR_COUNT = 4;
const bool CTRL_PRESSED = true;
const bool SHIFT_PRESS = true;

enum VariableType {
    VariableType_Invalid,
    VariableType_Color,
    VariableType_String,
    VariableType_Int,
    VariableType_Float,
};

struct Variable {
    VariableType type;
    String name;
    union {
        void *addr;
        Color *color;
        Int *i;
        double *f;
        String *string;
    };
};

struct Register_Variable {
    Register_Variable(Array<Variable> *variables, VariableType type, String name, void *addr) {
        Add(variables, {type, name, addr});
    }
};
#define RegisterVariable(type, name, ...) \
    type name = __VA_ARGS__; \
    Register_Variable var_##name(&Variables, VariableType_##type, #name, &name)

void AddHook(Array<CommandData> *arr, String name, String binding, Function *function);


enum OpenKind {
    OpenKind_Invalid,
    OpenKind_Skip,
    OpenKind_Exec,
    OpenKind_BackgroundExec,
    OpenKind_Goto,
    OpenKind_Command,
};

struct ResolvedOpen {
    OpenKind kind;
    String path;
    Int line, col;
    bool existing_buffer;
};
ResolvedOpen ResolveOpen(Allocator scratch, String path, String meta);
