#!/usr/bin/env python3
#
# Generate the Android TV launcher banner (res/drawable-xhdpi/banner.png).
#
# Android TV quality requires a 320x180 xhdpi banner that shows the app name, so
# a plain scaled launcher icon is not enough.  This composites the shared 32x32
# icon (resources/kat5200.png) onto a dark panel and renders the app name with a
# tiny built-in 5x7 bitmap font, so it needs NO image tooling (stdlib zlib only)
# and stays reproducible.  The output PNG IS committed; re-run only when the art
# or name changes.  Replace with polished 320x180 artwork for a release build.
#
#   ./gen-tv-banner.py
#
import os
import struct
import zlib

W, H = 320, 180
BG = (24, 26, 32)          # dark panel, close to the GUI theme background
FG = (236, 238, 242)       # near-white app-name text
ICON_SRC = "../resources/kat5200.png"
OUT = "app/src/main/res/drawable-xhdpi/banner.png"

# 5x7 uppercase bitmap font, only the glyphs used by the app name.
FONT = {
    'K': ["10001", "10010", "10100", "11000", "10100", "10010", "10001"],
    'A': ["01110", "10001", "10001", "11111", "10001", "10001", "10001"],
    'T': ["11111", "00100", "00100", "00100", "00100", "00100", "00100"],
    '5': ["11111", "10000", "11110", "00001", "00001", "10001", "01110"],
    '2': ["01110", "10001", "00001", "00010", "00100", "01000", "11111"],
    '0': ["01110", "10001", "10011", "10101", "11001", "10001", "01110"],
}
NAME = "KAT5200"


def new_canvas(w, h, color):
    row = list(color) * w
    return [list(row) for _ in range(h)]


def put(canvas, x, y, color):
    if 0 <= x < W and 0 <= y < H:
        canvas[y][x * 3:x * 3 + 3] = list(color)


def draw_text(canvas, text, x0, y0, scale, color):
    cx = x0
    for ch in text:
        glyph = FONT[ch]
        for ry, bits in enumerate(glyph):
            for rx, bit in enumerate(bits):
                if bit == '1':
                    for sy in range(scale):
                        for sx in range(scale):
                            put(canvas, cx + rx * scale + sx,
                                        y0 + ry * scale + sy, color)
        cx += (5 + 1) * scale     # 1-column gap between glyphs


def read_png_rgb(path):
    """Minimal PNG reader for the 8-bit RGB/RGBA icon (stdlib only)."""
    with open(path, "rb") as f:
        data = f.read()
    assert data[:8] == b"\x89PNG\r\n\x1a\n", "not a PNG"
    pos = 8
    width = height = bit_depth = color_type = None
    idat = b""
    while pos < len(data):
        length = struct.unpack(">I", data[pos:pos + 4])[0]
        ctype = data[pos + 4:pos + 8]
        chunk = data[pos + 8:pos + 8 + length]
        if ctype == b"IHDR":
            width, height, bit_depth, color_type = struct.unpack(">IIBB", chunk[:10])
        elif ctype == b"IDAT":
            idat += chunk
        elif ctype == b"IEND":
            break
        pos += 12 + length
    assert bit_depth == 8 and color_type in (2, 6), "expect 8-bit RGB/RGBA icon"
    chans = 3 if color_type == 2 else 4
    raw = zlib.decompress(idat)
    stride = width * chans
    pixels = [[0, 0, 0] for _ in range(width * height)]
    prev = bytearray(stride)
    rp = 0
    for y in range(height):
        ft = raw[rp]; rp += 1
        line = bytearray(raw[rp:rp + stride]); rp += stride
        for i in range(stride):
            a = line[i - chans] if i >= chans else 0
            b = prev[i]
            c = prev[i - chans] if i >= chans else 0
            if ft == 1:
                line[i] = (line[i] + a) & 0xff
            elif ft == 2:
                line[i] = (line[i] + b) & 0xff
            elif ft == 3:
                line[i] = (line[i] + ((a + b) >> 1)) & 0xff
            elif ft == 4:
                p = a + b - c
                pa, pb, pc = abs(p - a), abs(p - b), abs(p - c)
                pr = a if pa <= pb and pa <= pc else (b if pb <= pc else c)
                line[i] = (line[i] + pr) & 0xff
        for x in range(width):
            o = x * chans
            pixels[y * width + x] = [line[o], line[o + 1], line[o + 2]]
        prev = line
    return width, height, pixels


def blit_scaled(canvas, iw, ih, pix, dx, dy, dsize):
    """Nearest-neighbour scale the square-ish icon to dsize and paste it."""
    for y in range(dsize):
        sy = min(ih - 1, y * ih // dsize)
        for x in range(dsize):
            sx = min(iw - 1, x * iw // dsize)
            put(canvas, dx + x, dy + y, pix[sy * iw + sx])


def write_png(path, canvas):
    raw = bytearray()
    for row in canvas:
        raw.append(0)            # filter type 0 (none)
        raw.extend(row)

    def chunk(tag, payload):
        return (struct.pack(">I", len(payload)) + tag + payload +
                struct.pack(">I", zlib.crc32(tag + payload) & 0xffffffff))

    ihdr = struct.pack(">IIBBBBB", W, H, 8, 2, 0, 0, 0)   # 8-bit RGB
    out = (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) +
           chunk(b"IDAT", zlib.compress(bytes(raw), 9)) + chunk(b"IEND", b""))
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "wb") as f:
        f.write(out)


def main():
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
    canvas = new_canvas(W, H, BG)

    iw, ih, pix = read_png_rgb(ICON_SRC)
    icon_size = 96
    icon_x, icon_y = 28, (H - icon_size) // 2
    blit_scaled(canvas, iw, ih, pix, icon_x, icon_y, icon_size)

    scale = 7                                   # 5x7 glyph -> 35px tall
    text_w = (len(NAME) * 6 - 1) * scale
    text_x = icon_x + icon_size + 24
    text_y = (H - 7 * scale) // 2
    # Keep the name inside the canvas if the layout ever grows.
    if text_x + text_w > W - 12:
        scale = max(4, (W - 12 - text_x) // (len(NAME) * 6 - 1))
        text_y = (H - 7 * scale) // 2
    draw_text(canvas, NAME, text_x, text_y, scale, FG)

    write_png(OUT, canvas)
    print("wrote", OUT, "(%dx%d)" % (W, H))


if __name__ == "__main__":
    main()
