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

static EFI_GUID gop_guid = {0x9042a9de,0x23dc,0x4a38,{0x96,0xfb,0x7a,0xde,0xd0,0x80,0x51,0x6a}};

#define GW 60
#define GH 20

static UINT8 B[GH][GW], NB[GH][GW];
static EFI_GRAPHICS_OUTPUT_PROTOCOL *GOP;
static UINT8 *FB;
static UINTN FW, FH, STRIDE, BPP;
static UINT32 C_BLACK, C_WHITE, C_GRAY, C_GREEN;
static int FMT_BGR;

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++); }

#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) return;
    UINT8 *p = FB + (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 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 ser_dump(UINT32 gen, int bx, int cs) {
    char b[12]; int i=0; UINT32 g=gen;
    ser_puts("[GEN ");
    if(!g) b[i++]='0';
    while(g){ b[i++]=(char)('0'+(int)(g%10u)); g/=10u; }
    for(int k=0;k<i/2;k++){ char t=b[k]; b[k]=b[i-1-k]; b[i-1-k]=t; }
    b[i]=0; ser_puts(b); ser_puts("] fb="); 
    { char t[16]; int n=0; UINTN v=FW; if(!v) t[n++]='0'; while(v){t[n++]=(char)('0'+(v%10));v/=10;} while(n) ser_putc(t[--n]); }
    ser_puts("x");
    { char t[16]; int n=0; UINTN v=FH; if(!v) t[n++]='0'; while(v){t[n++]=(char)('0'+(v%10));v/=10;} while(n) ser_putc(t[--n]); }
    ser_puts(" cell=");
    { char t[16]; int n=0; int v=cs; if(!v) t[n++]='0'; while(v){t[n++]=(char)('0'+(v%10));v/=10;} while(n) ser_putc(t[--n]); }
    ser_puts("\r\n");
    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 draw(UINT32 gen) {
    rect(0,0,FW,FH,C_BLACK);
    UINTN sc = (FW >= 900) ? 3 : 2;
    text("LIFEOS - CONWAYS GAME OF LIFE", 12, 10, sc, C_WHITE);
    UINTN by = 10 + 7*sc + 16;
    UINTN cs1 = (FW > 24) ? (FW - 24)/GW : 2;
    UINTN cs2 = (FH > by + 70) ? (FH - by - 70)/GH : 2;
    UINTN cs = (cs1 < cs2) ? cs1 : cs2;
    if (cs < 2) cs = 2;
    UINTN bx = 12, bw = cs*GW, bh = cs*GH;
    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);
    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;
    UINTN tsc = (FW >= 900) ? 3 : 2;
    text(buf, 12, by+bh+12, tsc, C_WHITE);
    if (FW > 400) text("ESC:HALT  R:RESET", bx+bw-17*6*tsc, by+bh+12, tsc, C_GRAY);
}

EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *st) {
    InitializeLib(image, st);
    ser_init();
    ser_puts("=== LIFEOS EFI/GOP ENTERED ===\r\n");

    EFI_STATUS s = st->BootServices->LocateProtocol(&gop_guid, NULL, (void**)&GOP);
    if (EFI_ERROR(s) || GOP == NULL || GOP->Mode == NULL || GOP->Mode->Info == NULL) {
        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;
    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);

    init_board();

    UINT32 gen = 0;
    EFI_INPUT_KEY key;
    for (;;) {
        draw(gen);
        if (gen == 0u || (gen % 50u) == 0u) ser_dump(gen, 0, 0);
        gen++;
        step();
        st->BootServices->Stall(120000);
        if (st->ConIn->ReadKeyStroke(st->ConIn, &key) == EFI_SUCCESS) {
            if (key.ScanCode == 0x17) break;
            if (key.UnicodeChar == L'r' || key.UnicodeChar == L'R') { init_board(); gen = 0; }
        }
    }
    rect(0,0,FW,FH,C_BLACK);
    text("HALTED - POWER OFF OR RESET", 40, FH/2, 3, C_WHITE);
    for (;;) st->BootServices->Stall(1000000);
    return EFI_SUCCESS;
}
