struct Buffer;
struct BufferID { Int id; Buffer *o; };
union  Range { struct { Int min; Int max; }; Int e[2]; };
struct Caret { union  { Range range; Int pos[2]; }; Int ifront;};
struct XY    { Int col; Int line; };

struct Edit {
    Range    range;
    String16 string;
};

struct HistoryEntry {
    Array<Edit>  edits;
    Array<Caret> carets;
    double time;
};

struct Buffer {
    BufferID id;
    String   name;
    Int      begin_frame_change_id;
    Int      change_id;
    Int      user_change_id;
    Int      file_mod_time;

    union {
        U16     *data;
        char16_t*str;
    };
    Int        len;
    Int        cap;
    Array<Int> line_starts;

    Array<HistoryEntry> undo_stack;
    Array<HistoryEntry> redo_stack;
    int                 edit_phase;
    struct {
        unsigned no_history : 1;
        unsigned no_line_starts : 1;
        unsigned dirty : 1;
        unsigned changed_on_disk : 1;
        unsigned garbage : 1;
        unsigned dont_try_to_save_in_bulk_ops : 1;
        unsigned close : 1;
        unsigned special : 1;
        unsigned is_dir : 1;
    };
};

struct FuzzyPair {
    Int index;
    Int rating;
};

enum {
    KILL_SELECTION = 1,
};
constexpr Int LAST_LINE = INT64_MAX;

// @todo: buffer init, deinit
API Range MakeRange(Int a, Int b);
API Range MakeRange(Int a);
API Int GetSize(Range range);
API bool AreEqual(Range a, Range b);
API bool AreOverlapping(Range a, Range b);
API bool InBounds(Range range, Int pos);
API Range operator-(Range a, Int value);
API Range operator-=(Range &range, Int value);
API Range operator+(Range a, Int value);
API Range operator+=(Range &range, Int value);
API Range GetBufferEndAsRange(Buffer *buffer);
API Range GetBufferBeginAsRange(Buffer *buffer);
API Range GetRange(Buffer *buffer);
API Int Clamp(const Buffer *buffer, Int pos);
API Range Clamp(const Buffer *buffer, Range range);

API Int GetFront(Caret caret);
API Int GetBack(Caret caret);
API Caret MakeCaret(Int pos);
API Caret MakeCaret(Int front, Int back);
API Caret SetBack(Caret caret, Int back);
API Caret SetFront(Caret caret, Int front);
API Caret SetFrontWithAnchor(Caret caret, Caret anchor, Int p);
API bool AreEqual(Caret a, Caret b);
API bool AreOverlapping(Caret a, Caret b);

API Range GetLineRange(Buffer *buffer, Int line, Int *end_of_buffer = NULL);
API Range GetLineRangeWithoutNL(Buffer *buffer, Int line);
API String16 GetString(Buffer *buffer, Range range = {0, INT64_MAX});
API String AllocCharString(Allocator allocator, Buffer *buffer, Range range = {0, INT64_MAX});
API String16 GetLineString(Buffer *buffer, Int line, Int *end_of_buffer = NULL);
API String16 GetLineStringWithoutNL(Buffer *buffer, Int line);

API XY XYLine(Int line);
API Int LastLine(Buffer *buffer);
API Int PosToLine(Buffer *buffer, Int pos);
API XY PosToXY(Buffer *buffer, Int pos);
API Int XYToPos(Buffer *buffer, XY xy);
API Int XYToPosWithoutNL(Buffer *buffer, XY xy);
API Int XYToPosErrorOutOfBounds(Buffer *buffer, XY xy);

API char16_t GetChar(Buffer *buffer, Int pos);
API bool InBounds(const Buffer *buffer, Int pos);
API Int GetWordStart(Buffer *buffer, Int pos);
API Int GetWordEnd(Buffer *buffer, Int pos);
API bool IsLoadWord(char16_t w);
API Int GetLoadWordStart(Buffer *buffer, Int pos);
API Int GetLoadWordEnd(Buffer *buffer, Int pos);
API Range EncloseLoadWord(Buffer *buffer, Int pos);
API Int GetNextWordEnd(Buffer *buffer, Int pos);
API Int GetPrevWordStart(Buffer *buffer, Int pos);
API Int GetLineStart(Buffer *buffer, Int pos);
API Int GetLineEnd(Buffer *buffer, Int pos);
API Int GetFullLineStart(Buffer *buffer, Int pos);
API Int GetFullLineEnd(Buffer *buffer, Int pos, Int *eof = NULL);
API Int GetBufferEnd(Buffer *buffer);
API Int GetBufferStart(Buffer *buffer);
API Int GetNextEmptyLineStart(Buffer *buffer, Int pos);
API Int GetPrevEmptyLineStart(Buffer *buffer, Int pos);
API Range EncloseWord(Buffer *buffer, Int pos);
API Int SkipSpaces(Buffer *buffer, Int seek);
API Int FindScopeEnd(Buffer *buffer, Int seek, Int max_seek, char16_t open, char16_t close);
API Range EncloseScope(Buffer *buffer, Int pos_min, Int pos_max, char16_t open, char16_t close);
API Range EncloseLine(Buffer *buffer, Int pos);
API Range EncloseFullLine(Buffer *buffer, Int pos);
API Int OffsetByLine(Buffer *buffer, Int pos, Int line_offset);
API Int GetNextChar(Buffer *buffer, Int pos);
API Int GetPrevChar(Buffer *buffer, Int pos);
API Int GetLineIndent(Buffer *buffer, Int line);
API Int GetIndentAtPos(Buffer *buffer, Int pos);
API Range GetIndentRangeAtPos(Buffer *buffer, Int pos);

API Int              FuzzyRate(String16 string, String16 with);
API Array<FuzzyPair> FuzzySearchLines(Allocator allocator, Buffer *buffer, Int line_min, Int line_max, String16 needle);
API Int              FindRangeByPos(Array<Range> *ranges, Int pos);

// These don't handle history, just raw operations on buffer memory
API void RawReplaceText(Buffer *buffer, Range range, String16 string);
API void RawAppend(Buffer *buffer, String16 string);
API void RawAppend(Buffer *buffer, String string);
API void RawAppendf(Buffer *buffer, const char *fmt, ...);

// We can invoke SaveCaretHistoryBeforeBeginEdit(which is basically BeginEdit with
// different name before caret altering commands to save caret history
// and then call some editing command to edit which is not going to save carets
API void        SaveCaretHistoryBeforeBeginEdit(Buffer *buffer, Array<Caret> &carets);
API Array<Edit> BeginEdit(Allocator allocator, Buffer *buffer, Array<Caret> &carets);
API void        EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool kill_selection);
API void        AddEdit(Array<Edit> *e, Range range, String16 string);

// Merge carets that overlap, this needs to be handled before any edits to
// make sure overlapping edits won't happen.
//
// mouse_selection_anchor is special case for mouse handling !
API void MergeCarets(Buffer *buffer, Array<Caret> *carets);

// Adjusts caret copies after edit to make them not move after for example
// a bar modification. Sometimes works, sometimes doesn't, depends, not an all solving tool.
// For example in case of ReopenBuffer, when we select and replace entire buffer, it didn't quite work.
API void AdjustCarets(Array<Edit> edits, Array<Caret> *carets);
API void AssertRanges(Array<Caret> carets);

API void RedoEdit(Buffer *buffer, Array<Caret> *carets);
API void UndoEdit(Buffer *buffer, Array<Caret> *carets);
API void ResetHistory(Buffer *buffer);

API void DeallocHistoryArray(Array<HistoryEntry> *entries);
API void DeallocHistoryEntries(Array<HistoryEntry> *entries);
