/*
 * Mr. 4th Dimention - Allen Webster
 *
 * 03.01.2017
 *
 * File layer for 4coder
 *
 */

// TOP

internal String_Const_u8
string_from_file_name(Editing_File_Name *name){
    return(SCu8(name->name_space, name->name_size));
}

////////////////////////////////

internal void
file_edit_positions_set_cursor(File_Edit_Positions *edit_pos, i64 pos){
    edit_pos->cursor_pos = pos;
    edit_pos->last_set_type = EditPos_CursorSet;
}

internal void
file_edit_positions_set_scroll(File_Edit_Positions *edit_pos, Buffer_Scroll scroll){
    edit_pos->scroll = scroll;
    edit_pos->last_set_type = EditPos_ScrollSet;
}

internal void
file_edit_positions_push(Editing_File *file, File_Edit_Positions edit_pos){
    if (file->state.edit_pos_stack_top + 1 < ArrayCount(file->state.edit_pos_stack)){
        file->state.edit_pos_stack_top += 1;
        file->state.edit_pos_stack[file->state.edit_pos_stack_top] = edit_pos;
    }
}

internal File_Edit_Positions
file_edit_positions_pop(Editing_File *file){
    File_Edit_Positions edit_pos = {};
    if (file->state.edit_pos_stack_top >= 0){
        edit_pos = file->state.edit_pos_stack[file->state.edit_pos_stack_top];
        file->state.edit_pos_stack_top -= 1;
    }
    else{
        edit_pos = file->state.edit_pos_most_recent;
    }
    return(edit_pos);
}

////////////////////////////////

internal Face*
file_get_face(Models *models, Editing_File *file){
    return(font_set_face_from_id(&models->font_set, file->settings.face_id));
}

internal Access_Flag
file_get_access_flags(Editing_File *file){
    Access_Flag flags = Access_Read|Access_Visible;
    if (!file->settings.read_only){
        flags |= Access_Write;
    }
    return(flags);
}

internal b32
file_needs_save(Editing_File *file){
    b32 result = false;
    if (HasFlag(file->state.dirty, DirtyState_UnsavedChanges)){
        result = true;
    }
    return(result);
}

internal b32
file_can_save(Editing_File *file){
    b32 result = false;
    if (HasFlag(file->state.dirty, DirtyState_UnsavedChanges) ||
        HasFlag(file->state.dirty, DirtyState_UnloadedChanges)){
        result = true;
    }
    return(result);
}

internal void
file_set_unimportant(Editing_File *file, b32 val){
    if (val){
        file->state.dirty = DirtyState_UpToDate;
    }
    file->settings.unimportant = (b8)(val);
}

internal void
file_add_dirty_flag(Editing_File *file, Dirty_State state){
    if (!file->settings.unimportant){
        file->state.dirty |= state;
    }
    else{
        file->state.dirty = DirtyState_UpToDate;
    }
}

internal void
file_clear_dirty_flags(Editing_File *file){
    file->state.dirty = DirtyState_UpToDate;
}

////////////////////////////////

internal void
file_name_terminate(Editing_File_Name *name){
    u64 size = name->name_size;
    size = clamp_top(size, sizeof(name->name_space) - 1);
    name->name_space[size] = 0;
    name->name_size = size;
}

////////////////////////////////

// TODO(allen): file_name should be String_Const_u8
internal b32
save_file_to_name(Thread_Context *tctx, Models *models, Editing_File *file, u8 *file_name){
    b32 result = false;
    b32 using_actual_file_name = false;
    
    if (file_name == 0){
        file_name_terminate(&file->canon);
        file_name = file->canon.name_space;
        using_actual_file_name = true;
    }
    
    if (file_name != 0){
        if (models->save_file != 0){
            Application_Links app = {};
            app.tctx = tctx;
            app.cmd_context = models;
            models->save_file(&app, file->id);
        }
        
        Gap_Buffer *buffer = &file->state.buffer;
        b32 dos_write_mode = file->settings.dos_write_mode;
        
        Scratch_Block scratch(tctx);
        
        if (!using_actual_file_name){
            String_Const_u8 s_file_name = SCu8(file_name);
            String_Const_u8 canonical_file_name = system_get_canonical(scratch, s_file_name);
            if (string_match(canonical_file_name, string_from_file_name(&file->canon))){
                using_actual_file_name = true;
            }
        }
        
        String_Const_u8 saveable_string = buffer_stringify(scratch, buffer, Ii64(0, buffer_size(buffer)));
        
        File_Attributes new_attributes = system_save_file(scratch, (char*)file_name, saveable_string);
        if (new_attributes.last_write_time > 0 &&
            using_actual_file_name){
            file->state.save_state = FileSaveState_SavedWaitingForNotification;
            file_clear_dirty_flags(file);
        }
        LogEventF(log_string(M), scratch, file->id, 0, system_thread_get_id(),
                  "save file [last_write_time=0x%llx]", new_attributes.last_write_time);
    }
    
    return(result);
}

internal b32
save_file(Thread_Context *tctx, Models *models, Editing_File *file){
    return(save_file_to_name(tctx, models, file, 0));
}

////////////////////////////////

internal Buffer_Cursor
file_compute_cursor(Editing_File *file, Buffer_Seek seek){
    Buffer_Cursor result = {};
    switch (seek.type){
        case buffer_seek_pos:
        {
            result = buffer_cursor_from_pos(&file->state.buffer, seek.pos);
        }break;
        case buffer_seek_line_col:
        {
            result = buffer_cursor_from_line_col(&file->state.buffer, seek.line, seek.col);
        }break;
    }
    return(result);
}

////////////////////////////////

function Layout_Function*
file_get_layout_func(Editing_File *file){
    return(file->settings.layout_func);
}

internal void
file_create_from_string(Thread_Context *tctx, Models *models, Editing_File *file, String_Const_u8 val, File_Attributes attributes){
    Scratch_Block scratch(tctx);
    
    Base_Allocator *allocator = tctx->allocator;
    block_zero_struct(&file->state);
    buffer_init(&file->state.buffer, val.str, val.size, allocator);
    
    if (buffer_size(&file->state.buffer) < (i64)val.size){
        file->settings.dos_write_mode = true;
    }
    file_clear_dirty_flags(file);
    file->attributes = attributes;
    
    file->settings.layout_func = models->layout_func;
    file->settings.face_id = models->global_face_id;
    
    buffer_measure_starts(scratch, &file->state.buffer);
    
    file->lifetime_object = lifetime_alloc_object(&models->lifetime_allocator, DynamicWorkspace_Buffer, file);
    history_init(tctx, models, &file->state.history);
    
    file->state.cached_layouts_arena = make_arena(allocator);
    file->state.line_layout_table = make_table_Data_u64(allocator, 500);
    
    file->settings.is_initialized = true;
    
    {
        Temp_Memory temp = begin_temp(scratch);
        String_Const_u8 name = SCu8(file->unique_name.name_space, file->unique_name.name_size);
        name = string_escape(scratch, name);
        LogEventF(log_string(M), scratch, file->id, 0, system_thread_get_id(),
                  "init file [lwt=0x%llx] [name=\"%.*s\"]",
                  attributes.last_write_time, string_expand(name));
        end_temp(temp);
    }
    
    ////////////////////////////////
    
    if (models->begin_buffer != 0){
        Application_Links app = {};
        app.tctx = tctx;
        app.cmd_context = models;
        models->begin_buffer(&app, file->id);
    }
}

internal void
file_free(Thread_Context *tctx, Models *models, Editing_File *file){
    Lifetime_Allocator *lifetime_allocator = &models->lifetime_allocator;
    Working_Set *working_set = &models->working_set;
    
    lifetime_free_object(lifetime_allocator, file->lifetime_object);
    
    Gap_Buffer *buffer = &file->state.buffer;
    if (buffer->data){
        base_free(buffer->allocator, buffer->data);
        base_free(buffer->allocator, buffer->line_starts);
    }
    
    history_free(tctx, &file->state.history);
    
    linalloc_clear(&file->state.cached_layouts_arena);
    table_free(&file->state.line_layout_table);
}

////////////////////////////////

internal i32
file_get_current_record_index(Editing_File *file){
    return(file->state.current_record_index);
}

internal Managed_Scope
file_get_managed_scope(Editing_File *file){
    Managed_Scope scope = 0;
    if (file != 0){
        Assert(file->lifetime_object != 0);
        scope = (Managed_Scope)file->lifetime_object->workspace.scope_id;
    }
    return(scope);
}

////////////////////////////////

internal Layout_Item_List
file_get_line_layout(Thread_Context *tctx, Models *models, Editing_File *file,
                     Layout_Function *layout_func, f32 width, Face *face, i64 line_number){
    Layout_Item_List result = {};
    
    i64 line_count = buffer_line_count(&file->state.buffer);
    if (1 <= line_number && line_number <= line_count){
        Line_Layout_Key key = {};
        key.face_id = face->id;
        key.face_version_number = face->version_number;
        key.width = width;
        key.line_number = line_number;
        
        Data key_data = make_data_struct(&key);
        
        Layout_Item_List *list = 0;
        
        Table_Lookup lookup = table_lookup(&file->state.line_layout_table, key_data);
        if (lookup.found_match){
            u64 val = 0;
            table_read(&file->state.line_layout_table, lookup, &val);
            list = (Layout_Item_List*)IntAsPtr(val);
        }
        else{
            list = push_array(&file->state.cached_layouts_arena, Layout_Item_List, 1);
            Range_i64 line_range = buffer_get_pos_range_from_line_number(&file->state.buffer, line_number);
            
            Application_Links app = {};
            app.tctx = tctx;
            app.cmd_context = models;
            *list = layout_func(&app, &file->state.cached_layouts_arena,
                                file->id, line_range, face->id, width);
            key_data = push_data_copy(&file->state.cached_layouts_arena, key_data);
            table_insert(&file->state.line_layout_table, key_data, (u64)PtrAsInt(list));
        }
        block_copy_struct(&result, list);
    }
    
    return(result);
}

internal void
file_clear_layout_cache(Editing_File *file){
    linalloc_clear(&file->state.cached_layouts_arena);
    table_clear(&file->state.line_layout_table);
}

internal Line_Shift_Vertical
file_line_shift_y(Thread_Context *tctx, Models *models, Editing_File *file,
                  Layout_Function *layout_func, f32 width, Face *face,
                  i64 line_number, f32 y_delta){
    Line_Shift_Vertical result = {};
    
    f32 line_y = 0.f;
    
    if (y_delta < 0.f){
        // NOTE(allen): Iterating upward
        b32 has_result = false;
        for (;;){
            if (line_y <= y_delta){
                has_result = true;
                result.line = line_number;
                result.y_delta = line_y;
                break;
            }
            line_number -= 1;
            if (line_number <= 0){
                line_number = 1;
                break;
            }
            Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func,
                                                         width, face, line_number);
            line_y -= line.height;
        }
        if (!has_result){
            result.line = line_number;
            result.y_delta = line_y;
        }
    }
    else{
        // NOTE(allen): Iterating downward
        b32 has_result = false;
        i64 line_count = buffer_line_count(&file->state.buffer);
        for (;;line_number += 1){
            Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func,
                                                         width, face, line_number);
            f32 next_y = line_y + line.height;
            if (y_delta < next_y){
                has_result = true;
                result.line = line_number;
                result.y_delta = line_y;
                break;
            }
            if (line_number >= line_count){
                break;
            }
            line_y = next_y;
        }
        if (!has_result){
            result.line = line_number;
            result.y_delta = line_y;
        }
    }
    
    return(result);
}

internal f32
file_line_y_difference(Thread_Context *tctx, Models *models, Editing_File *file,
                       Layout_Function *layout_func, f32 width, Face *face,
                       i64 line_a, i64 line_b){
    f32 result = 0.f;
    if (line_a != line_b){
        Range_i64 line_range = Ii64(line_a, line_b);
        for (i64 i = line_range.min; i < line_range.max; i += 1){
            Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, i);
            result += line.height;
        }
        if (line_a < line_b){
            result *= -1.f;
        }
    }
    return(result);
}

internal i64
file_pos_at_relative_xy(Thread_Context *tctx, Models *models, Editing_File *file,
                        Layout_Function *layout_func, f32 width, Face *face,
                        i64 base_line, Vec2_f32 relative_xy){
    Line_Shift_Vertical shift = file_line_shift_y(tctx, models, file, layout_func, width, face, base_line, relative_xy.y);
    relative_xy.y -= shift.y_delta;
    Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, shift.line);
    return(layout_nearest_pos_to_xy(line, relative_xy));
}

internal Rect_f32
file_relative_box_of_pos(Thread_Context *tctx, Models *models, Editing_File *file,
                         Layout_Function *layout_func, f32 width, Face *face,
                         i64 base_line, i64 pos){
    i64 line_number = buffer_get_line_index(&file->state.buffer, pos) + 1;
    Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, line_number);
    Rect_f32 result = layout_box_of_pos(line, pos);
    
    f32 y_difference = file_line_y_difference(tctx, models, file, layout_func, width, face, line_number, base_line);
    result.y0 += y_difference;
    result.y1 += y_difference;
    
    return(result);
}

function Vec2_f32
file_relative_xy_of_pos(Thread_Context *tctx, Models *models, Editing_File *file,
                        Layout_Function *layout_func, f32 width, Face *face,
                        i64 base_line, i64 pos){
    Rect_f32 rect = file_relative_box_of_pos(tctx, models, file, layout_func, width, face, base_line, pos);
    return(rect_center(rect));
}

function Rect_f32
file_padded_box_of_pos(Thread_Context *tctx, Models *models, Editing_File *file,
                       Layout_Function *layout_func, f32 width, Face *face,
                       i64 base_line, i64 pos){
    i64 line_number = buffer_get_line_index(&file->state.buffer, pos) + 1;
    Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, line_number);
    Rect_f32 result = layout_padded_box_of_pos(line, pos);
    
    f32 y_difference = file_line_y_difference(tctx, models, file, layout_func, width, face, line_number, base_line);
    result.y0 += y_difference;
    result.y1 += y_difference;
    
    return(result);
}

internal Buffer_Point
file_normalize_buffer_point(Thread_Context *tctx, Models *models, Editing_File *file,
                            Layout_Function *layout_func, f32 width, Face *face,
                            Buffer_Point point){
    Line_Shift_Vertical shift = file_line_shift_y(tctx, models, file, layout_func, width, face, point.line_number, point.pixel_shift.y);
    point.line_number = shift.line;
    point.pixel_shift.y -= shift.y_delta;
    point.pixel_shift.x = clamp_bot(0.f, point.pixel_shift.x);
    point.pixel_shift.y = clamp_bot(0.f, point.pixel_shift.y);
    return(point);
}

internal Vec2_f32
file_buffer_point_difference(Thread_Context *tctx, Models *models, Editing_File *file,
                             Layout_Function *layout_func, f32 width, Face *face,
                             Buffer_Point a, Buffer_Point b){
    f32 y_difference = file_line_y_difference(tctx, models, file, layout_func, width, face, a.line_number, b.line_number);
    Vec2_f32 result = a.pixel_shift - b.pixel_shift;
    result.y += y_difference;
    return(result);
}

internal Line_Shift_Character
file_line_shift_characters(Thread_Context *tctx, Models *models, Editing_File *file, Layout_Function *layout_func, f32 width, Face *face, i64 line_number, i64 character_delta){
    Line_Shift_Character result = {};
    
    i64 line_character = 0;
    
    if (character_delta < 0){
        // NOTE(allen): Iterating upward
        b32 has_result = false;
        for (;;){
            if (line_character <= character_delta){
                has_result = true;
                result.line = line_number;
                result.character_delta = line_character;
                break;
            }
            line_number -= 1;
            if (line_number <= 0){
                line_number = 1;
                break;
            }
            Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, line_number);
            line_character -= line.character_count;
        }
        if (!has_result){
            result.line = line_number;
            result.character_delta = line_character;
        }
    }
    else{
        // NOTE(allen): Iterating downward
        b32 has_result = false;
        i64 line_count = buffer_line_count(&file->state.buffer);
        for (;;line_number += 1){
            Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, line_number);
            i64 next_character = line_character + line.character_count;
            if (character_delta < next_character){
                has_result = true;
                result.line = line_number;
                result.character_delta = line_character;
                break;
            }
            if (line_number >= line_count){
                break;
            }
            line_character = next_character;
        }
        if (!has_result){
            result.line = line_number;
            result.character_delta = line_character;
        }
    }
    
    return(result);
}

internal i64
file_line_character_difference(Thread_Context *tctx, Models *models, Editing_File *file, Layout_Function *layout_func, f32 width, Face *face, i64 line_a, i64 line_b){
    i64 result = 0;
    if (line_a != line_b){
        Range_i64 line_range = Ii64(line_a, line_b);
        for (i64 i = line_range.min; i < line_range.max; i += 1){
            Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, i);
            result += line.character_count;
        }
        if (line_a < line_b){
            result *= -1;
        }
    }
    return(result);
}

internal i64
file_pos_from_relative_character(Thread_Context *tctx, Models *models, Editing_File *file,
                                 Layout_Function *layout_func, f32 width, Face *face,
                                 i64 base_line, i64 relative_character){
    Line_Shift_Character shift = file_line_shift_characters(tctx, models, file, layout_func, width, face, base_line, relative_character);
    relative_character -= shift.character_delta;
    Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, shift.line);
    return(layout_get_pos_at_character(line, relative_character));
}

internal i64
file_relative_character_from_pos(Thread_Context *tctx, Models *models, Editing_File *file, Layout_Function *layout_func, f32 width, Face *face,
                                 i64 base_line, i64 pos){
    i64 line_number = buffer_get_line_index(&file->state.buffer, pos) + 1;
    Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, line_number);
    i64 result = layout_character_from_pos(line, pos);
    result += file_line_character_difference(tctx, models, file, layout_func, width, face, line_number, base_line);
    return(result);
}

// BOTTOM