#include "fb.h"
#include "font.h"
#include "arch.h"
#include "util.h"
#include "printf.h"
#include "usbkbd.h"
#include "mouse.h"
#include "dbg.h"

struct fb_info fb;
static int cursor_col;
static int cursor_row;
static int cursor_visible;

/* Сканкоды клавиатуры, набор 1, раскладка US */
static const unsigned char kbd_us[128] = {
    0,  27, '1','2','3','4','5','6','7','8','9','0','-','=', '\b',
    '\t','q','w','e','r','t','y','u','i','o','p','[',']','\n',
    0,  'a','s','d','f','g','h','j','k','l',';','\'','`',
    0, '\\','z','x','c','v','b','n','m',',','.','/', 0, '*',
    0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-',0,0,0,'+',
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

static const unsigned char kbd_us_shift[128] = {
    0,  27, '!','@','#','$','%','^','&','*','(',')','_','+', '\b',
    '\t','Q','W','E','R','T','Y','U','I','O','P','{','}','\n',
    0,  'A','S','D','F','G','H','J','K','L',':','"','~',
    0, '|','Z','X','C','V','B','N','M','<','>','?', 0, '*',
    0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-',0,0,0,'+',
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

/* Раскладка ЙЦУКЕН (CP1251) — 0 = как в US */
static const unsigned char kbd_ru[128] = {
    0,  27, '1','2','3','4','5','6','7','8','9','0','-','=', '\b',
    '\t',0xE9,0xF6,0xF3,0xEA,0xE5,0xED,0xE3,0xF8,0xF9,0xE7,0xF5,0xFA,'\n',
    0,  0xF4,0xFB,0xE2,0xE0,0xEF,0xF0,0xEE,0xEB,0xE4,0xE6,0xFD,0xB8,
    0, '\\',0xFF,0xF7,0xF1,0xEC,0xE8,0xF2,0xFC,0xE1,0xFE,0x2E, 0, '*',
    0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-',0,0,0,'+',
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

static const unsigned char kbd_ru_shift[128] = {
    0,  27, '!','@','#','$','%','^','&','*','(',')','_','+', '\b',
    '\t',0xC9,0xD6,0xD3,0xCA,0xC5,0xCD,0xC3,0xD8,0xD9,0xC7,0xD5,0xDA,'\n',
    0,  0xD4,0xDB,0xC2,0xC0,0xCF,0xD0,0xCE,0xCB,0xC4,0xC6,0xDD,0xA8,
    0, '|',0xDF,0xD7,0xD1,0xCC,0xC8,0xD2,0xDC,0xC1,0xDE,0x2C, 0, '*',
    0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-',0,0,0,'+',
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

static int shift_pressed;
static int alt_pressed;
int kbd_layout; /* 0 = US, 1 = RU */

/* Вычисляем сдвиг для битовой маски */
static int mask_shift(uint32_t mask) {
    if(!mask) return 0;
    int shift = 0;
    while(!(mask & 1)) { shift++; mask >>= 1; }
    return shift;
}

/* Вычисляем размер маски в битах */
static int mask_size(uint32_t mask) {
    if(!mask) return 0;
    int size = 0;
    while(mask & 1) { size++; mask >>= 1; }
    /* Также проверяем старшие биты */
    int shift = mask_shift(mask);
    mask >>= shift;
    while(mask) { size++; mask >>= 1; }
    return size;
}

uint32_t make_color(uint8_t r, uint8_t g, uint8_t b) {
    /* Если маски определены (BitMask формат) — используем их */
    if(fb.r_mask || fb.g_mask || fb.b_mask) {
        int rs = mask_shift(fb.r_mask);
        int gs = mask_shift(fb.g_mask);
        int bs = mask_shift(fb.b_mask);
        int rbits = mask_size(fb.r_mask);
        int gbits = mask_size(fb.g_mask);
        int bbits = mask_size(fb.b_mask);

        /* Масштабируем 8-битные компоненты до нужного размера */
        uint32_t rv = (uint32_t)r >> (8 - rbits);
        uint32_t gv = (uint32_t)g >> (8 - gbits);
        uint32_t bv = (uint32_t)b >> (8 - bbits);

        return (rv << rs) | (gv << gs) | (bv << bs);
    }

    /* Стандартный формат: rgb=1 → RGBA, rgb=0 → BGRA */
    if(fb.rgb)
        return (r << 16) | (g << 8) | b;
    else
        return (b << 16) | (g << 8) | r;
}

void fb_init(struct fb_info *info) {
    memcpy(&fb, info, sizeof(fb));
    cursor_col = 0;
    cursor_row = 0;
    cursor_visible = 1;
    fb.fg = make_color(0xFF, 0xD4, 0xF9); /* светло-розовый */
    fb.bg = make_color(0x3a, 0x00, 0x57); /* тёмно-фиолетовый */
    shift_pressed = 0;
    alt_pressed = 0;
    kbd_layout = 0;
    fb_clear();
}

void fb_clear(void) {
    uint32_t *ptr = (uint32_t*)(uintptr_t)fb.base;
    uint32_t bg = fb.bg;
    int px, py, pw, ph;
    dbg_get_panel_rect(&px, &py, &pw, &ph);
    int stride = fb.pitch / 4;
    /* Top bar (0..BAR_H) and bottom hint bar are managed by dbg.c */
    extern int dbg_panel_visible(void);
    for(uint32_t y = 0; y < fb.height; y++) {
        uint32_t *row = &ptr[y * stride];
        for(uint32_t x = 0; x < fb.width; x++) {
            /* Skip debug panel area */
            if(dbg_panel_visible() && (int)x >= px && (int)x < px + pw &&
               (int)y >= py && (int)y < py + ph)
                continue;
            row[x] = bg;
        }
    }
    cursor_col = 0;
    cursor_row = 2; /* skip top bar (2 char rows = 32px) */
}

void fb_putchar_direct(int x, int y, unsigned char c, uint32_t fg, uint32_t bg) {
    if(x >= (int)fb.width || y >= (int)fb.height) return;
    uint32_t *ptr = (uint32_t*)(uintptr_t)fb.base;
    int stride = fb.pitch / 4;
    for(int row = 0; row < 16; row++) {
        unsigned char bits = font8x16_basic[c][row];
        for(int col = 0; col < 8; col++) {
            int px = x + col;
            int py = y + row;
            if(px >= (int)fb.width || py >= (int)fb.height) continue;
            if(bits & (0x80 >> col))
                ptr[py * stride + px] = fg;
            else
                ptr[py * stride + px] = bg;
        }
    }
}

void fb_scroll(void) {
    int char_height = 16;
    int bar_top = 18;   /* top status bar height */
    int bar_bot = 18;   /* bottom hint bar height */
    int usable_y = bar_top;
    int usable_h = (int)fb.height - bar_top - bar_bot;
    uint32_t *ptr = (uint32_t*)(uintptr_t)fb.base;
    int stride = fb.pitch / 4;
    uint32_t bg = fb.bg;

    /* Scroll usable area up by one char row */
    memmove(&ptr[usable_y * stride],
            &ptr[(usable_y + char_height) * stride],
            (usable_h - char_height) * stride * sizeof(uint32_t));

    /* Clear the bottom char row of usable area */
    int clear_y = usable_y + usable_h - char_height;
    for(int y = 0; y < char_height; y++) {
        uint32_t *row = &ptr[(clear_y + y) * stride];
        for(int x = 0; x < (int)fb.width; x++)
            row[x] = bg;
    }
    cursor_row--;
}

void fb_putchar(char c) {
    if(c == '\n') {
        cursor_col = 0;
        cursor_row++;
        int max_row = ((int)fb.height - 18 - 16) / 16; /* above hint bar */
        if(cursor_row > max_row) {
            fb_scroll();
            cursor_row = max_row;
        }
        return;
    }
    if(c == '\r') {
        cursor_col = 0;
        return;
    }
    if(c == '\b') {
        if(cursor_col > 0) cursor_col--;
        return;
    }
    if(c == '\t') {
        cursor_col = (cursor_col + 8) & ~7;
        return;
    }
    if((unsigned char)c < 32) return;

    int x = cursor_col * 8;
    int y = cursor_row * 16;
    fb_putchar_direct(x, y, (unsigned char)c, fb.fg, fb.bg);
    cursor_col++;
    int max_col = (int)fb.width / 8;
    if(cursor_col >= max_col) {
        cursor_col = 0;
        cursor_row++;
    }
    int max_row = ((int)fb.height - 18 - 16) / 16;
    if(cursor_row > max_row) {
        fb_scroll();
        cursor_row = max_row;
    }
}

void fb_puts(const char *s) {
    while(*s) fb_putchar(*s++);
}

void fb_printf(const char *fmt, ...) {
    static char buf[1024];
    __builtin_va_list args;
    __builtin_va_start(args, fmt);
    vsnprintf(buf, sizeof(buf), fmt, args);
    __builtin_va_end(args);
    fb_puts(buf);
}

void fb_set_cursor(int col, int row) {
    cursor_col = col;
    cursor_row = row;
}

void fb_get_cursor(int *col, int *row) {
    *col = cursor_col;
    *row = cursor_row;
}

void fb_set_fg(uint32_t color) { fb.fg = color; }
void fb_set_bg(uint32_t color) { fb.bg = color; }

static void hsv_to_rgb(int h, uint8_t s, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b) {
    uint8_t region = h / 60;
    uint8_t remainder = (h % 60) * 255 / 60;
    uint8_t p = (uint8_t)((uint32_t)v * (255 - s) / 255);
    uint8_t q = (uint8_t)((uint32_t)v * (255 - (uint32_t)s * remainder / 255) / 255);
    uint8_t t = (uint8_t)((uint32_t)v * (255 - (uint32_t)s * (255 - remainder) / 255) / 255);
    switch(region) {
        case 0: *r=v; *g=t; *b=p; break;
        case 1: *r=q; *g=v; *b=p; break;
        case 2: *r=p; *g=v; *b=t; break;
        case 3: *r=p; *g=q; *b=v; break;
        case 4: *r=t; *g=p; *b=v; break;
        default: *r=v; *g=p; *b=q; break;
    }
}

void fb_rainbow(void) {
    uint32_t *ptr = (uint32_t*)(uintptr_t)fb.base;
    int stride = fb.pitch / 4;
    int px, py, pw, ph;
    dbg_get_panel_rect(&px, &py, &pw, &ph);
    for(uint32_t y = 0; y < fb.height; y++) {
        for(uint32_t x = 0; x < fb.width; x++) {
            if((int)x >= px && (int)x < px + pw && (int)y >= py && (int)y < py + ph)
                continue;
            int hue = (x * 360) / fb.width;
            uint8_t r, g, b;
            hsv_to_rgb(hue, 255, 255, &r, &g, &b);
            ptr[y * stride + x] = make_color(r, g, b);
        }
    }
    cursor_col = 0;
    cursor_row = 0;
}

/* Serial port input (COM1) for -nographic QEMU mode */
#define COM1_DATA 0x3F8
#define COM1_LSR 0x3FD
#define LSR_DR   1

static char serial_getchar(void) {
    uint8_t lsr = inb(COM1_LSR);
    if(lsr != 0xFF && (lsr & LSR_DR))
        return (char)inb(COM1_DATA);
    return 0;
}

static char ps2_decode(uint8_t sc) {
    /* Обрабатываем Shift */
    if(sc == 0x2A || sc == 0x36) {
        shift_pressed = 1;
        if(alt_pressed) { kbd_layout ^= 1; shift_pressed = 0; alt_pressed = 0; }
        return 0;
    }
    if(sc == 0xAA || sc == 0xB6) { shift_pressed = 0; return 0; }
    /* Обрабатываем Alt */
    if(sc == 0x38) {
        alt_pressed = 1;
        if(shift_pressed) { kbd_layout ^= 1; shift_pressed = 0; alt_pressed = 0; }
        return 0;
    }
    if(sc == 0xB8) { alt_pressed = 0; return 0; }
    if(sc & 0x80) return 0; /* break code */

    /* Extended scancode prefix (0xE0) */
    if(sc == 0xE0) {
        while(!(inb(0x64) & 1)) {}
        uint8_t ex = inb(0x60);
        if(ex & 0x80) return 0; /* extended break */
        switch(ex) {
            case 0x48: return 0x80; /* Up    — ESC [ A */
            case 0x50: return 0x81; /* Down  — ESC [ B */
            case 0x4D: return 0x82; /* Right — ESC [ C */
            case 0x4B: return 0x83; /* Left  — ESC [ D */
            case 0x47: return 0x84; /* Home  — ESC [ H */
            case 0x4F: return 0x85; /* End   — ESC [ F */
            case 0x49: return 0x86; /* PgUp  — ESC [ 5 ~ */
            case 0x51: return 0x87; /* PgDn  — ESC [ 6 ~ */
            case 0x52: return 0x88; /* Ins   — ESC [ 2 ~ */
            case 0x53: return 0x89; /* Del   — ESC [ 3 ~ */
            default: return 0;
        }
    }

    /* F-keys */
    if(sc >= 0x3B && sc <= 0x44) return (char)(0x90 + sc - 0x3B); /* F1-F10 → 0x90-0x99 */
    if(sc == 0x57) return (char)0x9A; /* F11 */
    if(sc == 0x58) return (char)0x9B; /* F12 */

    if(sc >= 128) return 0;
    if(kbd_layout)
        return shift_pressed ? kbd_ru_shift[sc] : kbd_ru[sc];
    return shift_pressed ? kbd_us_shift[sc] : kbd_us[sc];
}

char getchar(void) {
    mouse_poll();
    /* USB keyboard first — avoids PS/2 status register issues */
    char usb_c = usb_kbd_getchar();
    if(usb_c) return usb_c;

    /* Serial input (COM1, для -nographic) */
    char ser_c = serial_getchar();
    if(ser_c) {
        dbg_putchar(ser_c);
        return ser_c;
    }

    /* Быстрый опрос PS/2 (без ожидания) — skip if aux (mouse) data */
    if((inb(0x64) & 1) && !(inb(0x64) & 0x20)) {
        uint8_t sc = inb(0x60);
        char c = ps2_decode(sc);
        if(c) return c;
        /* Serial input (COM1) */
        ser_c = serial_getchar();
        if(ser_c) return ser_c;
        /* Modifier/break consumed — still try USB once more */
        usb_c = usb_kbd_getchar();
        if(usb_c) return usb_c;
    }

    /* PS/2 с таймаутом (~100 мс) как запас для систем без USB-клавиатуры */
    uint64_t timeout = rdtsc() + 200000000ULL;
    while(1) {
        mouse_poll();
        /* Always check USB regardless of PS/2 status */
        usb_c = usb_kbd_getchar();
        if(usb_c) return usb_c;
        /* Serial input (COM1, для -nographic) */
        ser_c = serial_getchar();
        if(ser_c) return ser_c;
        /* If PS/2 keyboard data available (not aux/mouse), process it */
        if((inb(0x64) & 1) && !(inb(0x64) & 0x20)) {
            uint8_t sc = inb(0x60);
            char c = ps2_decode(sc);
            if(c) return c;
            /* Modifier/break — loop back and check USB again */
        }
        if(rdtsc() > timeout) return 0;
    }
}

char getchar_wait(void) {
    char c;
    while(1) {
        c = getchar();
        if(c) return c;
    }
}

void fb_cursor_pixel(int show) {
    uint32_t *ptr = (uint32_t*)(uintptr_t)fb.base;
    int stride = fb.pitch / 4;
    int x = cursor_col * 8;
    int y = cursor_row * 16;
    uint32_t col = show ? make_color(0xFF, 0xB3, 0xFF) : fb.bg;
    for(int r = 12; r < 16; r++)
        for(int c = 0; c < 8; c++)
            ptr[(y + r) * stride + (x + c)] = col;
}

void fb_readline(char *buf, int size) {
    int i = 0;
    uint64_t blink = rdtsc() + 400000000ULL;
    int cursor_on = 1;
    fb_cursor_pixel(1);
    while(i < size - 1) {
        char c = getchar();
        if(c) {
            blink = rdtsc() + 400000000ULL;
            if(!cursor_on) {
                fb_cursor_pixel(1);
                cursor_on = 1;
            }
            if(c == '\n' || c == '\r') {
                fb_cursor_pixel(0);
                fb_puts("\n");
                buf[i] = '\0';
                return;
            }
            if(c == '\b') {
                if(i > 0) {
                    fb_cursor_pixel(0);
                    i--;
                    fb_putchar('\b');
                    fb_putchar(' ');
                    fb_putchar('\b');
                    fb_cursor_pixel(1);
                }
                continue;
            }
            if((unsigned char)c >= ' ') {
                fb_cursor_pixel(0);
                buf[i++] = c;
                fb_putchar(c);
                fb_cursor_pixel(1);
            }
        } else if(rdtsc() > blink) {
            blink = rdtsc() + 400000000ULL;
            cursor_on = !cursor_on;
            fb_cursor_pixel(cursor_on);
        }
    }
    fb_cursor_pixel(0);
    buf[i] = '\0';
}
