/*
 * Mr. 4th Dimention - Allen Webster
 *
 * 24.10.2015
 *
 * Buffer features based on the stringify loop,
 *  and other abstract buffer features.
 *
 */

// TOP

#define Buffer_Init_Type cat_4tech(Buffer_Type, _Init)
#define Buffer_Stream_Type cat_4tech(Buffer_Type, _Stream)

inline_4tech void
buffer_stringify(Buffer_Type *buffer, i32 start, i32 end, char *out){
    Buffer_Stream_Type stream = {0};
    
    i32 i = start;
    if (buffer_stringify_loop(&stream, buffer, i, end)){
        b32 still_looping = 0;
        do{
            i32 size = stream.end - i;
            memcpy_4tech(out, stream.data + i, size);
            i = stream.end;
            out += size;
            still_looping = buffer_stringify_next(&stream);
        }while(still_looping);
    }
}

internal_4tech i32
buffer_convert_out(Buffer_Type *buffer, char *dest, i32 max){
    Buffer_Stream_Type stream = {0};
    i32 i = 0;
    i32 size = buffer_size(buffer);
    assert_4tech(size + buffer->line_count <= max);
    
    i32 pos = 0;
    if (buffer_stringify_loop(&stream, buffer, 0, size)){
        b32 still_looping = 0;
        do{
            i32 size = stream.end - i;
            i32 out_size = 0;
            i32 result = eol_convert_out(dest + pos, max - pos, stream.data + i, size, &out_size);
            assert_4tech(result);
            i = stream.end;
            pos += out_size;
            still_looping = buffer_stringify_next(&stream);
        }while(still_looping);
    }
    
    return(pos);
}

internal_4tech i32
buffer_count_newlines(Buffer_Type *buffer, i32 start, i32 end){
    Buffer_Stream_Type stream = {0};
    i32 i = start;
    i32 count = 0;
    
    assert_4tech(0 <= start);
    assert_4tech(start <= end);
    assert_4tech(end <= buffer_size(buffer));
    
    if (buffer_stringify_loop(&stream, buffer, i, end)){
        b32 still_looping = 0;
        do{
            for (; i < stream.end; ++i){
                count += (stream.data[i] == '\n');
            }
            still_looping = buffer_stringify_next(&stream);
        }while(still_looping);
    }
    
    return(count);
}

typedef struct Buffer_Measure_Starts{
    i32 i;
    i32 count;
    i32 start;
} Buffer_Measure_Starts;

// TODO(allen): Rewrite this with a duff routine
// Also make it so that the array goes one past the end
// and stores the size in the extra spot.
internal_4tech i32
buffer_measure_starts(Buffer_Measure_Starts *state, Buffer_Type *buffer){
    Buffer_Stream_Type stream = {0};
    i32 size = buffer_size(buffer);
    i32 start = state->start, i = state->i;
    i32 *start_ptr = buffer->line_starts + state->count;
    i32 *start_end = buffer->line_starts + buffer->line_max;
    i32 result = 1;
    
    if (buffer_stringify_loop(&stream, buffer, i, size)){
        b32 still_looping = 0;
        do{
            for (; i < stream.end; ++i){
                char ch = stream.data[i];
                if (ch == '\n'){
                    if (start_ptr == start_end){
                        goto buffer_measure_starts_widths_end;
                    }
                    
                    *start_ptr++ = start;
                    start = i + 1;
                }
            }
            still_looping = buffer_stringify_next(&stream);
        }while(still_looping);
    }
    
    assert_4tech(i == size);
    
    if (start_ptr == start_end){
        goto buffer_measure_starts_widths_end;
    }
    *start_ptr++ = start;
    result = 0;
    
    buffer_measure_starts_widths_end:;
    state->i = i;
    state->count = (i32)(start_ptr - buffer->line_starts);
    state->start = start;
    
    return(result);
}

internal_4tech void
buffer_measure_character_starts(Buffer_Type *buffer, i32 *character_starts, i32 mode, i32 virtual_white){
    assert_4tech(mode == 0);
    
    Buffer_Stream_Type stream = {0};
    i32 i = 0;
    i32 size = buffer_size(buffer);
    
    i32 line_index = 0;
    i32 character_index = 0;
    
    b32 skipping_whitespace = 0;
    
    character_starts[line_index++] = character_index;
    
    if (virtual_white){
        skipping_whitespace = 1;
    }
    
    stream.use_termination_character = 1;
    stream.terminator = '\n';
    if (buffer_stringify_loop(&stream, buffer, i, size)){
        b32 still_looping = 0;
        do{
            for (; i < stream.end; ++i){
                u8 ch = (u8)stream.data[i];
                if (ch == '\n'){
                    ++character_index;
                    character_starts[line_index++] = character_index;
                    if (virtual_white){
                        skipping_whitespace = 1;
                    }
                }
                else{
                    if (ch != ' ' && ch != '\t'){
                        skipping_whitespace = 0;
                    }
                    
                    if (!skipping_whitespace){
                        ++character_index;
                    }
                }
            }
            still_looping = buffer_stringify_next(&stream);
        }while(still_looping);
    }
    
    assert_4tech(line_index-1 == buffer->line_count);
}

enum{
    BLStatus_Finished,
    BLStatus_NeedWrapLineShift,
    BLStatus_NeedLineShift,
    BLStatus_NeedWrapDetermination,
};

struct Buffer_Layout_Stop{
    u32 status;
    i32 line_index;
    i32 wrap_line_index;
    i32 pos;
    f32 x;
};

struct Buffer_Measure_Wrap_Params{
    Buffer_Type *buffer;
    i32 *wrap_line_index;
    f32 *adv;
    f32 width;
    b32 virtual_white;
};

struct Buffer_Measure_Wrap_State{
    Buffer_Stream_Type stream;
    i32 i;
    i32 size;
    b32 still_looping;
    
    i32 line_index;
    
    i32 current_wrap_index;
    f32 current_adv;
    f32 x;
    
    b32 skipping_whitespace;
    i32 wrap_unit_end;
    b32 did_wrap;
    b32 first_of_the_line;
    
    u8 ch;
    
    i32 __pc__;
};

// duff-routine defines
#define DrCase(PC) case PC: goto resumespot_##PC
#define DrYield(PC, n) { *S_ptr = S; S_ptr->__pc__ = PC; return(n); resumespot_##PC:; }
#define DrReturn(n) { *S_ptr = S; S_ptr->__pc__ = -1; return(n); }

internal_4tech Buffer_Layout_Stop
buffer_measure_wrap_y(Buffer_Measure_Wrap_State *S_ptr, Buffer_Measure_Wrap_Params params,
                      f32 line_shift, b32 do_wrap, i32 wrap_unit_end){
    Buffer_Measure_Wrap_State S = *S_ptr;
    Buffer_Layout_Stop S_stop;
    
    switch (S.__pc__){
        DrCase(1);
        DrCase(2);
        DrCase(3);
        DrCase(4);
    }
    
    S.size = buffer_size(params.buffer);
    
    if (params.virtual_white){
        S_stop.status = BLStatus_NeedLineShift;
        S_stop.line_index = S.line_index;
        S_stop.wrap_line_index = S.current_wrap_index;
        S_stop.pos = S.i;
        DrYield(1, S_stop);
    }
    
    S.x = line_shift;
    params.wrap_line_index[S.line_index++] = 0;
    
    if (params.virtual_white){
        S.skipping_whitespace = 1;
    }
    
    S.first_of_the_line = 1;
    if (buffer_stringify_loop(&S.stream, params.buffer, S.i, S.size)){
        S.still_looping = 0;
        do{
            for (; S.i < S.stream.end; ++S.i){
                S.ch = (u8)S.stream.data[S.i];
                if (S.ch == '\n'){
                    ++S.current_wrap_index;
                    params.wrap_line_index[S.line_index++] = S.current_wrap_index;
                    
                    if (params.virtual_white){
                        S_stop.status          = BLStatus_NeedLineShift;
                        S_stop.line_index      = S.line_index - 1;
                        S_stop.wrap_line_index = S.current_wrap_index;
                        S_stop.pos             = S.i+1;
                        DrYield(2, S_stop);
                    }
                    
                    S.x = line_shift;
                    
                    if (params.virtual_white){
                        S.skipping_whitespace = 1;
                    }
                    S.first_of_the_line = 1;
                }
                else{
                    if (S.ch != ' ' && S.ch != '\t'){
                        S.skipping_whitespace = 0;
                    }
                    
                    if (!S.skipping_whitespace){
                        S.current_adv = params.adv[S.ch];
                        
                        S.did_wrap = 0;
                        if (S.i >= S.wrap_unit_end){
                            S_stop.status          = BLStatus_NeedWrapDetermination;
                            S_stop.line_index      = S.line_index - 1;
                            S_stop.wrap_line_index = S.current_wrap_index;
                            S_stop.pos             = S.i;
                            S_stop.x               = S.x;
                            DrYield(4, S_stop);
                            
                            S.wrap_unit_end = wrap_unit_end;
                            
                            if (do_wrap && !S.first_of_the_line){
                                S.did_wrap = 1;
                                ++S.current_wrap_index;
                                
                                if (params.virtual_white){
                                    S_stop.status = BLStatus_NeedWrapLineShift;
                                    S_stop.line_index = S.line_index - 1;
                                    S_stop.wrap_line_index = S.current_wrap_index;
                                    S_stop.pos = S.i+1;
                                    DrYield(3, S_stop);
                                }
                                
                                S.x = line_shift + S.current_adv;
                            }
                        }
                        
                        if (!S.did_wrap){
                            S.x += S.current_adv;
                        }
                        
                        S.first_of_the_line = 0;
                    }
                }
            }
            S.still_looping = buffer_stringify_next(&S.stream);
        }while(S.still_looping);
    }
    
    ++S.current_wrap_index;
    params.wrap_line_index[S.line_index++] = S.current_wrap_index;
    
    assert_4tech(S.line_index-1 == params.buffer->line_count);
    
    S_stop.status = BLStatus_Finished;
    DrReturn(S_stop);
}

#undef DrCase
#undef DrYield
#undef DrReturn

internal_4tech void
buffer_remeasure_starts(Buffer_Type *buffer, i32 line_start, i32 line_end, i32 line_shift, i32 text_shift){
    i32 *starts = buffer->line_starts;
    i32 line_count = buffer->line_count;
    
    assert_4tech(0 <= line_start);
    assert_4tech(line_start <= line_end);
    assert_4tech(line_end < line_count);
    assert_4tech(line_count + line_shift <= buffer->line_max);
    
    ++line_end;
    
    // Adjust
    if (text_shift != 0){
        i32 line_i = line_end;
        starts += line_i;
        for (; line_i < line_count; ++line_i, ++starts){
            *starts += text_shift;
        }
        starts = buffer->line_starts;
    }
    
    // Shift
    i32 new_line_count = line_count;
    i32 new_line_end = line_end;
    if (line_shift != 0){
        new_line_count += line_shift;
        new_line_end += line_shift;
        
        memmove_4tech(starts + line_end + line_shift, starts + line_end,
                      sizeof(i32)*(line_count - line_end));
    }
    
    // Iteration data (yikes! Need better loop system)
    Buffer_Stream_Type stream = {0};
    i32 size = buffer_size(buffer);
    i32 char_i = starts[line_start];
    i32 line_i = line_start;
    
    // Line start measurement
    i32 start = char_i;
    
    if (buffer_stringify_loop(&stream, buffer, char_i, size)){
        b32 still_looping = 0;
        do{
            for (; char_i < stream.end; ++char_i){
                u8 ch = (u8)stream.data[char_i];
                
                if (ch == '\n'){
                    starts[line_i] = start;
                    ++line_i;
                    start = char_i + 1;
                    
                    // TODO(allen): I would like to know that I am not guessing here,
                    // so let's try to turn the && into an Assert.
                    if (line_i >= new_line_end && (line_i >= new_line_count || start == starts[line_i])){
                        goto buffer_remeasure_starts_end;
                    }
                }
            }
            still_looping = buffer_stringify_next(&stream);
        }while(still_looping);
    }
    
    // TODO(allen): I suspect this can just go away.
    if (char_i == size){
        starts[line_i++] = start;
    }
    
    buffer_remeasure_starts_end:;
    assert_4tech(line_count >= 1);
    buffer->line_count = new_line_count;
}

internal_4tech void
buffer_remeasure_character_starts(Buffer_Type *buffer, i32 line_start, i32 line_end, i32 line_shift,
                                  i32 *character_starts, i32 mode, i32 virtual_whitespace){
    assert_4tech(mode == 0);
    
    i32 new_line_count = buffer->line_count;
    
    assert_4tech(0 <= line_start);
    assert_4tech(line_start <= line_end);
    assert_4tech(line_end < new_line_count - line_shift);
    
    ++line_end;
    
    // Shift
    i32 line_count = new_line_count;
    i32 new_line_end = line_end;
    if (line_shift != 0){
        line_count -= line_shift;
        new_line_end += line_shift;
        
        memmove_4tech(character_starts + line_end + line_shift, character_starts + line_end,
                      sizeof(i32)*(line_count - line_end + 1));
    }
    
    // Iteration data (yikes! Need better loop system)
    Buffer_Stream_Type stream = {0};
    i32 size = buffer_size(buffer);
    i32 char_i = buffer->line_starts[line_start];
    i32 line_i = line_start;
    
    // Character measurement
    i32 last_char_start = character_starts[line_i];
    i32 current_char_start = last_char_start;
    
    b32 skipping_whitespace = 0;
    
    if (virtual_whitespace){
        skipping_whitespace = 1;
    }
    
    stream.use_termination_character = 1;
    stream.terminator = '\n';
    if (buffer_stringify_loop(&stream, buffer, char_i, size)){
        b32 still_looping = 0;
        do{
            for (; char_i < stream.end; ++char_i){
                u8 ch = (u8)stream.data[char_i];
                if (ch == '\n'){
                    character_starts[line_i++] = last_char_start;
                    ++current_char_start;
                    last_char_start = current_char_start;
                    if (virtual_whitespace){
                        skipping_whitespace = 1;
                    }
                    
                    if (line_i >= new_line_end){
                        goto buffer_remeasure_character_starts_end;
                    }
                }
                else{
                    if (ch != ' ' && ch != '\t'){
                        skipping_whitespace = 0;
                    }
                    
                    if (!skipping_whitespace){
                        ++current_char_start;
                    }
                }
            }
            still_looping = buffer_stringify_next(&stream);
        }while(still_looping);
    }
    
    assert_4tech(line_i >= new_line_end);
    
    buffer_remeasure_character_starts_end:;
    
    // Adjust
    if (line_i <= new_line_end){
        i32 character_shift = current_char_start - character_starts[line_i];
        
        if (character_shift != 0){
            character_starts += line_i;
            for (; line_i <= new_line_count; ++line_i, ++character_starts){
                *character_starts += character_shift;
            }
        }
    }
}

internal_4tech void
buffer_remeasure_wrap_y(Buffer_Type *buffer, i32 line_start, i32 line_end, i32 line_shift,
                        f32 *wraps, f32 font_height, f32 *adv, f32 max_width){
    i32 new_line_count = buffer->line_count;
    
    assert_4tech(0 <= line_start);
    assert_4tech(line_start <= line_end);
    assert_4tech(line_end < new_line_count - line_shift);
    
    ++line_end;
    
    // Shift
    i32 line_count = new_line_count;
    i32 new_line_end = line_end;
    if (line_shift != 0){
        line_count -= line_shift;
        new_line_end += line_shift;
        
        memmove_4tech(wraps + line_end + line_shift, wraps + line_end,
                      sizeof(i32)*(line_count - line_end + 1));
    }
    
    // Iteration data (yikes! Need better loop system)
    Buffer_Stream_Type stream = {0};
    i32 size = buffer_size(buffer);
    i32 char_i = buffer->line_starts[line_start];
    i32 line_i = line_start;
    
    // Line wrap measurement
    f32 last_wrap = wraps[line_i];
    f32 current_wrap = last_wrap;
    f32 x = 0.f;
    
    if (buffer_stringify_loop(&stream, buffer, char_i, size)){
        b32 still_looping = 0;
        do{
            for (; char_i < stream.end; ++char_i){
                u8 ch = (u8)stream.data[char_i];
                
                if (ch == '\n'){
                    wraps[line_i] = last_wrap;
                    ++line_i;
                    current_wrap += font_height;
                    last_wrap = current_wrap;
                    x = 0.f;
                    
                    // TODO(allen): I would like to know that I am not guessing here.
                    if (line_i >= new_line_end){
                        goto buffer_remeasure_wraps_end;
                    }
                }
                else{
                    f32 current_adv = adv[ch];
                    if (x + current_adv > max_width){
                        current_wrap += font_height;
                        x = current_adv;
                    }
                    else{
                        x += current_adv;
                    }
                }
            }
            still_looping = buffer_stringify_next(&stream);
        }while(still_looping);
    }
    
    wraps[line_i++] = last_wrap;
    
    buffer_remeasure_wraps_end:;
    
    // Adjust
    if (line_i <= new_line_end){
        f32 y_shift = current_wrap - wraps[line_i];
        
        if (y_shift != 0){
            wraps += line_i;
            for (; line_i <= new_line_count; ++line_i, ++wraps){
                *wraps += y_shift;
            }
        }
    }
}

internal_4tech i32
buffer_get_line_index_range(Buffer_Type *buffer, i32 pos, i32 l_bound, i32 u_bound){
    i32 *lines = buffer->line_starts;
    i32 start = l_bound, end = u_bound;
    i32 i = 0;
    
    assert_4tech(0 <= l_bound);
    assert_4tech(l_bound <= u_bound);
    assert_4tech(u_bound <= buffer->line_count);
    
    assert_4tech(lines != 0);
    
    for (;;){
        i = (start + end) >> 1;
        if (lines[i] < pos){
            start = i;
        }
        else if (lines[i] > pos){
            end = i;
        }
        else{
            break;
        }
        assert_4tech(start < end);
        if (start == end - 1){
            i = start;
            break;
        }
    }
    
    return(i);
}

inline_4tech i32
buffer_get_line_index(Buffer_Type *buffer, i32 pos){
    i32 result = buffer_get_line_index_range(buffer, pos, 0, buffer->line_count);
    return(result);
}

// TODO(allen): Merge all these binary searches.
internal_4tech i32
buffer_get_line_index_from_character_pos(i32 *character_starts, i32 pos, i32 l_bound, i32 u_bound){
    i32 start = l_bound, end = u_bound;
    i32 i = 0;
    
    for (;;){
        i = (start + end) >> 1;
        if (character_starts[i] < pos){
            start = i;
        }
        else if (character_starts[i] > pos){
            end = i;
        }
        else{
            break;
        }
        assert_4tech(start < end);
        if (start == end - 1){
            i = start;
            break;
        }
    }
    
    return(i);
}

internal_4tech i32
buffer_get_line_index_from_wrapped_y(i32 *wrap_line_index, f32 y, f32 line_height, i32 l_bound, i32 u_bound){
    i32 wrap_index = FLOOR32(y/line_height);
    i32 start = l_bound, end = u_bound;
    i32 i = 0;
    for (;;){
        i = (start + end) >> 1;
        if (wrap_line_index[i] < wrap_index){
            start = i;
        }
        else if (wrap_line_index[i] > wrap_index){
            end = i;
        }
        else{
            break;
        }
        assert_4tech(start < end);
        if (start == end - 1){
            i = start;
            break;
        }
    }
    return(i);
}

internal_4tech Partial_Cursor
buffer_partial_from_pos(Buffer_Type *buffer, i32 pos){
    Partial_Cursor result = {0};
    
    int32_t size = buffer_size(buffer);
    if (pos > size){
        pos = size;
    }
    if (pos < 0){
        pos = 0;
    }
    
    i32 line_index = buffer_get_line_index_range(buffer, pos, 0, buffer->line_count);
    result.pos = pos;
    result.line = line_index+1;
    result.character = pos - buffer->line_starts[line_index] + 1;
    
    return(result);
}

internal_4tech Partial_Cursor
buffer_partial_from_line_character(Buffer_Type *buffer, i32 line, i32 character){
    Partial_Cursor result = {0};
    
    i32 line_index = line - 1;
    if (line_index >= buffer->line_count) line_index = buffer->line_count - 1;
    if (line_index < 0) line_index = 0;
    
    int32_t size = buffer_size(buffer);
    i32 this_start = buffer->line_starts[line_index];
    i32 max_character = (size-this_start) + 1;
    if (line_index+1 < buffer->line_count){
        i32 next_start = buffer->line_starts[line_index+1];
        max_character = (next_start-this_start);
    }
    
    if (character <= 0){
        character = 1;
    }
    if (character > max_character){
        character = max_character;
    }
    
    result.pos = this_start + character - 1;
    result.line = line_index+1;
    result.character = character;
    
    return(result);
}

struct Buffer_Cursor_Seek_Params{
    Buffer_Type *buffer;
    Buffer_Seek seek;
    f32 width;
    f32 font_height;
    f32 *adv;
    i32 *wrap_line_index;
    i32 *character_starts;
    b32 virtual_white;
    b32 return_hint;
    Full_Cursor *cursor_out;
};

struct Buffer_Cursor_Seek_State{
    Full_Cursor next_cursor;
    Full_Cursor this_cursor;
    Full_Cursor prev_cursor;
    
    Buffer_Stream_Type stream;
    b32 still_looping;
    i32 i;
    i32 size;
    i32 wrap_unit_end;
    
    b32 first_of_the_line;
    b32 xy_seek;
    f32 ch_width;
    u8 ch;
    
    i32 __pc__;
};

// dialogical-routine defines
#define DrCase(PC) case PC: goto resumespot_##PC
#define DrYield(PC, n) { *S_ptr = S; S_ptr->__pc__ = PC; return(n); resumespot_##PC:; }
#define DrReturn(n) { *S_ptr = S; S_ptr->__pc__ = -1; return(n); }

internal_4tech Buffer_Layout_Stop
buffer_cursor_seek(Buffer_Cursor_Seek_State *S_ptr, Buffer_Cursor_Seek_Params params,
                   f32 line_shift, b32 do_wrap, i32 wrap_unit_end){
    Buffer_Cursor_Seek_State S = *S_ptr;
    Buffer_Layout_Stop S_stop;
    
    switch (S.__pc__){
        DrCase(1);
        DrCase(2);
        DrCase(3);
        DrCase(4);
    }
    
    S.xy_seek = (params.seek.type == buffer_seek_wrapped_xy || params.seek.type == buffer_seek_unwrapped_xy);
    S.size = buffer_size(params.buffer);
    
    // Get cursor hint
    i32 line_index = 0;
    switch (params.seek.type){
        case buffer_seek_pos:
        {
            if (params.seek.pos > S.size){
                params.seek.pos = S.size;
            }
            if (params.seek.pos < 0){
                params.seek.pos = 0;
            }
            
            line_index = buffer_get_line_index(params.buffer, params.seek.pos);
        }break;
        
        case buffer_seek_character_pos:
        {
            i32 line_count = params.buffer->line_count;
            i32 max_character = params.character_starts[line_count] - 1;
            if (params.seek.pos > max_character){
                params.seek.pos = max_character;
            }
            if (params.seek.pos < 0){
                params.seek.pos = 0;
            }
            
            line_index = buffer_get_line_index_from_character_pos(params.character_starts, params.seek.pos,
                                                                  0, params.buffer->line_count);
        }break;
        
        case buffer_seek_line_char:
        {
            line_index = params.seek.line - 1;
            if (line_index >= params.buffer->line_count){
                line_index = params.buffer->line_count - 1;
            }
            if (line_index < 0){
                line_index = 0;
            }
        }break;
        
        case buffer_seek_unwrapped_xy:
        {
            line_index = (i32)(params.seek.y / params.font_height);
            if (line_index >= params.buffer->line_count){
                line_index = params.buffer->line_count - 1;
            }
            if (line_index < 0){
                line_index = 0;
            }
        }break;
        
        case buffer_seek_wrapped_xy:
        {
            line_index = buffer_get_line_index_from_wrapped_y(params.wrap_line_index, params.seek.y, params.font_height,
                                                              0, params.buffer->line_count);
        }break;
        
        default: InvalidCodePath;
    }
    
    // Build the cursor hint
    {
        S.next_cursor.pos = params.buffer->line_starts[line_index];
        S.next_cursor.character_pos = params.character_starts[line_index];
        S.next_cursor.line = line_index + 1;
        S.next_cursor.character = 1;
        S.next_cursor.wrap_line = params.wrap_line_index[line_index] + 1;
        S.next_cursor.unwrapped_y = (f32)(line_index * params.font_height);
        S.next_cursor.unwrapped_x = 0;
        S.next_cursor.wrapped_y = (f32)(params.wrap_line_index[line_index] * params.font_height);
        S.next_cursor.wrapped_x = 0;
    }
    
    // Get the initial line shift.
    // Adjust the non-screen based coordinates to point to the first
    // non-virtual character of the line.
    if (params.virtual_white){
        S_stop.status          = BLStatus_NeedLineShift;
        S_stop.line_index      = S.next_cursor.line-1;
        S_stop.wrap_line_index = S.next_cursor.wrap_line-1;
        DrYield(1, S_stop);
        
        S.next_cursor.unwrapped_x += line_shift;
        S.next_cursor.wrapped_x += line_shift;
        
        S.stream.use_termination_character = 1;
        S.stream.terminator = '\n';
        if (buffer_stringify_loop(&S.stream, params.buffer, S.next_cursor.pos, S.size)){
            do{
                for (; S.next_cursor.pos < S.stream.end; ++S.next_cursor.pos){
                    S.ch = (u8)S.stream.data[S.next_cursor.pos];
                    
                    if (S.ch != ' ' && S.ch != '\t'){
                        goto double_break_vwhite;
                    }
                    else{
                        ++S.next_cursor.character;
                    }
                }
                S.still_looping = buffer_stringify_next(&S.stream);
            }while(S.still_looping);
        }
        InvalidCodePath;
        double_break_vwhite:;
    }
    
    // If the user just wants the hint, return that now.
    if (params.return_hint){
        *params.cursor_out = S.next_cursor;
        S_stop.status = BLStatus_Finished;
        DrReturn(S_stop);
    }
    
    // If we are already passed the point we want to be at, then just take this.
    S.this_cursor = S.next_cursor;
    switch (params.seek.type){
        case buffer_seek_pos:
        {
            if (S.next_cursor.pos >= params.seek.pos){
                goto buffer_cursor_seek_end;
            }
        }break;
        
        case buffer_seek_character_pos:
        {
            if (S.next_cursor.character_pos >= params.seek.pos){
                goto buffer_cursor_seek_end;
            }
        }break;
        
        case buffer_seek_line_char:
        {
            if ((S.next_cursor.line == params.seek.line &&
                 S.next_cursor.character >= params.seek.character) ||
                S.next_cursor.line > params.seek.line){
                goto buffer_cursor_seek_end;
            }
        }break;
        
        case buffer_seek_unwrapped_xy:
        {
            if (S.next_cursor.unwrapped_y > params.seek.y){
                goto buffer_cursor_seek_end;
            }
        }break;
        
        case buffer_seek_wrapped_xy:
        {
            if (S.next_cursor.wrapped_y > params.seek.y){
                goto buffer_cursor_seek_end;
            }
        }break;
    }
    
    // Main seek loop
    S.i = S.next_cursor.pos;
    
    S.stream = null_buffer_stream;
    S.stream.use_termination_character = 1;
    S.stream.terminator = 0;
    
    S.first_of_the_line = 1;
    if (buffer_stringify_loop(&S.stream, params.buffer, S.i, S.size)){
        S.still_looping = 0;
        do{
            for (; S.i < S.stream.end; ++S.i){
                S.ch = (u8)S.stream.data[S.i];
                
                S.prev_cursor = S.this_cursor;
                S.this_cursor = S.next_cursor;
                
                switch (S.ch){
                    case '\n':
                    {
                        ++S.next_cursor.character_pos;
                        ++S.next_cursor.line;
                        ++S.next_cursor.wrap_line;
                        S.next_cursor.unwrapped_y += params.font_height;
                        S.next_cursor.wrapped_y += params.font_height;
                        S.next_cursor.character = 1;
                        S.next_cursor.unwrapped_x = 0;
                        
                        if (params.virtual_white){
                            S_stop.status          = BLStatus_NeedLineShift;
                            S_stop.line_index      = S.next_cursor.line-1;
                            S_stop.wrap_line_index = S.next_cursor.wrap_line-1;
                            DrYield(2, S_stop);
                        }
                        
                        S.next_cursor.wrapped_x = line_shift;
                        S.first_of_the_line = 1;
                    }break;
                    
                    default:
                    {
                        S.ch_width = params.adv[S.ch];
                        
                        if (S.i >= S.wrap_unit_end){
                            S_stop.status          = BLStatus_NeedWrapDetermination;
                            S_stop.line_index      = S.next_cursor.line-1;
                            S_stop.wrap_line_index = S.next_cursor.wrap_line-1;
                            S_stop.pos             = S.i;
                            S_stop.x               = S.next_cursor.wrapped_x;
                            DrYield(4, S_stop);
                            
                            S.wrap_unit_end = wrap_unit_end;
                            
                            if (do_wrap && !S.first_of_the_line){
                                S.next_cursor.wrapped_y += params.font_height;
                                
                                ++S.next_cursor.wrap_line;
                                if (params.virtual_white){
                                    S_stop.status          = BLStatus_NeedWrapLineShift;
                                    S_stop.line_index      = S.next_cursor.line-1;
                                    S_stop.wrap_line_index = S.next_cursor.wrap_line-1;
                                    DrYield(3, S_stop);
                                }
                                
                                S.next_cursor.wrapped_x = line_shift;
                                S.this_cursor = S.next_cursor;
                            }
                        }
                        
                        ++S.next_cursor.character_pos;
                        ++S.next_cursor.character;
                        S.next_cursor.unwrapped_x += S.ch_width;
                        S.next_cursor.wrapped_x += S.ch_width;
                        
                        S.first_of_the_line = 0;
                    }break;
                }
                
                ++S.next_cursor.pos;
                
                if (S.next_cursor.pos > S.size){
                    goto buffer_cursor_seek_end;
                }
                
                f32 x = 0, px = 0, y = 0, py = 0;
                switch (params.seek.type){
                    case buffer_seek_pos:
                    if (S.this_cursor.pos >= params.seek.pos){
                        goto buffer_cursor_seek_end;
                    }break;
                    
                    case buffer_seek_character_pos:
                    if (S.this_cursor.character_pos >= params.seek.pos){
                        goto buffer_cursor_seek_end;
                    }break;
                    
                    case buffer_seek_wrapped_xy:
                    {
                        x = S.this_cursor.wrapped_x;
                        px = S.prev_cursor.wrapped_x;
                        y = S.this_cursor.wrapped_y;
                        py = S.prev_cursor.wrapped_y;
                    }break;
                    
                    case buffer_seek_unwrapped_xy:
                    {
                        x = S.this_cursor.unwrapped_x;
                        px = S.prev_cursor.unwrapped_x;
                        y = S.this_cursor.unwrapped_y;
                        py = S.prev_cursor.wrapped_y;
                    }break;
                    
                    case buffer_seek_line_char:
                    if (S.this_cursor.line == params.seek.line && S.this_cursor.character >= params.seek.character){
                        goto buffer_cursor_seek_end;
                    }
                    else if (S.this_cursor.line > params.seek.line){
                        S.this_cursor = S.prev_cursor;
                        goto buffer_cursor_seek_end;
                    }break;
                }
                
                if (S.xy_seek){
                    if (y > params.seek.y){
                        S.this_cursor = S.prev_cursor;
                        goto buffer_cursor_seek_end;
                    }
                    
                    if (y > params.seek.y - params.font_height && x >= params.seek.x){
                        if (!params.seek.round_down){
                            if (py >= y && S.ch != '\n' && (params.seek.x - px) < (x - params.seek.x)){
                                S.this_cursor = S.prev_cursor;
                            }
                            goto buffer_cursor_seek_end;
                        }
                        
                        S.this_cursor = S.prev_cursor;
                        goto buffer_cursor_seek_end;
                    }
                }
            }
            S.still_looping = buffer_stringify_next(&S.stream);
        }while(S.still_looping);
    }
    
    InvalidCodePath;
    
    buffer_cursor_seek_end:;
    *params.cursor_out = S.this_cursor;
    S_stop.status = BLStatus_Finished;
    DrReturn(S_stop);
}

#undef DrCase
#undef DrYield
#undef DrReturn

internal_4tech void
buffer_invert_edit_shift(Buffer_Type *buffer, Buffer_Edit edit, Buffer_Edit *inverse, char *strings,
                         i32 *str_pos, i32 max, i32 shift_amount){
    i32 pos = *str_pos;
    i32 len = edit.end - edit.start;
    assert_4tech(pos >= 0);
    assert_4tech(pos + len <= max);
    *str_pos = pos + len;
    
    inverse->str_start = pos;
    inverse->len = len;
    inverse->start = edit.start + shift_amount;
    inverse->end = edit.start + edit.len + shift_amount;
    buffer_stringify(buffer, edit.start, edit.end, strings + pos);
}

inline_4tech void
buffer_invert_edit(Buffer_Type *buffer, Buffer_Edit edit, Buffer_Edit *inverse, char *strings,
                   i32 *str_pos, i32 max){
    buffer_invert_edit_shift(buffer, edit, inverse, strings, str_pos, max, 0);
}

typedef struct Buffer_Invert_Batch{
    i32 i;
    i32 shift_amount;
    i32 len;
} Buffer_Invert_Batch;

internal_4tech i32
buffer_invert_batch(Buffer_Invert_Batch *state, Buffer_Type *buffer, Buffer_Edit *edits, i32 count,
                    Buffer_Edit *inverse, char *strings, i32 *str_pos, i32 max){
    i32 shift_amount = state->shift_amount;
    i32 i = state->i;
    Buffer_Edit *edit = edits + i;
    Buffer_Edit *inv_edit = inverse + i;
    i32 result = 0;
    
    for (; i < count; ++i, ++edit, ++inv_edit){
        if (*str_pos + edit->end - edit->start <= max){
            buffer_invert_edit_shift(buffer, *edit, inv_edit, strings, str_pos, max, shift_amount);
            shift_amount += (edit->len - (edit->end - edit->start));
        }
        else{
            result = 1;
            state->len = edit->end - edit->start;
        }
    }
    
    state->i = i;
    state->shift_amount = shift_amount;
    
    return(result);
}

enum Buffer_Render_Flag{
    BRFlag_Special_Character = (1 << 0)
};

typedef struct Buffer_Render_Item{
    i32 index;
    u16 glyphid;
    u16 flags;
    f32 x0, y0;
    f32 x1, y1;
} Buffer_Render_Item;

typedef struct Render_Item_Write{
    Buffer_Render_Item *item;
    f32 x, y;
    f32 *adv;
    f32 font_height;
    f32 x_min;
    f32 x_max;
} Render_Item_Write;

inline_4tech Render_Item_Write
write_render_item(Render_Item_Write write, i32 index, u16 glyphid, u16 flags){
    f32 ch_width = write.adv[(u8)glyphid];
    
    if (write.x <= write.x_max && write.x + ch_width >= write.x_min){
        write.item->index = index;
        write.item->glyphid = glyphid;
        write.item->flags = flags;
        write.item->x0 = write.x;
        write.item->y0 = write.y;
        write.item->x1 = write.x + ch_width;
        write.item->y1 = write.y + write.font_height;
        
        ++write.item;
    }
    
    write.x += ch_width;
    
    return(write);
}

// TODO(allen): Reduce the number of parameters.
struct Buffer_Render_Params{
    Buffer_Type *buffer;
    Buffer_Render_Item *items;
    i32 max;
    i32 *count;
    f32 port_x;
    f32 port_y;
    f32 clip_w;
    f32 scroll_x;
    f32 scroll_y;
    f32 width;
    f32 height;
    Full_Cursor start_cursor;
    i32 wrapped;
    f32 font_height;
    f32 *adv;
    b32 virtual_white;
};

struct Buffer_Render_State{
    Buffer_Stream_Type stream;
    b32 still_looping;
    i32 i;
    
    f32 ch_width;
    u8 ch;
    
    Render_Item_Write write;
    
    i32 line;
    i32 wrap_line;
    b32 skipping_whitespace;
    b32 first_of_the_line;
    i32 wrap_unit_end;
    
    i32 __pc__;
};

// duff-routine defines
#define DrCase(PC) case PC: goto resumespot_##PC
#define DrYield(PC, n) { *S_ptr = S; S_ptr->__pc__ = PC; return(n); resumespot_##PC:; }
#define DrReturn(n) { *S_ptr = S; S_ptr->__pc__ = -1; return(n); }

internal_4tech Buffer_Layout_Stop
buffer_render_data(Buffer_Render_State *S_ptr, Buffer_Render_Params params,
                   f32 line_shift, b32 do_wrap, i32 wrap_unit_end){
    Buffer_Render_State S = *S_ptr;
    Buffer_Layout_Stop S_stop;
    
    i32 size = buffer_size(params.buffer);
    f32 shift_x = params.port_x - params.scroll_x;
    f32 shift_y = params.port_y - params.scroll_y;
    
    if (params.wrapped){
        shift_y += params.start_cursor.wrapped_y;
    }
    else{
        shift_y += params.start_cursor.unwrapped_y;
    }
    
    Buffer_Render_Item *item_end = params.items + params.max;
    
    switch (S.__pc__){
        DrCase(1);
        DrCase(2);
        DrCase(3);
        DrCase(4);
    }
    
    S.line = params.start_cursor.line - 1;
    S.wrap_line = params.start_cursor.wrap_line - 1;
    
    if (params.virtual_white){
        S_stop.status          = BLStatus_NeedLineShift;
        S_stop.line_index      = S.line;
        S_stop.wrap_line_index = S.wrap_line;
        DrYield(1, S_stop);
    }
    
    S.write.item        = params.items;
    S.write.x           = shift_x + line_shift;
    S.write.y           = shift_y;
    S.write.adv         = params.adv;
    S.write.font_height = params.font_height;
    S.write.x_min       = params.port_x;
    S.write.x_max       = params.port_x + params.clip_w;
    
    if (params.virtual_white){
        S.skipping_whitespace = 1;
    }
    
    S.first_of_the_line = 1;
    S.i = params.start_cursor.pos;
    if (buffer_stringify_loop(&S.stream, params.buffer, S.i, size)){
        do{
            for (; S.i < S.stream.end; ++S.i){
                S.ch = (u8)S.stream.data[S.i];
                S.ch_width = params.adv[S.ch];
                
                if (S.ch != '\n' && S.i >= S.wrap_unit_end){
                    S_stop.status          = BLStatus_NeedWrapDetermination;
                    S_stop.line_index      = S.line;
                    S_stop.wrap_line_index = S.wrap_line;
                    S_stop.pos             = S.i;
                    S_stop.x               = S.write.x - shift_x;
                    DrYield(4, S_stop);
                    
                    S.wrap_unit_end = wrap_unit_end;
                    
                    if (do_wrap && !S.first_of_the_line){
                        if (params.virtual_white){
                            S_stop.status          = BLStatus_NeedWrapLineShift;
                            S_stop.line_index      = S.line;
                            S_stop.wrap_line_index = S.wrap_line + 1;
                            DrYield(2, S_stop);
                        }
                        
                        ++S.wrap_line;
                        
                        if (params.wrapped){
                            S.write.x = shift_x + line_shift;
                            S.write.y += params.font_height;
                        }
                    }
                }
                
                if (S.write.y > params.height + params.port_y){
                    goto buffer_get_render_data_end;
                }
                
                if (S.ch != ' ' && S.ch != '\t'){
                    S.skipping_whitespace = 0;
                }
                
                if (!S.skipping_whitespace){
                    S.first_of_the_line = 0;
                    switch (S.ch){
                        case '\n':
                        if (S.write.item < item_end){
                            S.write = write_render_item(S.write, S.i, ' ', 0);
                            
                            if (params.virtual_white){
                                S_stop.status          = BLStatus_NeedLineShift;
                                S_stop.line_index      = S.line+1;
                                S_stop.wrap_line_index = S.wrap_line+1;
                                DrYield(3, S_stop);
                                
                                S.skipping_whitespace = 1;
                            }
                            
                            ++S.line;
                            ++S.wrap_line;
                            
                            S.write.x = shift_x + line_shift;
                            S.write.y += params.font_height;
                            
                            S.first_of_the_line = 1;
                        }
                        break;
                        
                        case '\r':
                        if (S.write.item < item_end){
                            S.write = write_render_item(S.write, S.i, '\\', BRFlag_Special_Character);
                            
                            if (S.write.item < item_end){
                                S.write = write_render_item(S.write, S.i, 'r', BRFlag_Special_Character);
                            }
                        }
                        break;
                        
                        case '\t':
                        if (S.write.item < item_end){
                            f32 new_x = S.write.x + S.ch_width;
                            S.write = write_render_item(S.write, S.i, ' ', 0);
                            S.write.x = new_x;
                        }
                        break;
                        
                        default:
                        if (S.write.item < item_end){
                            if (S.ch >= ' ' && S.ch <= '~'){
                                S.write = write_render_item(S.write, S.i, S.ch, 0);
                            }
                            else{
                                S.write = write_render_item(S.write, S.i, '\\', BRFlag_Special_Character);
                                
                                u8 ch = S.ch;
                                char C = '0' + (ch / 0x10);
                                if ((ch / 0x10) > 0x9){
                                    C = ('A' - 0xA) + (ch / 0x10);
                                }
                                
                                if (S.write.item < item_end){
                                    S.write = write_render_item(S.write, S.i, C, BRFlag_Special_Character);
                                }
                                
                                ch = (ch % 0x10);
                                C = '0' + ch;
                                if (ch > 0x9){
                                    C = ('A' - 0xA) + ch;
                                }
                                
                                if (S.write.item < item_end){
                                    S.write = write_render_item(S.write, S.i, C, BRFlag_Special_Character);
                                }
                            }
                        }
                        break;
                    }
                }
            }
            S.still_looping = buffer_stringify_next(&S.stream);
        }while(S.still_looping);
    }
    
    buffer_get_render_data_end:;
    if (S.write.y <= params.height + shift_y || S.write.item == params.items){
        if (S.write.item < item_end){
            S.write = write_render_item(S.write, size, ' ', 0);
        }
    }
    
    *params.count = (i32)(S.write.item - params.items);
    assert_4tech(*params.count <= params.max);
    
    S_stop.status = BLStatus_Finished;
    DrReturn(S_stop);
}

#undef DrYield
#undef DrReturn
#undef DrCase

// BOTTOM