/*
 * Mr. 4th Dimention - Allen Webster
 *
 * 20.02.2016
 *
 * GUI system for 4coder
 *
 */

// TOP

struct Query_Slot{
    Query_Slot *next;
    Query_Bar *query_bar;
};

struct Query_Set{
    Query_Slot slots[8];
    Query_Slot *free_slot;
    Query_Slot *used_slot;
};

internal void
init_query_set(Query_Set *set){
    Query_Slot *slot = set->slots;
    int32_t i;
    set->free_slot = slot;
    set->used_slot = 0;
    for (i = 0; i+1 < ArrayCount(set->slots); ++i, ++slot){
        slot->next = slot + 1;
    }
}

internal Query_Slot*
alloc_query_slot(Query_Set *set){
    Query_Slot *slot = set->free_slot;
    if (slot != 0){
        set->free_slot = slot->next;
        slot->next = set->used_slot;
        set->used_slot = slot;
    }
    return(slot);
}

internal void
free_query_slot(Query_Set *set, Query_Bar *match_bar){
    Query_Slot *slot = 0, *prev = 0;
    
    for (slot = set->used_slot; slot != 0; slot = slot->next){
        if (slot->query_bar == match_bar) break;
        prev = slot;
    }
    
    if (slot){
        if (prev){
            prev->next = slot->next;
        }
        else{
            set->used_slot = slot->next;
        }
        slot->next = set->free_slot;
        set->free_slot = slot;
    }
}

struct Super_Color{
    Vec4 hsla;
    Vec4 rgba;
    u32 *out;
};

internal Super_Color
super_color_create(u32 packed){
    Super_Color result = {};
    result.rgba = unpack_color4(packed);
    result.hsla = rgba_to_hsla(result.rgba);
    return result;
}

internal void
super_color_post_hsla(Super_Color *color, Vec4 hsla){
    color->hsla = hsla;
    if (hsla.h == 1.f)
        hsla.h = 0.f;
    color->rgba = hsla_to_rgba(hsla);
    *color->out = pack_color4(color->rgba);
}

internal void
super_color_post_rgba(Super_Color *color, Vec4 rgba){
    color->rgba = rgba;
    color->hsla = rgba_to_hsla(rgba);
    *color->out = pack_color4(rgba);
}

internal void
super_color_post_packed(Super_Color *color, u32 packed){
    color->rgba = unpack_color4(packed);
    color->hsla = rgba_to_hsla(color->rgba);
    *color->out = packed;
}

u32 super_color_clear_masks[] = {0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00};
u32 super_color_shifts[] = {16, 8, 0};

internal u32
super_color_post_byte(Super_Color *color, i32 channel, u8 byte){
    u32 packed = *color->out;
    packed &= super_color_clear_masks[channel];
    packed |= (byte << super_color_shifts[channel]);
    super_color_post_packed(color, packed);
    return packed;
}

struct GUI_Target{
    Partition push;
    
    GUI_id active;
    GUI_id mouse_hot;
    GUI_id auto_hot;
    GUI_id hover;
    
    // TODO(allen): Can we remove original yet?
    GUI_Scroll_Vars scroll_original;
    i32_Rect region_original;
    
    //GUI_Scroll_Vars scroll_updated;
    i32_Rect region_updated;
    
    // TODO(allen): Would rather have a way of tracking this
    // for more than one list.  Perhaps just throw in a hash table?
    // Or maybe this only needs to be tracked for the active list.
    i32 list_max;
    b32 has_list_index_position;
    i32_Rect list_index_position;
    i32 list_view_min;
    i32 list_view_max;
    
    GUI_id scroll_id; 
    i32 delta;
    b32 has_keys;
    b32 animating;
    b32 did_file;
};

struct GUI_Item_Update{
    i32 partition_point;
    
    b32 has_adjustment;
    i32 adjustment_value;
    
    b32 has_index_position;
    i32_Rect index_position;
};

struct GUI_Header{
    i32 type;
    i32 size;
};

struct GUI_Interactive{
    GUI_Header h;
    GUI_id id;
};

struct GUI_Edit{
    GUI_Header h;
    GUI_id id;
    void *out;
};

enum GUI_Command_Type{
    guicom_null,
    guicom_begin_serial,
    guicom_end_serial,
    guicom_top_bar,
    guicom_file,
    guicom_text_field,
    guicom_color_button,
    guicom_font_button,
    guicom_text_with_cursor,
    guicom_begin_list,
    guicom_end_list,
    guicom_file_option,
    guicom_fixed_option,
    guicom_button,
    guicom_fixed_option_checkbox,
    guicom_style_preview,
    guicom_scrollable,
    guicom_scrollable_bar,
    guicom_scrollable_top,
    guicom_scrollable_slider,
    guicom_scrollable_bottom,
    guicom_scrollable_invisible,
    guicom_begin_scrollable_section,
    guicom_end_scrollable_section,
};

inline b32
gui_id_eq(GUI_id id1, GUI_id id2){
    b32 result = (id1.id[0] == id2.id[0] && id1.id[1] == id2.id[1]);
    return(result);
}

inline b32
gui_id_is_null(GUI_id id){
    b32 result = (id.id[0] == 0 && id.id[1] == 0);
    return(result);
}

internal i32
gui_active_level(GUI_Target *target, GUI_id id){
    i32 level = 0;
    if (gui_id_eq(target->active, id)){
        level = 4;
    }
    else if (gui_id_eq(target->auto_hot, id)){
        level = 3;
    }
    else if (gui_id_eq(target->mouse_hot, id)){
        if (gui_id_eq(target->hover, id)){
            level = 3;
        }
        else{
            level = 2;
        }
    }
    else if (gui_id_eq(target->hover, id) && gui_id_is_null(target->mouse_hot)){
        level = 1;
    }
    return(level);
}

internal void
gui_rollback(GUI_Target *target, GUI_Item_Update *update){
    target->push.pos = update->partition_point;
}

internal void
gui_fill_update(GUI_Item_Update *update, GUI_Target *target, GUI_Header *h){
    if (update){
        update->partition_point = (i32)((char*)h - (char*)target->push.base);
        update->has_adjustment = 0;
        update->has_index_position = 0;
    }
}

internal void
gui_update_adjustment(GUI_Item_Update *update, i32 adjustment_value){
    if (update){
        update->has_adjustment = 1;
        update->adjustment_value = adjustment_value;
    }
}

internal void
gui_update_position(GUI_Item_Update *update, i32_Rect position){
    if (update){
        update->has_index_position = 1;
        update->index_position = position;
    }
}

internal void*
gui_push_item(GUI_Target *target, void *item, i32 size){
    void *dest = 0;
    if (size == 0){
        dest = partition_current(&target->push);
    }
    else{
        dest = partition_allocate(&target->push, size);
        if (dest && item){
            memcpy(dest, item, size);
        }
    }
    return(dest);
}

internal void*
gui_align(GUI_Target *target){
    void *ptr;
    partition_align(&target->push, 8);
    ptr = partition_current(&target->push);
    return(ptr);
}

internal void*
gui_align(GUI_Target *target, GUI_Header *h){
    void *ptr;
    partition_align(&target->push, 8);
    ptr = partition_current(&target->push);
    h->size = (i32)((char*)ptr - (char*)h);
    return(ptr);
}

internal void*
advance_to_alignment(void *ptr){
    u64 p = (u64)ptr;
    p = (p + 7) & (~7);
    return (void*)p;
}

internal void*
gui_push_aligned_item(GUI_Target *target, GUI_Header *h, void *item, i32 size){
    char *ptr = (char*)partition_allocate(&target->push, size);
    if (ptr && item){
        memcpy(ptr, item, size);
    }
    gui_align(target, h);
    return(ptr);
}

internal void*
gui_push_item(GUI_Target *target, GUI_Header *h, void *item, i32 size){
    void *ptr;
    ptr = (char*)partition_allocate(&target->push, size);
    if (ptr && item){
        memcpy(ptr, item, size);
    }
    h->size += size;
    return(ptr);
}

internal GUI_Header*
gui_push_simple_command(GUI_Target *target, i32 type){
    GUI_Header *result = 0;
    GUI_Header item;
    item.type = type;
    item.size = sizeof(item);
    result = (GUI_Header*)gui_push_item(target, &item, sizeof(item));
    return(result);
}

internal GUI_Edit*
gui_push_string_edit_command(GUI_Target *target, i32 type, GUI_id id, void *out){
    GUI_Edit *result = 0;
    GUI_Edit item;
    item.h.type = type;
    item.h.size = sizeof(item);
    item.id = id;
    item.out = out;
    result = (GUI_Edit*)gui_push_item(target, &item, sizeof(item));
    return(result);
}

internal GUI_Interactive*
gui_push_button_command(GUI_Target *target, i32 type, GUI_id id){
    GUI_Interactive *result = 0;
    GUI_Interactive item;
    item.h.type = type;
    item.h.size = sizeof(item);
    item.id = id;
    result = (GUI_Interactive*)gui_push_item(target, &item, sizeof(item));
    return(result);
}

internal void
gui_push_string(GUI_Target *target, GUI_Header *h, String s, i32 extra){
    u8 *start, *end, *str_start;
    i32 size;
    i32 *cap;
    
    start = (u8*)gui_push_item(target, &s.size, sizeof(s.size));
    cap = (i32*)gui_push_item(target, 0, sizeof(i32));
    str_start = (u8*)gui_push_item(target, s.str, s.size);
    if (extra) gui_push_item(target, 0, extra);
    end = (u8*)gui_align(target);
    size = (i32)(end - start);
    *cap = (i32)(end - str_start);
    h->size += size;
}

internal void
gui_push_string(GUI_Target *target, GUI_Header *h, String s){
    gui_push_string(target, h, s, 0);
}

internal void
gui_begin_serial_section(GUI_Target *target){
    gui_push_simple_command(target, guicom_begin_serial);
}

internal void
gui_end_serial_section(GUI_Target *target){
    gui_push_simple_command(target, guicom_end_serial);
}

internal void
gui_begin_top_level(GUI_Target *target, Input_Summary input){
    target->push.pos = 0;
    target->has_keys = (input.keys.count > 0);
    target->animating = 0;
    target->did_file = 0;
}

internal void
gui_end_top_level(GUI_Target *target){
    gui_push_simple_command(target, guicom_null);
    target->has_list_index_position = 0;
}

internal void
gui_do_top_bar(GUI_Target *target){
    gui_push_simple_command(target, guicom_top_bar);
}

internal void
gui_do_file(GUI_Target *target){
    gui_push_simple_command(target, guicom_file);
    target->did_file = 1;
}

internal void
gui_do_text_field(GUI_Target *target, String prompt, String text){
    GUI_Header *h = gui_push_simple_command(target, guicom_text_field);
    gui_push_string(target, h, prompt);
    gui_push_string(target, h, text);
}

internal b32
gui_do_text_with_cursor(GUI_Target *target, i32 pos, String text, GUI_Item_Update *update){
    b32 result = 0;
    GUI_Header *h = gui_push_simple_command(target, guicom_text_with_cursor);
    gui_push_string(target, h, text);
    gui_push_item(target, h, &pos, sizeof(i32));
    
    result = target->has_keys;
    if (result){
        gui_fill_update(update, target, h);
        target->animating = 1;
    }
    
    return(result);
}

internal b32
gui_do_color_button(GUI_Target *target, GUI_id id, u32 fore, u32 back, String text){
    b32 result = 0;
    GUI_Interactive *b = gui_push_button_command(target, guicom_color_button, id);
    GUI_Header *h = (GUI_Header*)b;
    gui_push_item(target, h, &fore, sizeof(fore));
    gui_push_item(target, h, &back, sizeof(back));
    gui_push_string(target, h, text);
    
    if (gui_id_eq(id, target->active)){
        result = 1;
        target->animating = 1;
    }
    
    return(result);
}

internal b32
gui_do_font_button(GUI_Target *target, GUI_id id, Font_ID font_id, String text){
    b32 result = 0;
    i32 font_id32 = font_id;
    GUI_Interactive *b = gui_push_button_command(target, guicom_font_button, id);
    GUI_Header *h = (GUI_Header*)b;
    gui_push_item(target, h, &font_id32, sizeof(font_id32));
    gui_push_string(target, h, text);
    
    if (gui_id_eq(id, target->active)){
        result = 1;
        target->animating = 1;
    }
    
    return(result);
}

internal b32
gui_begin_list(GUI_Target *target, GUI_id id, i32 list_i,
               b32 activate_item, b32 snap_into_view, GUI_Item_Update *update){
    b32 result = 0;
    b32 active = 0;
    
    i32 list_min = 0;
    i32 list_max = target->list_max;
    
    GUI_Interactive *b = gui_push_button_command(target, guicom_begin_list, id);
    GUI_Header *h = (GUI_Header*)b;
    gui_push_item(target, h, &list_i, sizeof(list_i));
    gui_push_item(target, h, &activate_item, sizeof(activate_item));
    
    result = target->has_keys || target->has_list_index_position;
    if (gui_id_eq(id, target->active)){
        active = 1;
        result = 1;
    }
    
    if (snap_into_view){
        if (target->list_view_min > list_min){
            list_min = target->list_view_min;
        }
        if (target->list_view_max < list_max){
            list_max = target->list_view_max;
        }
    }
    
    if (list_max > 0){
        if (list_i < list_min || list_i >= list_max){
            result = 1;
        }
    }
    
    if (result){
        gui_fill_update(update, target, h);
        if (list_i < list_min){
            gui_update_adjustment(update, list_min);
        }
        else if (list_i >= list_max){
            if (list_max > 0){
                gui_update_adjustment(update, list_max - 1);
            }
            else{
                gui_update_adjustment(update, 0);
            }
        }
        if (target->has_list_index_position){
            gui_update_position(update, target->list_index_position);
        }
        
        target->animating = 1;
    }
    
    return(result);
}

internal void
gui_end_list(GUI_Target *target){
    gui_push_simple_command(target, guicom_end_list);
}

internal b32
gui_do_file_option(GUI_Target *target, GUI_id id, String filename,
                   b32 is_folder, String message){
    b32 result = 0;
    GUI_Interactive *b = gui_push_button_command(target, guicom_file_option, id);
    GUI_Header *h = (GUI_Header*)b;
    gui_push_item(target, h, &is_folder, sizeof(is_folder));
    gui_push_string(target, h, filename, 1);
    gui_push_string(target, h, message);
    
    if (gui_id_eq(id, target->active)){
        result = 1;
        target->animating = 1;
    }
    
    return(result);
}

internal b32
gui_do_button(GUI_Target *target, GUI_id id, String message){
    b32 result = 0;
    GUI_Interactive *b = gui_push_button_command(target, guicom_button, id);
    GUI_Header *h = (GUI_Header*)b;
    gui_push_string(target, h, message);
    gui_align(target, h);
    
    if (gui_id_eq(id, target->active)){
        result = 1;
        target->animating = 1;
    }
    
    return(result);
}

internal b32
gui_do_fixed_option(GUI_Target *target, GUI_id id, String message, char key){
    b32 result = 0;
    GUI_Interactive *b = gui_push_button_command(target, guicom_fixed_option, id);
    GUI_Header *h = (GUI_Header*)b;
    gui_push_string(target, h, message);
    gui_push_item(target, h, &key, 1);
    gui_align(target, h);
    
    if (gui_id_eq(id, target->active)){
        result = 1;
        target->animating = 1;
    }
    
    return(result);
}

internal b32
gui_do_fixed_option_checkbox(GUI_Target *target, GUI_id id, String message, char key, b8 state){
    b32 result = 0;
    GUI_Interactive *b = gui_push_button_command(target, guicom_fixed_option_checkbox, id);
    GUI_Header *h = (GUI_Header*)b;
    gui_push_string(target, h, message);
    gui_push_item(target, h, &key, 1);
    gui_push_item(target, h, &state, 1);
    gui_align(target, h);
    
    if (gui_id_eq(id, target->active)){
        result = 1;
        target->animating = 1;
    }
    
    return(result);
}

internal b32
gui_do_style_preview(GUI_Target *target, GUI_id id, i32 style_index){
    b32 result = 0;
    GUI_Interactive *b = gui_push_button_command(target, guicom_style_preview, id);
    GUI_Header *h = (GUI_Header*)b;
    gui_push_item(target, h, &style_index, sizeof(style_index));
    gui_align(target, h);
    
    if (gui_id_eq(id, target->active)){
        result = 1;
        target->animating = 1;
    }
    
    return(result);
}

internal GUI_id
gui_id_scrollbar(){
    GUI_id id;
    id.id[0] = 0;
    id.id[1] = max_u64;
    return(id);
}

internal GUI_id
gui_id_scrollbar_top(){
    GUI_id id;
    id.id[0] = 1;
    id.id[1] = max_u64;
    return(id);
}

internal GUI_id
gui_id_scrollbar_slider(){
    GUI_id id;
    id.id[0] = 2;
    id.id[1] = max_u64;
    return(id);
}

internal GUI_id
gui_id_scrollbar_bottom(){
    GUI_id id;
    id.id[0] = 3;
    id.id[1] = max_u64;
    return(id);
}

internal b32
gui_scroll_eq(GUI_Scroll_Vars *a, GUI_Scroll_Vars *b){
    b32 result = (memcmp(a, b, sizeof(*a)) == 0);
    return(result);
}

#if 0
// TODO(allen): Rethink this a little, seems like there are two separate things we want to do here:
// Getting the updated scroll vars, and telling the user when scrolling actions occur.
internal b32
gui_get_scroll_vars(GUI_Target *target, GUI_id scroll_context_id, i32_Rect *region_out){
    b32 result = 0;
    if (gui_id_eq(scroll_context_id, target->scroll_id)){
        *region_out = target->region_updated;
        
        if (gui_id_eq(target->active, gui_id_scrollbar())){
            result = 1;
            target->animating = 1;
        }
    }
    return(result);
}
#endif

internal b32
gui_scroll_was_activated(GUI_Target *target, GUI_id scroll_context_id){
    b32 result = false;
    
    if (gui_id_eq(scroll_context_id, target->scroll_id)){
        if (gui_id_eq(target->active, gui_id_scrollbar())){
            result = true;
            target->animating = true;
        }
    }
    
    return(result);
}

internal void
gui_post_scroll_vars(GUI_Target *target, GUI_Scroll_Vars *vars_in, i32_Rect region_in){
    if (!rect_equal(region_in, target->region_updated)){
        target->region_updated = region_in;
        target->animating = 1;
        target->active = gui_id_scrollbar();
    }
}

internal void
gui_begin_scrollable(GUI_Target *target, GUI_id scroll_context_id,
                     GUI_Scroll_Vars scroll_vars, i32 delta, b32 show_bar){
    GUI_Header *h;
    
    gui_begin_serial_section(target);
    
    target->delta = delta;
    h = gui_push_simple_command(target, guicom_scrollable);
    
    target->scroll_original = scroll_vars;
    target->scroll_id = scroll_context_id;
    
    if (show_bar){
        gui_push_simple_command(target, guicom_scrollable_bar);
        gui_push_simple_command(target, guicom_scrollable_top);
        gui_push_simple_command(target, guicom_scrollable_slider);
        gui_push_simple_command(target, guicom_scrollable_bottom);
    }
    else{
        gui_push_simple_command(target, guicom_scrollable_invisible);
    }
    gui_push_simple_command(target, guicom_begin_scrollable_section);
}

internal void
gui_end_scrollable(GUI_Target *target){
    gui_push_simple_command(target, guicom_end_scrollable_section);
    gui_end_serial_section(target);
}

internal void
gui_activate_scrolling(GUI_Target *target){
    target->active = gui_id_scrollbar();
}

struct GUI_Section{
    i32 max_v, v, top_v;
};

struct GUI_List_Vars{
    b32 in_list;
    i32 index;
    i32 auto_hot;
    i32 auto_activate;
};

struct GUI_Session{
    i32_Rect full_rect;
    i32_Rect rect;
    
    i32 suggested_max_y;
    i32 clip_y;
    
    i32 line_height;
    b32 is_scrollable;
    i32 scrollable_items_bottom;
    
    i32_Rect scroll_region;
    i32_Rect scroll_rect;
    f32 scroll_top, scroll_bottom;
    
    GUI_List_Vars list;
    
    GUI_Section sections[64];
    i32 t;
};

#define GUIScrollbarWidth 16

// TODO(allen): We can probably totally get rid of this now.
internal i32
gui_session_get_eclipsed_y(GUI_Session *session){
    i32 count = session->t + 1;
    i32 max_v = session->sections[count-1].top_v;
    return(max_v);
}

internal i32
gui_session_get_current_top(GUI_Session *session){
    i32 result = session->sections[session->t].top_v;
    return(result);
}

inline GUI_Session
gui_session_zero(){
    GUI_Session session={0};
    return(session);
}

internal void
gui_session_init(GUI_Session *session, GUI_Target *target,
                 i32_Rect full_rect, i32 line_height){
    GUI_Section *section;
    
    *session = gui_session_zero();
    session->full_rect = full_rect;
    session->line_height = line_height;
    
    section = &session->sections[0];
    section->v = full_rect.y0;
    section->max_v = full_rect.y0;
    
    target->list_view_min = max_i32;
    target->list_view_max = min_i32;
}

internal void
gui_section_end_item(GUI_Section *section, i32 v){
    section->v = v;
    if (section->max_v < v){
        section->max_v = v;
    }
}

inline i32_Rect
gui_layout_top_bottom(GUI_Session *session, i32 y0, i32 y1){
    i32_Rect rect = {0};
    
    rect.y0 = y0;
    rect.y1 = y1;
    rect.x0 = session->full_rect.x0;
    rect.x1 = session->full_rect.x1;
    
    if (session->is_scrollable){
        rect.x0 = session->scroll_region.x0;
        rect.x1 = session->scroll_region.x1;
    }
    
    return(rect);
}

inline i32_Rect
gui_layout_fixed_h(GUI_Session *session, i32 y, i32 h){
    i32_Rect rect;
    rect = gui_layout_top_bottom(session, y, y + h);
    return(rect);
}

internal void
gui_scrollbar_top(i32_Rect bar, i32_Rect *top){
    i32 w = (bar.x1 - bar.x0);
    top->x0 = bar.x0;
    top->x1 = bar.x1;
    top->y0 = bar.y0;
    top->y1 = top->y0 + w;
}

internal void
gui_scrollbar_slider(i32_Rect bar, i32_Rect *slider, f32 s, f32 *min_out, f32 *max_out, i32 target_min, i32 target_max){
    i32 h = 0, w = (bar.x1 - bar.x0);
    i32 min = 0, max = 0, pos = 0;
    
    f32 screen_size = (f32)(bar.y1 - bar.y0);
    f32 full_size = (f32)(target_max - target_min + screen_size);
    f32 ratio = 1.f;
    
    if (full_size > screen_size){
        ratio = screen_size/full_size;
    }
    
    h = (i32)(ratio * bar.y1 - bar.y0 - w*2);
    
    if (h < w){
        h = w;
    }
    
    slider->x0 = bar.x0;
    slider->x1 = bar.x1;
    
    min = bar.y0 + w + h/2;
    max = bar.y1 - w - h/2;
    
    pos = lerp(min, s, max);
    
    slider->y0 = pos - h/2;
    slider->y1 = slider->y0 + h;
    
    *min_out = (f32)min;
    *max_out = (f32)max;
}

internal void
gui_scrollbar_bottom(i32_Rect bar, i32_Rect *bottom){
    i32 w = (bar.x1 - bar.x0);
    bottom->x0 = bar.x0;
    bottom->x1 = bar.x1;
    bottom->y1 = bar.y1;
    bottom->y0 = bottom->y1 - w;
}

#define NextHeader(h) ((GUI_Header*)((char*)(h) + (h)->size))

internal i8
gui_read_byte(void **ptr){
    i8 result;
    result = *(i8*)*ptr;
    *ptr = ((char*)*ptr) + 1;
    return(result);
}

internal i32
gui_read_integer(void **ptr){
    i32 result;
    result = *(i32*)*ptr;
    *ptr = ((char*)*ptr) + 4;
    return(result);
}

internal f32
gui_read_float(void **ptr){
    f32 result;
    result = *(f32*)*ptr;
    *ptr = ((char*)*ptr) + 4;
    return(result);
}

internal String
gui_read_string(void **ptr){
    String result;
    
    result.size = *(i32*)*ptr;
    *ptr = ((i32*)*ptr) + 1;
    result.memory_size = *(i32*)*ptr;
    *ptr = ((i32*)*ptr) + 1;
    
    result.str = (char*)*ptr;
    *ptr = result.str + result.memory_size;
    
    return(result);
}

internal void*
gui_read_out(void **ptr){
    void *result;
    result = *(void**)*ptr;
    *ptr = ((void**)ptr) + 1;
    return(result);
}

struct GUI_Interpret_Result{
    b32 has_info;
    b32 auto_hot;
    b32 auto_activate;
    i32 screen_orientation;
    
    b32 has_region;
    i32_Rect region;
};

internal GUI_Interpret_Result
gui_interpret(GUI_Target *target, GUI_Session *session, GUI_Header *h,
              GUI_Scroll_Vars vars, i32_Rect region, i32 max_y){
    GUI_Interpret_Result result = {0};
    GUI_Section *section = 0;
    GUI_Section *new_section = 0;
    GUI_Section *prev_section = 0;
    GUI_Section *end_section = 0;
    b32 give_to_user = 0;
    b32 always_give_to_user = 0;
    b32 do_layout = 1;
    b32 is_list_item = 0;
    i32_Rect rect = {0};
    i32 y = 0;
    i32 end_v = -1;
    f32 lerp_space_scroll_v = 0;
    i32 scroll_v = (i32)target->scroll_original.scroll_y;
    i32 target_v = (i32)vars.target_y;
    
    Assert(session->t < ArrayCount(session->sections));
    section = session->sections + session->t;
    y = section->v;
    
    if (!session->is_scrollable) scroll_v = 0;
    
    switch (h->type){
        case guicom_null: Assert(0); break;
        
        case guicom_begin_serial:
        ++session->t;
        Assert(session->t < ArrayCount(session->sections));
        new_section = &session->sections[session->t];
        new_section->v = y;
        new_section->max_v = y;
        new_section->top_v = y;
        break;
        
        case guicom_end_serial:
        Assert(session->t > 0);
        prev_section = &session->sections[--session->t];
        end_v = section->max_v;
        end_section = prev_section;
        break;
        
        case guicom_top_bar:
        give_to_user = 1;
        rect = gui_layout_fixed_h(session, y, session->line_height + 2);
        end_v = rect.y1;
        end_section = section;
        break;
        
        case guicom_file:
        give_to_user = 1;
        rect = gui_layout_top_bottom(session, y, session->full_rect.y1);
        end_v = rect.y1;
        end_section = section;
        scroll_v = 0;
        break;
        
        case guicom_text_with_cursor:
        case guicom_text_field:
        give_to_user = 1;
        rect = gui_layout_fixed_h(session, y, session->line_height + 2);
        end_v = rect.y1;
        end_section = section;
        break;
        
        case guicom_color_button:
        case guicom_font_button:
        give_to_user = 1;
        rect = gui_layout_fixed_h(session, y, session->line_height + 2);
        end_v = rect.y1;
        end_section = section;
        break;
        
        case guicom_begin_list:
        {
            GUI_Interactive *b = (GUI_Interactive*)h;
            void *ptr = (b + 1);
            i32 index = gui_read_integer(&ptr);
            b32 activate = (b32)gui_read_integer(&ptr);
            
            Assert(session->list.in_list == 0);
            session->list.in_list = 1;
            session->list.index = 0;
            session->list.auto_hot = index;
            session->list.auto_activate = -1;
            if (activate){
                session->list.auto_activate = index;
            }
        }break;
        
        case guicom_end_list:
        Assert(session->list.in_list == 1);
        session->list.in_list = 0;
        target->list_max = session->list.index;
        break;
        
        case guicom_file_option:
        case guicom_fixed_option:
        case guicom_fixed_option_checkbox:
        {
            give_to_user = 1;
            rect = gui_layout_fixed_h(session, y, session->line_height * 2);
            end_v = rect.y1;
            end_section = section;
            is_list_item = 1;
            
            if (session->list.in_list){
                if (session->list.auto_hot == session->list.index){
                    result.auto_hot = 1;
                    if (!rect_equal(target->list_index_position, rect)){
                        target->has_list_index_position = 1;
                        target->list_index_position = rect;
                    }
                }
                if (session->list.auto_activate == session->list.index){
                    result.auto_activate = 1;
                }
                
                ++session->list.index;
            }
        }break;
        
        case guicom_button:
        give_to_user = 1;
        rect = gui_layout_fixed_h(session, y, session->line_height * 2);
        end_v = rect.y1;
        end_section = section;
        break;
        
        case guicom_style_preview:
        give_to_user = 1;
        rect = gui_layout_fixed_h(session, y, session->line_height * 3 + 6);
        end_v = rect.y1;
        end_section = section;
        break;
        
        case guicom_scrollable:
        Assert(session->is_scrollable == 0);
        session->is_scrollable = 1;
        always_give_to_user = 1;
        
        {
            i32_Rect scrollable_rect = {0};
            scrollable_rect.x0 = session->full_rect.x0;
            scrollable_rect.x1 = session->full_rect.x1;
            scrollable_rect.y0 = y;
            scrollable_rect.y1 = session->full_rect.y1;
            result.has_region = 1;
            result.region = scrollable_rect;
            session->scroll_region = scrollable_rect;
        }
        break;
        
        case guicom_scrollable_bar:
        Assert(session->is_scrollable);
        give_to_user = 1;
        rect.x1 = session->full_rect.x1;
        rect.x0 = rect.x1 - GUIScrollbarWidth;
        rect.y0 = y;
        rect.y1 = session->full_rect.y1;
        session->scroll_rect = rect;
        
        {
            i32_Rect scrollable_rect = {0};
            scrollable_rect.x0 = session->full_rect.x0;
            scrollable_rect.x1 = rect.x0;
            scrollable_rect.y0 = y;
            scrollable_rect.y1 = session->full_rect.y1;
            result.has_region = 1;
            result.region = scrollable_rect;
            session->scroll_region = scrollable_rect;
        }
        scroll_v = 0;
        break;
        
        case guicom_scrollable_top:
        Assert(session->is_scrollable);
        give_to_user = 1;
        gui_scrollbar_top(session->scroll_rect, &rect);
        scroll_v = 0;
        break;
        
        case guicom_scrollable_slider:
        Assert(session->is_scrollable);
        give_to_user = 1;
        
        lerp_space_scroll_v =
            unlerp(0,
                   (f32)target->scroll_original.target_y,
                   (f32)max_y);
        
        gui_scrollbar_slider(session->scroll_rect, &rect, lerp_space_scroll_v,
                             &session->scroll_top, &session->scroll_bottom,
                             0, max_y);
        scroll_v = 0;
        break;
        
        case guicom_scrollable_invisible:
        Assert(session->is_scrollable);
        always_give_to_user = 1;
        break;
        
        case guicom_scrollable_bottom:
        Assert(session->is_scrollable);
        give_to_user = 1;
        gui_scrollbar_bottom(session->scroll_rect, &rect);
        scroll_v = 0;
        break;
        
        case guicom_begin_scrollable_section:
        always_give_to_user = 1;
        session->scrollable_items_bottom = 0;
        rect = gui_layout_top_bottom(session, y, session->full_rect.y1);
        end_v = rect.y1;
        break;
        
        case guicom_end_scrollable_section:
        always_give_to_user = 1;
        session->suggested_max_y = ceil32(session->scrollable_items_bottom - (session->full_rect.y0 + session->full_rect.y1)*.5f);
        if (session->suggested_max_y < 0){
            session->suggested_max_y = 0;
        }
        break;
    }
    
    if (do_layout){
        if (session->list.in_list && is_list_item){
            i32 list_i = session->list.index - 1;
            
            if (rect.y0 - target_v >= region.y0 &&
                rect.y1 - target_v <= region.y1){
                if (list_i < target->list_view_min){
                    target->list_view_min = list_i;
                }
                if (list_i+1 > target->list_view_max){
                    target->list_view_max = list_i+1;
                }
            }
        }
        
        if (give_to_user){
            if (session->is_scrollable){
                session->scrollable_items_bottom =
                    Max(session->scrollable_items_bottom, rect.y1);
            }
            
            rect.y0 -= scroll_v;
            rect.y1 -= scroll_v;
            
            if (rect.y1 > session->full_rect.y0){
                session->rect = rect;
            }
            else{
                give_to_user = 0;
                result.screen_orientation = -1;
            }
        }
        
        if (end_section){
            gui_section_end_item(end_section, end_v);
        }
        
        // TODO(allen): Why is this here, is there a particular reason?
        if (y - scroll_v >= session->full_rect.y1){
            give_to_user = 0;
            result.screen_orientation = 1;
        }
    }
    
    session->clip_y = gui_session_get_eclipsed_y(session);
    
    result.has_info = (give_to_user || always_give_to_user);
    
    return(result);
}

struct GUI_View_Jump{
    i32 view_min;
    i32 view_max;
};

internal GUI_View_Jump
gui_compute_view_jump(i32_Rect scroll_region, i32_Rect position){
    GUI_View_Jump jump = {0};
    i32 region_h = scroll_region.y1 - scroll_region.y0;
    jump.view_min = position.y1 - scroll_region.y0 - region_h;
    jump.view_max = position.y0 - scroll_region.y0;
    return(jump);
}

internal GUI_Scroll_Vars
gui_do_jump(GUI_Target *target, GUI_View_Jump jump, GUI_Scroll_Vars vars){
    if (jump.view_max < 0){
        jump.view_max = 0;
    }
    if (jump.view_min < 0){
        jump.view_min = 0;
    }
    if (vars.target_y < jump.view_min){
        vars.target_y = jump.view_min;
    }
    else if (vars.target_y > jump.view_max){
        vars.target_y = jump.view_max;
    }
    return(vars);
}

internal void
gui_standard_list(GUI_Target *target, GUI_id id, GUI_Scroll_Vars *vars, i32_Rect scroll_region,  Key_Input_Data *keys, i32 *list_i, GUI_Item_Update *update, Key_Code user_up_key, Key_Modifier user_up_key_modifier, Key_Code user_down_key, Key_Modifier user_down_key_modifier){
    if (update->has_adjustment){
        *list_i = update->adjustment_value;
    }
    
    if (update->has_index_position){
        GUI_View_Jump jump = gui_compute_view_jump(scroll_region, update->index_position);
        jump.view_min = jump.view_min + 45;
        jump.view_max = jump.view_max - 45;
        *vars = gui_do_jump(target, jump, *vars);
    }
    
    i8 modifiers_up[3];
    modifiers_up[0] = ((user_up_key_modifier & MDFR_CTRL) != 0);
    modifiers_up[1] = ((user_up_key_modifier & MDFR_ALT) != 0);
    modifiers_up[2] = ((user_up_key_modifier & MDFR_SHIFT) != 0);
    
    i8 modifiers_down[3];
    modifiers_down[0] = ((user_down_key_modifier & MDFR_CTRL) != 0);
    modifiers_down[1] = ((user_down_key_modifier & MDFR_ALT) != 0);
    modifiers_down[2] = ((user_down_key_modifier & MDFR_SHIFT) != 0);
    
    b32 indirectly_activate = 0;
    for (i32 j = 0; j < keys->count; ++j){
        Key_Event_Data key = keys->keys[j];
        b32 modifiers_match_up = false;
        b32 modifiers_match_down = false;
        
        if (modifiers_up[0] == key.modifiers[MDFR_CONTROL_INDEX] && modifiers_up[1] == key.modifiers[MDFR_ALT_INDEX] && modifiers_up[2] == key.modifiers[MDFR_SHIFT_INDEX]){
            modifiers_match_up = true;
        }
        
        if (modifiers_down[0] == key.modifiers[MDFR_CONTROL_INDEX] && modifiers_down[1] == key.modifiers[MDFR_ALT_INDEX] && modifiers_down[2] == key.modifiers[MDFR_SHIFT_INDEX]){
            modifiers_match_down = true;
        }
        
        if (key.keycode == user_up_key && modifiers_match_up){
            --*list_i;
        }
        else if (key.keycode == user_down_key && modifiers_match_down){
            ++*list_i;
        }
        
        else if (key.keycode == '\n' || key.keycode == '\t'){
            indirectly_activate = 1;
        }
    }
    
    gui_rollback(target, update);
    gui_begin_list(target, id, *list_i, indirectly_activate, 0, 0);
}

// BOTTOM