"""
PIX-8 Fantasy Console
Pure Python • TIC-80 inspired

Version: 0.0.6 (experimental, no GUI/audio)
"""

import sys
import json
import pathlib
import importlib.util
import zipfile
import time
import types

import pygame

# -------------------------
# Constants
# -------------------------

WIDTH = 128
HEIGHT = 128
SCALE = 4
FPS = 60

__version__ = "0.0.6"

SAVE_DIR = pathlib.Path.home() / ".pix8"
SAVE_FILE = SAVE_DIR / "save.json"

PALETTE = [
    (0, 0, 0), (29, 43, 83), (126, 37, 83), (0, 135, 81),
    (171, 82, 54), (95, 87, 79), (194, 195, 199), (255, 241, 232),
    (255, 0, 77), (255, 163, 0), (255, 236, 39), (0, 228, 54),
    (41, 173, 255), (131, 118, 156), (255, 119, 168), (255, 204, 170),
]

# -------------------------
# Input
# -------------------------

BUTTONS = {
    "left": pygame.K_LEFT,
    "right": pygame.K_RIGHT,
    "up": pygame.K_UP,
    "down": pygame.K_DOWN,
    "a": pygame.K_z,
    "b": pygame.K_x,
}

class Input:
    def __init__(self):
        self.keys = None
        self.prev = None

    def update(self):
        self.prev = self.keys
        self.keys = pygame.key.get_pressed()

    def btn(self, name):
        k = BUTTONS.get(name)
        return bool(k and self.keys[k])

    def btnp(self, name):
        k = BUTTONS.get(name)
        if not k or not self.prev:
            return False
        return self.keys[k] and not self.prev[k]

# -------------------------
# PIX-8 Core
# -------------------------

class Pix8:
    def __init__(self):
        pygame.init()
        pygame.font.init()

        self.window = pygame.display.set_mode(
            (WIDTH * SCALE, HEIGHT * SCALE)
        )
        pygame.display.set_caption("PIX-8")

        self.surface = pygame.Surface((WIDTH, HEIGHT))
        self.clock = pygame.time.Clock()
        self.input = Input()

        self.font = pygame.font.SysFont("consolas", 8)

        self.cart = None
        self.cart_path = None
        self.cart_mtime = 0

        self.running = True

        SAVE_DIR.mkdir(exist_ok=True)
        self.save_data = self._load_save()

    # -------- Graphics --------

    def cls(self, c=0):
        self.surface.fill(PALETTE[c])

    def pix(self, x, y, c):
        if 0 <= x < WIDTH and 0 <= y < HEIGHT:
            self.surface.set_at((x, y), PALETTE[c])

    def rect(self, x, y, w, h, c):
        pygame.draw.rect(self.surface, PALETTE[c], (x, y, w, h))

    def print(self, text, x, y, c=7):
        img = self.font.render(str(text), False, PALETTE[c])
        self.surface.blit(img, (x, y))

    # -------- Save --------

    def save(self, key, value):
        self.save_data[key] = value
        SAVE_FILE.write_text(json.dumps(self.save_data))

    def load(self, key, default=None):
        return self.save_data.get(key, default)

    def _load_save(self):
        if SAVE_FILE.exists():
            return json.loads(SAVE_FILE.read_text())
        return {}

    # -------- Cartridge loading --------

    def load_cart(self, path):
        self.cart_path = path

        if path.endswith(".pix8"):
            self.cart = self._load_pix8(path)
            self.cart_mtime = pathlib.Path(path).stat().st_mtime
        else:
            self.cart = self._load_py(path)
            self.cart_mtime = pathlib.Path(path).stat().st_mtime

        if hasattr(self.cart, "init"):
            self.cart.init(self)

    def _load_py(self, path):
        spec = importlib.util.spec_from_file_location("cart", path)
        mod = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(mod)
        return mod

    def _load_pix8(self, path):
        z = zipfile.ZipFile(path)
        code = z.read("main.py").decode()

        module = types.ModuleType("cart")
        exec(code, module.__dict__)
        return module

    def _hot_reload(self):
        if not self.cart_path:
            return

        try:
            mtime = pathlib.Path(self.cart_path).stat().st_mtime
            if mtime != self.cart_mtime:
                print("Reloading cart...")
                self.load_cart(self.cart_path)
        except:
            pass

    # -------- Main loop --------

    def run(self):
        while self.running:
            for e in pygame.event.get():
                if e.type == pygame.QUIT:
                    self.running = False

            self.input.update()
            self._hot_reload()

            if self.cart and hasattr(self.cart, "update"):
                self.cart.update(self)

            if self.cart and hasattr(self.cart, "draw"):
                self.cart.draw(self)

            scaled = pygame.transform.scale_by(self.surface, SCALE)
            self.window.blit(scaled, (0, 0))

            pygame.display.flip()
            self.clock.tick(FPS)

        pygame.quit()
        sys.exit()

# -------------------------
# Default cart
# -------------------------

def init(p):
    p.x = 60
    p.y = 60

def update(p):
    if p.input.btn("left"): p.x -= 1
    if p.input.btn("right"): p.x += 1
    if p.input.btn("up"): p.y -= 1
    if p.input.btn("down"): p.y += 1

def draw(p):
    p.cls(0)
    p.rect(p.x, p.y, 8, 8, 8)
    p.print("PIX-8 READY", 30, 60, 11)

# -------------------------
# CLI entry
# -------------------------

def main():
    p = Pix8()

    if len(sys.argv) > 1:
        p.load_cart(sys.argv[1])
    else:
        p.cart = sys.modules[__name__]

    p.run()

if __name__ == "__main__":
    main()
