#include "arc/console/buffer.h"

#include "arc/console/view.h"
#include "arc/std/string.h"
#include "arc/std/vector.h"
#include <stdlib.h>
#include <string.h>

struct ARC_ConsoleBuffer {
    ARC_Vector *bufferLines;
};

void ARC_ConsoleBuffer_Create(ARC_ConsoleBuffer **buffer){
    *buffer = (ARC_ConsoleBuffer *)malloc(sizeof(ARC_ConsoleBuffer));

    ARC_Vector_Create(&((*buffer)->bufferLines));

    //add first line to vector
    ARC_Vector_Add((*buffer)->bufferLines, NULL);
}

void ARC_ConsoleBuffer_Destroy(ARC_ConsoleBuffer *buffer){
    for(uint32_t i = 0; i < ARC_Vector_Size(buffer->bufferLines); i++){
        ARC_String *bufferLine = (ARC_String *)ARC_Vector_Get(buffer->bufferLines, i);

        if(bufferLine != NULL){
            ARC_String_Destroy(bufferLine);
        }
    }

    ARC_Vector_Destroy(buffer->bufferLines);
    free(buffer);
}

void ARC_ConsoleBuffer_Clear(ARC_ConsoleBuffer *buffer){
    for(uint32_t i = 0; i < ARC_Vector_Size(buffer->bufferLines); i++){
        ARC_String *bufferLine = (ARC_String *)ARC_Vector_Get(buffer->bufferLines, i);

        if(bufferLine != NULL){
            ARC_String_Destroy(bufferLine);
        }
    }

    ARC_Vector_Destroy(buffer->bufferLines);
    ARC_Vector_Create(&(buffer->bufferLines));

    //add first line to vector
    ARC_Vector_Add(buffer->bufferLines, NULL);
}

void ARC_ConsoleBuffer_Render(ARC_ConsoleBuffer *buffer, ARC_ConsoleView *view){
    ARC_Rect viewBounds = ARC_ConsoleView_GetBounds(view);

    uint32_t bufferStartIndex = 0;
    if(ARC_Vector_Size(buffer->bufferLines) > (uint32_t)viewBounds.h){
        bufferStartIndex = ARC_Vector_Size(buffer->bufferLines) - viewBounds.h;
    }

    for(uint32_t i = 0; i < (uint32_t)viewBounds.h; i++){
        if(i + bufferStartIndex > ARC_Vector_Size(buffer->bufferLines)){
            break;
        }

        ARC_String *bufferLine = (ARC_String *)ARC_Vector_Get(buffer->bufferLines, i + bufferStartIndex);
        if(bufferLine == NULL){
            continue;
        }

        ARC_ConsoleView_RenderStringAt(view, bufferLine, (ARC_Point){ 0, i });
    }
}

void ARC_ConsoleBuffer_RenderSection(ARC_ConsoleBuffer *buffer, ARC_ConsoleView *view, uint32_t startIndex, uint32_t lines){
    ARC_Rect viewBounds = ARC_ConsoleView_GetBounds(view);

    for(uint32_t i = 0; i < lines; i++){
        if(i + startIndex >= ARC_Vector_Size(buffer->bufferLines)){
            break;
        }

        if(i >= (uint32_t)viewBounds.h){
            break;
        }

        ARC_String *bufferLine = (ARC_String *)ARC_Vector_Get(buffer->bufferLines, i + startIndex);
        if(bufferLine == NULL){
            continue;
        }

        ARC_ConsoleView_RenderStringAt(view, bufferLine, (ARC_Point){ 0, i });
    }
}

void ARC_ConsoleBuffer_AddChar(ARC_ConsoleBuffer *buffer, char character){
    if(character == '\n'){
        ARC_Vector_Add(buffer->bufferLines, (void *)NULL);
        return;
    }

    //get the last line and add a char to it
    ARC_String *bufferLine = (ARC_String *)ARC_Vector_Get(buffer->bufferLines, ARC_Vector_Size(buffer->bufferLines) - 1);
    ARC_Vector_RemoveIndex(buffer->bufferLines, ARC_Vector_Size(buffer->bufferLines) - 1);

    if(bufferLine == NULL){
        ARC_String_Create(&bufferLine, &character, 1);
        ARC_Vector_Add(buffer->bufferLines, (void *)bufferLine);
        return;
    }

    //add char to the end of the bufferline
    ARC_String *nextChar;
    ARC_String_Create(&nextChar, &character, 1);

    ARC_String *tempBufferLine = bufferLine;
    ARC_String_Merge(&bufferLine, tempBufferLine, nextChar);

    ARC_String_Destroy(tempBufferLine);
    ARC_String_Destroy(nextChar);

    //add buffer line back to the bufferLines
    ARC_Vector_Add(buffer->bufferLines, (void *)bufferLine);
}

void ARC_ConsoleBuffer_AddString(ARC_ConsoleBuffer *buffer, ARC_String *string){
    //TODO: this in a more efficient way
    for(uint64_t i = 0; i < string->length; i++){
        ARC_ConsoleBuffer_AddChar(buffer, string->data[i]);
    }
}

void ARC_ConsoleBuffer_AddCString(ARC_ConsoleBuffer *buffer, char *cstring, uint64_t length){
    //TODO: this in a more efficient way
    for(uint64_t i = 0; i < length; i++){
        ARC_ConsoleBuffer_AddChar(buffer, cstring[i]);
    }
}

void ARC_ConsoleBuffer_AddCStringWithStrlen(ARC_ConsoleBuffer *buffer, char *cstring){
    //TODO: this in a more efficient way
    for(uint64_t i = 0; i < strlen(cstring); i++){
        ARC_ConsoleBuffer_AddChar(buffer, cstring[i]);
    }
}

uint32_t ARC_ConsoleBuffer_GetLineNumbers(ARC_ConsoleBuffer *buffer){
    return ARC_Vector_Size(buffer->bufferLines);
}
