//
// Created by bruno on 5.2.2025.
// Modified to use dynamic limits.
//

#include "texteditor.h"
#include "font.h"
#include "../cpu/core.h"

// Initialize the text editor with dynamic sizes.
void init_editor(TextEditor *editor, BitmapFont *font, int x, int y, SDL_Renderer *renderer,
                 int max_line_width, int max_lines_asm, int max_lines_display, bool readOnly) {
    editor->max_line_width = max_line_width;
    editor->maxLines = max_lines_asm;
    editor->displayLineCount = max_lines_display;
    editor->line_count = 0;
    editor->cursor_line = 0;
    editor->cursor_line_offset = 0;
    editor->cursor_pos = 0;
    editor->readOnly = readOnly;
    editor->font = font;

    editor->outRect = malloc(sizeof(SDL_Rect));
    editor->cursorRect = malloc(sizeof(SDL_Rect));
    editor->rect = malloc(sizeof(SDL_Rect));
    editor->highlightedLineRect = malloc(sizeof(SDL_Rect));

    memset(editor->outRect, 0, sizeof(SDL_Rect));
    memset(editor->cursorRect, 0, sizeof(SDL_Rect));
    memset(editor->rect, 0, sizeof(SDL_Rect));
    memset(editor->highlightedLineRect, 0, sizeof(SDL_Rect));


    // Allocate dynamic array for lines.
    editor->lines = (Line *) malloc(sizeof(Line) * editor->maxLines);
    if (!editor->lines) {
        fprintf(stderr, "Failed to allocate memory for lines.\n");
        exit(EXIT_FAILURE);
    }
    // For each line, allocate memory for the text (including space for '\0')
    for (int i = 0; i < editor->maxLines; i++) {
        editor->lines[i].text = (char *) malloc(sizeof(char) * (editor->max_line_width + 1));
        if (!editor->lines[i].text) {
            fprintf(stderr, "Failed to allocate memory for line %d.\n", i);
            exit(EXIT_FAILURE);
        }
        editor->lines[i].text[0] = '\0';
        editor->lines[i].active = 0;
    }

    // Allocate output and display strings.
    editor->outputString = (char *) malloc(sizeof(char) * ((editor->max_line_width + 1) * editor->maxLines + 1) + 1);
    editor->displayString = (char *) malloc(
            sizeof(char) * ((editor->max_line_width + 1) * editor->displayLineCount + 1) + 1);
    if (!editor->outputString || !editor->displayString) {
        fprintf(stderr, "Failed to allocate memory for output/display strings.\n");
        exit(EXIT_FAILURE);
    }
    editor->outputString[0] = '\0';
    editor->displayString[0] = '\0';

    // Initialize with two active lines (like the original code).
    editor->lines[0].active = 1;
    editor->lines[1].active = 1;
    editor->line_count = 2;

    // Set up the editor rectangle based on font size and dynamic max_line_width and displayLineCount.
    editor->rect->x = 2;
    editor->rect->y = 2;
    editor->rect->w = editor->max_line_width * (font->size + 1) + ((font->size + 1) / 2);
    editor->rect->h = editor->displayLineCount * (font->size + 1) + 2;
    editor->outRect->w = editor->rect->w;
    editor->outRect->h = editor->rect->h;
    editor->outRect->x = x;
    editor->outRect->y = y;

    editor->highlightedLineRect->x = 2;
    editor->highlightedLineRect->y = 2;
    editor->highlightedLineRect->w = editor->outRect->w - (2 * editor->highlightedLineRect->x);
    editor->highlightedLineRect->h = editor->font->size;

    editor->cursorRect->x = 3 + editor->cursor_pos * font->size + editor->outRect->x;
    editor->cursorRect->y =
            2 + (editor->cursor_line - editor->cursor_line_offset) * (font->size + 1) + editor->outRect->y;

    editor->cursorRect->w = editor->font->size;
    editor->cursorRect->h = editor->font->size;

    // Create texture for rendering.
    editor->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,
                                        SDL_TEXTUREACCESS_TARGET,
                                        editor->rect->w, editor->rect->h);
    if (!editor->texture) {
        fprintf(stderr, "Failed to create texture: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }
}

void insert_line(TextEditor *editor, int position, const char *text, SDL_Renderer *renderer) {
    if (editor->line_count >= editor->maxLines || position < 0 || position > editor->line_count) {
        printf("Invalid position or max lines reached!\n");
        return;
    }

    // Shift lines down.
    for (int i = editor->line_count; i > position; i--) {
        strcpy(editor->lines[i].text, editor->lines[i - 1].text);
        editor->lines[i].active = editor->lines[i - 1].active;
    }

    // Copy the text into the new line, ensuring it does not exceed max_line_width.
    strncpy(editor->lines[position].text, text, editor->max_line_width);
    editor->lines[position].text[editor->max_line_width] = '\0';
    editor->lines[position].active = 1;

    editor->line_count++;
    move_cursor(editor, editor->cursor_line + 1, 0, false, renderer);
    generate_string_display(editor, renderer);
}

void insert_line_rel(TextEditor *editor, SDL_Renderer *renderer) {
    int line = editor->cursor_line;
    int pos = editor->cursor_pos;
    char *current_text = editor->lines[line].text;

    // Split the current line at the cursor position
    char new_line_text[editor->max_line_width + 1];
    strcpy(new_line_text, &current_text[pos]); // Copy the text after cursor to the new line
    current_text[pos] = '\0'; // Truncate the current line at cursor position

    insert_line(editor, line + 1, new_line_text, renderer);
    editor->cursor_pos = 0;
}

void insert_character(TextEditor *editor, char ch, SDL_Renderer *renderer) {
    if (editor->cursor_line < 0 || editor->cursor_line >= editor->line_count) {
        printf("Invalid cursor position!\n");
        return;
    }

    Line *line = &editor->lines[editor->cursor_line];
    int len = strlen(line->text);

    if (len >= editor->max_line_width || editor->cursor_pos > len) {
        printf("Position out of bounds or line is full!\n");
        return;
    }

    // Shift characters to the right.
    for (int i = len; i >= editor->cursor_pos; i--) {
        line->text[i + 1] = line->text[i];
    }

    line->text[editor->cursor_pos] = ch;
    editor->cursor_pos++;
    generate_string_display(editor, renderer);
}

void remove_character(TextEditor *editor, bool isDelete, SDL_Renderer *renderer) {
    if (editor->cursor_line < 0 || editor->cursor_line >= editor->line_count) {
        printf("Invalid cursor position!\n");
        return;
    }

    Line *line = &editor->lines[editor->cursor_line];
    int len = strlen(line->text);

    if (isDelete) {
        // Delete character after cursor
        if (editor->cursor_pos < len) {
            for (int i = editor->cursor_pos; i < len; i++) {
                line->text[i] = line->text[i + 1];
            }
        } else if (editor->cursor_pos == len && editor->cursor_line < editor->line_count - 1) {
            // Merge next line with the current line when pressing delete at the end
            strcat(line->text, editor->lines[editor->cursor_line + 1].text);

            // Shift remaining lines up
            for (int i = editor->cursor_line + 1; i < editor->line_count - 1; i++) {
                strcpy(editor->lines[i].text, editor->lines[i + 1].text);
                editor->lines[i].active = editor->lines[i + 1].active;
            }
            editor->lines[editor->line_count - 1].text[0] = '\0';
            editor->lines[editor->line_count - 1].active = 0;
            editor->line_count--;
        }
    } else {
        // Backspace behavior (delete character before cursor)
        if (editor->cursor_pos == 0 && editor->cursor_line > 0) {
            // Append current line to previous line
            int prev_len = strlen(editor->lines[editor->cursor_line - 1].text);
            strcat(editor->lines[editor->cursor_line - 1].text, line->text);

            // Shift lines up
            for (int i = editor->cursor_line; i < editor->line_count - 1; i++) {
                strcpy(editor->lines[i].text, editor->lines[i + 1].text);
                editor->lines[i].active = editor->lines[i + 1].active;
            }
            editor->lines[editor->line_count - 1].text[0] = '\0';
            editor->lines[editor->line_count - 1].active = 0;
            editor->line_count--;

            // Move cursor to the end of the previous line
            editor->cursor_line--;
            editor->cursor_pos = prev_len;
        } else if (editor->cursor_pos > 0) {
            for (int i = editor->cursor_pos - 1; i < len; i++) {
                line->text[i] = line->text[i + 1];
            }
            editor->cursor_pos--;
        }
    }

    generate_string_display(editor, renderer);
}


void move_cursor_relative(TextEditor *editor, int line_offset, int pos_offset, bool keepPos, SDL_Renderer *renderer) {
    int new_line = editor->cursor_line + line_offset;
    int new_pos = editor->cursor_pos + pos_offset;

    move_cursor(editor, new_line, new_pos, keepPos, renderer);
}

void move_cursor(TextEditor *editor, int new_line, int new_pos, bool keepPos, SDL_Renderer *renderer) {
    if (new_line < 0) new_line = 0;
    int trgt = editor->line_count - editor->displayLineCount;
    if (trgt < 0) {
        trgt = 0;
    }
    if (new_line >= trgt && new_line >= editor->line_count) {
        new_line = editor->line_count -
                   1;
    }

    if (keepPos) {
        editor->cursor_line_offset = new_line;
    }

    if (new_line < editor->cursor_line_offset) {
        editor->cursor_line_offset = new_line;
    }
    if (new_line >= editor->cursor_line_offset + editor->displayLineCount) {
        editor->cursor_line_offset = new_line - editor->displayLineCount + 1;
    }

    if (editor->cursor_line_offset >= editor->line_count - editor->displayLineCount) {
        editor->cursor_line_offset = editor->line_count - editor->displayLineCount;
    }
    if (editor->cursor_line_offset < 0) {
        editor->cursor_line_offset = 0;
    }

    int line_length = strlen(editor->lines[new_line].text);
    if (new_pos < 0) new_pos = 0;
    if (new_pos > line_length) new_pos = line_length;

    editor->cursor_line = new_line;
    editor->cursor_pos = new_pos;
    generate_string_display(editor, renderer);
}

void generate_string_display(TextEditor *editor, SDL_Renderer *renderer) {
    if (editor->cursor_line_offset < 0 || editor->cursor_line_offset >= editor->line_count) {
        printf("Invalid start line!\n");
        return;
    }

    int end_line = editor->cursor_line_offset + editor->displayLineCount;
    if (end_line > editor->line_count)
        end_line = editor->line_count;

    // Clear the display string.
    editor->displayString[0] = '\0';

    SDL_SetRenderTarget(renderer, editor->texture);
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
    SDL_RenderClear(renderer);

    SDL_Rect charDstRect;
    charDstRect.x = 4;
    charDstRect.y = 3;
    charDstRect.w = editor->font->size;
    charDstRect.h = editor->font->size;

    SDL_Rect charRect;
    charRect.x = 0;
    charRect.y = 0;
    charRect.w = editor->font->size;
    charRect.h = editor->font->size;

    editor->cursorRect->x = 3 + editor->cursor_pos * (charDstRect.w + 1) + editor->outRect->x;
    editor->cursorRect->y =
            2 + (editor->cursor_line - editor->cursor_line_offset) * (charDstRect.h + 1) + editor->outRect->y;

    for (int line = editor->cursor_line_offset; line < end_line; line++) {
        if (editor->lines[line].active) {
            strcat(editor->displayString, editor->lines[line].text);
            char *linePTR = editor->lines[line].text;
            while (*linePTR) {
                SDL_RenderCopy(renderer, editor->font->texture[(unsigned char) *linePTR], &charRect, &charDstRect);
                charDstRect.x += charDstRect.w + 1;
                linePTR++;
            }
            charDstRect.x = 4;
            charDstRect.y += charDstRect.h + 1;
            strcat(editor->displayString, "\n");
        }
    }
    SDL_SetRenderTarget(renderer, NULL);
}

void editor_render(TextEditor *editor, SDL_Renderer *renderer, CPU *cpu, uint8_t editorIndex, bool isActive,
                   bool cursorBlink) {
    if (isActive) {
        SDL_Rect bgRect;
        bgRect = *editor->outRect;
        bgRect.x -= 6;
        bgRect.y -= 6;
        bgRect.w += 12;
        bgRect.h += 12;


        SDL_SetRenderDrawColor(renderer, editor->readOnly ? 128 : 0, editor->readOnly ? 0 : 128, 64, 255);
        SDL_RenderFillRect(renderer, &bgRect);

    }
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderFillRect(renderer, editor->outRect);

    uint32_t targetLine = 0;
    const uint32_t instrLine = cpu->pc / CPU_INSTRUCTION_SIZE;
    if (editorIndex == 0) {
        targetLine = cpu->addrToLineMapper[instrLine] - editor->cursor_line_offset;
        editor->highlightedLineRect->w = editor->rect->w - 1;
        editor->highlightedLineRect->x = 2;
    }
    if (editorIndex == 1) {
        targetLine = (instrLine / 2) - editor->cursor_line_offset;
        editor->highlightedLineRect->w = (editor->rect->w / 5 * 2) - 2;
        editor->highlightedLineRect->x =
                2 + (11 * editor->font->size) + (instrLine % 2 ? (28 * editor->font->size) : 0);

    }
    editor->highlightedLineRect->y = 2 + ((editor->font->size + 1) * targetLine);
    SDL_Rect highlightedLineAbs = *editor->highlightedLineRect;
    highlightedLineAbs.x += editor->outRect->x;
    highlightedLineAbs.y += editor->outRect->y;
    SDL_SetRenderDrawColor(renderer, 0, 64, 128, 255);
    if (targetLine >= editor->cursor_line_offset &&
        targetLine < editor->cursor_line_offset + editor->displayLineCount) {
        SDL_RenderFillRect(renderer, &highlightedLineAbs);
    }


    SDL_SetTextureBlendMode(editor
                                    ->texture, SDL_BLENDMODE_BLEND);
    SDL_RenderCopy(renderer, editor
            ->texture, editor->rect, editor->outRect);
    if (isActive && cursorBlink) {
        SDL_SetRenderDrawColor(renderer,
                               0, 255, 255, 128);
        SDL_RenderFillRect(renderer, editor
                ->cursorRect);

    }
}

void generate_string(TextEditor *editor) {
    editor->outputString[0] = '\0';

    for (int i = 0; i < editor->maxLines; i++) {
        if (editor->lines[i].active) {
            if (strlen(editor->lines[i].text)) {
                strcat(editor->outputString, editor->lines[i].text);
                strcat(editor->outputString, "\n");
            }
        }
    }
}

void fill_editor_from_string(TextEditor *editor, const char *content, int lineStart, bool isComplete,
                             SDL_Renderer *renderer) {
    if (!editor || !content) {
        printf("Invalid editor or content pointer!\n");
        return;
    }

    char *str = content;

    int numLines = 0;
    while (*str) if (*str++ == '\n') ++numLines;

    // Clear the current editor content
    for (int i = lineStart; i < editor->maxLines && i < lineStart + numLines + 1; i++) {
        editor->lines[i].text[0] = '\0';
        editor->lines[i].active = 0;
    }
    if (isComplete) {
        editor->line_count = numLines;
        if (editor->line_count >= editor->maxLines) {
            editor->line_count = editor->maxLines - 1;
        }
    }

    // Parse the content and fill the editor lines
    const char *ptr = content;
    int line_index = lineStart;

    while (*ptr && line_index < editor->maxLines && line_index < numLines + lineStart) {
        int char_count = 0;

        // Ensure the text buffer does not overflow
        while (*ptr && *ptr != '\n' && char_count < editor->max_line_width) {
            editor->lines[line_index].text[char_count++] = *ptr++;
        }

        editor->lines[line_index].text[char_count] = '\0'; // Null-terminate
        editor->lines[line_index].active = 1;
        line_index++;

        // Move past the newline character if present
        if (*ptr == '\n') ptr++;
    }
    editor->lines[line_index].active = 1;

    // Update the total number of lines in use
    //editor->line_count = line_index + (line_index < editor->maxLines - 1 ? 1 : 0);
    //if (editor->line_count >= editor-> maxLines)

    // Generate the visual representation
    generate_string_display(editor, renderer);
}


// Free all dynamically allocated memory.
void destroy_editor(TextEditor *editor) {
    if (editor->lines) {
        for (int i = 0; i < editor->maxLines; i++) {
            free(editor->lines[i].text);
        }
        free(editor->lines);
    }
    //free(editor->outputString);
    //free(editor->displayString);

    free(editor->outRect);
    free(editor->rect);
    free(editor->cursorRect);

    if (editor->texture) {
        SDL_DestroyTexture(editor->texture);
    }
}
