# Layer Stacker — web adapter / DEVICE EMULATOR (MicroPython via PyScript).
#
# Renders to an offscreen 160x120 surface (the Tufty's LORES screen) and
# pixel-scales it (integer, nearest-neighbour) into a bezel, so the browser
# shows exactly what the badge shows. Runs the SHARED core unchanged.
from pyscript import document, window
from pyscript.ffi import create_proxy
import stacker_core as sc

DEV_W, DEV_H = 160, 120                  # the Tufty's LORES screen

view = document.getElementById("c")      # visible canvas (fills window)
vctx = view.getContext("2d")
dev = document.createElement("canvas")   # offscreen device screen
dev.width = DEV_W
dev.height = DEV_H
dctx = dev.getContext("2d")
dctx.textBaseline = "top"

VW = [800]
VH = [600]


def resize(*_a):
    dpr = window.devicePixelRatio or 1
    if dpr > 2:
        dpr = 2
    VW[0] = view.clientWidth or window.innerWidth     # canvas owns the flex space above the controls
    VH[0] = view.clientHeight or window.innerHeight
    view.width = int(VW[0] * dpr)
    view.height = int(VH[0] * dpr)
    vctx.setTransform(dpr, 0, 0, dpr, 0, 0)


def css(c):
    if len(c) == 4:
        return "rgba(%d,%d,%d,%g)" % (c[0], c[1], c[2], c[3] / 255.0)
    return "rgb(%d,%d,%d)" % (c[0], c[1], c[2])


def fontstr(size):
    return "%dpx system-ui,'Segoe UI',Roboto,sans-serif" % int(size)


# Surface draws to the 160x120 device screen ---------------------------------
class Surface:
    @property
    def width(self):
        return DEV_W

    @property
    def height(self):
        return DEV_H

    def clear(self, c):
        dctx.fillStyle = css(c)
        dctx.fillRect(0, 0, DEV_W, DEV_H)

    def rect(self, x, y, w, h, c):
        if w <= 0 or h <= 0:
            return
        dctx.fillStyle = css(c)
        dctx.fillRect(x, y, w, h)

    def poly(self, pts, c, stroke=None):
        dctx.beginPath()
        dctx.moveTo(pts[0][0], pts[0][1])
        for i in range(1, len(pts)):
            dctx.lineTo(pts[i][0], pts[i][1])
        dctx.closePath()
        dctx.fillStyle = css(c)
        dctx.fill()
        if stroke:
            dctx.strokeStyle = css(stroke)
            dctx.lineWidth = 1
            dctx.stroke()

    def text(self, s, x, y, c, size=12):
        dctx.font = fontstr(size)
        dctx.fillStyle = css(c)
        dctx.fillText(s, x, y)

    def text_w(self, s, size=12):
        dctx.font = fontstr(size)
        return dctx.measureText(s).width


def present():
    # integer pixel scale that fits the window with a little margin for the bezel
    s = int(min(VW[0] / DEV_W, VH[0] / DEV_H) * 0.94)
    if s < 1:
        s = 1
    sw = DEV_W * s
    sh = DEV_H * s
    ox = (VW[0] - sw) / 2
    oy = (VH[0] - sh) / 2
    vctx.imageSmoothingEnabled = False
    vctx.fillStyle = "#0b0c0e"
    vctx.fillRect(0, 0, VW[0], VH[0])
    vctx.fillStyle = "#1c1f24"                       # bezel
    _round(vctx, ox - 10, oy - 10, sw + 20, sh + 30, 10)
    vctx.fill()
    vctx.drawImage(dev, 0, 0, DEV_W, DEV_H, ox, oy, sw, sh)
    vctx.fillStyle = "#5a5e66"
    vctx.font = "600 11px system-ui,sans-serif"
    vctx.textAlign = "center"
    vctx.fillText("TUFTY 2350", VW[0] / 2, oy + sh + 8)
    vctx.textAlign = "left"


def _round(c, x, y, w, h, r):
    c.beginPath()
    c.moveTo(x + r, y)
    c.arcTo(x + w, y, x + w, y + h, r)
    c.arcTo(x + w, y + h, x, y + h, r)
    c.arcTo(x, y + h, x, y, r)
    c.arcTo(x, y, x + w, y, r)
    c.closePath()


g = Surface()
game = sc.Game()

# browser-persistent leaderboard key (so the web build can register too)
try:
    _k = window.localStorage.getItem("ls_dev_key")
    if not _k:
        import random as _r
        _abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
        _k = "web" + "".join(_abc[int(_r.random() * 62)] for _ in range(9))
        window.localStorage.setItem("ls_dev_key", _k)
    game.device_key = _k
except Exception:
    game.device_key = ""

# ---- input ----------------------------------------------------------------
def on_keydown(e):
    code = e.code
    if code in ("Space", "KeyB"):            # B button: extrude / select / play again
        e.preventDefault()
        if not e.repeat:
            game.action_down()
    elif code == "KeyA":                      # A button: nav left / back to menu
        if not e.repeat:
            game.nav(-1)
            game.back()
    elif code == "KeyC":                      # C button: nav right
        if not e.repeat:
            game.nav(1)
    elif code in ("ArrowLeft", "ArrowUp"):
        game.nav(-1)
    elif code in ("ArrowRight", "ArrowDown"):
        game.nav(1)
    elif code == "Enter":
        game.select()
    elif code == "Escape":
        game.back()


def on_keyup(e):
    if e.code in ("Space", "KeyB"):
        game.action_up()


drag = [False, 0]


def on_down(e):
    if game.state == sc.RESULT:
        drag[0] = True
        drag[1] = e.clientX
    else:
        game.action_down()


def on_up(_e):
    drag[0] = False
    game.action_up()


def on_move(e):
    if drag[0]:
        game.drag(e.clientX - drag[1])
        drag[1] = e.clientX


document.addEventListener("keydown", create_proxy(on_keydown))
document.addEventListener("keyup", create_proxy(on_keyup))
view.addEventListener("pointerdown", create_proxy(on_down))
window.addEventListener("pointerup", create_proxy(on_up))
window.addEventListener("pointermove", create_proxy(on_move))
window.addEventListener("resize", create_proxy(resize))
resize()


# ---- on-screen A / B / C buttons (touch + mouse), mirroring the badge keys ---
def _bind(btn_id, press, release=None):
    el = document.getElementById(btn_id)
    if el is None:
        return

    def pd(e):
        e.preventDefault()
        press()
    el.addEventListener("pointerdown", create_proxy(pd))
    if release is not None:
        def pu(e):
            e.preventDefault()
            release()
        for ev in ("pointerup", "pointercancel", "pointerleave"):
            el.addEventListener(ev, create_proxy(pu))


def _btn_a():
    game.nav(-1); game.back()


_bind("btnA", _btn_a)
_bind("btnB", game.action_down, game.action_up)   # hold to extrude
_bind("btnC", lambda: game.nav(1))

_ld = document.getElementById("load")
if _ld:
    _ld.style.display = "none"

# ---- loop -----------------------------------------------------------------
_last = [0.0]


def frame(t):
    dt = (t - _last[0]) / 1000.0 if _last[0] else 0.016
    _last[0] = t
    game.update(g, dt)        # draws onto the 160x120 device surface
    present()                 # pixel-scale it into the bezel
    window.requestAnimationFrame(_loop)


_loop = create_proxy(frame)
window.requestAnimationFrame(_loop)
