#define BUFFER_DEBUG 0

API Range MakeRange(Int a, Int b) {
    Range result = {Min(a, b), Max(a, b)};
    return result;
}

API Range MakeRange(Int a) {
    Range result = {a, a};
    return result;
}

API Int GetSize(Range range) {
    Assert(range.max >= range.min);
    Int result = range.max - range.min;
    return result;
}

API bool AreEqual(Range a, Range b) {
    bool result = a.min == b.min && a.max == b.max;
    return result;
}

API bool AreOverlapping(Range a, Range b) {
    bool r1 = (a.max >= b.min && a.max <= b.max) || (a.min >= b.min && a.min <= b.max);
    bool r2 = (b.max >= a.min && b.max <= a.max) || (b.min >= a.min && b.min <= a.max);
    return r1 || r2;
}

API bool InBounds(Range range, Int pos) {
    bool result = pos >= range.min && pos < range.max;
    return result;
}

API Range operator-(Range a, Int value) {
    a.min -= value;
    a.max -= value;
    return a;
}

API Range operator-=(Range &range, Int value) {
    range = range - value;
    return range;
}

API Range operator+(Range a, Int value) {
    a.min += value;
    a.max += value;
    return a;
}

API Range operator+=(Range &range, Int value) {
    range = range + value;
    return range;
}

API Range GetBufferEndAsRange(Buffer *buffer) {
    Range result = {buffer->len, buffer->len};
    return result;
}

API Range GetBufferBeginAsRange(Buffer *buffer) {
    Range result = {0, 0};
    return result;
}

API Range GetRange(Buffer *buffer) {
    Range result = {0, buffer->len};
    return result;
}

API Int Clamp(const Buffer *buffer, Int pos) {
    Int result = Clamp(pos, (Int)0, buffer->len);
    return result;
}

API Range Clamp(const Buffer *buffer, Range range) {
    Range result = {};
    result.min   = Clamp(buffer, range.min);
    result.max   = Clamp(buffer, range.max);
    return result;
}

// Caret
API Int GetFront(Caret caret) {
    Int result = caret.pos[caret.ifront];
    return result;
}

API Int GetBack(Caret caret) {
    Int index  = (caret.ifront + 1) % 2;
    Int result = caret.pos[index];
    return result;
}

API Int GetMax(Caret caret) {
    Int result = Max(caret.pos[0], caret.pos[1]);
    return result;
}

API Int GetMin(Caret caret) {
    Int result = Min(caret.pos[0], caret.pos[1]);
    return result;
}

API Int GetSize(Caret caret) {
    return GetSize(caret.range);
}

API Caret MakeCaret(Int pos) {
    Caret result     = {};
    result.range.min = result.range.max = pos;
    return result;
}

API Caret MakeCaret(Int front, Int back) {
    Caret result = {};
    if (front >= back) {
        result.range.min = back;
        result.range.max = front;
        result.ifront    = 1;
    } else {
        result.range.min = front;
        result.range.max = back;
        result.ifront    = 0;
    }
    return result;
}

API Caret SetBack(Caret caret, Int back) {
    Int   front  = GetFront(caret);
    Caret result = MakeCaret(front, back);
    return result;
}

API Caret SetFront(Caret caret, Int front) {
    Int   back   = GetBack(caret);
    Caret result = MakeCaret(front, back);
    return result;
}

API Caret SetFrontWithAnchor(Caret caret, Caret anchor, Int p) {
    if (anchor.range.min > p) {
        caret = MakeCaret(p, anchor.range.max);
    } else if (anchor.range.max < p) {
        caret = MakeCaret(p, anchor.range.min);
    } else {
        caret = anchor;
    }
    return caret;
}

API bool AreEqual(Caret a, Caret b) {
    bool result = AreEqual(a.range, b.range) && a.ifront == b.ifront;
    return result;
}

API bool AreOverlapping(Caret a, Caret b) {
    bool result = AreOverlapping(a.range, b.range);
    return result;
}

API Range GetLineRange(Buffer *buffer, Int line, Int *end_of_buffer) {
    Range result = {buffer->line_starts[line], buffer->len};
    if (line + 1 < buffer->line_starts.len) {
        result.max = buffer->line_starts[line + 1];
    } else if (end_of_buffer) {
        *end_of_buffer = 1;
    }
    return result;
}

API Range GetLineRangeWithoutNL(Buffer *buffer, Int line) {
    Int   end_of_buffer = 0;
    Range line_range    = GetLineRange(buffer, line, &end_of_buffer);
    line_range.max      = line_range.max - 1 + end_of_buffer;
    return line_range;
}

API String16 GetString(Buffer *buffer, Range range) {
    range.min       = Clamp(range.min, (Int)0, buffer->len);
    range.max       = Clamp(range.max, (Int)0, buffer->len);
    String16 result = {(char16_t *)buffer->data + range.min, GetSize(range)};
    return result;
}

API String AllocCharString(Allocator allocator, Buffer *buffer, Range range) {
    String16 string16 = GetString(buffer, range);
    String   result   = ToString(allocator, string16);
    return result;
}

API XY XYLine(Int line) {
    XY result   = {};
    result.line = line;
    return result;
}

API char16_t GetChar(Buffer *buffer, Int pos) {
    if (pos >= 0 && pos < buffer->len) return buffer->str[pos];
    return 0;
}

API bool InBounds(const Buffer *buffer, Int pos) {
    bool result = pos >= 0 && pos < buffer->len;
    return result;
}

API Int LastLine(Buffer *buffer) {
    Int result = buffer->line_starts.len - 1;
    return result;
}

API String16 GetLineString(Buffer *buffer, Int line, Int *end_of_buffer) {
    Range    range  = GetLineRange(buffer, line, end_of_buffer);
    String16 string = GetString(buffer, range);
    return string;
}

API String16 GetLineStringWithoutNL(Buffer *buffer, Int line) {
    Range    range  = GetLineRangeWithoutNL(buffer, line);
    String16 string = GetString(buffer, range);
    return string;
}

API Int PosToLine(Buffer *buffer, Int pos) {
    Add(&buffer->line_starts, buffer->len + 1);

    // binary search
    Int low = 0;

    // -2 here because we use 2 indices and combine them into one line range so we
    // don't want to access last item since that would index past array.
    Int high   = buffer->line_starts.len - 2;
    Int result = 0;

    while (low <= high) {
        Int   mid   = low + (high - low) / 2;
        Range range = {buffer->line_starts[mid], buffer->line_starts[mid + 1]};
        if (pos >= range.min && pos < range.max) {
            result = mid;
            break;
        }

        if (range.min < pos) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }

    Pop(&buffer->line_starts);
    return result;
}

API XY PosToXY(Buffer *buffer, Int pos) {
    Int   line       = PosToLine(buffer, pos);
    Range line_range = GetLineRange(buffer, line);
    Int   col        = pos - line_range.min;
    XY    result     = {col, line};
    return result;
}

API Int XYToPos(Buffer *buffer, XY xy) {
    xy.line          = Clamp(xy.line, (Int)0, buffer->line_starts.len - 1);
    Range line_range = GetLineRange(buffer, xy.line);
    Int   pos        = Clamp(xy.col + line_range.min, line_range.min, line_range.max);
    return pos;
}

API Int XYToPosWithoutNL(Buffer *buffer, XY xy) {
    xy.line             = Clamp(xy.line, (Int)0, buffer->line_starts.len - 1);
    Int   end_of_buffer = 0;
    Range line_range    = GetLineRange(buffer, xy.line, &end_of_buffer);
    Int   pos           = Clamp(xy.col + line_range.min, line_range.min, line_range.max - 1 + end_of_buffer);
    return pos;
}

API Int XYToPosErrorOutOfBounds(Buffer *buffer, XY xy) {
    if (xy.line < 0 || xy.line >= buffer->line_starts.len) return -1;
    Range line_range = GetLineRange(buffer, xy.line);
    Int   pos        = xy.col + line_range.min;
    if (pos < line_range.min || pos > line_range.max) return -1;
    return pos;
}

API Int GetWordStart(Buffer *buffer, Int pos) {
    pos = Clamp(pos, (Int)0, buffer->len);
    for (Int i = pos - 1; i >= 0; i -= 1) {
        if (!IsWord(buffer->str[i])) break;
        pos = i;
    }
    return pos;
}

API Int GetWordEnd(Buffer *buffer, Int pos) {
    pos = Clamp(pos, (Int)0, buffer->len);
    for (Int i = pos;; i += 1) {
        pos = i;
        // this is because buffer end terminates the loop
        // too early and we cannot establish the proper range
        // semantics - proper max is one past last index
        if (!(i < buffer->len)) break;
        if (!IsWord(buffer->str[i])) break;
    }
    return pos;
}

API bool IsLoadWord(char16_t w) {
    bool result = w == u'(' || w == u')' || w == u'/' || w == u'\\' || w == u':' || w == u'$' || w == u'_' || w == u'.' || w == u'@' || w == u',';
    if (!result) {
        result = !(IsSymbol(w) || IsWhitespace(w));
    }
    return result;
}

API Int GetLoadWordStart(Buffer *buffer, Int pos) {
    pos = Clamp(pos, (Int)0, buffer->len);
    for (Int i = pos - 1; i >= 0; i -= 1) {
        if (!IsLoadWord(buffer->str[i])) break;
        pos = i;
    }
    return pos;
}

API Int GetLoadWordEnd(Buffer *buffer, Int pos) {
    pos = Clamp(pos, (Int)0, buffer->len);
    for (Int i = pos;; i += 1) {
        pos = i;
        // this is because buffer end terminates the loop
        // too early and we cannot establish the proper range
        // semantics - proper max is one past last index
        if (!(i < buffer->len)) break;
        if (!IsLoadWord(buffer->str[i])) break;
    }
    return pos;
}

API Range EncloseLoadWord(Buffer *buffer, Int pos) {
    Range result = {GetLoadWordStart(buffer, pos), GetLoadWordEnd(buffer, pos)};
    return result;
}

API Int GetNextWordEnd(Buffer *buffer, Int pos) {
    pos          = Clamp(pos, (Int)0, buffer->len);
    char16_t prev = 0;
    for (Int i = pos;; i += 1) {
        pos = i;
        // this is because buffer end terminates the loop
        // too early and we cannot establish the proper range
        // semantics - proper max is one past last index
        if (!(i < buffer->len)) break;
        if (prev == u'\n' || (prev && prev != buffer->str[i]) || IsWord(buffer->str[i])) {
            break;
        }
        prev = buffer->str[i];
    }
    Int result = prev == u'\n' ? pos : GetWordEnd(buffer, pos);
    return result;
}

API Int GetPrevWordStart(Buffer *buffer, Int pos) {
    pos          = Clamp(pos, (Int)0, buffer->len);
    char16_t prev = 0;
    Int     i    = pos - 1;
    for (; i >= 0; i -= 1) {
        if (prev == u'\n' || (prev && prev != buffer->str[i]) || IsWord(buffer->str[i])) {
            break;
        }
        pos  = i;
        prev = buffer->str[i];
    }
    bool new_line = prev == u'\n';
    Int  result   = new_line ? pos : GetWordStart(buffer, pos);
    return result;
}

API Int GetLineStart(Buffer *buffer, Int pos) {
    pos         = Clamp(pos, (Int)0, buffer->len);
    Int   line  = PosToLine(buffer, pos);
    Range range = GetLineRangeWithoutNL(buffer, line);
    return range.min;
}

API Int GetLineEnd(Buffer *buffer, Int pos) {
    pos         = Clamp(pos, (Int)0, buffer->len);
    Int   line  = PosToLine(buffer, pos);
    Range range = GetLineRangeWithoutNL(buffer, line);
    return range.max;
}

API Int GetFullLineStart(Buffer *buffer, Int pos) {
    pos         = Clamp(pos, (Int)0, buffer->len);
    Int   line  = PosToLine(buffer, pos);
    Range range = GetLineRange(buffer, line);
    return range.min;
}

API Int GetFullLineEnd(Buffer *buffer, Int pos, Int *eof) {
    pos         = Clamp(pos, (Int)0, buffer->len);
    Int   line  = PosToLine(buffer, pos);
    Range range = GetLineRange(buffer, line, eof);
    return range.max;
}

API Int GetBufferEnd(Buffer *buffer) {
    return buffer->len;
}

API Int GetBufferStart(Buffer *buffer) {
    return 0;
}

API Int GetNextEmptyLineStart(Buffer *buffer, Int pos) {
    Int result    = pos;
    Int next_line = PosToLine(buffer, pos) + 1;
    for (Int line = next_line; line < buffer->line_starts.len; line += 1) {
        Range line_range = GetLineRangeWithoutNL(buffer, line);
        result           = line_range.min;

        bool whitespace_line = true;
        for (Int i = line_range.min; i < line_range.max; i += 1) {
            if (!IsWhitespace(buffer->str[i])) {
                whitespace_line = false;
                break;
            }
        }
        if (whitespace_line) break;
    }
    return result;
}

API Int GetPrevEmptyLineStart(Buffer *buffer, Int pos) {
    Int result    = pos;
    Int next_line = PosToLine(buffer, pos) - 1;
    for (Int line = next_line; line >= 0; line -= 1) {
        Range line_range = GetLineRangeWithoutNL(buffer, line);
        result           = line_range.min;

        bool whitespace_line = true;
        for (Int i = line_range.min; i < line_range.max; i += 1) {
            if (!IsWhitespace(buffer->str[i])) {
                whitespace_line = false;
                break;
            }
        }
        if (whitespace_line) break;
    }
    return result;
}

API Range EncloseWord(Buffer *buffer, Int pos) {
    Range result = {GetWordStart(buffer, pos), GetWordEnd(buffer, pos)};
    return result;
}

API Int SkipSpaces(Buffer *buffer, Int seek) {
    for (; seek < buffer->len; seek += 1) {
        char16_t c = GetChar(buffer, seek);
        if (c != u' ') break;
    }
    return seek;
}

API Int FindScopeEnd(Buffer *buffer, Int seek, Int max_seek, char16_t open, char16_t close) {
    char16_t right = GetChar(buffer, seek);
    if (right == open) {
        int scope = 1;
        Int i     = seek + 1;
        for (; i < seek + max_seek && i < buffer->len; i += 1) {
            char16_t c = GetChar(buffer, i);

            if (open == close && c == '\\') {
                i += 1;
            } else if (open == close && c == open) {
                scope -= 1;
            } else if (c == open) {
                scope += 1;
            } else if (c == close) {
                scope -= 1;
            }

            if (c == u'\n' || scope == 0) break;
        }

        if (scope == 0) seek = i;
    }
    return seek;
}

API Range EncloseScope(Buffer *buffer, Int pos_min, Int pos_max, char16_t open, char16_t close) {
    Range result = {pos_min, pos_max};
    for (Int i = pos_min - 1; i >= 0; i -= 1) {
        if (buffer->str[i] == open) {
            result.min = i;
            break;
        }
    }
    for (Int i = pos_max; i < buffer->len; i += 1) {
        if (buffer->str[i] == close) {
            result.max = i + 1;
            break;
        }
    }
    return result;
}

API Range EncloseLine(Buffer *buffer, Int pos) {
    Range result = {GetLineStart(buffer, pos), GetLineEnd(buffer, pos)};
    return result;
}

API Range EncloseFullLine(Buffer *buffer, Int pos) {
    Range result = {GetFullLineStart(buffer, pos), GetFullLineEnd(buffer, pos)};
    return result;
}

API Int OffsetByLine(Buffer *buffer, Int pos, Int line_offset) {
    XY  xy     = PosToXY(buffer, pos);
    Int result = XYToPosWithoutNL(buffer, {xy.col, xy.line + line_offset});
    return result;
}

API Int GetNextChar(Buffer *buffer, Int pos) {
    Int result = Clamp(pos + 1, (Int)0, buffer->len);
    return result;
}

API Int GetPrevChar(Buffer *buffer, Int pos) {
    Int result = Clamp(pos - 1, (Int)0, buffer->len);
    return result;
}

API Int GetLineIndent(Buffer *buffer, Int line) {
    String16 string = GetLineStringWithoutNL(buffer, line);
    Int      indent = 0;
    for (Int i = 0; i < string.len; i += 1) {
        if (IsWhitespace(string.data[i])) {
            indent += 1;
        } else {
            break;
        }
    }
    return indent;
}

API Int GetIndentAtPos(Buffer *buffer, Int pos) {
    Int line   = PosToLine(buffer, pos);
    Int result = GetLineIndent(buffer, line);
    return result;
}

API Range GetIndentRangeAtPos(Buffer *buffer, Int pos) {
    Int   line   = PosToLine(buffer, pos);
    Int   indent = GetLineIndent(buffer, line);
    Range range  = GetLineRangeWithoutNL(buffer, line);
    return {range.min, range.min + indent};
}

API Int FindRangeByPos(Array<Range> *ranges, Int pos) {
    // binary search
    Int low    = 0;
    Int high   = ranges->len - 1;
    Int result = -1;

    while (low <= high) {
        Int   mid   = low + (high - low) / 2;
        Range range = ranges->data[mid];
        if (pos >= range.min && pos < range.max) {
            result = mid;
            break;
        }

        if (range.min < pos) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }

    return result;
}
///////////////////////////////
// Raw operations
///////////////////////////////

void RawGrow(Buffer *buffer, Int change_size) {
    Int new_size = buffer->len + change_size;
    if (new_size > buffer->cap) {
        Allocator alo       = buffer->line_starts.allocator;
        Int       outside   = new_size - buffer->cap;
        Int       new_cap   = (buffer->cap + outside) * 2;
        U16      *new_array = AllocArray(alo, U16, new_cap);
        MemoryCopy(new_array, buffer->data, buffer->len * sizeof(U16));
        Dealloc(alo, buffer->data);
        buffer->cap  = new_cap;
        buffer->data = new_array;
    }
}

void RawOffsetAllLinesForward(Buffer *buffer, Int line, Int *_offset) {
    Int offset = *_offset;
    *_offset   = 0;
    if (offset == 0) return;

    for (Int i = line; i < buffer->line_starts.len; i += 1) {
        buffer->line_starts[i] += offset;
    }
}

void UpdateLines(Buffer *buffer, Range range, String16 string) {
    if (buffer->no_line_starts) return;
    ProfileFunction();
    Array<Int> &ls              = buffer->line_starts;
    Int         min_line_number = PosToLine(buffer, range.min);
    Assert(min_line_number < ls.len);
    // Update lines remove
    {
        Int line_offset           = 0;
        Int lines_to_remove       = min_line_number + 1;
        Int lines_to_remove_count = 0;
        for (Int i = range.min; i < range.max; i += 1) {
            char16_t c = buffer->data[i];
            if (c == '\n') {
                lines_to_remove_count += 1;
            }
            line_offset -= 1;
        }
        RemoveManyByIndex(&ls, lines_to_remove, lines_to_remove_count);
        RawOffsetAllLinesForward(buffer, min_line_number + 1, &line_offset);
    }

    // Update lines add
    Int line_offset = 0;
    Int nl          = min_line_number + 1;
    for (Int i = 0; i < string.len; i += 1) {
        nl                   = min_line_number + 1;
        bool next_line_valid = nl < ls.len;

        if (string[i] == u'\n') {
            Int new_line_pos = range.min + i + 1;
            line_offset += 1;

            Insert(&ls, new_line_pos, nl);
            RawOffsetAllLinesForward(buffer, nl + 1, &line_offset);
            min_line_number = nl;
        } else if (next_line_valid) {
            line_offset += 1;
        }
    }
    RawOffsetAllLinesForward(buffer, nl, &line_offset);
}

void RawValidateLineStarts(Buffer *buffer) {
    Int line = 0;
    for (Int i = 0; i < buffer->len; i += 1) {
        Int l = PosToLine(buffer, i);
        Assert(l == line);
        if (buffer->data[i] == u'\n') line += 1;
    }
}

API void RawReplaceText(Buffer *buffer, Range range, String16 string) {
    ProfileFunction();
    Assert(range.max >= range.min);
    Assert(range.max >= 0 && range.max <= buffer->len);
    Assert(range.min >= 0 && range.min <= buffer->len);
    buffer->dirty = true;
    buffer->change_id += 1;

    Int size_to_remove = range.max - range.min;
    Int size_to_add    = string.len;
    Int change_size    = size_to_add - size_to_remove;
    Assert(change_size + buffer->len >= 0);
    RawGrow(buffer, change_size);

    Int  range_size   = range.max - range.min;
    U16 *begin_remove = buffer->data + range.min;
    U16 *end_remove   = begin_remove + range_size;
    Int  remain_len   = buffer->len - (range.min + range_size);

    UpdateLines(buffer, range, string);

    U16 *begin_add = begin_remove;
    U16 *end_add   = begin_add + string.len;
    MemoryMove(end_add, end_remove, remain_len * sizeof(U16));
    MemoryCopy(begin_add, string.data, string.len * sizeof(U16));
    buffer->len = buffer->len + change_size;

#if BUFFER_DEBUG
    RawValidateLineStarts(buffer);
#endif
}

API void RawAppend(Buffer *buffer, String16 string) {
    RawReplaceText(buffer, GetBufferEndAsRange(buffer), string);
}

API void RawAppend(Buffer *buffer, String string) {
    Scratch scratch(buffer->line_starts.allocator);
    RawAppend(buffer, ToString16(scratch, string));
}

API void RawAppendf(Buffer *buffer, const char *fmt, ...) {
    Scratch scratch(buffer->line_starts.allocator);
    STRING_FORMAT(scratch, fmt, string);
    String16 string16 = ToString16(scratch, string);
    RawReplaceText(buffer, GetBufferEndAsRange(buffer), string16);
}

///////////////////////////////
// multicursor
///////////////////////////////

void MergeSort(int64_t Count, Caret *First, Caret *Temp) {
    ProfileFunction();
    // SortKey = range.min
    if (Count == 1) {
        // NOTE(casey): No work to do.
    } else if (Count == 2) {
        Caret *EntryA = First;
        Caret *EntryB = First + 1;
        if (EntryA->range.min > EntryB->range.min) {
            Swap(EntryA, EntryB);
        }
    } else {
        int64_t Half0 = Count / 2;
        int64_t Half1 = Count - Half0;

        Assert(Half0 >= 1);
        Assert(Half1 >= 1);

        Caret *InHalf0 = First;
        Caret *InHalf1 = First + Half0;
        Caret *End     = First + Count;

        MergeSort(Half0, InHalf0, Temp);
        MergeSort(Half1, InHalf1, Temp);

        Caret *ReadHalf0 = InHalf0;
        Caret *ReadHalf1 = InHalf1;

        Caret *Out = Temp;
        for (int64_t Index = 0;
             Index < Count;
             ++Index) {
            if (ReadHalf0 == InHalf1) {
                *Out++ = *ReadHalf1++;
            } else if (ReadHalf1 == End) {
                *Out++ = *ReadHalf0++;
            } else if (ReadHalf0->range.min < ReadHalf1->range.min) {
                *Out++ = *ReadHalf0++;
            } else {
                *Out++ = *ReadHalf1++;
            }
        }
        Assert(Out == (Temp + Count));
        Assert(ReadHalf0 == InHalf1);
        Assert(ReadHalf1 == End);

        // TODO(casey): Not really necessary if we ping-pong
        for (int64_t Index = 0;
             Index < Count;
             ++Index) {
            First[Index] = Temp[Index];
        }
    }
}

void MergeSort(int64_t Count, Edit *First, Edit *Temp) {
    ProfileFunction();
    // SortKey = range.min
    if (Count == 1) {
        // NOTE(casey): No work to do.
    } else if (Count == 2) {
        Edit *EntryA = First;
        Edit *EntryB = First + 1;
        if (EntryA->range.min > EntryB->range.min) {
            Swap(EntryA, EntryB);
        }
    } else {
        int64_t Half0 = Count / 2;
        int64_t Half1 = Count - Half0;

        Assert(Half0 >= 1);
        Assert(Half1 >= 1);

        Edit *InHalf0 = First;
        Edit *InHalf1 = First + Half0;
        Edit *End     = First + Count;

        MergeSort(Half0, InHalf0, Temp);
        MergeSort(Half1, InHalf1, Temp);

        Edit *ReadHalf0 = InHalf0;
        Edit *ReadHalf1 = InHalf1;

        Edit *Out = Temp;
        for (int64_t Index = 0;
             Index < Count;
             ++Index) {
            if (ReadHalf0 == InHalf1) {
                *Out++ = *ReadHalf1++;
            } else if (ReadHalf1 == End) {
                *Out++ = *ReadHalf0++;
            } else if (ReadHalf0->range.min < ReadHalf1->range.min) {
                *Out++ = *ReadHalf0++;
            } else {
                *Out++ = *ReadHalf1++;
            }
        }
        Assert(Out == (Temp + Count));
        Assert(ReadHalf0 == InHalf1);
        Assert(ReadHalf1 == End);

        // TODO(casey): Not really necessary if we ping-pong
        for (int64_t Index = 0;
             Index < Count;
             ++Index) {
            First[Index] = Temp[Index];
        }
    }
}

API void ApplyEditsMultiCursor(Buffer *buffer, Array<Edit> edits) {
    ProfileFunction();
#if BUFFER_DEBUG
    Assert(buffer->line_starts.len);
    Assert(edits.len);
    For(edits) {
        Assert(it.range.min >= 0);
        Assert(it.range.max >= it.range.min);
        Assert(it.range.max <= buffer->len);
    }

    // Make sure edit ranges don't overlap
    ForItem(it1, edits) {
        ForItem(it2, edits) {
            if (&it1 == &it2) continue;

            bool a2_inside = it2.range.min >= it1.range.min && it2.range.min < it1.range.max;
            Assert(!a2_inside);

            bool b2_inside = it2.range.max > it1.range.min && it2.range.max <= it1.range.max;
            Assert(!b2_inside);
        }
    }
#endif

    // We need to sort from lowest to highest based on range.min
    Scratch     scratch(buffer->line_starts.allocator);
    Array<Edit> edits_copy = TightCopy(scratch, edits);
    if (edits.len > 1) MergeSort(edits.len, edits_copy.data, edits.data);
    edits = edits_copy;

#if BUFFER_DEBUG
    for (int64_t i = 0; i < edits.len - 1; i += 1) {
        Assert(edits[i].range.min <= edits[i + 1].range.min);
    }
#endif

    // @optimize: we can do all edits in one go with less memory copies probably
    // or something else entirely
    Int offset = 0;
    For(edits) {
        it.range.min += offset;
        it.range.max += offset;
        offset += it.string.len - GetSize(it.range);
        RawReplaceText(buffer, it.range, it.string);
    }
}

API void AddEdit(Array<Edit> *e, Range range, String16 string) {
    Add(e, {range, string});
}

///////////////////////////////
// multicursor + history
///////////////////////////////

HistoryEntry *SaveHistoryBeforeMergeCursor(Buffer *buffer, Array<HistoryEntry> *stack, Array<Caret> &carets) {
    if (buffer->no_history) return NULL;
    HistoryEntry entry = {};
    entry.time = GetTimeSeconds();
    entry.carets = TightCopy(GetSystemAllocator(), carets);
    Add(stack, entry);
    return GetLast(*stack);
}

void SaveHistoryBeforeApplyEdits(Buffer *buffer, Array<HistoryEntry> *stack, Array<Edit> &edits) {
    ProfileFunction();
    if (buffer->no_history) return;
    HistoryEntry *entry = GetLast(*stack);
    Allocator sys_allocator = GetSystemAllocator();
    entry->edits = TightCopy(sys_allocator, edits);

    // Make reverse edits
    For(entry->edits) {
        Range    new_range = {it.range.min, it.range.min + it.string.len};
        String16 string    = GetString(buffer, it.range);
        it.string          = Copy16(sys_allocator, string);
        it.range           = new_range;
    }

    Scratch     scratch;
    Array<Edit> temp_edits = TightCopy(scratch, entry->edits);

    // Fix reverse edits
    ForItem(edit, edits) {
        Int remove_size = GetSize(edit.range);
        Int insert_size = edit.string.len;
        Int offset      = insert_size - remove_size;

        for (Int i = 0; i < entry->edits.len; i += 1) {
            Edit &new_edit = entry->edits.data[i];
            Edit &old_edit = temp_edits.data[i];
            if (old_edit.range.min > edit.range.min) {
                new_edit.range.min += offset;
                new_edit.range.max += offset;
            }
        }
    }
}

API void RedoEdit(Buffer *buffer, Array<Caret> *carets) {
    ProfileFunction();
    if (buffer->no_history) return;
    if (buffer->redo_stack.len <= 0) return;

    HistoryEntry entry = Pop(&buffer->redo_stack);

    HistoryEntry *e = SaveHistoryBeforeMergeCursor(buffer, &buffer->undo_stack, *carets);
    e->time = entry.time;
    SaveHistoryBeforeApplyEdits(buffer, &buffer->undo_stack, entry.edits);
    ApplyEditsMultiCursor(buffer, entry.edits);

    Dealloc(carets);
    *carets = entry.carets;

    Allocator sys_allocator = GetSystemAllocator();
    For(entry.edits) Dealloc(sys_allocator, it.string.data);
    Dealloc(&entry.edits);

    if (buffer->redo_stack.len > 0) {
        HistoryEntry *next = GetLast(buffer->redo_stack);
        if (next->time - entry.time <= ConfigUndoMergeTimeWindow) {
            RedoEdit(buffer, carets);
        }
    }
}

API void UndoEdit(Buffer *buffer, Array<Caret> *carets) {
    ProfileFunction();
    if (buffer->no_history) return;
    if (buffer->undo_stack.len <= 0) return;

    HistoryEntry entry = Pop(&buffer->undo_stack);
    HistoryEntry *e = SaveHistoryBeforeMergeCursor(buffer, &buffer->redo_stack, *carets);
    e->time = entry.time;
    SaveHistoryBeforeApplyEdits(buffer, &buffer->redo_stack, entry.edits);
    ApplyEditsMultiCursor(buffer, entry.edits);

    Dealloc(carets);
    *carets = entry.carets;

    Allocator sys_allocator = GetSystemAllocator();
    For(entry.edits) Dealloc(sys_allocator, it.string.data);
    Dealloc(&entry.edits);

    if (buffer->undo_stack.len > 0) {
        HistoryEntry *next = GetLast(buffer->undo_stack);
        if (entry.time - next->time <= ConfigUndoMergeTimeWindow) {
            UndoEdit(buffer, carets);
        }
    }
}

API void DeallocHistoryEntries(Array<HistoryEntry> *entries) {
    For(*entries) {
        Dealloc(&it.carets);
        ForItem(edit, it.edits) Dealloc(it.edits.allocator, edit.string.data);
        Dealloc(&it.edits);
    }
    entries->len = 0;
}

API void ResetHistory(Buffer *buffer) {
    DeallocHistoryEntries(&buffer->redo_stack);
    DeallocHistoryEntries(&buffer->undo_stack);
}

API void ResetBuffer(Buffer *buffer) {
    ResetHistory(buffer);
    buffer->change_id += 1;
    buffer->line_starts.len = 0;
    buffer->len = 0;
    if (!buffer->no_line_starts) {
        Add(&buffer->line_starts, (Int)0);
    }
}

void ClearRedoStack(Buffer *buffer) {
    DeallocHistoryEntries(&buffer->redo_stack);
}

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

API Array<Edit> BeginEdit(Allocator allocator, Buffer *buffer, Array<Caret> &carets) {
    Assert(buffer->edit_phase == 0 || buffer->edit_phase == 1);
    if (buffer->edit_phase == 0) {
        buffer->edit_phase += 1;
        Assert(carets.len);
        SaveHistoryBeforeMergeCursor(buffer, &buffer->undo_stack, carets);
        ClearRedoStack(buffer);
    }
    Array<Edit> result = {allocator};
    return result;
}

API void SaveCaretHistoryBeforeBeginEdit(Buffer *buffer, Array<Caret> &carets) {
    BeginEdit({}, buffer, carets);
}

API void AssertRanges(Array<Caret> carets) {
    For(carets) {
        Assert(it.range.max >= it.range.min);
    }
}

API void AdjustCarets(Array<Edit> edits, Array<Caret> *carets) {
    Scratch      scratch;
    Array<Caret> new_carets = TightCopy(scratch, *carets);
    ForItem(edit, edits) {
        Int remove_size = GetSize(edit.range);
        Int insert_size = edit.string.len;
        Int offset      = insert_size - remove_size;

        for (Int i = 0; i < carets->len; i += 1) {
            Caret &oldc = carets->data[i];
            Caret &newc = new_carets.data[i];

            if (oldc.range.min > edit.range.min) {
                newc.range.min += offset;
                newc.range.max += offset;
            }
        }
    }

    for (Int i = 0; i < carets->len; i += 1) carets->data[i] = new_carets[i];
}

API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool kill_selection) {
    ProfileFunction();

    {
        Assert(buffer->edit_phase == 1);
        buffer->edit_phase += 1;
        if (edits->len) {
            SaveHistoryBeforeApplyEdits(buffer, &buffer->undo_stack, *edits);
            ApplyEditsMultiCursor(buffer, *edits);
        } else {
            HistoryEntry entry = Pop(&buffer->undo_stack);
            Dealloc(&entry.carets);
            Assert(entry.edits.len == 0);
        }
    }

    Assert(buffer->edit_phase == 2);
    buffer->edit_phase -= 2;

#if BUFFER_DEBUG
    if (buffer->no_history == false) {
        HistoryEntry *entry = GetLast(buffer->undo_stack);
        Assert(entry->carets.len);
        Assert(entry->edits.len);
        for (Int i = 0; i < edits->len - 1; i += 1) {
            Assert(edits->data[i].range.min <= edits->data[i + 1].range.min);
        }
    }
#endif

    // Adjust carets
    // this one also moves the carets forward if they are aligned with edit
    //
    Scratch      scratch;
    Array<Caret> new_carets = TightCopy(scratch, *carets);
    ForItem(edit, *edits) {
        Int remove_size = GetSize(edit.range);
        Int insert_size = edit.string.len;
        Int offset      = insert_size - remove_size;

        for (Int i = 0; i < carets->len; i += 1) {
            Caret &old_cursor = carets->data[i];
            Caret &new_cursor = new_carets.data[i];

            if (old_cursor.range.min == edit.range.min) {
                new_cursor.range.min += insert_size;
            } else if (old_cursor.range.min > edit.range.min) {
                new_cursor.range.min += offset;
            }

            if (old_cursor.range.max == edit.range.max) {
                new_cursor.range.max += insert_size;
            } else if (old_cursor.range.max > edit.range.max) {
                new_cursor.range.max += offset;
            }

            Assert(new_cursor.range.max >= new_cursor.range.min);
        }
    }

    for (Int i = 0; i < carets->len; i += 1) {
        carets->data[i] = new_carets[i];
        if (kill_selection) {
            carets->data[i].range.max = carets->data[i].range.min;
        }
    }
}

// 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) {
    ProfileFunction();
    For(*carets) it.range = Clamp(buffer, it.range);
    Caret first_caret          = carets->data[0];

    Scratch      scratch;
    Array<Caret> c1 = TightCopy(scratch, *carets);
    if (carets->len > 1) MergeSort(carets->len, c1.data, carets->data);
    carets->len = 0;

    Int first_caret_index = 0;
    Add(carets, c1[0]);
    for (Int i = 1; i < c1.len; i += 1) {
        Caret &it   = c1[i];
        Caret *last = GetLast(*carets);

        if (AreOverlapping(*last, it)) {
            last->range.max = Max(last->range.max, it.range.max);
        } else {
            Add(carets, it);
        }

        if (AreEqual(it, first_caret)) first_caret_index = carets->len - 1;
    }

    Swap(&carets->data[first_caret_index], &carets->data[0]);
}

API void InitBuffer(Allocator allocator, Buffer *buffer, BufferID id = {}, String name = "", Int size = 4096) {
    buffer->id = id;
    buffer->name = name;
    buffer->cap = size;
    buffer->data = AllocArray(allocator, U16, buffer->cap);
    buffer->line_starts.allocator = allocator;
    buffer->undo_stack.allocator  = allocator;
    buffer->redo_stack.allocator  = allocator;
    if (!buffer->no_line_starts) {
        Add(&buffer->line_starts, (Int)0);
    }
}

API void DeinitBuffer(Buffer *buffer) {
    Allocator allocator = buffer->line_starts.allocator;
    Dealloc(allocator, buffer->data);
    Dealloc(&buffer->line_starts);
    DeallocHistoryArray(&buffer->undo_stack);
    DeallocHistoryArray(&buffer->redo_stack);
}

// Indexing starts from 0 not 1 because this routine creates also the zero buffer
// which is the buffer that often is defaulted to in case of errors
API Buffer *AllocBuffer(Allocator allocator, String name = "", Int size = 4096) {
    Buffer *buffer = AllocType(allocator, Buffer);
    buffer->id = {BufferIDs++, buffer};
    buffer->name = Intern(&GlobalInternTable, name);
    InitBuffer(allocator, buffer, buffer->id, buffer->name, size);
    return buffer;
}

API void DeallocBuffer(Buffer *buffer) {
    DeinitBuffer(buffer);
    Dealloc(buffer->line_starts.allocator, buffer);
}

API Buffer *CreateTempBuffer(Allocator allocator, Int size = 4096) {
    Buffer *result         = AllocType(allocator, Buffer);
    result->no_history     = true;
    result->no_line_starts = true;
    InitBuffer(allocator, result, {}, "*temp*", size);
    return result;
}

void RunBufferTest() {
    {
        Scratch scratch;
        Buffer buffer = {};
        InitBuffer(scratch, &buffer);
        Assert(buffer.line_starts.len == 1);
        String16 test_string = u"Thing itself";
        {
            RawReplaceText(&buffer, {}, test_string);
            Assert(buffer.cap == 4096);
            Assert(buffer.len == 12);
            String16 a = GetString(&buffer);
            Assert(a == test_string);
            Assert(buffer.line_starts.len == 1);
            Assert(buffer.line_starts[0] == 0);
            Assert(PosToLine(&buffer, 4) == 0);
        }
        {
            RawReplaceText(&buffer, {0, 5}, u"");
            Assert(buffer.cap == 4096);
            Assert(buffer.len == 12 - 5);
            String16 a = GetString(&buffer);
            Assert(a == u" itself");
            Assert(buffer.line_starts.len == 1);
            Assert(buffer.line_starts[0] == 0);
            Assert(PosToLine(&buffer, 4) == 0);
        }
        {
            RawReplaceText(&buffer, GetBufferEndAsRange(&buffer), u" and");
            Assert(buffer.cap == 4096);
            Assert(buffer.len == 12 - 5 + 4);
            String16 a = GetString(&buffer);
            Assert(a == u" itself and");
            Assert(buffer.line_starts.len == 1);
            Assert(buffer.line_starts[0] == 0);
            Assert(PosToLine(&buffer, 4) == 0);
        }
        {
            RawReplaceText(&buffer, GetRange(&buffer), u"");
            Assert(buffer.cap == 4096);
            Assert(buffer.len == 0);
            String16 a = GetString(&buffer);
            Assert(a == u"");
            Assert(buffer.line_starts.len == 1);
            Assert(buffer.line_starts[0] == 0);
        }
        {
            RawReplaceText(&buffer, GetBufferEndAsRange(&buffer), u"Memes and other\nthings");
            Assert(buffer.line_starts.len == 2);
            Assert(PosToLine(&buffer, 17) == 1);
            Assert(PosToLine(&buffer, 16) == 1);
            Assert(PosToLine(&buffer, 15) == 0);
            Assert(buffer.data[15] == L'\n');
            Assert(buffer.data[16] == L't');

            RawReplaceText(&buffer, GetBufferBeginAsRange(&buffer), u"Things as is\nand stuff\n");
            Assert(buffer.line_starts.len == 4);
            Assert(PosToLine(&buffer, 12) == 0);
            Assert(buffer.data[12] == L'\n');
            Assert(PosToLine(&buffer, 13) == 1);
            Assert(PosToLine(&buffer, 21) == 1);
            Assert(PosToLine(&buffer, 22) == 1);
            Assert(buffer.data[22] == L'\n');
            Assert(PosToLine(&buffer, 23) == 2);
            Assert(PosToLine(&buffer, 37) == 2);
            Assert(PosToLine(&buffer, 38) == 2);
            Assert(buffer.data[38] == L'\n');
            Assert(PosToLine(&buffer, 39) == 3);
            Assert(buffer.data[39] == L't');

            RawReplaceText(&buffer, GetBufferBeginAsRange(&buffer), u"a");
            Assert(buffer.line_starts.len == 4);
            Assert(PosToLine(&buffer, 13) == 0);
            Assert(PosToLine(&buffer, 14) == 1);
        }
    }

    {
        Scratch scratch;
        Buffer buffer = {};
        InitBuffer(scratch, &buffer);
        RawReplaceText(&buffer, {}, u"Thing\nmeme");
        Assert(PosToLine(&buffer, 5) == 0);
        Assert(PosToLine(&buffer, 6) == 1);
        RawReplaceText(&buffer, {0, 1}, u"");
        Assert(PosToLine(&buffer, 4) == 0);
        Assert(PosToLine(&buffer, 5) == 1);
        RawReplaceText(&buffer, {4, 5}, u"");
        Assert(buffer.line_starts.len == 1);
        RawValidateLineStarts(&buffer);
    }

    // Make sure all the line starts are properly created
    {
        Scratch scratch;
        Buffer buffer = {};
        InitBuffer(scratch, &buffer);
        RawReplaceText(&buffer, {}, u"Thing\nmeme");
        RawReplaceText(&buffer, {0, 5}, u"per\ncop");
        Assert(buffer.line_starts.len == (Int)3);
        Assert(PosToLine(&buffer, 3) == 0);
        Assert(PosToLine(&buffer, 4) == 1);
        RawValidateLineStarts(&buffer);

        RawReplaceText(&buffer, {0, 8}, u"Thing\nmeme");
        RawReplaceText(&buffer, {0, 3}, u"Thing\nmeme");
        RawReplaceText(&buffer, {9, 13}, u"Thing\nmeme");
        RawReplaceText(&buffer, {4, 5}, u"Thing\nmeme\n\n\n");
        RawReplaceText(&buffer, {22, 23}, u"\n\n\nThing\nmeme\n\n\n");
        RawReplaceText(&buffer, {22, 23}, u"\n\n\nThing\nmeme\n\n\n");
        RawReplaceText(&buffer, {22, 23}, u"\n\n\nThing\nmeme\n\n\n");
        RawReplaceText(&buffer, {22, 23}, u"\n\n\nThing\nmeme\n\n\n");
        RawValidateLineStarts(&buffer);
    }

    // Basic case make sure no leaks
    {
        Scratch scratch;
        Buffer buffer = {};
        InitBuffer(GetTrackingAllocator(), &buffer);
        RawReplaceText(&buffer, {}, u"Thing\nmeme");
        RawReplaceText(&buffer, GetBufferEndAsRange(&buffer), u"\nnewThing");
        DeinitBuffer(&buffer);
        TrackingAllocatorCheck();
    }

    // Testing Edit API and making sure no leaks
    {
        Scratch scratch;
        Buffer buffer = {};
        InitBuffer(GetTrackingAllocator(), &buffer);
        RawReplaceText(&buffer, {}, u"Testing\nthings");
        Array<Caret> carets = {scratch};
        Add(&carets, MakeCaret(0,7));
        Add(&carets, MakeCaret(8,9));
        Add(&carets, MakeCaret(GetBufferEnd(&buffer)));
        Array<Edit> edits = BeginEdit(scratch, &buffer, carets);
        MergeCarets(&buffer, &carets);
        AddEdit(&edits, {0, 7}, u"t");
        AddEdit(&edits, {8, 9}, u"T");
        AddEdit(&edits, GetBufferEndAsRange(&buffer), u"\nnewThing");
        EndEdit(&buffer, &edits, &carets, KILL_SELECTION);
        String16 s = GetString(&buffer);
        Assert(s == u"t\nThings\nnewThing");
        DeinitBuffer(&buffer);
        TrackingAllocatorCheck();
    }

    // Make sure no_history and no line_starts properly makes sure of these
    {
        Scratch scratch;
        Buffer buffer = {};
        buffer.no_history = true;
        buffer.no_line_starts = true;
        InitBuffer(GetTrackingAllocator(), &buffer);
        RawReplaceText(&buffer, {}, u"Testing\nthings");
        Array<Caret> carets = {scratch};
        Add(&carets, MakeCaret(0,7));
        Add(&carets, MakeCaret(8,9));
        Add(&carets, MakeCaret(GetBufferEnd(&buffer)));
        Array<Edit> edits = BeginEdit(scratch, &buffer, carets);
        MergeCarets(&buffer, &carets);
        AddEdit(&edits, {0, 7}, u"t");
        AddEdit(&edits, {8, 9}, u"T");
        AddEdit(&edits, GetBufferEndAsRange(&buffer), u"\nnewThing");
        EndEdit(&buffer, &edits, &carets, KILL_SELECTION);
        String16 s = GetString(&buffer);
        Assert(s == u"t\nThings\nnewThing");
        Assert(buffer.line_starts.len == 0);
        Assert(buffer.line_starts.data == 0);
        Assert(buffer.redo_stack.len == 0);
        Assert(buffer.redo_stack.data == 0);
        Assert(buffer.undo_stack.len == 0);
        Assert(buffer.undo_stack.data == 0);
        DeinitBuffer(&buffer);
        TrackingAllocatorCheck();
    }
} RegisterFunction(&TestFunctions, RunBufferTest);

///////////////////////////////
// Management
BufferID AllocBufferID(Buffer *buffer) {
    return {BufferIDs++, buffer};
}

Buffer *GetBuffer(BufferID id, Buffer *default_buffer = Buffers[0]) {
    For(Buffers) {
        if (it->id == id) return it;
    }
    return default_buffer;
}

Buffer *GetBuffer(String name, Buffer *default_buffer = Buffers[0]) {
    For(Buffers) {
        if (it->name == name) return it;
    }
    return default_buffer;
}

bool IsNull(Buffer *buffer) {
    return buffer->id.id == 0;
}

void Close(BufferID id) {
    Buffer *buffer = GetBuffer(id, NULL);
    if (buffer) {
        if (buffer->id.id == 0 || buffer->special) {
            return;
        }

        buffer->close = true;
        RunGCThisFrame = true;
    }
}

Buffer *CreateBuffer(Allocator allocator, String name, Int size) {
    Buffer *result = AllocBuffer(allocator, name, size);
    Add(&Buffers, result);
    return result;
}

String GetUniqueBufferName(String working_dir, String prepend_name, String extension = ".log") {
    Scratch scratch;
    String buffer_name = {};
    for (int i = 1; i < 1000; i += 1) {
        buffer_name = Format(scratch, "%S/%S%d%S", working_dir, prepend_name, i, extension);
        buffer_name = GetAbsolutePath(scratch, buffer_name);
        Buffer *exists = GetBuffer(buffer_name, NULL);
        if (!exists && !FileExists(buffer_name)) {
            break;
        }
    }
    buffer_name = Intern(&GlobalInternTable, buffer_name);
    return buffer_name;
}

void InitBuffers() {
    Allocator sys_allocator = GetSystemAllocator();
    Scratch scratch;
    Buffer *null_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "console"));
    null_buffer->special = true;
    View *null_view = CreateView(null_buffer->id);
    null_view->special = true;
    Assert(null_buffer->id == NullBufferID && null_view->id == NullViewID);
    TraceBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "trace"));
    TraceBuffer->special = true;
    TraceBuffer->no_history = true;
    GCInfoBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "gc"));
    GCInfoBuffer->special = true;
    GCInfoBuffer->no_history = true;
    EventBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "events"));
    EventBuffer->no_history = true;
    EventBuffer->special = true;
    BuildBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "build"));
    BuildBuffer->no_history = true;
    BuildBuffer->special = true;
}

Int ConvertUTF8ToUTF16UnixLine(String string, char16_t *buffer, Int buffer_cap) {
    if (string.len == 0) {
        return 0;
    }

    Int buffer_len = 0;
    Assert(buffer_cap > string.len * 2);
    for (Int i = 0; i < string.len;) {
        if (string.data[i] == '\r') {
            i += 1;
            continue;
        }
        if (string.data[i] == '\t') {
            // @WARNING: DONT INCREASE THE SIZE CARELESSLY, WE NEED TO ADJUST BUFFER SIZE
            for (Int i = 0; i < 4; i += 1) buffer[buffer_len++] = u' ';
            i += 1;
            continue;
        }

        uint32_t    u32    = '?';
        UTF32Result decode = UTF8ToUTF32((uint8_t *)(string.data + i), (int64_t)(string.len - i));
        if (!decode.error) {
            i += decode.advance;
            u32 = decode.out_str;
        } else {
            i += 1;
        }

        UTF16Result encode = UTF32ToUTF16(u32);
        if (!encode.error) {
            for (int64_t encode_i = 0; encode_i < encode.len; encode_i += 1) {
                buffer[buffer_len++] = encode.out_str[encode_i];
                Assert(buffer_len < buffer_cap);
            }
        } else {
            buffer[buffer_len++] = u'?';
        }
    }
    return buffer_len;
}

String16 ToUnixString16(Allocator allocator, String string_) {
    Int      cap             = string_.len * 3;
    char16_t *string16_buffer = AllocArray(allocator, char16_t, cap);
    Int      len             = ConvertUTF8ToUTF16UnixLine(string_, string16_buffer, cap);
    String16 string          = {string16_buffer, len};
    return string;
}

Buffer *BufferOpenFile(String path) {
    ProfileFunction();
    Allocator sys_allocator = GetSystemAllocator();
    Scratch scratch;

    path = GetAbsolutePath(scratch, path);
    Buffer *buffer = GetBuffer(path);
    if (!IsNull(buffer) || (IsNull(buffer) && buffer->name == path)) {
        return buffer;
    }

    if (!FileExists(path)) {
        buffer = CreateBuffer(sys_allocator, path);
    } else if (IsDir(path)) {
        buffer = CreateBuffer(sys_allocator, path);
        buffer->is_dir = true;
        buffer->garbage = true;
    } else {
        String string = ReadFile(scratch, path);
        buffer = CreateBuffer(sys_allocator, path, string.len * 4 + 4096);
        buffer->len = ConvertUTF8ToUTF16UnixLine(string, buffer->str, buffer->cap);
        buffer->file_mod_time = GetFileModTime(path);
        UpdateLines(buffer, {}, String16{(char16_t *)buffer->data, buffer->len});
    }

    return buffer;
}

bool BufferIsReferenced(BufferID buffer_id) {
    if (buffer_id == NullBufferID) {
        return true;
    }

    if (FindView(buffer_id, NULL)) {
        return true;
    }

    return false;
}

void ReopenBuffer(Buffer *buffer) {
    Scratch scratch;
    String string = ReadFile(scratch, buffer->name);
    if (string.len == 0) {
        return;
    }

    String16 string16 = ToUnixString16(scratch, string);
    ReplaceWithoutMovingCarets(buffer, GetRange(buffer), string16);
    buffer->file_mod_time = GetFileModTime(buffer->name);
    buffer->changed_on_disk = false;
    buffer->dirty = false;
}

void SaveBuffer(Buffer *buffer) {
    Scratch scratch;
    String  string  = AllocCharString(scratch, buffer);
    bool    success = WriteFile(buffer->name, string);

    if (success) {
        buffer->file_mod_time = GetFileModTime(buffer->name);
        buffer->dirty         = false;
        buffer->garbage       = false;
    } else {
        ReportWarningf("Failed to save file with name: %S", buffer->name);
    }
}
