/*
 * Mr. 4th Dimention - Allen Webster
 *
 * 25.03.2018
 *
 * High level edit procedures
 *
 */

// TOP

internal void
edit_pre_state_change(System_Functions *system, Heap *heap, Models *models, Editing_File *file){
    if (file->state.still_lexing){
        system->cancel_job(BACKGROUND_THREADS, file->state.lex_job);
        if (file->state.swap_array.tokens){
            heap_free(heap, file->state.swap_array.tokens);
            file->state.swap_array.tokens = 0;
        }
        file->state.still_lexing = 0;
    }
    file_add_dirty_flag(file, DirtyState_UnsavedChanges);
    file_unmark_edit_finished(file);
    Layout *layout = &models->layout;
    for (Panel *panel = layout_get_first_open_panel(layout);
         panel != 0;
         panel = layout_get_next_open_panel(layout, panel)){
        View *view = panel->view;
        if (view->file_data.file == file){
            Full_Cursor render_cursor = view_get_render_cursor(system, view);
            Full_Cursor target_cursor = view_get_render_cursor_target(system, view);
            view->temp_view_top_left_pos        = render_cursor.pos;
            view->temp_view_top_left_target_pos = target_cursor.pos;
        }
    }
}

internal void
edit_fix_markers__write_workspace_markers(Dynamic_Workspace *workspace, Buffer_ID buffer_id,
                                          Cursor_With_Index *cursors, Cursor_With_Index *r_cursors, i32 *cursor_count, i32 *r_cursor_count){
    for (Managed_Buffer_Markers_Header *node = workspace->buffer_markers_list.first;
         node != 0;
         node = node->next){
        if (node->buffer_id != buffer_id) continue;
        Marker *markers = (Marker*)(node + 1);
        Assert(sizeof(*markers) == node->std_header.item_size);
        i32 count = node->std_header.count;
        for (i32 i = 0; i < count; i += 1){
            if (markers[i].lean_right){
                write_cursor_with_index(r_cursors, r_cursor_count, markers[i].pos);
            }
            else{
                write_cursor_with_index(cursors  , cursor_count  , markers[i].pos);
            }
        }
    }
}

internal void
edit_fix_markers__read_workspace_markers(Dynamic_Workspace *workspace, Buffer_ID buffer_id,
                                         Cursor_With_Index *cursors, Cursor_With_Index *r_cursors, i32 *cursor_count, i32 *r_cursor_count){
    for (Managed_Buffer_Markers_Header *node = workspace->buffer_markers_list.first;
         node != 0;
         node = node->next){
        if (node->buffer_id != buffer_id) continue;
        Marker *markers = (Marker*)(node + 1);
        Assert(sizeof(*markers) == node->std_header.item_size);
        i32 count = node->std_header.count;
        for (i32 i = 0; i < count; i += 1){
            if (markers[i].lean_right){
                markers[i].pos = r_cursors[(*r_cursor_count)++].pos;
            }
            else{
                markers[i].pos = cursors[(*cursor_count)++].pos;
            }
        }
    }
}

internal f32
edit_fix_markers__compute_scroll_y(i32 line_height, f32 old_y_val, f32 new_y_val_aligned){
    f32 y_offset = MOD(old_y_val, line_height);
    f32 y_position = new_y_val_aligned + y_offset;
    return(y_position);
}

internal i32
edit_fix_markers__compute_scroll_y(i32 line_height, i32 old_y_val, f32 new_y_val_aligned){
    return((i32)edit_fix_markers__compute_scroll_y(line_height, (f32)old_y_val, new_y_val_aligned));
}

internal void
edit_fix_markers(System_Functions *system, Models *models, Editing_File *file, Edit_Array edits){
    Assert(edits.count > 0);
    
    Partition *part = &models->mem.part;
    Layout *layout = &models->layout;
    
    Temp_Memory cursor_temp = begin_temp_memory(part);
    
    Lifetime_Object *file_lifetime_object = file->lifetime_object;
    Buffer_ID file_id = file->id.id;
    Assert(file_lifetime_object != 0);
    
    i32 cursor_max = layout_get_open_panel_count(layout)*4;
    i32 total_marker_count = 0;
    {
        total_marker_count += file_lifetime_object->workspace.total_marker_count;
        
        i32 key_count = file_lifetime_object->key_count;
        i32 key_index = 0;
        for (Lifetime_Key_Ref_Node *key_node = file_lifetime_object->key_node_first;
             key_node != 0;
             key_node = key_node->next){
            i32 count = clamp_top(lifetime_key_reference_per_node, key_count - key_index);
            for (i32 i = 0; i < count; i += 1){
                Lifetime_Key *key = key_node->keys[i];
                total_marker_count += key->dynamic_workspace.total_marker_count;
            }
            key_index += count;
        }
    }
    
    cursor_max += total_marker_count;
    Cursor_With_Index *cursors = push_array(part, Cursor_With_Index, cursor_max);
    Cursor_With_Index *r_cursors = push_array(part, Cursor_With_Index, cursor_max);
    i32 cursor_count = 0;
    i32 r_cursor_count = 0;
    Assert(cursors != 0);
    Assert(r_cursors != 0);
    
    for (Panel *panel = layout_get_first_open_panel(layout);
         panel != 0;
         panel = layout_get_next_open_panel(layout, panel)){
        View *view = panel->view;
        if (view->file_data.file == file){
            File_Edit_Positions edit_pos = view_get_edit_pos(view);
            write_cursor_with_index(cursors, &cursor_count, edit_pos.cursor_pos);
            write_cursor_with_index(cursors, &cursor_count, view->mark);
            write_cursor_with_index(cursors, &cursor_count, view->temp_view_top_left_pos);
            write_cursor_with_index(cursors, &cursor_count, view->temp_view_top_left_target_pos);
        }
    }
    
    edit_fix_markers__write_workspace_markers(&file_lifetime_object->workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_count);
    
    {
        i32 key_count = file_lifetime_object->key_count;
        i32 key_index = 0;
        for (Lifetime_Key_Ref_Node *key_node = file_lifetime_object->key_node_first;
             key_node != 0;
             key_node = key_node->next){
            i32 count = clamp_top(lifetime_key_reference_per_node, key_count - key_index);
            for (i32 i = 0; i < count; i += 1){
                Lifetime_Key *key = key_node->keys[i];
                edit_fix_markers__write_workspace_markers(&key->dynamic_workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_count);
            }
            key_index += count;
        }
    }
    
    if (cursor_count > 0 || r_cursor_count > 0){
        buffer_sort_cursors(  cursors,   cursor_count);
        buffer_sort_cursors(r_cursors, r_cursor_count);
        if (edits.count > 1){
            buffer_batch_edit_update_cursors(  cursors,   cursor_count, edits, false);
            buffer_batch_edit_update_cursors(r_cursors, r_cursor_count, edits, true);
        }
        else{
            Edit edit = edits.vals[0];
            buffer_update_cursors(  cursors,   cursor_count, edit.range.first, edit.range.one_past_last, edit.length, false);
            buffer_update_cursors(r_cursors, r_cursor_count, edit.range.first, edit.range.one_past_last, edit.length, true);
        }
        buffer_unsort_cursors(  cursors,   cursor_count);
        buffer_unsort_cursors(r_cursors, r_cursor_count);
        
        cursor_count = 0;
        r_cursor_count = 0;
        for (Panel *panel = layout_get_first_open_panel(layout);
             panel != 0;
             panel = layout_get_next_open_panel(layout, panel)){
            View *view = panel->view;
            if (view->file_data.file == file){
                i32 cursor_pos = cursors[cursor_count++].pos;
                Full_Cursor new_cursor = file_compute_cursor(system, file, seek_pos(cursor_pos));
                
                File_Edit_Positions edit_pos = view_get_edit_pos(view);
                GUI_Scroll_Vars scroll = edit_pos.scroll;
                
                view->mark = cursors[cursor_count++].pos;
                i32 line_height = view->line_height;
                i32 top_left_pos = cursors[cursor_count++].pos;
                i32 top_left_target_pos = cursors[cursor_count++].pos;
                f32 new_y_val_aligned = 0;
                if (view->temp_view_top_left_pos != top_left_pos){
                    Full_Cursor new_position_cursor = file_compute_cursor(system, file, seek_pos(top_left_pos));
                    if (file->settings.unwrapped_lines){
                        new_y_val_aligned = new_position_cursor.unwrapped_y;
                    }
                    else{
                        new_y_val_aligned = new_position_cursor.wrapped_y;
                    }
                    scroll.scroll_y = edit_fix_markers__compute_scroll_y(line_height, scroll.scroll_y, new_y_val_aligned);
                }
                if (view->temp_view_top_left_target_pos != top_left_target_pos){
                    if (top_left_target_pos != top_left_pos){
                        Full_Cursor new_position_cursor = file_compute_cursor(system, file, seek_pos(top_left_target_pos));
                        if (file->settings.unwrapped_lines){
                            new_y_val_aligned = new_position_cursor.unwrapped_y;
                        }
                        else{
                            new_y_val_aligned = new_position_cursor.wrapped_y;
                        }
                    }
                    scroll.target_y = edit_fix_markers__compute_scroll_y(line_height, scroll.target_y, new_y_val_aligned);
                }
                
                view_set_cursor_and_scroll(view, new_cursor, true, scroll);
            }
        }
        
        edit_fix_markers__read_workspace_markers(&file_lifetime_object->workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_count);
        
        i32 key_count = file_lifetime_object->key_count;
        i32 key_index = 0;
        for (Lifetime_Key_Ref_Node *key_node = file_lifetime_object->key_node_first;
             key_node != 0;
             key_node = key_node->next){
            i32 count = clamp_top(lifetime_key_reference_per_node, key_count - key_index);
            for (i32 i = 0; i < count; i += 1){
                Lifetime_Key *key = key_node->keys[i];
                edit_fix_markers__read_workspace_markers(&key->dynamic_workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_count);
            }
            key_index += count;
        }
    }
    
    end_temp_memory(cursor_temp);
}

internal void
edit_fix_markers(System_Functions *system, Models *models, Editing_File *file, Edit edit){
    Edit_Array edits = {};
    edits.vals = &edit;
    edits.count = 1;
    edit_fix_markers(system, models, file, edits);
}

internal void
edit_single(System_Functions *system, Models *models, Editing_File *file, Edit edit, Edit_Behaviors behaviors){
    Mem_Options *mem = &models->mem;
    Heap *heap = &mem->heap;
    Partition *part = &mem->part;
    
    Gap_Buffer *buffer = &file->state.buffer;
    Assert(0 <= edit.range.first);
    Assert(edit.range.first <= edit.range.one_past_last);
    Assert(edit.range.one_past_last <= buffer_size(buffer));
    
    // NOTE(allen): history update
    if (!behaviors.do_not_post_to_history){
        history_dump_records_after_index(&file->state.history, file->state.current_record_index);
        history_record_edit(heap, &models->global_history, &file->state.history, buffer, edit);
        file->state.current_record_index = history_get_record_count(&file->state.history);
    }
    
    // NOTE(allen): fixing stuff beforewards????
    edit_pre_state_change(system, &mem->heap, models, file);
    
    // NOTE(allen): expand spec, compute shift
    i32 shift_amount = buffer_replace_range_compute_shift(edit.range.first, edit.range.one_past_last, edit.length);
    
    // NOTE(allen): actual text replacement
    i32 scratch_size = part_remaining(part);
    Assert(scratch_size > 0);
    i32 request_amount = 0;
    for (;buffer_replace_range(buffer, edit.range.first, edit.range.one_past_last, edit.str, edit.length,
                               shift_amount, part->base + part->pos, scratch_size, &request_amount);){
        void *new_data = 0;
        if (request_amount > 0){
            new_data = heap_allocate(heap, request_amount);
        }
        void *old_data = buffer_edit_provide_memory(buffer, new_data, request_amount);
        if (old_data){
            heap_free(heap, old_data);
        }
    }
    
    // NOTE(allen): line meta data
    i32 line_start = buffer_get_line_number(buffer, edit.range.first);
    i32 line_end = buffer_get_line_number(buffer, edit.range.one_past_last);
    i32 replaced_line_count = line_end - line_start;
    i32 new_line_count = buffer_count_newlines(buffer, edit.range.first, edit.range.first + edit.length);
    i32 line_shift =  new_line_count - replaced_line_count;
    
    Font_Pointers font = system->font.get_pointers_by_id(file->settings.font_id);
    Assert(font.valid);
    
    file_grow_starts_as_needed(heap, buffer, line_shift);
    buffer_remeasure_starts(buffer, line_start, line_end, line_shift, shift_amount);
    
    file_allocate_character_starts_as_needed(heap, file);
    buffer_remeasure_character_starts(system, font, buffer, line_start, line_end, line_shift, file->state.character_starts, 0, file->settings.virtual_white);
    
    // NOTE(allen): token fixing
    if (file->settings.tokens_exist){
        file_relex(system, models, file, edit.range.first, edit.range.one_past_last, shift_amount);
    }
    
    // NOTE(allen): wrap meta data
    file_measure_wraps(system, &models->mem, file, font);
    
    // NOTE(allen): cursor fixing
    edit_fix_markers(system, models, file, edit);
    
    // NOTE(allen): mark edit finished
    if (file->settings.tokens_exist){
        if (file->settings.virtual_white){
            file_mark_edit_finished(&models->working_set, file);
        }
    }
    else{
        file_mark_edit_finished(&models->working_set, file);
    }
}

internal void
edit_batch(System_Functions *system, Models *models, Editing_File *file, Edit_Array edits, Edit_Behaviors behaviors){
    Mem_Options *mem = &models->mem;
    Heap *heap = &mem->heap;
    Partition *part = &mem->part;
    
    Gap_Buffer *buffer = &file->state.buffer;
    Assert(edits.count > 0);
    
    // NOTE(allen): history update
    if (!behaviors.do_not_post_to_history){
        history_dump_records_after_index(&file->state.history, file->state.current_record_index);
        history_record_edit(heap, &models->global_history, &file->state.history, buffer, edits, behaviors.batch_type);
        file->state.current_record_index = history_get_record_count(&file->state.history);
    }
    
    // NOTE(allen): fixing stuff "beforewards"???
    edit_pre_state_change(system, &mem->heap, models, file);
    
    // NOTE(allen): actual text replacement
    void *scratch = push_array(part, u8, 0);
    i32 scratch_size = part_remaining(part);
    Buffer_Batch_State state = {};
    i32 request_amount = 0;
    for (;buffer_batch_edit_step(&state, buffer, edits, scratch, scratch_size, &request_amount);){
        void *new_data = 0;
        if (request_amount > 0){
            new_data = heap_allocate(heap, request_amount);
        }
        void *old_data = buffer_edit_provide_memory(&file->state.buffer, new_data, request_amount);
        if (old_data){
            heap_free(heap, old_data);
        }
    }
    i32 shift_total = state.shift_total;
    
    // NOTE(allen): line meta data
    // TODO(allen): Let's try to switch to remeasuring here moron!
    file_measure_starts(heap, &file->state.buffer);
    
    Font_Pointers font = system->font.get_pointers_by_id(file->settings.font_id);
    Assert(font.valid);
    
    file_allocate_character_starts_as_needed(heap, file);
    buffer_measure_character_starts(system, font, &file->state.buffer, file->state.character_starts, 0, file->settings.virtual_white);
    
    // NOTE(allen): token fixing
    switch (behaviors.batch_type){
        case BatchEdit_Normal:
        {
            if (file->settings.tokens_exist){
                // TODO(allen): Write a smart fast one here someday.
                i32 start = edits.vals[0].range.first;
                i32 end   = edits.vals[edits.count - 1].range.one_past_last;
                file_relex(system, models, file, start, end, shift_total);
            }
            else{
                file_mark_edit_finished(&models->working_set, file);
            }
        }break;
        
        case BatchEdit_PreserveTokens:
        {
            if (file->state.tokens_complete){
                Cpp_Token_Array tokens = file->state.token_array;
                Cpp_Token *token = tokens.tokens;
                Cpp_Token *end_token = tokens.tokens + tokens.count;
                Cpp_Token original = {};
                
                Edit *edit = edits.vals;
                Edit *one_past_last_edit = edits.vals + edits.count;
                
                i32 shift_amount = 0;
                i32 local_shift = 0;
                
                for (;token < end_token; ++token){
                    original = *token;
                    for (;edit < one_past_last_edit && edit->range.first <= original.start;
                         ++edit){
                        local_shift = (edit->length - (edit->range.one_past_last - edit->range.first));
                        shift_amount += local_shift;
                    }
                    token->start += shift_amount;
                    local_shift = 0;
                    i32 original_end = original.start + original.size;
                    for (;edit < one_past_last_edit && edit->range.first < original_end;
                         ++edit){
                        local_shift += (edit->length - (edit->range.one_past_last - edit->range.first));
                    }
                    token->size += local_shift;
                    shift_amount += local_shift;
                }
                file_mark_edit_finished(&models->working_set, file);
            }
        }break;
    }
    
    // NOTE(allen): wrap meta data
    file_measure_wraps(system, &models->mem, file, font);
    
    // NOTE(allen): cursor fixing
    edit_fix_markers(system, models, file, edits);
    
    // NOTE(allen): mark edit finished
    if (file->settings.tokens_exist){
        if (file->settings.virtual_white){
            file_mark_edit_finished(&models->working_set, file);
        }
    }
    else{
        file_mark_edit_finished(&models->working_set, file);
    }
}

internal void
file_end_file(Models *models, Editing_File *file){
    if (models->hook_end_file != 0){
        models->hook_end_file(&models->app_links, file->id.id);
    }
    
    Heap *heap = &models->mem.heap;
    Lifetime_Allocator *lifetime_allocator = &models->lifetime_allocator;
    lifetime_free_object(heap, lifetime_allocator, file->lifetime_object);
    file->lifetime_object = lifetime_alloc_object(heap, lifetime_allocator, DynamicWorkspace_Buffer, file);
}

internal void
edit_clear(System_Functions *system, Models *models, Editing_File *file){
    file_end_file(models, file);
    
    b32 no_views_see_file = true;
    
    Layout *layout = &models->layout;
    for (Panel *panel = layout_get_first_open_panel(layout);
         panel != 0;
         panel = layout_get_next_open_panel(layout, panel)){
        View *view = panel->view;
        if (view->file_data.file == file){
            Full_Cursor cursor = {};
            cursor.line = 1;
            cursor.character = 1;
            cursor.wrap_line = 1;
            view_set_cursor(system, view, cursor, true);
            no_views_see_file = false;
        }
    }
    
    if (no_views_see_file){
        block_zero_struct(&file->state.edit_pos_most_recent);
        block_zero(file->state.edit_pos_stack, sizeof(file->state.edit_pos_stack));
        file->state.edit_pos_stack_top = -1;
    }
    
    Edit edit = {};
    edit.range.one_past_last = buffer_size(&file->state.buffer);
    
    Edit_Behaviors behaviors = {};
    edit_single(system, models, file, edit, behaviors);
}

internal void
edit__apply_record_forward(System_Functions *system, Models *models, Editing_File *file, Record *record, Edit_Behaviors behaviors_prototype){
    // NOTE(allen): // NOTE(allen): // NOTE(allen): // NOTE(allen): // NOTE(allen): 
    // Whenever you change this also change the backward version!
    
    switch (record->kind){
        case RecordKind_Single:
        {
            Edit edit = {};
            edit.str = record->single.str_forward;
            edit.length = record->single.length_forward;
            edit.range.first = record->single.first;
            edit.range.one_past_last = edit.range.first + record->single.length_backward;
            edit_single(system, models, file, edit, behaviors_prototype);
        }break;
        
        case RecordKind_Batch:
        {
            Partition *scratch = &models->mem.part;
            Temp_Memory temp = begin_temp_memory(scratch);
            
            i32 count = record->batch.count;
            Edit_Array edits = {};
            edits.vals = push_array(scratch, Edit, count);
            edits.count = count;
            
            Edit *edit = edits.vals;
            Record_Batch_Slot *batch_slot = record->batch.batch_records;
            char *str_base_forward = record->batch.str_base_forward;
            for (i32 i = 0; i < count; i += 1, edit += 1, batch_slot += 1){
                edit->str = str_base_forward;
                edit->length = batch_slot->length_forward;
                edit->range.first = batch_slot->first;
                edit->range.one_past_last = edit->range.first + batch_slot->length_backward;
                str_base_forward += batch_slot->length_forward;
            }
            
            Edit_Behaviors behaviors = behaviors_prototype;
            behaviors.batch_type = record->batch.type;
            edit_batch(system, models, file, edits, behaviors);
            
            end_temp_memory(temp);
        }break;
        
        case RecordKind_Group:
        {
            Node *sentinel = &record->group.children;
            for (Node *node = sentinel->next;
                 node != sentinel;
                 node = node->next){
                Record *sub_record = CastFromMember(Record, node, node);
                edit__apply_record_forward(system, models, file, sub_record, behaviors_prototype);
            }
        }break;
        
        default:
        {
            InvalidCodePath;
        }break;
    }
}

internal void
edit__apply_record_backward(System_Functions *system, Models *models, Editing_File *file, Record *record, Edit_Behaviors behaviors_prototype){
    // NOTE(allen): // NOTE(allen): // NOTE(allen): // NOTE(allen): // NOTE(allen): 
    // Whenever you change this also change the forward version!
    
    switch (record->kind){
        case RecordKind_Single:
        {
            Edit edit = {};
            edit.str = record->single.str_backward;
            edit.length = record->single.length_backward;
            edit.range.first = record->single.first;
            edit.range.one_past_last = edit.range.first + record->single.length_forward;
            edit_single(system, models, file, edit, behaviors_prototype);
        }break;
        
        case RecordKind_Batch:
        {
            Partition *scratch = &models->mem.part;
            Temp_Memory temp = begin_temp_memory(scratch);
            
            i32 count = record->batch.count;
            Edit_Array edits = {};
            edits.vals = push_array(scratch, Edit, count);
            edits.count = count;
            
            i32 shift_amount = 0;
            
            Edit *edit = edits.vals;
            Record_Batch_Slot *batch_slot = record->batch.batch_records;
            char *str_base_backward = record->batch.str_base_backward;
            for (i32 i = 0; i < count; i += 1, edit += 1, batch_slot += 1){
                edit->str = str_base_backward;
                edit->length = batch_slot->length_backward;
                edit->range.first = batch_slot->first + shift_amount;
                edit->range.one_past_last = edit->range.first + batch_slot->length_forward;
                str_base_backward += batch_slot->length_backward;
                shift_amount += batch_slot->length_forward - batch_slot->length_backward;
            }
            
            Edit_Behaviors behaviors = behaviors_prototype;
            behaviors.batch_type = record->batch.type;
            edit_batch(system, models, file, edits, behaviors);
            
            end_temp_memory(temp);
        }break;
        
        case RecordKind_Group:
        {
            Node *sentinel = &record->group.children;
            for (Node *node = sentinel->prev;
                 node != sentinel;
                 node = node->prev){
                Record *sub_record = CastFromMember(Record, node, node);
                edit__apply_record_backward(system, models, file, sub_record, behaviors_prototype);
            }
        }break;
        
        default:
        {
            InvalidCodePath;
        }break;
    }
}

internal void
edit_change_current_history_state(System_Functions *system, Models *models, Editing_File *file, i32 target_index){
    History *history = &file->state.history;
    if (history->activated && file->state.current_record_index != target_index){
        Assert(0 <= target_index && target_index <= history->record_count);
        
        i32 current = file->state.current_record_index;
        Record *record = history_get_record(history, current);
        Assert(record != 0);
        Record *dummy_record = history_get_dummy_record(history);
        
        Edit_Behaviors behaviors_prototype = {};
        behaviors_prototype.do_not_post_to_history = true;
        
        if (current < target_index){
            do{
                current += 1;
                record = CastFromMember(Record, node, record->node.next);
                Assert(record != dummy_record);
                edit__apply_record_forward(system, models, file, record, behaviors_prototype);
            } while (current != target_index);
        }
        else{
            do{
                Assert(record != dummy_record);
                edit__apply_record_backward(system, models, file, record, behaviors_prototype);
                current -= 1;
                record = CastFromMember(Record, node, record->node.prev);
            } while (current != target_index);
        }
        
        file->state.current_record_index = current;
    }
}

// BOTTOM