/*
 * Mr. 4th Dimention - Allen Webster
 *
 * 19.08.2018
 *
 * Ways of calling lexer for the text in Editing_File
 *
 */

// TOP

// TODO(allen): this needs a rewrite _BAD_
internal void
job_full_lex(System_Functions *system, Thread_Context *thread, Thread_Memory *memory, void *data[4]){
    Editing_File *file = (Editing_File*)data[0];
    Heap *heap = (Heap*)data[1];
    Models *models = (Models*)data[2];
    
    Parse_Context parse_context = parse_context_get(&models->parse_context_memory, file->settings.parse_context_id, memory->data, memory->size);
    if (!parse_context.valid){
        return;
    }
    
    Gap_Buffer *buffer = &file->state.buffer;
    i32 text_size = buffer_size(buffer);
    
    u32 aligned_buffer_size = (text_size + 3)&(~3);
    
    for (;memory->size < aligned_buffer_size + parse_context.memory_size;){
        void *old_base = memory->data;
        system->grow_thread_memory(memory);
        parse_context_rebase(&parse_context, old_base, memory->data);
    }
    
    u8 *data_ptr = (u8*)memory->data;
    umem data_size = memory->size;
    data_ptr += parse_context.memory_size;
    data_size -= parse_context.memory_size;
    
    Cpp_Token_Array tokens = {};
    tokens.tokens = (Cpp_Token*)(data_ptr);
    tokens.max_count = (u32)(data_size / sizeof(Cpp_Token));
    tokens.count = 0;
    
    b32 still_lexing = true;
    
    Cpp_Lex_Data lex = cpp_lex_data_init(file->settings.tokens_without_strings, parse_context.kw_table, parse_context.pp_table);
    
    String_Const_u8 chunk_space[3];
    Cursor chunk_cursor = make_cursor(chunk_space, sizeof(chunk_space));
    String_Const_u8_Array chunks = buffer_get_chunks(&chunk_cursor, buffer);
    
    i32 chunk_index = 0;
    
    do{
        u8 *chunk = chunks.vals[chunk_index].str;
        umem chunk_size = chunks.vals[chunk_index].size;
        
        i32 result = cpp_lex_step(&lex, (char*)chunk, (i32)chunk_size, text_size, &tokens, 2048);
        switch (result){
            case LexResult_NeedChunk:
            {
                ++chunk_index;
                Assert(chunk_index < chunks.count);
            }break;
            
            case LexResult_NeedTokenMemory:
            {
                if (system->check_cancel(thread)){
                    return;
                }
                
                void *old_base = memory->data;
                system->grow_thread_memory(memory);
                cpp_rebase_tables(&lex, old_base, memory->data);
                
                data_ptr = (u8*)memory->data;
                data_size = memory->size;
                data_ptr += parse_context.memory_size;
                data_size -= parse_context.memory_size;
                tokens.tokens = (Cpp_Token*)(data_ptr);
                tokens.max_count = (u32)(data_size / sizeof(Cpp_Token));
            }break;
            
            case LexResult_HitTokenLimit:
            {
                if (system->check_cancel(thread)){
                    return;
                }
            }break;
            
            case LexResult_Finished:
            {
                still_lexing = false;
            }break;
        }
    }while(still_lexing);
    
    i32 new_max = round_up_i32(tokens.count + 1, KB(1));
    
    system->acquire_lock(FRAME_LOCK);
    {
        Assert(file->state.swap_array.tokens == 0);
        file->state.swap_array.tokens = heap_array(heap, Cpp_Token, new_max);
    }
    system->release_lock(FRAME_LOCK);
    
    u8 *dest = (u8*)file->state.swap_array.tokens;
    u8 *src = (u8*)tokens.tokens;
    
    memcpy(dest, src, tokens.count*sizeof(Cpp_Token));
    
    system->acquire_lock(FRAME_LOCK);
    {
        Cpp_Token_Array *file_token_array = &file->state.token_array;
        file_token_array->count = tokens.count;
        file_token_array->max_count = new_max;
        if (file_token_array->tokens){
            heap_free(heap, file_token_array->tokens);
        }
        file_token_array->tokens = file->state.swap_array.tokens;
        file->state.swap_array.tokens = 0;
    }
    file->state.tokens_complete = true;
    file->state.still_lexing = false;
    file_mark_edit_finished(&models->working_set, file);
    system->release_lock(FRAME_LOCK);
}

internal void
file_kill_tokens(System_Functions *system, Heap *heap, Editing_File *file){
    file->settings.tokens_exist = false;
    if (file->state.still_lexing){
        system->cancel_job(BACKGROUND_THREADS, file->state.lex_job);
        if (file->state.swap_array.tokens){
            heap_free(heap, file->state.swap_array.tokens);
            file->state.swap_array.tokens = 0;
        }
    }
    if (file->state.token_array.tokens){
        heap_free(heap, file->state.token_array.tokens);
    }
    file->state.tokens_complete = false;
    file->state.token_array = null_cpp_token_array;
}

internal void
file_first_lex_parallel(System_Functions *system, Models *models, Editing_File *file){
    Heap *heap = &models->mem.heap;
    file->settings.tokens_exist = true;
    
    if (!file->is_loading && !file->state.still_lexing){
        Assert(file->state.token_array.tokens == 0);
        
        file->state.tokens_complete = false;
        file->state.still_lexing = true;
        
        Job_Data job;
        job.callback = job_full_lex;
        job.data[0] = file;
        job.data[1] = heap;
        job.data[2] = models;
        file->state.lex_job = system->post_job(BACKGROUND_THREADS, job);
    }
}

internal void
file_first_lex_serial(System_Functions *system, Models *models, Editing_File *file){
    Mem_Options *mem = &models->mem;
    Arena *scratch = &mem->arena;
    Heap *heap = &mem->heap;
    file->settings.tokens_exist = true;
    
    Assert(!file->state.still_lexing);
    
    if (file->is_loading == 0){
        Assert(file->state.token_array.tokens == 0);
        
        Temp_Memory temp = begin_temp(scratch);
        
        Parse_Context parse_context = parse_context_get(scratch, &models->parse_context_memory, file->settings.parse_context_id);
        Assert(parse_context.valid);
        
        Gap_Buffer *buffer = &file->state.buffer;
        i32 text_size = buffer_size(buffer);
        
        // TODO(allen): REWRITE REWRITE REWRITE
        Cpp_Token_Array new_tokens = {};
        new_tokens.max_count = Million(1);
        new_tokens.count = 0;
        new_tokens.tokens = push_array(scratch, Cpp_Token, new_tokens.max_count);
        
        b32 still_lexing = true;
        
        Cpp_Lex_Data lex = cpp_lex_data_init(file->settings.tokens_without_strings, parse_context.kw_table, parse_context.pp_table);
        
        String_Const_u8_Array chunk_space[3];
        Cursor chunk_cursor = make_cursor(chunk_space, sizeof(chunk_space));
        String_Const_u8_Array chunks = buffer_get_chunks(&chunk_cursor, buffer, BufferGetChunk_ZeroTerminated);
        
        i32 chunk_index = 0;
        
        Cpp_Token_Array *swap_array = &file->state.swap_array;
        
        do{
            u8 *chunk = chunks.vals[chunk_index].str;
            umem chunk_size = chunks.vals[chunk_index].size;
            
            i32 result = cpp_lex_step(&lex, (char*)chunk, (i32)chunk_size, text_size, &new_tokens, NO_OUT_LIMIT);
            
            switch (result){
                case LexResult_NeedChunk:
                {
                    ++chunk_index;
                    Assert(chunk_index < chunks.count);
                }break;
                
                case LexResult_Finished:
                case LexResult_NeedTokenMemory:
                {
                    u32 new_max = round_up_u32(swap_array->count + new_tokens.count + 1, KB(1));
                    if (swap_array->tokens == 0){
                        swap_array->tokens = heap_array(heap, Cpp_Token, new_max);
                    }
                    else{
                        u32 old_count = swap_array->count;
                        Cpp_Token *new_token_mem = heap_array(heap, Cpp_Token, new_max);
                        memcpy(new_token_mem, swap_array->tokens, sizeof(*new_token_mem)*old_count);
                        heap_free(heap, swap_array->tokens);
                        swap_array->tokens = new_token_mem;
                    }
                    swap_array->max_count = new_max;
                    
                    Assert(swap_array->count + new_tokens.count <= swap_array->max_count);
                    memcpy(swap_array->tokens + swap_array->count, new_tokens.tokens, new_tokens.count*sizeof(Cpp_Token));
                    swap_array->count += new_tokens.count;
                    new_tokens.count = 0;
                    
                    if (result == LexResult_Finished){
                        still_lexing = false;
                    }
                }break;
                
                case LexResult_HitTokenLimit: InvalidPath;
            }
        } while (still_lexing);
        
        Cpp_Token_Array *token_array = &file->state.token_array;
        token_array->count = swap_array->count;
        token_array->max_count = swap_array->max_count;
        if (token_array->tokens != 0){
            heap_free(heap, token_array->tokens);
        }
        token_array->tokens = swap_array->tokens;
        
        swap_array->tokens = 0;
        swap_array->count = 0;
        swap_array->max_count = 0;
        
        file->state.tokens_complete = true;
        file->state.still_lexing = false;
        
        end_temp(temp);
        
        file->state.tokens_complete = true;
        file_mark_edit_finished(&models->working_set, file);
    }
}

internal b32
file_relex_parallel(System_Functions *system, Models *models, Editing_File *file, i32 start_i, i32 end_i, i32 shift_amount){
    Mem_Options *mem = &models->mem;
    Arena *scratch = &mem->arena;
    Heap *heap = &mem->heap;
    
    b32 result = true;
    if (file->state.token_array.tokens == 0){
        file_first_lex_parallel(system, models, file);
    }
    else{
        b32 internal_lex = !file->state.still_lexing;
        if (internal_lex){
            Gap_Buffer *buffer = &file->state.buffer;
            i32 extra_tolerance = 100;
            
            Cpp_Token_Array *array = &file->state.token_array;
            Cpp_Relex_Range relex_range = cpp_get_relex_range(array, start_i, end_i);
            
            i32 relex_space_size = relex_range.end_token_index - relex_range.start_token_index + extra_tolerance;
            
            Temp_Memory temp = begin_temp(scratch);
            Parse_Context parse_context = parse_context_get(scratch, &models->parse_context_memory, file->settings.parse_context_id);
            Assert(parse_context.valid);
            
            Cpp_Token_Array relex_array;
            relex_array.count = 0;
            relex_array.max_count = relex_space_size;
            relex_array.tokens = push_array(scratch, Cpp_Token, relex_array.max_count);
            
            i32 size = buffer_size(buffer);
            
            Cpp_Relex_Data state = cpp_relex_init(array, start_i, end_i, shift_amount, file->settings.tokens_without_strings, parse_context.kw_table, parse_context.pp_table);
            
            String_Const_u8_Array chunk_space[3];
            Cursor chunk_cursor = make_cursor(chunk_space, sizeof(chunk_space));
            String_Const_u8_Array chunks = buffer_get_chunks(&chunk_cursor, buffer, BufferGetChunk_ZeroTerminated);
            
            i32 chunk_index = 0;
            u8 *chunk = chunks.vals[chunk_index].str;
            umem chunk_size = chunks.vals[chunk_index].size;
            
            for (;!cpp_relex_is_start_chunk(&state, (char*)chunk, (i32)chunk_size);){
                ++chunk_index;
                Assert(chunk_index < chunks.count);
                chunk = chunks.vals[chunk_index].str;
                chunk_size = chunks.vals[chunk_index].size;
            }
            
            for (;;){
                Cpp_Lex_Result lex_result =
                    cpp_relex_step(&state, (char*)chunk, (i32)chunk_size, size, array, &relex_array);
                
                switch (lex_result){
                    case LexResult_NeedChunk:
                    {
                        ++chunk_index;
                        Assert(chunk_index < chunks.count);
                        chunk = chunks.vals[chunk_index].str;
                        chunk_size = chunks.vals[chunk_index].size;
                    }break;
                    
                    case LexResult_NeedTokenMemory:
                    {
                        internal_lex = false;
                    }goto doublebreak;
                    
                    case LexResult_Finished: goto doublebreak;
                }
            }
            doublebreak:;
            
            if (internal_lex){
                i32 new_count = cpp_relex_get_new_count(&state, array->count, &relex_array);
                if (new_count > array->max_count){
                    i32 new_max = round_up_i32(new_count, KB(1));
                    void *memory = heap_allocate(heap, new_max*sizeof(Cpp_Token));
                    memcpy(memory, array->tokens, array->count*sizeof(Cpp_Token));
                    heap_free(heap, array->tokens);
                    array->tokens = (Cpp_Token*)memory;
                    array->max_count = new_max;
                }
                
                cpp_relex_complete(&state, array, &relex_array);
                file_mark_edit_finished(&models->working_set, file);
            }
            else{
                cpp_relex_abort(&state, array);
            }
            
            end_temp(temp);
        }
        
        if (!internal_lex){
            Cpp_Token_Array *array = &file->state.token_array;
            Cpp_Get_Token_Result get_token_result = cpp_get_token(*array, end_i);
            i32 end_token_i = get_token_result.token_index;
            
            if (end_token_i < 0){
                end_token_i = 0;
            }
            else if (end_i > array->tokens[end_token_i].start){
                ++end_token_i;
            }
            
            cpp_shift_token_starts(array, end_token_i, shift_amount);
            --end_token_i;
            if (end_token_i >= 0){
                Cpp_Token *token = array->tokens + end_token_i;
                if (token->start < end_i && token->start + token->size > end_i){
                    token->size += shift_amount;
                }
            }
            
            file->state.still_lexing = true;
            
            Job_Data job = {};
            job.callback = job_full_lex;
            job.data[0] = file;
            job.data[1] = heap;
            job.data[2] = models;
            file->state.lex_job = system->post_job(BACKGROUND_THREADS, job);
            result = false;
        }
    }
    return(result);
}

internal b32
file_relex_serial(System_Functions *system, Models *models, Editing_File *file, i32 start_i, i32 end_i, i32 shift_amount){
    Mem_Options *mem = &models->mem;
    Arena *scratch = &mem->arena;
    Heap *heap = &mem->heap;
    
    if (file->state.token_array.tokens == 0){
        file_first_lex_serial(system, models, file);
    }
    else{
        Assert(!file->state.still_lexing);
        
        Gap_Buffer *buffer = &file->state.buffer;
        Cpp_Token_Array *array = &file->state.token_array;
        
        Temp_Memory temp = begin_temp(scratch);
        Parse_Context parse_context = parse_context_get(scratch, &models->parse_context_memory, file->settings.parse_context_id);
        Assert(parse_context.valid);
        
        Cpp_Token_Array relex_array = {};
        relex_array.count = 0;
        relex_array.max_count = Million(1);
        relex_array.tokens = push_array(scratch, Cpp_Token, relex_array.max_count);
        
        i32 size = buffer_size(buffer);
        
        Cpp_Relex_Data state = cpp_relex_init(array, start_i, end_i, shift_amount, file->settings.tokens_without_strings, parse_context.kw_table, parse_context.pp_table);
        
        String_Const_u8_Array chunk_space[3];
        Cursor chunk_cursor = make_cursor(chunk_space, sizeof(chunk_space));
        String_Const_u8_Array chunks = buffer_get_chunks(&chunk_cursor, buffer, BufferGetChunk_ZeroTerminated);
        
        i32 chunk_index = 0;
        u8 *chunk = chunks.vals[chunk_index].str;
        umem chunk_size = chunks.vals[chunk_index].size;
        
        for (;!cpp_relex_is_start_chunk(&state, (char*)chunk, (i32)chunk_size);){
            ++chunk_index;
            Assert(chunk_index < chunks.count);
            chunk = chunks.vals[chunk_index].str;
            chunk_size = chunks.vals[chunk_index].size;
        }
        
        for(;;){
            Cpp_Lex_Result lex_result = cpp_relex_step(&state, (char*)chunk, (i32)chunk_size, size, array, &relex_array);
            
            switch (lex_result){
                case LexResult_NeedChunk:
                {
                    ++chunk_index;
                    Assert(chunk_index < chunks.count);
                    chunk = chunks.vals[chunk_index].str;
                    chunk_size = chunks.vals[chunk_index].size;
                }break;
                
                case LexResult_NeedTokenMemory: InvalidPath;
                
                case LexResult_Finished: goto doublebreak;
            }
        }
        doublebreak:;
        
        i32 new_count = cpp_relex_get_new_count(&state, array->count, &relex_array);
        if (new_count > array->max_count){
            i32 new_max = round_up_i32(new_count, KB(1));
            Cpp_Token *new_tokens = heap_array(heap, Cpp_Token, new_max);
            memcpy(new_tokens, array->tokens, array->count*sizeof(Cpp_Token));
            heap_free(heap, array->tokens);
            array->tokens = new_tokens;
            array->max_count = new_max;
        }
        
        cpp_relex_complete(&state, array, &relex_array);
        
        end_temp(temp);
    }
    
    return(true);
}

internal void
file_first_lex(System_Functions *system, Models *models, Editing_File *file){
    if (!file->settings.virtual_white){
        file_first_lex_parallel(system, models, file);
    }
    else{
        file_first_lex_serial(system, models, file);
    }
}

internal void
file_relex(System_Functions *system, Models *models, Editing_File *file, i32 start, i32 end, i32 shift_amount){
    if (!file->settings.virtual_white){
        file_relex_parallel(system, models, file, start, end, shift_amount);
    }
    else{
        file_relex_serial(system, models, file, start, end, shift_amount);
    }
}

// BOTTOM