#include "arc/console/view.h"

#include "key.h"
#include "arc/console/element.h"
#include "arc/std/bool.h"
#include "arc/std/errno.h"
#include "arc/std/vector.h"
#include "arc/std/string.h"
#include <locale.h>
#include <ncurses.h>
#include <stdlib.h>

uint8_t arc_ncurses_win_size = 0;

struct ARC_ConsoleView {
    WINDOW *window;
    ARC_Rect bounds;
    ARC_Bool echo;

    ARC_Vector *elements;
};

void ARC_ConsoleView_Create(ARC_ConsoleView **view, ARC_Rect bounds){
    if(arc_ncurses_win_size == ~(uint8_t)0){
        arc_errno = ARC_ERRNO_OVERFLOW;
        ARC_DEBUG_ERR("ARC_NCurses_Create(ncurses), max num of ARC_NCurses have been created, consider making arc_ncurses_win_size a uint32_t to increase the max");
        *view = NULL;
        return;
    }

    //if this is the first ncurses, init ncurses
    if(arc_ncurses_win_size == 0){
        setlocale(LC_ALL, "");
        initscr();
        //start_color();
        cbreak();
        keypad(stdscr, TRUE);
        refresh();
    }

    *view = (ARC_ConsoleView *)malloc(sizeof(ARC_ConsoleView));

    ARC_Rect viewBounds = { 0, 0, COLS, LINES };
    if(bounds.w != 0 && bounds.h != 0){
        viewBounds = bounds;
    }

    (*view)->window = newwin(viewBounds.h, viewBounds.w, viewBounds.y, viewBounds.x);
    (*view)->bounds = viewBounds;

    keypad((*view)->window, TRUE);

    noecho();
    (*view)->echo = false;

    ARC_Vector_Create(&(*view)->elements);

    wrefresh((*view)->window);

    arc_ncurses_win_size++;
}

void ARC_ConsoleView_Destroy(ARC_ConsoleView *ncurses){
    arc_ncurses_win_size--;

    ARC_Vector_Destroy(ncurses->elements);

    delwin(ncurses->window);
    free(ncurses);

    if(arc_ncurses_win_size == 0){
        endwin();
    }
}

void ARC_ConsoleView_AddElement(ARC_ConsoleView *view, ARC_ConsoleElement *element){
    ARC_Vector_Add(view->elements, (void *)element);
}

void ARC_ConsoleView_RemoveElement(ARC_ConsoleView *view, uint32_t index){
    ARC_Vector_RemoveIndex(view->elements, index);
}

void ARC_ConsoleView_Clear(ARC_ConsoleView *view){
    wclear(view->window);
    wrefresh(view->window);
}

void ARC_ConsoleView_RenderCharAt(ARC_ConsoleView *view, char character, ARC_Point pos){
    mvwprintw(view->window, pos.y, pos.x, "%c", character);
    wrefresh(view->window);
}

void ARC_ConsoleView_RenderWCharAt(ARC_ConsoleView *view, wchar_t character, ARC_Point pos){
    mvwprintw(view->window, pos.y, pos.x, "%lc", character);
    wrefresh(view->window);
}

void ARC_ConsoleView_RenderKeyAt(ARC_ConsoleView *view, ARC_ConsoleKey key, ARC_Point pos){
    mvwprintw(view->window, pos.y, pos.x, "%c", (char)key.key);
    wrefresh(view->window);
}

void ARC_ConsoleView_RenderUint32At(ARC_ConsoleView *view, uint32_t uint32, ARC_Point pos){
    mvwprintw(view->window, pos.y, pos.x, "%d", uint32);
    wrefresh(view->window);
}

void ARC_ConsoleView_RenderStringAt(ARC_ConsoleView *view, ARC_String *text, ARC_Point pos){
    mvwprintw(view->window, pos.y, pos.x, "%s", text->data);
    wrefresh(view->window);
}

void ARC_ConsoleView_RenderCStringWithStrlenAt(ARC_ConsoleView *view, char *cstr, ARC_Point pos){
    mvwprintw(view->window, pos.y, pos.x, "%s", cstr);
    wrefresh(view->window);
}

void ARC_ConsoleView_RenderRect(ARC_ConsoleView *view, ARC_Rect bounds){
    //render corners
    ARC_ConsoleView_RenderWCharAt(view, L'┌', (ARC_Point){                  bounds.x,                  bounds.y });
    ARC_ConsoleView_RenderWCharAt(view, L'└', (ARC_Point){                  bounds.x, (bounds.h - 1) + bounds.y });
    ARC_ConsoleView_RenderWCharAt(view, L'┐', (ARC_Point){ (bounds.w - 1) + bounds.x,                  bounds.y });
    ARC_ConsoleView_RenderWCharAt(view, L'┘', (ARC_Point){ (bounds.w - 1) + bounds.x, (bounds.h - 1) + bounds.y });

    //render virticle lines
    for(int32_t x = 1; x < bounds.w - 1; x++){
        ARC_ConsoleView_RenderWCharAt(view, L'─', (ARC_Point){ bounds.x + x,                  bounds.y });
        ARC_ConsoleView_RenderWCharAt(view, L'─', (ARC_Point){ bounds.x + x, (bounds.h - 1) + bounds.y });
    }

    //render horizontal lines
    for(int32_t y = 1; y < bounds.h - 1; y++){
        ARC_ConsoleView_RenderWCharAt(view, L'│', (ARC_Point){                  bounds.x, bounds.y + y });
        ARC_ConsoleView_RenderWCharAt(view, L'│', (ARC_Point){ (bounds.w - 1) + bounds.x, bounds.y + y });
    }
}

void ARC_ConsoleView_RenderElements(ARC_ConsoleView *view){
    for(uint32_t i = 0; i < ARC_Vector_Size(view->elements); i++){
        ARC_ConsoleElement *element = (ARC_ConsoleElement *)ARC_Vector_Get(view->elements, i);
        element->renderFn(view, element);
        wrefresh(view->window);
    }
}

ARC_Rect ARC_ConsoleView_GetBounds(ARC_ConsoleView *view){
    return view->bounds;
}

ARC_ConsoleElement *ARC_ConsoleView_GetElement(ARC_ConsoleView *view, uint32_t index){
    return (ARC_ConsoleElement *)ARC_Vector_Get(view->elements, index);
}

char ARC_ConsoleView_GetChar(ARC_ConsoleView *view){
    return wgetch(view->window);
}

char ARC_ConsoleView_GetCharAt(ARC_ConsoleView *view, ARC_Point pos){
    return mvwgetch(view->window, pos.y, pos.x);
}

ARC_ConsoleKey ARC_ConsoleView_GetConsoleKeyAt(ARC_ConsoleView *view, ARC_Point pos){
    return (ARC_ConsoleKey){ mvwgetch(view->window, pos.y, pos.x) };
}

ARC_ConsoleKey *ARC_ConsoleView_GetCreateConsoleKeyAt(ARC_ConsoleView *view, ARC_Point pos){
    ARC_ConsoleKey *key;
    ARC_ConsoleKey_Create(&key, NULL);
    key->key = mvwgetch(view->window, pos.y, pos.x);
    return key;
}

ARC_String *ARC_ConsoleView_GetStringInput(ARC_ConsoleView *view, ARC_Point pos, ARC_ConsoleView_OverrideCharInputFn *overrideCharInputFn, void *userdata){
    noecho();

    uint32_t cstringSize = view->bounds.w - pos.x;
    char cstring[view->bounds.w - pos.x];

    ARC_ConsoleKey temp = ARC_ConsoleView_GetConsoleKeyAt(view, pos);
    uint32_t index = 0;
    while(temp.key != '\n'){
        //store the last size to be able to clear efficeintly
        uint32_t lastSize = index;

        //if override function exists and it overrode the current char
        if(overrideCharInputFn != NULL && (*overrideCharInputFn)(&temp, cstring, &index, cstringSize, userdata)){
            for(uint32_t i = 0; i < lastSize; i++){
                ARC_ConsoleView_RenderCharAt(view, ' ', (ARC_Point){ pos.x + i, pos.y });
            }

            for(uint32_t i = 0; i < index; i++){
                ARC_ConsoleView_RenderCharAt(view, cstring[i], (ARC_Point){ pos.x + i, pos.y });
            }

            temp = ARC_ConsoleView_GetConsoleKeyAt(view, (ARC_Point){ pos.x + index, pos.y });
            continue;
        }

        if(temp.key == KEY_BACKSPACE || temp.key == KEY_DC || temp.key == 127){
            if(index == 0){
                temp = ARC_ConsoleView_GetConsoleKeyAt(view, (ARC_Point){ pos.x + index, pos.y });
                continue;
            }

            index--;
            ARC_ConsoleView_RenderCharAt(view, ' ', (ARC_Point){ pos.x + index, pos.y });
            cstring[index] = '\0';
            temp = ARC_ConsoleView_GetConsoleKeyAt(view, (ARC_Point){ pos.x + index, pos.y });
            continue;
        }

        if(index < (view->bounds.w - 1) - pos.x){
            ARC_ConsoleView_RenderCharAt(view, (char)(temp.key), (ARC_Point){ pos.x + index, pos.y });
            cstring[index] = (char)(temp.key);
            index++;
        }

        temp = ARC_ConsoleView_GetConsoleKeyAt(view, (ARC_Point){ pos.x + index, pos.y });
    }

    if(view->echo){
        echo();
    }

    if(index == 0){
        return NULL;
    }

    ARC_String *string;
    ARC_String_Create(&string, cstring, index);
    return string;
}

void ARC_ConsoleView_SetCursorVisibility(ARC_ConsoleView *view, uint8_t visibility){
    switch(visibility){
        case ARC_CONSOLE_VIEW_CURSOR_HIDDEN:
            curs_set(0);
            break;
        case ARC_CONSOLE_VIEW_CURSOR_VISIBLE:
            curs_set(1);
            break;
    }
}

void ARC_ConsoleView_SetBorder(ARC_ConsoleView *view, uint32_t border){
    switch(border){
        case ARC_CONSOLE_VIEW_BORDER_NONE:
            wborder(view->window, ' ', ' ', ' ',' ',' ',' ',' ',' ');
            break;
        case ARC_CONSOLE_VIEW_BORDER_DEFAULT:
            box(view->window, 0, 0);
            break;
    }

    wrefresh(view->window);
}

void ARC_ConsoleView_SetAttribute(ARC_ConsoleView *view, uint32_t attribute){
    switch(attribute){
        case ARC_CONSOLE_VIEW_ATTRIBUTE_NONE:
            wattroff(view->window, A_REVERSE);
            break;
        case ARC_CONSOLE_VIEW_ATTRIBUTE_REVERSE:
            wattron(view->window, A_REVERSE);
            break;
    }
}
