/*
 * Mr. 4th Dimention - Allen Webster
 *
 * 12.12.2014
 *
 * Application layer for project codename "4ed"
 *
 */

/*
 * Quick and Dirty Partition System
 */

struct Partition_Cursor{
    u8 *memory_base;
    u8 *memory_cursor;
    i32 max_size;
};

internal Partition_Cursor
partition_open(void *memory, i32 size){
    Partition_Cursor partition = {};
    partition.memory_base =
        partition.memory_cursor = (u8*)memory;
    partition.max_size = size;
    return partition;
}

internal void*
partition_allocate(Partition_Cursor *data, i32 size){
    void *ret = 0;
    if ((data->memory_cursor - data->memory_base) + size <= data->max_size && size > 0){
        ret = data->memory_cursor;
        data->memory_cursor += size;
    }
    return ret;
}

/*
 * Plaform Independent File Name Helpers
 */

struct File_List_Iterator{
    b32 folder_stage;
    u8 **filename_ptr;
};

internal File_List_Iterator
files_iterator_init(File_List *files){
    File_List_Iterator files_it = {};
    if (files->folder_count > 0){
        files_it.filename_ptr = files->folder_names;
        files_it.folder_stage = 1;
    }
    else if (files->file_count > 0){
        files_it.filename_ptr = files->file_names;
    }
    return files_it;
}

internal void
files_iterator_step(File_List *files, File_List_Iterator *files_it){
    ++files_it->filename_ptr;
    if (files_it->folder_stage){
        if (files_it->filename_ptr >= files->folder_names + files->folder_count){
            if (files->file_count > 0){
                files_it->filename_ptr = files->file_names;
            }
            else{
                files_it->filename_ptr = 0;
            }
            files_it->folder_stage = 0;
        }
    }
    else{
        if (files_it->filename_ptr >= files->file_names + files->file_count){
            files_it->filename_ptr = 0;
        }
    }
}

/*
 * Drawing Functions
 */

internal u32
style_token_color(Editing_Style *style,
                  Cpp_Token_Type type){
    u32 result;
    switch (type){
        case CPP_TOKEN_COMMENT:
            result = style->comment_color;
            break;

        case CPP_TOKEN_KEYWORD:
            result = style->keyword_color;
            break;

        case CPP_TOKEN_STRING_CONSTANT:
        case CPP_TOKEN_CHARACTER_CONSTANT:
        case CPP_TOKEN_INTEGER_CONSTANT:
        case CPP_TOKEN_FLOATING_CONSTANT:
        case CPP_TOKEN_BOOLEAN_CONSTANT:
        case CPP_TOKEN_INCLUDE_FILE:
            result = style->constant_color;
            break;

        default:
            result = style->default_color;
    }
    return result;
}

internal void
panel_draw(Thread_Context *thread, Render_Target *target,
           Editing_Panel *panel,
           bool32 is_active){
    Editing_File *file = panel->file;
    Editing_Style *style = file->style;
    Font *font = style->font;

    i32 character_w = (i32)(style_get_character_width(style));
    i32 character_h = (i32)(font->line_skip);
    i32 offset_x = (i32)(panel->x);
    i32 offset_y = (i32)(panel->y);
    i32 max_x = (i32)(panel->w);
    i32 max_y = (i32)(panel->h);

    Blit_Rect panel_area;
    panel_area.x_start = offset_x;
    panel_area.y_start = offset_y;
    panel_area.x_end = offset_x + max_x;
    panel_area.y_end = offset_y + max_y;

    if (!file || !file->data || file->is_dummy){
        i32 start_x = (panel_area.x_start + panel_area.x_end)/2;
        i32 start_y = (panel_area.y_start + panel_area.y_end)/2;
        persist String null_file_message = make_lit_string("NULL FILE");
        start_x -= (character_w*null_file_message.size)/2;
        start_y -= (character_h)/2;

        real32 pos_x = 0;
        real32 pos_y = 0;

        for (i32 i = 0; i < null_file_message.size; ++i){
            u8 to_render = null_file_message.str[i];

            if (font->glyphs[to_render].data){
                font_draw_glyph_clipped(target, font, to_render,
                                        (real32)start_x + pos_x, (real32)start_y + pos_y,
                                        style->special_character_color,
                                        panel_area);
                pos_x += character_w;
            }
        }
    }

    else{
        u32 tab_width = style->tab_width;
        i32 size = (i32)file->size;
        u8 *data = (u8*)file->data;

        real32 shift_x = 0;
        shift_x = -panel->scroll_x * character_w;

        i32 truncated_start_y = (i32)panel->scroll_y;
        real32 scroll_amount = panel->scroll_y - truncated_start_y;

        Panel_Cursor_Data start_cursor;
        start_cursor = panel->scroll_y_cursor;
        start_cursor = panel_compute_cursor_from_xy(panel, 0, truncated_start_y);
        panel->scroll_y_cursor = start_cursor;

        i32 start_character = start_cursor.pos;

        real32 pos_x = 0;
        real32 pos_y = -character_h*scroll_amount;

        Cpp_Token_Stack token_stack = file->token_stack;
        u32 main_color = style->default_color;
        u32 highlight_color = 0x00000000;
        i32 token_i = 0;
        bool32 tokens_exist = file->tokens_exist;

        if (tokens_exist){
            // TODO(allen): Use cpp_get_token, it binary searches this shit!
            while (token_i < token_stack.count &&
                   start_character > token_stack.tokens[token_i].start){
                ++token_i;
            }
            if (token_i != 0){
                main_color =
                    style_token_color(style, token_stack.tokens[token_i-1].type);
                if (token_stack.tokens[token_i-1].type == CPP_TOKEN_JUNK){
                    highlight_color = style->highlight_junk_color;
                }
            }
        }

        // TODO(allen): Render through NULLS
        for (i32 i = start_character; i < size && data[i]; ++i){
            u8 to_render = data[i];

            u32 fade_color = 0xFFFF00FF;
            real32 fade_amount = 0.f;
            if (style->use_paste_color && (panel->paste_effect.tick_down > 0) &&
                panel->paste_effect.start <= i && i < panel->paste_effect.end){
                fade_color = style->paste_color;
                fade_amount = (real32)(panel->paste_effect.tick_down) / panel->paste_effect.tick_max;
            }

            if (tokens_exist && token_i < token_stack.count){
                if (i == token_stack.tokens[token_i].start){
                    main_color =
                        style_token_color(style, token_stack.tokens[token_i].type);
                    ++token_i;
                }
                if (token_i > 0 &&
                    i >= token_stack.tokens[token_i-1].start + token_stack.tokens[token_i-1].size){
                    main_color = 0xFFFFFFFF;
                }
            }

            highlight_color = 0x00000000;

            if (tokens_exist && token_i > 0 &&
                token_stack.tokens[token_i-1].type == CPP_TOKEN_JUNK &&
                i >= token_stack.tokens[token_i-1].start &&
                i <= token_stack.tokens[token_i-1].start + token_stack.tokens[token_i-1].size){
                highlight_color = style->highlight_junk_color;
            }
            else if (panel->show_whitespace &&
                     character_is_any_whitespace(data[i])){
                highlight_color = style->highlight_white_color;
            }

            i32 cursor_mode = 0;
            if (panel->show_temp_highlight){
                if (panel->temp_highlight.pos <= i && i < panel->temp_highlight_end_pos){
                    cursor_mode = 2;
                }
            }
            else{
                if (panel->cursor.pos == i){
                    cursor_mode = 1;
                }
            }

            if (!panel->unwrapped_lines &&
                pos_x + character_w > max_x){
                pos_x = 0;
                pos_y += font->line_skip;
            }

            if (highlight_color != 0){
                if (file->endline_mode == ENDLINE_RN_COMBINED &&
                    to_render == '\r' &&
                    i + 1 < size &&
                    data[i+1] == '\n'){
                    // DO NOTHING
                }
                else{
                    draw_rectangle_clipped(target,
                                           (i32)shift_x + offset_x + (i32)pos_x,
                                           offset_y + (i32)pos_y,
                                           character_w, font->line_skip,
                                           highlight_color, panel_area);
                }
            }

            if (cursor_mode){
                if (is_active){
                    u32 color = 0x00000000;
                    switch (cursor_mode){
                    case 1:
                        color = style->cursor_color;
                        break;
                    case 2:
                        color = style->highlight_color;
                        break;
                    }
                    draw_rectangle_clipped(target,
                                           (i32)shift_x + offset_x + (i32)pos_x,
                                           offset_y + (i32)pos_y,
                                           character_w, font->line_skip,
                                           color, panel_area);
                }
                else{
                    draw_rectangle_outline_clipped(target, (i32)shift_x + offset_x + (i32)pos_x, offset_y + (i32)pos_y,
                                                   character_w, font->line_skip, style->cursor_color,
                                                   panel_area);
                }
            }

            if (i == panel->mark){
                draw_rectangle_outline_clipped(target, (i32)shift_x + offset_x + (i32)pos_x, offset_y + (i32)pos_y,
                                               character_w, font->line_skip, style->mark_color,
                                               panel_area);
            }

            if (to_render == '\r'){
                switch (file->endline_mode){
                case ENDLINE_RN_COMBINED:
                {
                    if (i + 1 < size &&
                        data[i+1] == '\n'){
                        // DO NOTHING
                    }
                    else{
                        u32 char_color = style->special_character_color;
                        if (cursor_mode && is_active){
                            switch (cursor_mode){
                            case 1:
                                char_color = style->at_cursor_color;
                                break;
                            case 2:
                                char_color = style->at_highlight_color;
                                break;
                            }
                        }

                        char_color = color_blend(char_color, fade_amount, fade_color);

                        font_draw_glyph_clipped(target, font, '\\',
                                                shift_x + offset_x + pos_x,
                                                (real32)offset_y + pos_y,
                                                char_color, panel_area);

                        pos_x += character_w;
                        draw_rectangle_clipped(target,
                                               (i32)shift_x + offset_x + (i32)pos_x,
                                               offset_y + (i32)pos_y,
                                               character_w, font->line_skip,
                                               highlight_color, panel_area);

                        font_draw_glyph_clipped(target, font, 'r',
                                                shift_x + offset_x + pos_x,
                                                (real32)offset_y + pos_y,
                                                char_color, panel_area);
                        pos_x += character_w;
                    }
                }break;

                case ENDLINE_RN_SEPARATE:
                {
                    pos_x = 0;
                    pos_y += font->line_skip;
                }break;

                case ENDLINE_RN_SHOWALLR:
                {
                    u32 char_color = style->special_character_color;
                    if (cursor_mode && is_active){
                        switch (cursor_mode){
                        case 1:
                            char_color = style->at_cursor_color;
                            break;
                        case 2:
                            char_color = style->at_highlight_color;
                            break;
                        }
                    }

                    char_color = color_blend(char_color, fade_amount, fade_color);

                    font_draw_glyph_clipped(target, font, '\\',
                                            shift_x + offset_x + pos_x,
                                            (real32)offset_y + pos_y,
                                            char_color, panel_area);

                    pos_x += character_w;
                    draw_rectangle_clipped(target,
                                           (i32)shift_x + offset_x + (i32)pos_x,
                                           offset_y + (i32)pos_y,
                                           character_w, font->line_skip,
                                           highlight_color, panel_area);

                    font_draw_glyph_clipped(target, font, 'r',
                                            shift_x + offset_x + pos_x,
                                            (real32)offset_y + pos_y,
                                            char_color, panel_area);
                    pos_x += character_w;
                }break;
                }
            }

            else if (to_render == '\n'){
                pos_x = 0;
                pos_y += font->line_skip;
            }
            else if (to_render == '\t'){
                if (highlight_color != 0){
                    draw_rectangle_clipped(target,
                                           (i32)shift_x + offset_x + (i32)pos_x + character_w,
                                           offset_y + (i32)pos_y,
                                           character_w*(tab_width-1), font->line_skip,
                                           highlight_color, panel_area);
                }
                pos_x += character_w*tab_width;
            }

            else if (font->glyphs[to_render].data){
                u32 char_color = main_color;
                if (cursor_mode && is_active){
                    switch (cursor_mode){
                    case 1:
                        char_color = style->at_cursor_color;
                        break;
                    case 2:
                        char_color = style->at_highlight_color;
                        break;
                    }
                }

                char_color = color_blend(char_color, fade_amount, fade_color);

                font_draw_glyph_clipped(target, font, to_render,
                                        shift_x + offset_x + pos_x,
                                        (real32)offset_y + pos_y, char_color,
                                        panel_area);
                pos_x += character_w;
            }

            else{
                pos_x += character_w;
            }

            if (pos_y > max_y){
                break;
            }
        }
    }
}

/*
 * Hot Directory
 */

struct Hot_Directory{
    String string;
    File_List file_list;
};

internal void
hot_directory_init(Hot_Directory *hot_directory){
    hot_directory->string = make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);

    i32 dir_size = system_get_working_directory(hot_directory->string.str,
                                                hot_directory->string.memory_size);

    if (dir_size <= 0){
        dir_size = system_get_easy_directory(hot_directory->string.str);
    }

    hot_directory->string.size = dir_size;

    append(&hot_directory->string, (u8*)"\\");
}

internal void
hot_directory_reload_list(Hot_Directory *hot_directory){
    if (hot_directory->file_list.block){
        system_free_file_list(hot_directory->file_list);
    }
    hot_directory->file_list = system_get_files(hot_directory->string);
}

internal bool32
hot_directory_set(Hot_Directory *hot_directory, String str){
    bool32 did_set = 0;
    if (copy_checked(&hot_directory->string, str)){
        did_set = 1;
        system_free_file_list(hot_directory->file_list);
        hot_directory->file_list = system_get_files(str);
    }
    return did_set;
}

struct Hot_Directory_Match{
    u8 *filename;
    bool32 is_folder;
};

internal Hot_Directory_Match
hot_directory_first_match(Hot_Directory *hot_directory,
                          String str,
                          bool32 include_files,
                          bool32 exact_match){
    Hot_Directory_Match result = {};

    File_List files = hot_directory->file_list;
    File_List_Iterator files_it = files_iterator_init(&files);

    while (files_it.filename_ptr &&
           (include_files || files_it.folder_stage)){
        u8 *filename = *files_it.filename_ptr;

        bool32 is_match = 0;
        if (exact_match){
            if (match(filename, str)){
                is_match = 1;
            }
        }
        else{
            if (str.size == 0 ||
                has_substr_unsensitive(filename, str)){
                is_match = 1;
            }
        }

        if (is_match){
            result.is_folder = files_it.folder_stage;
            result.filename = filename;
            break;
        }

        files_iterator_step(&files, &files_it);
    }

    return result;
}

/*
 * App Structs
 */

struct Command_Data;
typedef void (*Command_Function)(Command_Data *command);

enum Input_Request_Type{
    REQUEST_SYS_FILE,
    REQUEST_LIVE_FILE,
    // never below this
    REQUEST_COUNT
};

struct Input_Request{
    Input_Request_Type type;
    union{
        struct{
            String query;
            String dest;
            bool32 hit_ctrl_newline;
            bool32 fast_folder;
        } sys_file;

        struct{
            String query;
            String dest;
            bool32 hit_ctrl_newline;
        } live_file;
    };
};

enum App_State{
    APP_STATE_EDIT,
    APP_STATE_SEARCH,
    APP_STATE_RESIZING,
    // never below this
    APP_STATE_COUNT
};

struct App_State_Incremental_Search{
    String str;
    bool32 reverse;
    i32 pos;
};

struct App_State_Resizing{
    Editing_Panel *left, *right;
};

struct Command_Map{
    Command_Function basic_mode_key[1+sizeof(Key_Codes)/2];
    Command_Function control_ascii[128];
    Command_Function control_key[1+sizeof(Key_Codes)/2];
};

struct App_Vars{
    Command_Map map;

    Font font;
    Editing_Style style;
    Interactive_Style command_style;

    Editing_Working_Set working_set;

    Editing_Layout layout;
    real32 last_click_x, last_click_y;

    Hot_Directory hot_directory;

    Input_Request request_queue[16];
    i32 request_count, request_filled, request_max;

    Command_Function pending_command;

    App_State state;
    union{
        App_State_Incremental_Search isearch;
        App_State_Resizing resizing;
    };
};

/*
 * Commands
 */

struct Command_Data{
    Editing_Panel *panel;
    Editing_Working_Set *working_set;
    Editing_Layout *layout;
    Editing_Style *style;
    App_Vars *vars;

    i32 screen_width, screen_height;
    i32 screen_y_off;
    Key_Event_Data key;

    Input_Request *requests;
    i32 request_count, request_filled;
};

internal void
app_clear_request_queue(App_Vars *vars){
    for (i32 i = 0; i < vars->request_count; ++i){
        Input_Request *request = vars->request_queue + i;
        switch (request->type){
        case REQUEST_SYS_FILE:
        {
            system_free_memory(request->sys_file.query.str);
            system_free_memory(request->sys_file.dest.str);
        }break;

        case REQUEST_LIVE_FILE:
        {
            system_free_memory(request->live_file.query.str);
            system_free_memory(request->live_file.dest.str);
        }break;
        }
    }
    vars->request_count = 0;
    vars->request_filled = 0;
}

internal void
command_write_character(Command_Data *command){
    Editing_Panel *panel = command->panel;
    panel_write_character(panel, (u8)command->key.character);
    if (panel->unwrapped_lines){
        panel->preferred_x = panel->cursor.unwrapped_x;
    }
    else{
        panel->preferred_x = panel->cursor.wrapped_x;
    }
    panel->file->cursor.pos = panel->cursor.pos;

    switch ((u8)command->key.character){
        case '{': case '}':
        case '(': case ')':
        case ';': case ':':
        case '#': case '\n':
        {
            panel_auto_tab(panel, panel->cursor.pos, panel->cursor.pos);
        }break;
    }
    panel_measure_all_wrapped_y(panel);
}

internal void
command_move_left(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    u8 *data = (u8*)file->data;

    i32 pos = panel->cursor.pos;
    if (pos > 0){
        if (file->endline_mode == ENDLINE_RN_COMBINED){
            pos = pos_adjust_to_left(pos, data);
            if (pos > 0){
                --pos;
            }
            else{
                pos = pos_adjust_to_self(pos, data, file->size);
            }
        }
        else{
            --pos;
        }
    }

    panel_cursor_move(panel, pos);
}

internal void
command_move_right(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    i32 size = file->size;
    u8* data = (u8*)file->data;

    i32 pos = panel->cursor.pos;
    if (pos < size){
        ++pos;
        if (file->endline_mode == ENDLINE_RN_COMBINED){
            pos = pos_adjust_to_self(pos, data, size);
        }
    }

    panel_cursor_move(panel, pos);
}

internal void
command_backspace(Command_Data *command){
    Editing_Panel *panel = command->panel;
    i32 cursor_pos = panel->cursor.pos;
    Editing_File *file = panel->file;
    i8 *data = (i8*)file->data;

    if (cursor_pos > 0 && cursor_pos <= (i32)file->size){

        if (file->endline_mode == ENDLINE_RN_COMBINED){
            i32 target_pos = cursor_pos;
            if (cursor_pos > 1 &&
                data[cursor_pos] == '\n' &&
                data[cursor_pos-1] == '\r'){
                --target_pos;
            }

            if (target_pos > 1 &&
                data[target_pos-1] == '\n' &&
                data[target_pos-2] == '\r'){

                buffer_delete_range(file, target_pos-2, target_pos);
                if (panel->mark >= cursor_pos){
                    panel->mark -= 2;
                }
                cursor_pos -= 2;
            }
            else{
                if (target_pos > 0){
                    buffer_delete(file, target_pos-1);
                    if (panel->mark >= cursor_pos){
                        --panel->mark;
                    }
                    --cursor_pos;
                }
            }
        }
        else{
            buffer_delete(file, cursor_pos-1);
            if (panel->mark >= cursor_pos){
                --panel->mark;
            }
            --cursor_pos;
        }

        panel_measure_all_wrapped_y(panel);
        panel_cursor_move(panel, cursor_pos);
    }
}
internal void
command_delete(Command_Data *command){
    Editing_Panel *panel = command->panel;
    i32 cursor_pos = panel->cursor.pos;
    Editing_File *file = panel->file;
    i8 *data = (i8*)file->data;

    if (file->size > 0){
        if (file->endline_mode == ENDLINE_RN_COMBINED){
            if (cursor_pos > 0 &&
                data[cursor_pos-1] == '\r' &&
                data[cursor_pos] == '\n'){
                buffer_delete_range(file, cursor_pos-1, cursor_pos+1);
                if (panel->mark > cursor_pos){
                    panel->mark -= 2;
                }
                cursor_pos -= 1;
            }

            else{
                buffer_delete(file, cursor_pos);
                if (panel->mark > cursor_pos){
                    --panel->mark;
                }
            }
        }

        else{
            buffer_delete(file, cursor_pos);
            if (panel->mark > cursor_pos){
                --panel->mark;
            }
        }

        panel_measure_all_wrapped_y(panel);
        panel_cursor_move(panel, cursor_pos);
    }
}

internal void
command_move_up(Command_Data *command){
    Editing_Panel *panel = command->panel;
    i32 cy = panel_get_cursor_y(panel)-1;
    i32 px = panel->preferred_x;
    if (cy >= 0){
        panel->cursor = panel_compute_cursor_from_xy(panel, px, cy);
        panel->file->cursor.pos = panel->cursor.pos;
    }
}

internal void
command_move_down(Command_Data *command){
    Editing_Panel *panel = command->panel;
    i32 cy = panel_get_cursor_y(panel)+1;
    i32 px = panel->preferred_x;

    panel->cursor = panel_compute_cursor_from_xy(panel, px, cy);
    panel->file->cursor.pos = panel->cursor.pos;
}

internal void
command_seek_end_of_line(Command_Data *command){
    Editing_Panel *panel = command->panel;

    i32 pos = panel_find_end_of_line(panel, panel->cursor.pos);

    panel_cursor_move(panel, pos);
}

internal void
command_seek_beginning_of_line(Command_Data *command){
    Editing_Panel *panel = command->panel;

    i32 pos = panel_find_beginning_of_line(panel, panel->cursor.pos);

    panel_cursor_move(panel, pos);
}

internal void
command_seek_whitespace_right(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    u32 size = file->size;
    u8* data = (u8*)file->data;

    u32 pos = panel->cursor.pos;
    while (pos < size && character_is_any_whitespace(data[pos])){
        ++pos;
    }

    while (pos < size && !character_is_any_whitespace(data[pos])){
        ++pos;
    }

    panel_cursor_move(panel, pos);
}

internal void
command_seek_whitespace_left(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    u8* data = (u8*)file->data;

    u32 pos = panel->cursor.pos;
    --pos;
    while (pos > 0 && character_is_any_whitespace(data[pos])){
        --pos;
    }

    while (pos > 0 && !character_is_any_whitespace(data[pos])){
        --pos;
    }

    if (pos != 0){
        ++pos;
    }

    panel_cursor_move(panel, pos);
}

internal void
command_seek_whitespace_up(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    u8* data = (u8*)file->data;

    u32 pos = panel->cursor.pos;
    while (pos > 0 && character_is_any_whitespace(data[pos])){
        --pos;
    }

    bool32 no_hard_character = 0;
    while (pos > 0){
        if (starts_new_line(data[pos], file->endline_mode)){
            if (no_hard_character){
                break;
            }
            else{
                no_hard_character = 1;
            }
        }
        else{
            if (!character_is_any_whitespace(data[pos])){
                no_hard_character = 0;
            }
        }
        --pos;
    }

    if (pos != 0){
        ++pos;
    }

    if (file->endline_mode == ENDLINE_RN_COMBINED){
        pos = pos_adjust_to_self(pos, data, file->size);
    }

    panel_cursor_move(panel, pos);
}

internal void
command_seek_whitespace_down(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    i32 size = file->size;
    u8* data = (u8*)file->data;

    i32 pos = panel->cursor.pos;
    while (pos < size && character_is_any_whitespace(data[pos])){
        ++pos;
    }

    bool32 no_hard_character = 0;
    i32 prev_endline = -1;
    while (pos < size){
        if (starts_new_line(data[pos], file->endline_mode)){
            if (no_hard_character){
                break;
            }
            else{
                no_hard_character = 1;
                prev_endline = pos;
            }
        }
        else{
            if (!character_is_any_whitespace(data[pos])){
                no_hard_character = 0;
            }
        }
        ++pos;
    }

    if (prev_endline == -1 || prev_endline+1 >= size){
        pos = size-1;
    }
    else{
        pos = prev_endline+1;
    }

    panel_cursor_move(panel, pos);
}

internal void
command_seek_token_left(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    TentativeAssert(file->tokens_exist);
    i32 current_token;

    Cpp_Get_Token_Result get_result = cpp_get_token(&file->token_stack, panel->cursor.pos);
    current_token = get_result.token_index;

    // TODO(allen): Make nulltoken?
    if (current_token == -1){
        current_token = 0;
    }
    Cpp_Token *token = &file->token_stack.tokens[current_token];
    if (token->start == panel->cursor.pos && current_token > 0){
        --token;
    }

    panel_cursor_move(panel, token->start);
}

internal void
command_seek_token_right(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    TentativeAssert(file->tokens_exist);
    // TODO(allen): Make nulltoken?
    i32 current_token;

    Cpp_Get_Token_Result get_result = cpp_get_token(&file->token_stack, panel->cursor.pos);
    current_token = get_result.token_index;
    if (get_result.in_whitespace){
        ++current_token;
    }

    if (current_token >= file->token_stack.count){
        current_token = file->token_stack.count - 1;
    }

    Cpp_Token *token = &file->token_stack.tokens[current_token];

    panel_cursor_move(panel, token->start + token->size);
}

internal void
command_begin_search_state(Command_Data *command){
    App_Vars *vars = command->vars;
    vars->state = APP_STATE_SEARCH;
    vars->isearch.str =
        make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
    vars->isearch.reverse = 0;
    vars->isearch.pos = command->panel->cursor.pos;
}

internal void
command_begin_rsearch_state(Command_Data *command){
    App_Vars *vars = command->vars;
    vars->state = APP_STATE_SEARCH;
    vars->isearch.str =
        make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
    vars->isearch.reverse = 1;
    vars->isearch.pos = command->panel->cursor.pos;
}

internal void
command_set_mark(Command_Data *command){
    Editing_Panel *panel = command->panel;
    panel->mark = (i32)panel->cursor.pos;
}

internal void
command_copy(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_Working_Set *working_set = command->working_set;
    Range range = get_range(panel->cursor.pos, panel->mark);
    if (range.smaller < range.larger){
        u8 *data = (u8*)panel->file->data;
        if (panel->file->endline_mode == ENDLINE_RN_COMBINED){
            range = range_adjust_to_left(range, data);
        }
        clipboard_copy(working_set, data, range);
    }
}

internal void
command_cut(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_Working_Set *working_set = command->working_set;
    Range range = get_range(panel->cursor.pos, panel->mark);
    if (range.smaller < range.larger){
        Editing_File *file = panel->file;
        u8 *data = (u8*)file->data;
        if (file->endline_mode == ENDLINE_RN_COMBINED){
            range = range_adjust_to_left(range, data);
        }
        clipboard_copy(working_set, data, range);
        buffer_delete_range(file, range);
        panel->mark = pos_universal_fix(range.smaller,
                                        file->data, file->size,
                                        file->endline_mode);

        panel_measure_all_wrapped_y(panel);
        panel_cursor_move(panel, panel->mark);
    }
}

internal void
command_paste(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_Working_Set *working_set = command->working_set;
    if (working_set->clipboard_size > 0){
        panel->next_mode.rewrite = 1;

        Editing_File *file = panel->file;

        String *src = working_set_clipboard_head(working_set);
        i32 pos_left = panel->cursor.pos;
        if (file->endline_mode == ENDLINE_RN_COMBINED){
            pos_left = pos_adjust_to_left(pos_left, file->data);
        }
        panel_write_chunk(panel, src, pos_left);
        panel_measure_all_wrapped_y(panel);
        panel->mark = pos_universal_fix(pos_left,
                                        file->data, file->size,
                                        file->endline_mode);

        i32 ticks = 20;
        panel->paste_effect.start = pos_left;
        panel->paste_effect.end = pos_left + src->size;
        panel->paste_effect.color = file->style->paste_color;
        panel->paste_effect.tick_down = ticks;
        panel->paste_effect.tick_max = ticks;
    }
}

internal void
command_paste_next(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_Working_Set *working_set = command->working_set;
    if (working_set->clipboard_size > 0 && panel->mode.rewrite){
        panel->next_mode.rewrite = 1;

        Range range = get_range(panel->mark, panel->cursor.pos);

        if (range.smaller < range.larger){
            Editing_File *file = panel->file;
            if (file->endline_mode == ENDLINE_RN_COMBINED){
                range = range_adjust_to_left(range, file->data);
            }

            String *src = working_set_clipboard_roll_down(working_set);
            buffer_replace_range(file, range.smaller, range.larger,
                                 src->str, src->size);

            panel->cursor = panel_compute_cursor_from_pos(panel, range.smaller+src->size);
            panel->preferred_x = panel_get_cursor_x(panel);
            panel->file->cursor.pos = panel->cursor.pos;

            // TODO(allen): faster way to recompute line measurements afterwards
            buffer_measure_all_lines(file);
            panel_measure_all_wrapped_y(panel);
            panel->mark = pos_universal_fix(range.smaller,
                                            file->data, file->size,
                                            file->endline_mode);

            i32 ticks = 20;
            panel->paste_effect.start = range.smaller;
            panel->paste_effect.end = range.smaller + src->size;
            panel->paste_effect.color = file->style->paste_color;
            panel->paste_effect.tick_down = ticks;
            panel->paste_effect.tick_max = ticks;
        }
    }
}

internal void
command_delete_chunk(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Range range = get_range(panel->cursor.pos, panel->mark);
    if (range.smaller < range.larger){
        Editing_File *file = panel->file;
        if (file->endline_mode == ENDLINE_RN_COMBINED){
            range = range_adjust_to_left(range, file->data);
        }
        buffer_delete_range(file, range);
        panel_measure_all_wrapped_y(panel);
        panel->cursor = panel_compute_cursor_from_pos(panel, range.smaller);
        panel->mark = pos_universal_fix(range.smaller,
                                        file->data, file->size,
                                        file->endline_mode);
        panel->file->cursor.pos = panel->cursor.pos;
    }
}

// TODO(allen): Make this preserve order (look at layout_open_panel)
// Make this preserve old ratios or sizes of panels instead of
// resizing them all.
internal void
command_open_panel(Command_Data *command){
    Editing_Layout *layout = command->layout;
    Editing_Working_Set *working_set = command->working_set;
    // TODO(allen): This is wrong.  We should not be passing in the style
    // like this.  The system should be looking it up here.
    Editing_Style *style = command->style;
    // TODO(allen): change the screen info to real32 at the base level?
    real32 screen_full_width = (real32)command->screen_width;
    real32 screen_full_height = (real32)command->screen_height;
    real32 screen_y_off = (real32)command->screen_y_off;

    layout_open_panel(layout, working_set->files, style);
    real32 panel_w = ((real32)screen_full_width / layout->panel_count);
    real32 panel_x_pos = 0;

    for (i32 panel_i = 0; panel_i < layout->panel_count; ++panel_i){
        Editing_Panel *panel = &layout->panels[panel_i];

        panel->full_x = Floor(panel_x_pos);
        panel->full_w = Floor(panel_w);
        panel->full_y = Floor(screen_y_off);
        panel->full_h = Floor(screen_full_height - screen_y_off);

        panel_fix_internal_area(panel);

        panel_x_pos += panel_w;
    }
}

internal void
command_close_panel(Command_Data *command){
    Editing_Layout *layout = command->layout;
    i32 share_w = layout->panels[layout->active_panel].full_w;
    layout_close_panel(layout, layout->active_panel);
    layout_redistribute_width(layout, share_w);
}

internal Input_Request*
app_request_sys_file(App_Vars *vars, String query, String dest_init, bool32 fast_folder){
    Input_Request *request = vars->request_queue + (vars->request_count++);
    *request = {};
    request->type = REQUEST_SYS_FILE;
    request->sys_file.fast_folder = 1;

    // TODO(allen): Where does this memory come from IRL?  I don't like calling to
    // a system function all the time, knowing it might start doing weird things.
    // Better to put some limitations on the system so we can gaurantee the memory
    // this needs will exist.
    request->sys_file.query =
        make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
    request->sys_file.dest =
        make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);

    copy(&request->sys_file.query, query);
    copy(&request->sys_file.dest, dest_init);
    return request;
}


internal Input_Request*
app_request_live_file(App_Vars *vars, String query, String dest_init){
    Input_Request *request = vars->request_queue + (vars->request_count++);
    *request = {};
    request->type = REQUEST_LIVE_FILE;

    // TODO(allen): Where does this memory come from IRL?  I don't like calling to
    // a system function all the time, knowing it might start doing weird things.
    // Better to put some limitations on the system so we can gaurantee the memory
    // this needs will exist.
    request->live_file.query =
        make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
    request->live_file.dest =
        make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);

    copy(&request->live_file.query, query);
    copy(&request->live_file.dest, dest_init);
    return request;
}

internal void
panel_set_to_new(Editing_Panel *panel){
    panel->cursor = {};
    panel->cursor.pos =
        pos_adjust_to_self(0, panel->file->data, panel->file->size);
    panel->scroll_y = 0;
    panel->target_y = 0;
    panel->vel_y = 1.f;
    panel->scroll_x = 0;
    panel->target_x = 0;
    panel->vel_x = 1.f;
}

internal void
command_reopen(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    if (!file->is_dummy){
        Editing_File temp_file;
        if (buffer_load(&temp_file, (u8*)make_c_str(file->source_path))){
            buffer_close(file);

            // TODO(allen): Deduplicate!!
            Cpp_File cpp_file;
            cpp_file.data = temp_file.data;
            cpp_file.size = temp_file.size;
            {
                i32 size = cpp_lex_file_token_count(cpp_file);
                size = cpp_get_token_stack_size(size);
                temp_file.token_stack = cpp_make_token_stack(size);
            }
            cpp_lex_file(cpp_file, &temp_file.token_stack);
            temp_file.tokens_complete = 1;
            temp_file.tokens_exist = 1;

            *file = temp_file;
            panel_set_to_new(panel);
            panel_measure_all_wrapped_y(panel);
            // TODO(allen): Update all other panels that also view this file.
        }
    }
}

internal void
command_interactive_open(Command_Data *command){
    App_Vars *vars = command->vars;
    if (command->request_count == 0){
        // TODO(allen): Can all of this be simplified?  I don't like having this much
        // complexity for issuing a single request.
        vars->pending_command = command_interactive_open;

        Assert(vars->request_count == 0);
        app_request_sys_file(vars, make_lit_string("Open: "), vars->hot_directory.string, 1);

        hot_directory_reload_list(&vars->hot_directory);
    }
    else{
        String *string = &command->requests[0].sys_file.dest;
        Editing_File *new_file;
        new_file = buffer_open(&vars->working_set, string->str, command->style);

        if (!new_file){
            // TODO(allen): Here's a really tough one.  This crap is now happening twice.
            // Once here and also in the single_file_input function which checks all of it
            // whenever the fast_folder_select option is on to see if the user is selecting
            // a folder.  It would be nice to be able to share that information to here when it
            // is available so we could avoid doing the exact same computations here.
            u8 front_name_space[256];
            String front_name =
                make_string(front_name_space, 0, ArrayCount(front_name_space));
            get_front_of_directory(&front_name, *string);

            Hot_Directory_Match match =
                hot_directory_first_match(&vars->hot_directory,
                                          front_name, 1, 0);

            if (match.filename){
                // NOTE(allen): is_folder case is handled by the single_file_input function
                // which would only pass control to this command if the user did not match to
                // a folder.
                Assert(!match.is_folder);
                new_file = buffer_open(&vars->working_set, string->str, command->style);
            }
        }

        if (new_file){
            Editing_Panel *active_panel = command->panel;
            active_panel->file = new_file;
            panel_set_to_new(active_panel);
            panel_measure_all_wrapped_y(active_panel);

            Cpp_File file;
            file.data = new_file->data;
            file.size = new_file->size;
            // TODO(allen): Where should the memory for tokens come from IRL?
            {
                i32 size = cpp_lex_file_token_count(file);
                size = cpp_get_token_stack_size(size);
                new_file->token_stack = cpp_make_token_stack(size);
            }
            cpp_lex_file(file, &new_file->token_stack);
            new_file->tokens_complete = 1;
            new_file->tokens_exist = 1;
        }
    }
}

internal void
command_save(Command_Data *command){
    Editing_Panel *panel = command->panel;
    String *file_path = &panel->file->source_path;
    if (file_path->size > 0){
        buffer_save(panel->file, file_path->str);
    }
}

internal void
command_interactive_save_as(Command_Data *command){
    App_Vars *vars = command->vars;
    if (command->request_count == 0){
        vars->pending_command = command_interactive_save_as;

        Assert(vars->request_count == 0);
        app_request_sys_file(vars, make_lit_string("Save As: "), vars->hot_directory.string, 1);

        hot_directory_reload_list(&vars->hot_directory);
    }
    else{
        String *string = &command->requests[0].sys_file.dest;

        buffer_save_and_set_names(command->panel->file, string->str);
    }
}

internal void
command_change_active_panel(Command_Data *command){
    Editing_Layout *layout = command->layout;
    if (layout->panel_count > 1){
        ++layout->active_panel;
        if (layout->active_panel >= layout->panel_count){
            layout->active_panel = 0;
        }
    }
}

internal void
command_interactive_switch_file(Command_Data *command){
    App_Vars *vars = command->vars;
    if (command->request_count == 0){
        vars->pending_command = command_interactive_switch_file;

        Assert(vars->request_count == 0);
        app_request_live_file(vars, make_lit_string("Switch To: "), make_lit_string(""));
    }
    else{
        String *string = &command->requests[0].live_file.dest;

        Editing_File *file;
        file = working_set_lookup_file(command->working_set, *string);

        if (file){
            Editing_Panel *active_panel = command->panel;
            active_panel->file = file;
            Panel_Cursor_Data cursor_data;
            cursor_data = panel_compute_cursor_from_pos(active_panel, file->cursor.pos);
            active_panel->cursor = cursor_data;
        }
    }
}

internal void
command_kill_file(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    if (file && file->max_size != 0){
        buffer_close(file);
        buffer_get_dummy(file, command->style);
    }
}

internal void
command_interactive_kill_file(Command_Data *command){
    App_Vars *vars = command->vars;
    if (command->request_count == 0){
        vars->pending_command = command_interactive_kill_file;

        Assert(vars->request_count == 0);
        app_request_live_file(vars, make_lit_string("Kill: "), make_lit_string(""));
    }
    else{
        String *string = &command->requests[0].live_file.dest;

        Editing_File *file;
        file = working_set_lookup_file(&vars->working_set, *string);

        if (file){
            buffer_close(file);
            buffer_get_dummy(file, command->style);
        }
    }
}

internal void
command_toggle_line_wrap(Command_Data *command){
    Editing_Panel *panel = command->panel;
    if (panel->unwrapped_lines){
        panel->unwrapped_lines = 0;
        panel->target_x = 0;
        panel->cursor =
            panel_compute_cursor_from_pos(panel, panel->cursor.pos);
    }
    else{
        panel->unwrapped_lines = 1;
        panel->cursor =
            panel_compute_cursor_from_pos(panel, panel->cursor.pos);
    }
}

internal void
command_toggle_endline_mode(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    switch (file->endline_mode){
        case ENDLINE_RN_COMBINED:
        {
            panel->cursor.pos = pos_adjust_to_left(panel->cursor.pos, panel->file->data);
            file->endline_mode = ENDLINE_RN_SEPARATE;
            panel->cursor =
                panel_compute_cursor_from_pos(panel, panel->cursor.pos);
        }break;

        case ENDLINE_RN_SEPARATE:
        {
            file->endline_mode = ENDLINE_RN_SHOWALLR;
            panel->cursor =
                panel_compute_cursor_from_pos(panel, panel->cursor.pos);
        }break;

        case ENDLINE_RN_SHOWALLR:
        {
            panel->cursor.pos = pos_adjust_to_self(panel->cursor.pos, panel->file->data,
                                                   panel->file->size);
            file->endline_mode = ENDLINE_RN_COMBINED;
            panel->cursor =
                panel_compute_cursor_from_pos(panel, panel->cursor.pos);
        }break;
    }
}

internal void
command_to_uppercase(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Range range = get_range(panel->cursor.pos, panel->mark);
    if (range.smaller < range.larger){
        Editing_File *file = panel->file;
        u8 *data = file->data;
        for (i32 i = range.smaller; i < range.larger; ++i){
            if (data[i] >= 'a' && data[i] <= 'z'){
                data[i] += (u8)('A' - 'a');
            }
        }
        // TODO(allen): RELEX POINT
        if (file->token_stack.tokens){
            Cpp_File cpp_file;
            cpp_file.size = file->size;
            cpp_file.data = (u8*)file->data;
            cpp_relex_file(cpp_file, &file->token_stack, range.smaller, range.larger, 0);
            file->tokens_complete = 1;
            file->tokens_exist = 1;
        }
    }
}

internal void
command_to_lowercase(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Range range = get_range(panel->cursor.pos, panel->mark);
    if (range.smaller < range.larger){
        Editing_File *file = panel->file;
        u8 *data = file->data;
        for (i32 i = range.smaller; i < range.larger; ++i){
            if (data[i] >= 'A' && data[i] <= 'Z'){
                data[i] -= (u8)('A' - 'a');
            }
        }
        // TODO(allen): RELEX POINT
        if (file->token_stack.tokens){
            Cpp_File cpp_file;
            cpp_file.size = file->size;
            cpp_file.data = (u8*)file->data;
            cpp_relex_file(cpp_file, &file->token_stack, range.smaller, range.larger, 0);
            file->tokens_complete = 1;
            file->tokens_exist = 1;
        }
    }
}

internal void
command_toggle_show_whitespace(Command_Data *command){
    Editing_Panel *panel = command->panel;
    panel->show_whitespace = !panel->show_whitespace;
}

internal void
command_clean_line(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    u8 *data = (u8*)file->data;

    i32 pos = panel_find_beginning_of_line(panel, panel->cursor.pos);

    i32 last_hard_start = pos-1;
    i32 last_hard = last_hard_start;
    while (pos < file->size && data[pos] != '\n'){
        if (!character_is_any_whitespace(data[pos])){
            last_hard = pos;
        }
        ++pos;
    }

    if (last_hard != last_hard_start){
        pos = pos_adjust_to_left(pos, data);

        if (last_hard + 1 < pos){
            buffer_replace_range(file, last_hard+1, pos, 0, 0, REP_WHITESPACE);
            panel_measure_all_wrapped_y(panel);

            if (panel->cursor.pos > last_hard){
                panel->cursor = panel_compute_cursor_from_pos(panel, last_hard + 1);
            }
            if (panel->mark > last_hard && panel->mark <= pos){
                panel->mark = pos_adjust_to_self(last_hard+1, file->data, file->size);
            }
            else if (panel->mark > pos){
                panel->mark -= pos - (last_hard + 1);
            }
        }
    }
    else{
        panel_measure_all_wrapped_y(panel);
        panel_auto_tab(panel, pos, pos);
    }
}

internal void
command_clean_all_lines(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Editing_File *file = panel->file;
    u8 *data = (u8*)file->data;

    i32 cursor_pos = panel->cursor.pos;
    i32 pos = 0;
    i32 last_hard = -1;
    bool32 is_all_white = 1;
    while (pos <= file->size){
        if (pos == file->size || data[pos] == '\n'){
            i32 line_pos = pos;
            if (pos < file->size && data[pos] == '\n'){
                line_pos = pos_adjust_to_left(pos, data);
            }

            // TODO(allen): This should be optimized by either keeping track of the nesting level
            // in this funciton, or by at least having a nesting hint that is used and updated
            // every time an auto tab happens.  Also auto tab should take a nesting hint.
            if (is_all_white){
                panel_auto_tab(panel, pos, pos);
            }
            else{
                if (last_hard + 1 < line_pos){
                    buffer_replace_range(file, last_hard+1, line_pos, 0, 0, REP_WHITESPACE);

                    if (cursor_pos > last_hard && cursor_pos <= pos){
                        cursor_pos = pos_adjust_to_self(last_hard+1, file->data, file->size);
                    }
                    else if (cursor_pos > pos){
                        cursor_pos -= line_pos - (last_hard + 1);
                    }
                    if (panel->mark > last_hard && panel->mark <= pos){
                        panel->mark = pos_adjust_to_self(last_hard+1, file->data, file->size);
                    }
                    else if (panel->mark > pos){
                        panel->mark -= line_pos - (last_hard + 1);
                    }
                    pos -= line_pos - (last_hard + 1);
                }
            }

            last_hard = pos;
            is_all_white = 1;
        }
        else if (!character_is_any_whitespace(data[pos])){
            last_hard = pos;
            is_all_white = 0;
        }
        ++pos;
    }
    panel_measure_all_wrapped_y(panel);
    panel->cursor = panel_compute_cursor_from_pos(panel, cursor_pos);
}

internal void
command_eol_dosify(Command_Data *command){
    Editing_Panel *panel = command->panel;
    panel_endline_convert(panel, ENDLINE_RN, ENDLINE_ERASE, ENDLINE_RN);
    panel_measure_all_wrapped_y(panel);
}

internal void
command_eol_nixify(Command_Data *command){
    Editing_Panel *panel = command->panel;
    panel_endline_convert(panel, ENDLINE_N, ENDLINE_ERASE, ENDLINE_N);
    panel_measure_all_wrapped_y(panel);
}

internal void
command_auto_tab(Command_Data *command){
    Editing_Panel *panel = command->panel;
    Range range = get_range(panel->cursor.pos, panel->mark);
    panel_auto_tab(panel, range.smaller, range.larger);
    panel_measure_all_wrapped_y(panel);
}

internal void
setup_commands(Command_Map *commands, Key_Codes *codes){
    commands->basic_mode_key[codes->left] = command_move_left;
    commands->basic_mode_key[codes->right] = command_move_right;
    commands->basic_mode_key[codes->del] = command_delete;
    commands->basic_mode_key[codes->back] = command_backspace;
    commands->basic_mode_key[codes->up] = command_move_up;
    commands->basic_mode_key[codes->down] = command_move_down;
    commands->basic_mode_key[codes->end] = command_seek_end_of_line;
    commands->basic_mode_key[codes->home] = command_seek_beginning_of_line;

#if 0
    commands->control_key[codes->right] = command_seek_token_right;
    commands->control_ascii['m'] = command_seek_token_right;
    commands->control_key[codes->left] = command_seek_token_left;
    commands->control_ascii['n'] = command_seek_token_left;
#else
    commands->control_key[codes->right] = command_seek_whitespace_right;
    commands->control_ascii['m'] = command_seek_whitespace_right;
    commands->control_key[codes->left] = command_seek_whitespace_left;
    commands->control_ascii['n'] = command_seek_whitespace_left;
#endif

    commands->control_key[codes->up] = command_seek_whitespace_up;
    commands->control_ascii['y'] = command_seek_whitespace_up;
    commands->control_key[codes->down] = command_seek_whitespace_down;
    commands->control_ascii['h'] = command_seek_whitespace_down;

    commands->control_ascii['\t'] = command_auto_tab;

    commands->control_ascii[' '] = command_set_mark;
    commands->control_ascii['c'] = command_copy;
    commands->control_ascii['x'] = command_cut;
    commands->control_ascii['v'] = command_paste;
    commands->control_ascii['V'] = command_paste_next;
    commands->control_ascii['d'] = command_delete_chunk;
    commands->control_ascii['p'] = command_open_panel;
    commands->control_ascii['P'] = command_close_panel;
    commands->control_ascii['o'] = command_interactive_open;
    commands->control_ascii['O'] = command_reopen;
    commands->control_ascii['s'] = command_save;
    commands->control_ascii['w'] = command_interactive_save_as;
    commands->control_ascii[','] = command_change_active_panel;
    commands->control_ascii['i'] = command_interactive_switch_file;
    commands->control_ascii['k'] = command_interactive_kill_file;
    commands->control_ascii['K'] = command_kill_file;
    commands->control_ascii['l'] = command_toggle_line_wrap;
    commands->control_ascii['L'] = command_toggle_endline_mode;
    commands->control_ascii['u'] = command_to_uppercase;
    commands->control_ascii['j'] = command_to_lowercase;
    commands->control_ascii['?'] = command_toggle_show_whitespace;
    commands->control_ascii['`'] = command_clean_line;
    commands->control_ascii['~'] = command_clean_all_lines;
    commands->control_ascii['1'] = command_eol_dosify;
    commands->control_ascii['!'] = command_eol_nixify;
    commands->control_ascii['f'] = command_begin_search_state;
    commands->control_ascii['r'] = command_begin_rsearch_state;
}

/*
 * Interactive Bar
 */

internal void
intbar_draw_string(Render_Target *target,
                   Interactive_Bar *bar, u8 *str,
                   u32 char_color){
    i32 char_w = font_get_character_width(bar->style.font);
    for (i32 i = 0; str[i]; ++i){
        font_draw_glyph(target, bar->style.font, str[i],
                        (real32)bar->pos_x, (real32)bar->pos_y, char_color);
        bar->pos_x += char_w;
    }
}

internal void
intbar_draw_string(Render_Target *target,
                   Interactive_Bar *bar, String str,
                   u32 char_color){
    i32 char_w = font_get_character_width(bar->style.font);
    for (i32 i = 0; i < str.size; ++i){
        font_draw_glyph(target, bar->style.font, str.str[i],
                        (real32)bar->pos_x, (real32)bar->pos_y, char_color);
        bar->pos_x += char_w;
    }
}

internal void
hot_directory_draw_helper(Render_Target *target,
                          Hot_Directory *hot_directory,
                          Interactive_Bar *bar, String *string,
                          bool32 include_files){
    persist u8 str_open_bracket[] = " {";
    persist u8 str_close_bracket[] = "}";
    persist u8 str_comma[] = ", ";

    intbar_draw_string(target, bar, *string, bar->style.pop1_color);

    u8 front_name_space[256];
    String front_name =
        make_string(front_name_space, 0, ArrayCount(front_name_space));

    get_front_of_directory(&front_name, *string);

    intbar_draw_string(target, bar, str_open_bracket, bar->style.base_color);

    bool32 is_first_string = 1;

    File_List files = hot_directory->file_list;
    File_List_Iterator files_it = files_iterator_init(&files);

    while (files_it.filename_ptr &&
           (include_files || files_it.folder_stage)){
        u8 *filename = *files_it.filename_ptr;

        if (front_name.size == 0 || has_substr_unsensitive(filename, front_name)){
            if (is_first_string){
                is_first_string = 0;
            }
            else{
                intbar_draw_string(target, bar, str_comma, bar->style.base_color);
            }
            if (files_it.folder_stage){
                intbar_draw_string(target, bar, filename, bar->style.pop1_color);
                intbar_draw_string(target, bar, (u8*)"/", bar->style.pop1_color);
            }
            else{
                intbar_draw_string(target, bar, filename, bar->style.base_color);
            }
        }

        files_iterator_step(&files, &files_it);
    }
    intbar_draw_string(target, bar, str_close_bracket, bar->style.base_color);
}

internal void
live_file_draw_helper(Render_Target *target,
                      Editing_Working_Set *working_set,
                      Interactive_Bar *bar, String *string){
    persist u8 str_open_bracket[] = " {";
    persist u8 str_close_bracket[] = "}";
    persist u8 str_comma[] = ", ";

    intbar_draw_string(target, bar, *string, bar->style.base_color);

    intbar_draw_string(target, bar, str_open_bracket, bar->style.base_color);

    bool32 is_first_string = 1;
    for (i32 file_i = 0;
         file_i < working_set->file_index_count;
         ++file_i){
        Editing_File *file = &working_set->files[file_i];
        if (file->live_name.str &&
            (string->size == 0 || has_substr_unsensitive(file->live_name, *string))){
            if (is_first_string){
                is_first_string = 0;
            }
            else{
                intbar_draw_string(target, bar, str_comma, bar->style.base_color);
            }
            intbar_draw_string(target, bar, file->live_name, bar->style.base_color);
        }
    }
    intbar_draw_string(target, bar, str_close_bracket, bar->style.base_color);
}

/*
 * App Functions
 */

internal bool32
app_init(Thread_Context *thread, Application_Memory *memory,
         Key_Codes *loose_codes, Clipboard_Contents clipboard){
    Partition_Cursor partition =
        partition_open(memory->vars_memory, memory->vars_memory_size);

    App_Vars *vars = (App_Vars*)
        partition_allocate(&partition, sizeof(App_Vars));

    Assert(vars);
    *vars = {};

    u32 panel_max_count = vars->layout.panel_max_count = 4;
    u32 panel_count = vars->layout.panel_count = 1;
    Assert(panel_max_count >= 1);
    Editing_Panel *panels = vars->layout.panels = (Editing_Panel*)
        partition_allocate(&partition, sizeof(Editing_Panel)*panel_max_count);

    Assert(panels);
    // TODO(allen): improved Assert

    // NOTE(allen): command map setup
    setup_commands(&vars->map, loose_codes);

    // NOTE(allen): font setup
    if (font_init() != 0){
        FatalError("Error initializing fonts");
        return 0;
    }

    i32 memory_used = 0;
    if (font_load(&vars->font, 15, memory->font_memory,
                  font_predict_size(15), &memory_used) != 0){
        FatalError("Could not find any fonts");
    }

    // NOTE(allen): file setup
    vars->working_set.file_index_count = 1;
    vars->working_set.file_max_count = 29;

    vars->working_set.files = (Editing_File*)
        partition_allocate(&partition,
                           sizeof(Editing_File)*vars->working_set.file_max_count);

    buffer_get_dummy(&vars->working_set.files[0], &vars->style);

    // NOTE(allen): clipboard setup
    vars->working_set.clipboard_max_size = ArrayCount(vars->working_set.clipboards);
    vars->working_set.clipboard_size = 0;
    vars->working_set.clipboard_current = 0;
    vars->working_set.clipboard_rolling = 0;

    if (clipboard.str){
        String *dest = working_set_next_clipboard_string(&vars->working_set, clipboard.size);
        copy(dest, make_string(clipboard.str, clipboard.size));
    }

    // TODO(allen): more robust allocation solution for the clipboard?

    // NOTE(allen): style setup
    // TODO(allen): style_set_font function
    vars->style.font = &vars->font;
    vars->style.font_metrics.character_width = vars->font.glyphs[' '].advance;
    vars->style.font_metrics.line_skip = vars->font.line_skip;

    vars->style.back_color = 0xFF0C0C0C;
    vars->style.margin_color = 0xFF181818;
    vars->style.cursor_color = 0xFF00EE00;
    vars->style.highlight_color = 0xFFDDEE00;
    vars->style.mark_color = 0xFF494949;
    vars->style.default_color = 0xFF90B080;
    vars->style.at_cursor_color = vars->style.back_color;
    vars->style.at_highlight_color = 0xFFFF44DD;
    vars->style.comment_color = 0xFF2090F0;
    vars->style.keyword_color = 0xFFD08F20;
    vars->style.constant_color = 0xFF50FF30;
    vars->style.special_character_color = 0xFFFF0000;

    vars->style.use_paste_color = 1;
    vars->style.paste_color = 0xFFDDEE00;

    vars->style.highlight_junk_color = 0x44FF0000;
    vars->style.highlight_white_color = 0x1100FFFF;
    vars->style.tab_width = 4;
    vars->style.margin_width = 5;

    Interactive_Style file_info_style;
    file_info_style.font = &vars->font;
    file_info_style.bar_color = 0xFF888888;
    file_info_style.base_color = 0xFF000000;
    file_info_style.pop1_color = 0xFF4444AA;
    file_info_style.pop2_color = 0xFFFF0000;
    file_info_style.height = vars->style.font->line_skip + 2;

    vars->style.file_info_style = file_info_style;

    Interactive_Style command_style;
    command_style.font = &vars->font;
    command_style.bar_color = 0xFF0C0C0C;
    command_style.base_color = 0xFFDDDDBB;
    command_style.pop1_color = 0xFF4444AA;
    command_style.pop2_color = 0xFF44FF44;
    command_style.height = vars->style.font->line_skip + 2;

    vars->command_style = command_style;

    // NOTE(allen): panel setup
    AllowLocal(panel_count);
    panel_init(&panels[0], &vars->working_set.files[0]);

    // NOTE(allen): hot directory setup
    hot_directory_init(&vars->hot_directory);

    // NOTE(allen): request stack setup
    vars->request_count = 0;
    vars->request_filled = 0;
    vars->request_max = ArrayCount(vars->request_queue);

    return 1;
}

struct Single_Line_Input_Step{
    bool32 hit_newline;
    bool32 hit_ctrl_newline;
    bool32 hit_a_character;
    bool32 hit_backspace;
    bool32 hit_esc;
    bool32 made_a_change;
    bool32 did_command;
};

enum Single_Line_Input_Type{
    SINGLE_LINE_STRING,
    SINGLE_LINE_FILE
};

struct Single_Line_Mode{
    Single_Line_Input_Type type;
    String *string;
    Hot_Directory *hot_directory;
    bool32 fast_folder_select;
};

internal Single_Line_Input_Step
app_single_line_input_core(Key_Codes *codes,
                           Key_Input_Data *input,
                           Single_Line_Mode mode){
    Single_Line_Input_Step result = {};

    if (input->has_press){
        if (input->press.keycode == codes->back){
            result.hit_backspace = 1;
            switch (mode.type){
            case SINGLE_LINE_STRING:
            {
                // TODO(allen): Is this still okay?  It looks like it's keeping a NULL terminator
                // which is at least unecessary given the new protocol.
                if (mode.string->size > 0){
                    --mode.string->size;
                    mode.string->str[mode.string->size] = 0;
                    result.made_a_change = 1;
                }
            }break;

            case SINGLE_LINE_FILE:
            {
                if (mode.string->size > 0){
                    --mode.string->size;
                    i8 end_character = mode.string->str[mode.string->size];
                    if (character_is_slash(end_character)){
                        mode.string->size = reverse_seek_slash(*mode.string) + 1;
                        // TODO(allen): Is this still okay?  It looks like it's keeping a NULL terminator
                        // which is at least unecessary given the new protocol.
                        // TODO(allen): What to do when the string becomes empty though?
                        mode.string->str[mode.string->size] = 0;
                        hot_directory_set(mode.hot_directory, *mode.string);
                    }
                    else{
                        // TODO(allen): Is this still okay?  It looks like it's keeping a NULL terminator
                        // which is at least unecessary given the new protocol.
                        mode.string->str[mode.string->size] = 0;
                    }
                    result.made_a_change = 1;
                }
            }break;
            }
        }

        else if (input->press.keycode == codes->newline){
            if (input->control_keys[CONTROL_KEY_CONTROL]){
                result.hit_ctrl_newline = 1;
                result.made_a_change = 1;
            }

            else{
                result.made_a_change = 1;
                if (mode.fast_folder_select){
                    u8 front_name_space[256];
                    String front_name =
                        make_string(front_name_space, 0, ArrayCount(front_name_space));
                    get_front_of_directory(&front_name, *mode.string);
                    Hot_Directory_Match match;
                    match = hot_directory_first_match(mode.hot_directory, front_name, 1, 1);

                    if (!match.filename){
                        match = hot_directory_first_match(mode.hot_directory, front_name, 1, 0);
                    }

                    if (match.filename){
                        if (match.is_folder){
                            set_last_folder(mode.string, match.filename);
                            hot_directory_set(mode.hot_directory, *mode.string);
                        }
                        else{
                            mode.string->size = reverse_seek_slash(*mode.string) + 1;
                            append(mode.string, match.filename);
                            result.hit_newline = 1;
                        }
                    }
                    else{
                        result.hit_newline = 1;
                    }
                }
                else{
                    result.hit_newline = 1;
                }
            }
        }

        else if (input->press.keycode == codes->esc){
            result.hit_esc = 1;
            result.made_a_change = 1;
        }

        else if (input->press.character){
            result.hit_a_character = 1;
            if (!input->control_keys[CONTROL_KEY_CONTROL]){
                switch (mode.type){
                case SINGLE_LINE_STRING:
                {
                    if (mode.string->size+1 < mode.string->memory_size){
                        mode.string->str[mode.string->size] = (i8)input->press.character;
                        mode.string->size++;
                        // TODO(allen): More of this keeping a NULL terminator business...
                        // I want out of this business for the String struct.
                        mode.string->str[mode.string->size] = 0;
                        result.made_a_change = 1;
                    }
                }break;

                case SINGLE_LINE_FILE:
                {
                    if (mode.string->size+1 < mode.string->memory_size){
                        i8 new_character = (i8)input->press.character;
                        mode.string->str[mode.string->size] = new_character;
                        mode.string->size++;
                        // TODO(allen): More of this keeping a NULL terminator business...
                        // I want out of this business for the String struct.
                        mode.string->str[mode.string->size] = 0;

                        if (character_is_slash(new_character)){
                            hot_directory_set(mode.hot_directory, *mode.string);
                        }
                        result.made_a_change = 1;
                    }
                }break;
                }
            }

            else{
                result.did_command = 1;
                result.made_a_change = 1;
            }
        }
    }

    return result;
}

inline internal Single_Line_Input_Step
app_single_line_input_step(Key_Codes *codes, Key_Input_Data *input, String *string){
    Single_Line_Mode mode = {};
    mode.type = SINGLE_LINE_STRING;
    mode.string = string;
    return app_single_line_input_core(codes, input, mode);
}

inline internal Single_Line_Input_Step
app_single_file_input_step(Key_Codes *codes, Key_Input_Data *input,
                           String *string, Hot_Directory *hot_directory,
                           bool32 fast_folder_select){
    Single_Line_Mode mode = {};
    mode.type = SINGLE_LINE_FILE;
    mode.string = string;
    mode.hot_directory = hot_directory;
    mode.fast_folder_select = fast_folder_select;
    return app_single_line_input_core(codes, input, mode);
}

inline internal Command_Function
app_get_command(App_Vars *vars, Key_Input_Data *input, Key_Event_Data key){
    Command_Function function = 0;
    if (input->control_keys[CONTROL_KEY_CONTROL]){
        if (key.character_no_caps_lock){
            function = vars->map.control_ascii[key.character_no_caps_lock];
        }
        else{
            function = vars->map.control_key[key.keycode];
        }
    }
    else if (!input->control_keys[CONTROL_KEY_ALT]){
        if (key.character != 0){
            function = command_write_character;
        }
        else{
            function = vars->map.basic_mode_key[key.keycode];
        }
    }
    return function;
}

internal bool32
smooth_camera_step(real32 *target, real32 *current, real32 *vel, real32 S, real32 T){
    bool32 result = 0;
    real32 targ = *target;
    real32 curr = *current;
    real32 v = *vel;
    if (curr != targ){
        real32 L = lerp(curr, T, targ);

        i32 sign = (targ > curr) - (targ < curr);
        real32 V = curr + sign*v;

        if (sign > 0){
            curr = Min(L, V);
        }
        else{
            curr = Max(L, V);
        }

        if (curr == V){
            v *= S;
        }

        if (curr > targ - .0001f && curr < targ + .0001f){
            curr = targ;
            v = 1.f;
        }
        *target = targ;
        *current = curr;
        *vel = v;
        result = 1;
    }
    return result;
}

#if FRED_INTERNAL
Application_Memory *GLOBAL;
#endif

internal Application_Step_Result
app_step(Thread_Context *thread, Key_Codes *codes,
         Key_Input_Data *input, Mouse_State *mouse,
         bool32 time_step, Render_Target *target,
         Application_Memory *memory,
         Clipboard_Contents clipboard,
         bool32 first_step){

#if FRED_INTERNAL
    GLOBAL = memory;
#endif

    ProfileStart(app_step);
    ProfileSection(thread, app_step, "start");

    Application_Step_Result app_result = {};
    app_result.redraw = 1;

    App_Vars *vars = (App_Vars*)memory->vars_memory;

    // TODO(allen): Let's find another way to do first_step
    // so we can get it out of app_step and the platform layer.
    if (first_step || !time_step){
        app_result.redraw = 1;
    }

    Editing_Panel *panels = vars->layout.panels;
    Editing_Panel *active_panel = &panels[vars->layout.active_panel];
    ProfileSection(thread, app_step, "setup");

    // NOTE(allen): OS clipboard event handling
    if (clipboard.str){
        String *dest = working_set_next_clipboard_string(&vars->working_set, clipboard.size);
        copy(dest, make_string(clipboard.str, clipboard.size));
    }

    // NOTE(allen): check files are up to date
    for (i32 i = 0; i < vars->working_set.file_index_count; ++i){
        Editing_File *file = vars->working_set.files + i;

        if (!file->is_dummy){
            Time_Stamp time_stamp;
            time_stamp = system_file_time_stamp((u8*)make_c_str(file->source_path));

            if (time_stamp.success){
                file->last_sys_write_time = time_stamp.time;
                if (file->last_sys_write_time != file->last_4ed_write_time){
                    app_result.redraw = 1;
                }
            }
        }
    }
    ProfileSection(thread, app_step, "OS syncing");

    // NOTE(allen): keyboard input handling
    if (time_step){
        switch (vars->state){
        case APP_STATE_EDIT:
        {
            Command_Data command_data;
            command_data.panel = active_panel;
            command_data.working_set = &vars->working_set;
            command_data.layout = &vars->layout;
            command_data.style = &vars->style;
            command_data.vars = vars;
            command_data.screen_width = target->width;
            command_data.screen_height = target->height;
            command_data.screen_y_off = vars->command_style.height;
            command_data.requests = 0;
            command_data.request_count = 0;
            command_data.request_filled = 0;

            if (vars->request_count == 0){
                Command_Function function = 0;
                if (input->has_press){
                    command_data.key = input->press;
                    function = app_get_command(vars, input, command_data.key);
                }

                else if (input->has_hold){
                    command_data.key = input->hold;
                    function = app_get_command(vars, input, command_data.key);

                }

                if (function){
                    command_data.panel->next_mode = {};
                    function(&command_data);
                    app_result.redraw = 1;
                    command_data.panel->mode = command_data.panel->next_mode;
                }
            }

            else{

                Input_Request *active_request = vars->request_queue + vars->request_filled;
                bool32 killed_command = 0;
                switch (active_request->type){
                case REQUEST_SYS_FILE:
                {
                    String *string = &active_request->sys_file.dest;
                    Single_Line_Input_Step result =
                        app_single_file_input_step(codes, input, string, &vars->hot_directory, 1);

                    if (result.made_a_change){
                        app_result.redraw = 1;
                    }

                    if (result.hit_ctrl_newline){
                        active_request->sys_file.hit_ctrl_newline = 1;
                    }

                    if (result.hit_newline || result.hit_ctrl_newline){
                        ++vars->request_filled;
                    }

                    if (result.hit_esc){
                        app_clear_request_queue(vars);
                        killed_command = 1;
                    }
                }break;

                case REQUEST_LIVE_FILE:
                {
                    String *string = &active_request->live_file.dest;
                    Single_Line_Input_Step result =
                        app_single_line_input_step(codes, input, string);

                    if (result.made_a_change){
                        app_result.redraw = 1;
                    }

                    if (result.hit_ctrl_newline){
                        active_request->live_file.hit_ctrl_newline = 1;
                    }

                    if (result.hit_newline || result.hit_ctrl_newline){
                        ++vars->request_filled;
                    }

                    if (result.hit_esc){
                        app_clear_request_queue(vars);
                        killed_command = 1;
                    }
                }break;

                }

                if (vars->request_filled == vars->request_count && !killed_command){
                    command_data.requests = vars->request_queue;
                    command_data.request_count = vars->request_count;
                    command_data.request_filled = vars->request_filled;
                    vars->pending_command(&command_data);
                    app_clear_request_queue(vars);
                }
            }

        }break;

        case APP_STATE_SEARCH:
        {
            String *string = &vars->isearch.str;
            Single_Line_Input_Step result =
                app_single_line_input_step(codes, input, string);

            if (result.made_a_change){
                app_result.redraw = 1;

                Editing_File *file = active_panel->file;

                bool32 step_forward = 0;
                bool32 step_backward = 0;

                if (input->has_press){
                    Key_Event_Data key = input->press;
                    Command_Function function = app_get_command(vars, input, key);
                    if (function == command_begin_search_state){
                        step_forward = 1;
                    }
                    if (function == command_begin_rsearch_state){
                        step_backward = 1;
                    }
                }

                if (!step_forward && !step_backward &&
                    !input->has_press && input->has_hold){
                    Key_Event_Data key = input->hold;
                    Command_Function function = app_get_command(vars, input, key);
                    if (function == command_begin_search_state){
                        step_forward = 1;
                    }
                    if (function == command_begin_rsearch_state){
                        step_backward = 1;
                    }
                }

                i32 start_pos = vars->isearch.pos;
                if (step_forward){
                    if (vars->isearch.reverse){
                        start_pos = active_panel->temp_highlight.pos - 1;
                        vars->isearch.pos = start_pos;
                        vars->isearch.reverse = 0;
                    }
                }
                if (step_backward){
                    if (!vars->isearch.reverse){
                        start_pos = active_panel->temp_highlight.pos + 1;
                        vars->isearch.pos = start_pos;
                        vars->isearch.reverse = 1;
                    }
                }

                String file_string = make_string(file->data, file->size);

                i32 pos;

                if (vars->isearch.reverse){
                    if (result.hit_backspace){
                        start_pos = active_panel->temp_highlight.pos + 1;
                        vars->isearch.pos = start_pos;
                    }
                    else{
                        pos = rfind_substr(file_string, start_pos - 1, *string);

                        if (pos >= 0){
                            if (step_backward){
                                vars->isearch.pos = pos;
                                start_pos = pos;
                                pos = rfind_substr(file_string, start_pos - 1, *string);
                                if (pos == -1){
                                    pos = start_pos;
                                }
                            }

                            panel_set_temp_highlight(active_panel, pos, pos+string->size);
                        }
                    }
                }
                else{
                    if (result.hit_backspace){
                        start_pos = active_panel->temp_highlight.pos - 1;
                        vars->isearch.pos = start_pos;
                    }
                    else{
                        pos = find_substr(file_string, start_pos + 1, *string);

                        if (pos < file->size){
                            if (step_forward){
                                vars->isearch.pos = pos;
                                start_pos = pos;
                                pos = find_substr(file_string, start_pos + 1, *string);
                                if (pos == file->size){
                                    pos = start_pos;
                                }
                            }

                            panel_set_temp_highlight(active_panel, pos, pos+string->size);
                        }
                    }
                }
            }

            if (result.hit_newline || result.hit_ctrl_newline){
                panel_cursor_move(active_panel, active_panel->temp_highlight);

                system_free_memory(vars->isearch.str.str);
                vars->state = APP_STATE_EDIT;
            }

            if (result.hit_esc){
                active_panel->show_temp_highlight = 0;

                system_free_memory(vars->isearch.str.str);
                vars->state = APP_STATE_EDIT;
            }

        }break;

        case APP_STATE_RESIZING:
        {
            if (input->has_press){
                vars->state = APP_STATE_EDIT;
            }
        }break;
        }
    }
    ProfileSection(thread, app_step, "keyboard input");

    // NOTE(allen): reorganizing panels on screen
    i32 prev_width = vars->layout.full_width;
    i32 prev_height = vars->layout.full_height;
    i32 full_width = vars->layout.full_width = target->width;
    i32 full_height = vars->layout.full_height = target->height;

    if (prev_width != full_width || prev_height != full_height){
        layout_refit(&vars->layout, 0, vars->command_style.height,
                     full_width, full_height,
                     prev_width, prev_height);

        for (i32 panel_i = 0;
             panel_i < vars->layout.panel_count &&
                 vars->layout.panel_count > 1;
             ++panel_i){
            Editing_Panel *panel = &panels[panel_i];

            Editing_Style *style = panel->file->style;
            i32 character_w = style_get_character_width(style);
            i32 margin_width = style->margin_width;
            if (panel->full_w < character_w*6 + margin_width*2){
                i32 share_w = panel->full_w;
                layout_close_panel(&vars->layout, panel_i);
                layout_redistribute_width(&vars->layout, share_w);
                panel_i = 0;
            }
        }

        for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){
            Editing_Panel *panel = panels + panel_i;
            if (!panel->file->is_dummy){
                panel->cursor = panel_compute_cursor_from_pos(panel, panel->cursor.pos);
            }
        }

        app_result.redraw = 1;
    }
    ProfileSection(thread, app_step, "reorganizing panels");

    // NOTE(allen): mouse input handling
    if (time_step && !mouse->out_of_window){

        i32 mx = mouse->x;
        i32 my = mouse->y;
        bool32 mouse_press_event = 0;

        if (mouse->left_button && !mouse->left_button_prev){
            // TODO(allen):
            // This means any mouse use will be impossible, I guess it will have
            // to depend on the type of the request seen at the top???
            app_clear_request_queue(vars);
            mouse_press_event = 1;

            switch (vars->state){
            case APP_STATE_SEARCH:
            {
                active_panel->show_temp_highlight = 0;

                system_free_memory(vars->isearch.str.str);
                vars->state = APP_STATE_EDIT;
            }break;
            }
        }

        switch (vars->state){
        case APP_STATE_EDIT:
        {
            for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){

                Editing_Panel *panel = &panels[panel_i];

                if (mx >= panel->x &&
                    mx < panel->w + panel->x &&
                    my >= panel->y &&
                    my < panel->h + panel->y){

                    app_result.mouse_cursor_type = APP_MOUSE_CURSOR_IBEAM;

                    if (mouse_press_event){
                        if (!panel->file->is_dummy){
                            app_result.redraw = 1;

                            Font *font = &vars->font;

                            i32 character_w = style_get_character_width(panel->file->style);
                            i32 character_h = font->line_skip;
                            i32 max_line_length = panel_compute_max_line_length(panel);
                            i32 max_lines = panel_compute_max_lines(panel);

                            real32 grid_x = ((real32)mx - panel->x) / character_w;
                            real32 grid_y = ((real32)my - panel->y) / character_h;

                            vars->last_click_x = grid_x;
                            vars->last_click_y = grid_y;

                            if (grid_x >= 0 && grid_x <= max_line_length &&
                                grid_y >= 0 && grid_y < max_lines){

                                i32 pos_x = (i32)(grid_x + active_panel->scroll_x);
                                i32 pos_y = (i32)(grid_y + active_panel->scroll_y);

                                panel_cursor_move(active_panel, pos_x, pos_y);
                            }
                        }
                        vars->layout.active_panel = panel_i;
                        active_panel = panel;
                    }
                    else{
                        // NOTE(allen): mouse inside editing area but no click
                    }
                }

                else if (mx >= panel->full_x &&
                         mx < panel->full_w + panel->full_x &&
                         my >= panel->full_y &&
                         my < panel->full_h + panel->full_y){
                    // NOTE(allen): not inside the editing area but within the margins
                    bool32 resize_area = 0;
                    app_result.mouse_cursor_type = APP_MOUSE_CURSOR_ARROW;
                    if (mx >= (panel->full_x+panel->full_w+panel->x+panel->w)/2){
                        if (panel_i != vars->layout.panel_count-1){
                            app_result.mouse_cursor_type = APP_MOUSE_CURSOR_LEFTRIGHT;
                            resize_area = 1;
                        }
                    }
                    else if (mx <= (panel->full_x+panel->x)/2){
                        if (panel_i != 0){
                            app_result.mouse_cursor_type = APP_MOUSE_CURSOR_LEFTRIGHT;
                            resize_area = -1;
                        }
                    }

                    if (resize_area != 0 && mouse_press_event){
                        vars->state = APP_STATE_RESIZING;
                        if (resize_area == 1){
                            vars->resizing.left = panel;
                            vars->resizing.right = panel+1;
                        }
                        else if (resize_area == -1){
                            vars->resizing.left = panel-1;
                            vars->resizing.right = panel;
                        }
                    }
                }
            }

            i32 cursor_y = panel_get_cursor_y(active_panel);
            real32 target_y = active_panel->target_y;
            i32 max_lines = panel_compute_max_lines(active_panel);

            bool32 wheel_used;

            real32 delta_target_y = Max(1, max_lines / 3.f);
            delta_target_y *= -mouse->wheel;

            target_y += delta_target_y;

            if (target_y < 0){
                target_y = 0;
            }

            if (mouse->wheel == 0){
                wheel_used = 0;
            }
            else{
                wheel_used = 1;

                if (cursor_y >= target_y + max_lines){
                    cursor_y = (i32)target_y + max_lines - 1;
                }
                if (cursor_y < target_y){
                    cursor_y = (i32)target_y + 1;
                }
            }

            active_panel->target_y = target_y;
            if (cursor_y != panel_get_cursor_y(active_panel)){
                active_panel->cursor =
                    panel_compute_cursor_from_xy(active_panel,
                                                 active_panel->preferred_x,
                                                 cursor_y);
            }

            if (wheel_used){
                app_result.redraw = 1;
            }
        }break;

        case APP_STATE_RESIZING:
        {
            if (mouse->left_button){
                Editing_Panel *left = vars->resizing.left;
                Editing_Panel *right = vars->resizing.right;
                i32 left_x = left->full_x;
                i32 left_w = left->full_w;
                i32 right_x = right->full_x;
                i32 right_w = right->full_w;

                AllowLocal(right_x);

                i32 new_left_x = left_x;
                i32 new_left_w = mx - left_x;
                i32 new_right_w = right_w - (new_left_w - left_w);
                i32 new_right_x = mx;

                if (left_w != new_left_w){
                    app_result.redraw = 1;

                    Editing_Style *left_style = left->file->style;
                    Editing_Style *right_style = right->file->style;

                    i32 left_character_w = style_get_character_width(left_style);
                    i32 right_character_w = style_get_character_width(right_style);

                    i32 left_margin_width = left_style->margin_width;
                    i32 right_margin_width = right_style->margin_width;
                    if (new_left_w > left_margin_width*2 + left_character_w*6 &&
                        new_right_w > right_margin_width*2 + right_character_w*6){
                        left->full_x = new_left_x;
                        left->full_w = new_left_w;
                        right->full_x = new_right_x;
                        right->full_w = new_right_w;

                        panel_fix_internal_area(left);
                        panel_fix_internal_area(right);
                    }
                }
            }
            else{
                app_result.redraw = 1;
                vars->state = APP_STATE_EDIT;
            }
        }break;
        }
    }
    ProfileSection(thread, app_step, "mouse input");

    // NOTE(allen): fix scrolling on all panels
    for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){
        Editing_Panel *panel = &panels[panel_i];
        i32 cursor_y;
        if (panel->show_temp_highlight){
            if (panel->unwrapped_lines){
                cursor_y = panel->temp_highlight.unwrapped_y;
            }
            else{
                cursor_y = panel->temp_highlight.wrapped_y;
            }
        }
        else{
            if (panel->unwrapped_lines){
                cursor_y = panel->cursor.unwrapped_y;
            }
            else{
                cursor_y = panel->cursor.wrapped_y;
            }
        }
        real32 target_y = panel->target_y;
        real32 original_target_y = target_y;
        i32 max_lines = panel_compute_max_lines(panel);

        while (cursor_y >= Floor(target_y) + max_lines){
            target_y += 3.f;
        }

        while (cursor_y < target_y){
            target_y -= 3.f;
        }

        if (target_y < 0){
            target_y = 0;
        }

        panel->target_y = target_y;

        i32 cursor_x = panel_get_cursor_x(panel);
        real32 target_x = panel->target_x;
        real32 original_target_x = target_x;
        i32 max_x = panel_compute_max_line_length(panel);
        if (cursor_x < target_x){
            target_x = (real32)Max(0, cursor_x - max_x/2);
        }
        else if (cursor_x >= target_x + max_x){
            target_x = (real32)(cursor_x - max_x/2);
        }

        panel->target_x = target_x;

        if (original_target_y != panel->target_y ||
            original_target_x != panel->target_x){
            app_result.redraw;
        }
    }
    ProfileSection(thread, app_step, "fix scrolling");

    // NOTE(allen): execute animations
    for (i32 i = 0; i < vars->layout.panel_count; ++i){
        Editing_Panel *panel = vars->layout.panels + i;

        // TODO(allen): Scrolling parameterization in style?
        if (smooth_camera_step(&panel->target_y, &panel->scroll_y, &panel->vel_y, 2.f, 1.f/9.f)){
            app_result.redraw = 1;
        }

        if (smooth_camera_step(&panel->target_x, &panel->scroll_x, &panel->vel_x, 2.f, 1.f/6.f)){
            app_result.redraw = 1;
        }

        if (panel->paste_effect.tick_down > 0){
            --panel->paste_effect.tick_down;
            app_result.redraw = 1;
        }

    }
    ProfileSection(thread, app_step, "execute animations");

    if (app_result.redraw){
        // NOTE(allen): render the panels
        for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){
            Editing_Panel *panel = &panels[panel_i];
            Editing_Style *style = panel->file->style;

            i32 full_left = panel->full_x;
            i32 full_top = panel->full_y;
            i32 full_right = full_left + panel->full_w;
            i32 full_bottom = full_top + panel->full_h;

            u32 back_color = style->back_color;
            draw_rectangle_2corner(target, full_left, full_top, full_right, full_bottom, back_color);

            u32 side_margin_color = style->margin_color;

            panel_draw(thread, target, panel, vars->layout.active_panel == panel_i);

            // NOTE(allen): file info bar
            {
                Interactive_Bar bar;
                bar.style = style->file_info_style;
                bar.pos_x = panel->x;
                bar.pos_y = full_top;

                draw_rectangle(target, full_left, bar.pos_y,
                               panel->full_w, bar.style.height,
                               bar.style.bar_color);

                Editing_File *file = panel->file;
                if (!file->is_dummy){
                    intbar_draw_string(target, &bar, panel->file->live_name, bar.style.base_color);
                    intbar_draw_string(target, &bar, make_lit_string(" - "), bar.style.base_color);

                    u8 line_number_space[30];
                    String line_number = make_string(line_number_space, 0, 30);
                    append(&line_number, (u8*)"L#");
                    append_int_to_str(panel->cursor.line, &line_number);

                    intbar_draw_string(target, &bar, line_number, bar.style.base_color);

                    if (file->last_4ed_write_time != file->last_sys_write_time){
                        persist String out_of_sync = make_lit_string(" FILE SYNC");
                        intbar_draw_string(target, &bar, out_of_sync, bar.style.pop2_color);
                    }
                }
            }

            // L
            draw_rectangle_2corner(target, full_left, panel->y,
                                   panel->x, full_bottom, side_margin_color);

            // R
            draw_rectangle_2corner(target, panel->x + panel->w, panel->y,
                                   full_right, full_bottom, side_margin_color);

            // B
            draw_rectangle_2corner(target, full_left, panel->y + panel->h,
                                   full_right, full_bottom, side_margin_color);

            if (panel_i != 0){
                draw_rectangle_2corner(target, panel->full_x-1, panel->full_y,
                                       panel->full_x+1, panel->full_y+panel->full_h,
                                       0xFFFFFFFF);
            }
        }
        ProfileSection(thread, app_step, "render files");

        // NOTE (allen): command bar
        {
            Interactive_Bar bar;
            bar.style = vars->command_style;
            bar.pos_x = 0;
            bar.pos_y = 0;

            draw_rectangle(target, 0, 0,
                           target->width, bar.style.height,
                           bar.style.bar_color);

            switch (vars->state){
            case APP_STATE_EDIT:
            {
                if (vars->request_count > 0){
                    Input_Request *request = vars->request_queue + vars->request_filled;

                    switch (request->type){
                    case REQUEST_SYS_FILE:
                    {
                        intbar_draw_string(target, &bar, request->sys_file.query, bar.style.pop2_color);

                        String *string = &request->sys_file.dest;
                        hot_directory_draw_helper(target, &vars->hot_directory, &bar, string, 1);
                    }break;

                    case REQUEST_LIVE_FILE:
                    {
                        intbar_draw_string(target, &bar, request->sys_file.query, bar.style.pop2_color);

                        String *string = &request->sys_file.dest;
                        live_file_draw_helper(target, &vars->working_set, &bar, string);
                    }break;

                    }
                }
            }break;

            case APP_STATE_SEARCH:
            {
                persist String search_str = make_lit_string("I-Search: ");
                persist String rsearch_str = make_lit_string("Reverse-I-Search: ");
                if (vars->isearch.reverse){
                    intbar_draw_string(target, &bar, rsearch_str, bar.style.pop2_color);
                }
                else{
                    intbar_draw_string(target, &bar, search_str, bar.style.pop2_color);
                }

                intbar_draw_string(target, &bar, vars->isearch.str, bar.style.base_color);
            }break;

            case APP_STATE_RESIZING:
            {
                intbar_draw_string(target, &bar, make_lit_string("Resizing!"), bar.style.pop2_color);
            }break;
            }
        }
        ProfileSection(thread, app_step, "interaction bar");

    }

    ProfileEnd(thread, app_step, "total");

    return app_result;
}

// BOTTOM

