#include <efi.h>
#include <efilib.h>

static EFI_GUID gop_guid    = {0x9042a9de,0x23dc,0x4a38,{0x96,0xfb,0x7a,0xde,0xd0,0x80,0x51,0x6a}};
static EFI_GUID simptr_guid = {0x31878c87,0x0b75,0x11d5,{0xb5,0x5c,0x00,0x00,0x00,0x00,0x00,0x00}};
static EFI_GUID absptr_guid = {0x8c7dd51f,0x3968,0x4a31,{0xa4,0x7b,0xd6,0x07,0x1a,0x2c,0x2e,0x4f}};

#define GW 60
#define GH 20
#ifndef SCAN_ESC
#define SCAN_ESC 0x0017
#endif
#ifndef SCAN_UP
#define SCAN_UP 0x0001
#endif
#ifndef SCAN_DOWN
#define SCAN_DOWN 0x0002
#endif
#ifndef SCAN_RIGHT
#define SCAN_RIGHT 0x0003
#endif
#ifndef SCAN_LEFT
#define SCAN_LEFT 0x0004
#endif

static UINT8 B[GH][GW], NB[GH][GW];
static EFI_GRAPHICS_OUTPUT_PROTOCOL *GOP;
static EFI_SYSTEM_TABLE *SysTable;
static UINT8 *FB, *BACK;
static UINTN FW, FH, STRIDE, BPP, ROWBYTES;
static UINT32 C_BLACK, C_WHITE, C_GRAY, C_GREEN, C_RED;
static int FMT_BGR;
static UINTN BX, BY, CS, BW, BH;
static INTN cur_x = 400, cur_y = 300;
static EFI_ABSOLUTE_POINTER_PROTOCOL *APT;
static EFI_SIMPLE_POINTER_PROTOCOL *SPT;
static UINT32 last_btn;
static int g_esc;

static inline void _outb(unsigned short p, unsigned char v) { __asm__ volatile("outb %0, %1" : : "a"(v), "Nd"(p)); }
static inline unsigned char _inb(unsigned short p) { unsigned char r; __asm__ volatile("inb %1, %0" : "=a"(r) : "Nd"(p)); return r; }
static void ser_init(void) {
    _outb(0x3F8+1,0); _outb(0x3F8+3,0x80); _outb(0x3F8+0,1); _outb(0x3F8+1,0);
    _outb(0x3F8+3,3); _outb(0x3F8+2,0xC7); _outb(0x3F8+4,0x0B);
}
static void ser_putc(char c) { long g=0; while(!(_inb(0x3F8+5)&0x20) && g++<500000){} _outb(0x3F8,(unsigned char)c); }
static void ser_puts(const char *s) { while(*s) ser_putc(*s++); }
static void ser_num(long v) {
    char t[24]; int n = 0; unsigned long u;
    if (v < 0) { ser_putc('-'); u = (unsigned long)(-v); } else u = (unsigned long)v;
    if (!u) t[n++] = '0';
    while (u) { t[n++] = (char)('0' + (u % 10)); u /= 10; }
    while (n) ser_putc(t[--n]);
}

#include "font.inc"

static UINT32 MK(UINT32 r, UINT32 g, UINT32 b) { return FMT_BGR ? ((r<<16)|(g<<8)|b) : ((b<<16)|(g<<8)|r); }

static void px(UINTN x, UINTN y, UINT32 c) {
    if (x >= FW || y >= FH || !BACK) return;
    UINT8 *p = BACK + (y*STRIDE + x)*BPP;
    if (BPP == 4) *(UINT32*)p = c;
    else if (BPP == 3) { p[0]=(UINT8)(c&0xFF); p[1]=(UINT8)((c>>8)&0xFF); p[2]=(UINT8)((c>>16)&0xFF); }
}
static void rect(UINTN x, UINTN y, UINTN w, UINTN h, UINT32 c) {
    for (UINTN j=0;j<h;j++) for (UINTN i=0;i<w;i++) px(x+i,y+j,c);
}
static void outline(UINTN x, UINTN y, UINTN w, UINTN h, UINTN t, UINT32 c) {
    rect(x,y,w,t,c); rect(x,y+h-t,w,t,c); rect(x,y,t,h,c); rect(x+w-t,y,t,h,c);
}
static void text(const char *s, UINTN x, UINTN y, UINTN sc, UINT32 col) {
    for (; *s; s++, x += 6*sc) {
        int idx = -1;
        for (int i=0; FONT_CHARS[i]; i++) if (FONT_CHARS[i] == *s) { idx = i; break; }
        if (idx < 0) continue;
        const unsigned char *g = FONT[idx];
        for (int r=0;r<7;r++) for (int k=0;k<5;k++) if (g[r] & (1u << (4-k))) rect(x+k*sc, y+r*sc, sc, sc, col);
    }
}
static void fast_copy(UINT8 *d, const UINT8 *s, UINTN n) {
    UINTN q = n >> 3;
    UINT64 *dq = (UINT64*)d;
    const UINT64 *sq = (const UINT64*)s;
    while (q--) *dq++ = *sq++;
    UINTN r = n & 7;
    UINT8 *db = (UINT8*)dq;
    const UINT8 *sb = (const UINT8*)sq;
    while (r--) *db++ = *sb++;
}
static void present(void) {
    if (!BACK || BACK == FB) return;
    for (UINTN y = 0; y < FH; y++) fast_copy(FB + y*ROWBYTES, BACK + y*ROWBYTES, ROWBYTES);
}

static void init_board(void) {
    SetMem(B, sizeof(B), 0);
    B[1][2]=1; B[2][3]=1; B[3][1]=1; B[3][2]=1; B[3][3]=1;
    B[8][10]=1; B[8][11]=1; B[8][12]=1;
    B[5][40]=1; B[5][41]=1; B[6][40]=1; B[6][41]=1;
    B[14][22]=1; B[14][23]=1; B[15][21]=1; B[15][23]=1; B[16][21]=1; B[16][22]=1;
    B[16][50]=1; B[17][51]=1; B[18][49]=1; B[18][50]=1; B[18][51]=1;
}
static int nb(int x, int y) {
    int c = 0;
    for (int dy=-1; dy<=1; dy++)
        for (int dx=-1; dx<=1; dx++) {
            if (!dx && !dy) continue;
            c += B[(y+dy+GH)%GH][(x+dx+GW)%GW];
        }
    return c;
}
static void step(void) {
    for (int y=0;y<GH;y++)
        for (int x=0;x<GW;x++) {
            int n = nb(x,y);
            NB[y][x] = B[y][x] ? ((n==2||n==3)?1:0) : ((n==3)?1:0);
        }
    for (int y=0;y<GH;y++) for (int x=0;x<GW;x++) B[y][x] = NB[y][x];
}

static void compute_layout(void) {
    UINTN sc = (FW >= 900) ? 3 : 2;
    UINTN lh = 7*sc + 6;
    BY = 10 + lh + 16;
    UINTN reserve = 2*lh + 20;
    UINTN cs1 = (FW > 24) ? (FW - 24)/GW : 2;
    UINTN cs2 = (FH > BY + reserve) ? (FH - BY - reserve)/GH : 2;
    CS = (cs1 < cs2) ? cs1 : cs2;
    if (CS < 2) CS = 2;
    BW = CS*GW; BH = CS*GH;
    BX = (FW > BW) ? (FW - BW)/2 : 0;
    cur_x = (INTN)(BX + BW/2);
    cur_y = (INTN)(BY + BH/2);
}

static void board_dump_serial(void) {
    for (int y=0;y<GH;y++) {
        for (int x=0;x<GW;x++) ser_putc(B[y][x] ? '#' : '.');
        ser_puts("\r\n");
    }
}
static void click_cell(INTN px_, INTN py_, int val) {
    if (px_ < 0 || py_ < 0) return;
    UINTN x = (UINTN)px_, y = (UINTN)py_;
    if (x < BX || x >= BX+BW || y < BY || y >= BY+BH) return;
    UINTN gx = (x - BX)/CS, gy = (y - BY)/CS;
    if (gx >= GW || gy >= GH) return;
    ser_puts("[CLICK cell=("); ser_num((long)gx); ser_puts(","); ser_num((long)gy); ser_puts(") -> ");
    ser_num(val); ser_puts("]\r\n");
    B[gy][gx] = (UINT8)(val ? 1 : 0);
    board_dump_serial();
}

static void apply_buttons(UINT32 b) {
    if ((b & 1u) && !(last_btn & 1u)) click_cell(cur_x, cur_y, 1);
    if ((b & 2u) && !(last_btn & 2u)) click_cell(cur_x, cur_y, 0);
    last_btn = b & 3u;
}
static void poll_pointer(void) {
    if (APT) {
        EFI_ABSOLUTE_POINTER_STATE s;
        if (!EFI_ERROR(APT->GetState(APT, &s))) {
            UINT64 minx = APT->Mode->AbsoluteMinX, maxx = APT->Mode->AbsoluteMaxX;
            UINT64 miny = APT->Mode->AbsoluteMinY, maxy = APT->Mode->AbsoluteMaxY;
            if (maxx > minx) cur_x = (INTN)((s.CurrentX - minx) * (UINT64)(FW-1) / (maxx - minx));
            if (maxy > miny) cur_y = (INTN)((s.CurrentY - miny) * (UINT64)(FH-1) / (maxy - miny));
            apply_buttons(s.ActiveButtons & 3u);
        }
    } else if (SPT) {
        EFI_SIMPLE_POINTER_STATE s;
        if (!EFI_ERROR(SPT->GetState(SPT, &s))) {
            cur_x += s.RelativeMovementX;
            cur_y += s.RelativeMovementY;
            if (cur_x < 0) cur_x = 0;
            if (cur_y < 0) cur_y = 0;
            if (cur_x > (INTN)FW-1) cur_x = (INTN)FW-1;
            if (cur_y > (INTN)FH-1) cur_y = (INTN)FH-1;
            apply_buttons((s.LeftButton?1u:0u) | (s.RightButton?2u:0u));
        }
    }
}
static void poll_keyboard(void) {
    EFI_INPUT_KEY k;
    if (SysTable->ConIn && !EFI_ERROR(SysTable->ConIn->ReadKeyStroke(SysTable->ConIn, &k))) {
        if (k.ScanCode == SCAN_ESC) g_esc = 1;
        if (k.ScanCode == SCAN_LEFT)  { if (cur_x > (INTN)CS) cur_x -= (INTN)CS; }
        if (k.ScanCode == SCAN_RIGHT) { cur_x += (INTN)CS; if (cur_x > (INTN)FW-1) cur_x = (INTN)FW-1; }
        if (k.ScanCode == SCAN_UP)    { if (cur_y > (INTN)CS) cur_y -= (INTN)CS; }
        if (k.ScanCode == SCAN_DOWN)  { cur_y += (INTN)CS; if (cur_y > (INTN)FH-1) cur_y = (INTN)FH-1; }
        if (k.UnicodeChar == L' ') click_cell(cur_x, cur_y, 1);
        if (k.UnicodeChar == 8)    click_cell(cur_x, cur_y, 0);
        if (k.UnicodeChar == L'r' || k.UnicodeChar == L'R') { init_board(); ser_puts("[RESET]\r\n"); }
    }
}

static void draw(UINT32 gen, int halted) {
    rect(0,0,FW,FH,C_BLACK);
    UINTN sc = (FW >= 900) ? 3 : 2;
    UINTN lh = 7*sc + 6;

    text("LIFEOS - CONWAYS GAME OF LIFE", 12, 10, sc, C_WHITE);

    outline(BX-3, BY-3, BW+6, BH+6, 2, C_GRAY);
    for (int y=0;y<GH;y++)
        for (int x=0;x<GW;x++)
            if (B[y][x]) rect(BX + (UINTN)x*CS, BY + (UINTN)y*CS, CS, CS, C_GREEN);

    UINTN ty = BY + BH + 12;
    char buf[24]; int i = 0;
    const char *p = "GEN: ";
    while (*p) buf[i++] = *p++;
    { char t[12]; int n=0; UINT32 g=gen; if(!g) t[n++]='0'; while(g){ t[n++]=(char)('0'+(int)(g%10u)); g/=10u; } while(n) buf[i++]=t[--n]; }
    buf[i] = 0;
    text(buf, 12, ty, sc, C_WHITE);
    text(halted ? "PAUSED - ESC:RESUME  R:RESET  CLICK:ADD  RMB:CLEAR"
                : "CLICK OR SPACE:ADD  RMB OR DEL:CLEAR  R:RESET  ESC:PAUSE",
         12, ty + lh, sc, C_GRAY);

    /* cursor: highlight hovered cell + crosshair at pointer */
    if (cur_x >= 0 && cur_y >= 0) {
        UINTN ux = (UINTN)cur_x, uy = (UINTN)cur_y;
        if (ux >= BX && ux < BX+BW && uy >= BY && uy < BY+BH) {
            UINTN gx = (ux - BX)/CS, gy = (uy - BY)/CS;
            outline(BX + gx*CS, BY + gy*CS, CS, CS, 2, C_RED);
        }
        rect(ux > 6 ? ux-6 : 0, uy, 13, 1, C_WHITE);
        rect(ux, uy > 6 ? uy-6 : 0, 1, 13, C_WHITE);
    }
}

EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *st) {
    InitializeLib(image, st);
    SysTable = st;
    ser_init();
    ser_puts("=== LIFEOS EFI (double-buffer + pointer) ===\r\n");

    if (EFI_ERROR(st->BootServices->LocateProtocol(&gop_guid, NULL, (void**)&GOP)) || !GOP || !GOP->Mode) {
        ser_puts("GOP not found\r\n");
        st->ConOut->OutputString(st->ConOut, L"LifeOS: Graphics Output Protocol not available.\r\n");
        for(;;) st->BootServices->Stall(1000000);
    }
    FW = GOP->Mode->Info->HorizontalResolution;
    FH = GOP->Mode->Info->VerticalResolution;
    STRIDE = GOP->Mode->Info->PixelsPerScanLine;
    FMT_BGR = (GOP->Mode->Info->PixelFormat == PixelBlueGreenRedReserved8BitPerColor);
    BPP = 4;
    ROWBYTES = STRIDE * BPP;
    FB = (UINT8*)(UINTN)GOP->Mode->FrameBufferBase;
    C_BLACK = MK(0,0,0); C_WHITE = MK(235,235,235); C_GRAY = MK(110,116,128); C_GREEN = MK(90,255,120); C_RED = MK(255,70,70);

    /* double buffer */
    BACK = NULL;
    EFI_STATUS as = st->BootServices->AllocatePool(EfiLoaderData, ROWBYTES * FH, (void**)&BACK);
    if (EFI_ERROR(as) || !BACK) { BACK = FB; ser_puts("BACK=direct(fallback)\r\n"); }
    else { SetMem(BACK, ROWBYTES*FH, 0); ser_puts("BACK=offscreen ok\r\n"); }

    /* pointer protocols */
    APT = NULL; SPT = NULL;
    if (EFI_ERROR(st->BootServices->LocateProtocol(&absptr_guid, NULL, (void**)&APT))) APT = NULL;
    if (!APT && EFI_ERROR(st->BootServices->LocateProtocol(&simptr_guid, NULL, (void**)&SPT))) SPT = NULL;
    ser_puts(APT ? "pointer=absolute\r\n" : (SPT ? "pointer=simple\r\n" : "pointer=none\r\n"));
    ser_puts("fb="); ser_num((long)FW); ser_puts("x"); ser_num((long)FH);
    ser_puts(" stride="); ser_num((long)STRIDE); ser_puts("\r\n");

    compute_layout();
    init_board();
    last_btn = 0;

    UINT32 gen = 0;
    int halted = 0;
    for (;;) {
        draw(gen, halted);
        present();
        if (gen == 0u || (gen % 50u) == 0u) ser_puts("[frame ok]\r\n");
        if (!halted) { gen++; step(); }

        for (int i = 0; i < 24; i++) {
            poll_pointer();
            poll_keyboard();
            st->BootServices->Stall(5000);
        }
        if (g_esc) { g_esc = 0; halted = !halted; ser_puts(halted ? "[PAUSED]\r\n" : "[RESUMED]\r\n"); }
    }
    return EFI_SUCCESS;
}
