struct Window; struct View; struct WindowSplit;
struct ViewID   { Int id; View *o; };
struct WindowID { Int id; Window *o; };

struct View {
    ViewID       id;
    BufferID     active_buffer;
    Vec2I        scroll;
    Array<Caret> carets;

    // window | view
    Caret main_caret_on_begin_frame;
    bool  update_scroll;

    bool fuzzy_search;
    String16 prev_search_line;
};

struct Window {
    WindowID id;
    ViewID active_view;

    Rect2I total_rect;
    Rect2I document_rect;
    Rect2I scrollbar_rect;
    Rect2I line_numbers_rect;
    Rect2I resizer_rect;

    Font  *font;
    double mouse_scroller_offset;
    int    z;
    double weight;
    Int status_bar_last_buffer_change_id;

    struct {
        bool draw_scrollbar : 1;
        bool draw_line_numbers : 1;
        bool draw_darker : 1;
        bool draw_line_highlight : 1;
        bool visible : 1;
        bool layout : 1;
        bool kill : 1;
        bool sync_visibility_with_focus : 1;
    };
};

struct Scroller {
    Rect2  rect;
    double begin;
    double end;
    Int    line_count;
};

struct BSet {
    Window *window;
    View   *view;
    Buffer *buffer;
};

// @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;

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

void        Command_SelectEntireBuffer(View *view);
void        Command_Replace(View *view, String16 string);
void        Command_SelectRangeOneCursor(View *view, Range range);
void        Command_Append(View *view, String16 string, bool scroll_to_end_if_cursor_on_last_line);
void        Command_Append(View *view, String string, bool scroll_to_end_if_cursor_on_last_line);
Array<Edit> Command_ReplaceEx(Allocator scratch, View *view, String16 string);
void        Command_Eval(String string);
void        Command_Eval(String16 string);
String      Command_GetMainDir();
void ReportDebugf(const char *fmt, ...);

void ReplaceWithoutMovingCarets(Buffer *buffer, Range range, String16 string);
void Command_Copy(View *view);
void Command_Paste(View *view);

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

Buffer *CreateBuffer(Allocator allocator, String name, Int size = 4096);
View   *CreateView(BufferID active_buffer);
void ReopenBuffer(Buffer *buffer);
inline Buffer *FindBuffer(String name);

inline bool operator==(WindowID a, WindowID 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; }
inline bool operator!=(WindowID a, WindowID 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);

#if DEBUG_BUILD
    #define IF_DEBUG(x) x
#else
    #define IF_DEBUG(x)
#endif