#!/usr/bin/env python3
"""ScratchOS Terminal — a Bash-like terminal in Scratch, rendered with pen stamps.

Commands: help, date
Keyboard: a-z, 0-9, space, dot, slash, minus
Backspace deletes last char, Enter executes.

Text is rendered using a monospace font sprite that stamps each character.
"""

import sys
import os
# Ensure scratchgen can be imported if it's in a specific directory
# sys.path.insert(0, "/sciezka/do/katalogu/z/scratchgen")

from scratchgen import *

project = Project()
project.add_extension("pen")

# ============================================================
# FONT SETUP
# ============================================================
CHAR_WIDTH = 10
CHAR_HEIGHT = 16
FONT_SIZE = 14
FONT_FAMILY = "Courier New, monospace"
FONT_COLOR = "#00ff00"  # green terminal text

CHARS = "abcdefghijklmnopqrstuvwxyz0123456789 .:/-_$"
MAX_COLS = 46           # chars per line (fits 480px width)
MAX_ROWS = 21           # visible lines on screen
LINE_HEIGHT = 16
SCREEN_TOP_Y = 168      # y of first line
SCREEN_LEFT_X = -226    # x of first char

def make_char_costume(ch):
    display = ch
    if ch == " ": display = " "
    elif ch == "<": display = "&lt;"
    elif ch == ">": display = "&gt;"
    elif ch == "&": display = "&amp;"

    # ALL costume names are "c_" + the literal char.
    # This ensures Scratch never interprets "1" as costume number.
    name = f"c_{ch}"

    svg = (f'<svg xmlns="http://www.w3.org/2000/svg" width="{CHAR_WIDTH}" height="{CHAR_HEIGHT}">'
           f'<text x="1" y="{CHAR_HEIGHT - 3}" font-family="{FONT_FAMILY}" '
           f'font-size="{FONT_SIZE}" fill="{FONT_COLOR}">{display}</text></svg>')
    return svg_from_string(name, svg)

char_costumes = [make_char_costume(ch) for ch in CHARS]

# Dark background backdrop
BG_SVG = ('<svg xmlns="http://www.w3.org/2000/svg" width="480" height="360">'
          '<rect width="480" height="360" fill="#1a1a2e"/></svg>')
project.stage.add_costume(svg_from_string("bg", BG_SVG, center=(240, 180)))

# ============================================================
# GLOBAL DATA
# ============================================================
terminal_lines = List("terminal_lines", [])
project.stage.add_list(terminal_lines)

current_input = Variable("current_input", "")
project.stage.add_variable(current_input)

needs_redraw = Variable("needs_redraw", 0)
project.stage.add_variable(needs_redraw)

font_chars = Variable("font_chars", CHARS)
project.stage.add_variable(font_chars)

# Log list (hidden)
logs = List("logs", [])
project.stage.add_list(logs)

# ============================================================
# TERMINAL SPRITE (logic, invisible)
# ============================================================
terminal = Sprite("Terminal", costumes=[svg_circle("dot", 1, color="#00000000")])

cmd_name = Variable("cmd_name", "")
cmd_args = Variable("cmd_args", "")
ti = Variable("ti", 0)
tmp = Variable("tmp", "")
padded = Variable("padded", "")
date_str = Variable("date_str", "")

for v in [cmd_name, cmd_args, ti, tmp, padded, date_str]:
    terminal.add_variable(v)

# --- Procedures ---
pad2 = Procedure("pad2", params=["value"], warp=True)
terminal.add_script(
    pad2.define(),
    if_else(pad2.arg("value") < 10,
        [padded.set_to(join("0", pad2.arg("value")))],
        [padded.set_to(pad2.arg("value"))])
)

parse_command = Procedure("parse_command", params=["raw"], warp=True)
terminal.add_script(
    parse_command.define(),
    cmd_name.set_to(""),
    cmd_args.set_to(""),
    ti.set_to(1),
    repeat_until(or_(ti > length_of(parse_command.arg("raw")),
                     letter_of(ti, parse_command.arg("raw")) == " "),
        cmd_name.set_to(join(cmd_name, letter_of(ti, parse_command.arg("raw")))),
        ti.change_by(1)),
    ti.change_by(1),
    repeat_until(ti > length_of(parse_command.arg("raw")),
        cmd_args.set_to(join(cmd_args, letter_of(ti, parse_command.arg("raw")))),
        ti.change_by(1)),
)

execute_command = Procedure("execute_command", params=["cmd"], warp=True)

help_branch = [
    terminal_lines.add("available commands: help date"),
]

date_branch = [
    # Build YYYY-MM-DD HH:MM:SS
    pad2.call(current("MONTH")),
    date_str.set_to(join(join(current("YEAR"), "-"), padded)),
    pad2.call(current("DATE")),
    date_str.set_to(join(join(date_str, "-"), padded)),
    pad2.call(current("HOUR")),
    date_str.set_to(join(join(date_str, " "), padded)),
    pad2.call(current("MINUTE")),
    date_str.set_to(join(join(date_str, ":"), padded)),
    pad2.call(current("SECOND")),
    date_str.set_to(join(join(date_str, ":"), padded)),
    terminal_lines.add(date_str),
]

unknown_branch = [
    terminal_lines.add(join("unknown command: ", cmd_name)),
]

terminal.add_script(
    execute_command.define(),
    terminal_lines.add(join("$ ", execute_command.arg("cmd"))),
    logs.add(join("exec: ", execute_command.arg("cmd"))),
    parse_command.call(execute_command.arg("cmd")),
    if_else(cmd_name == "help",
        help_branch,
        [if_else(cmd_name == "date", date_branch, unknown_branch)]),
    current_input.set_to(""),
    needs_redraw.set_to(1),
)

# Green flag
terminal.add_script(
    when_flag_clicked(),
    hide(),
    terminal_lines.delete_all(),
    logs.delete_all(),
    terminal_lines.add("scratchos 0.1"),
    terminal_lines.add("type help for commands."),
    terminal_lines.add(""),
    current_input.set_to(""),
    needs_redraw.set_to(1),
    logs.add("Terminal initialized"),
)

# Enter
terminal.add_script(
    when_key_pressed("enter"),
    if_(length_of(current_input) > 0,
        execute_command.call(current_input)),
)

# Backspace
terminal.add_script(
    when_key_pressed("backspace"),
    if_(length_of(current_input) > 0,
        tmp.set_to(""),
        ti.set_to(1),
        repeat(length_of(current_input) - 1,
            tmp.set_to(join(tmp, letter_of(ti, current_input))),
            ti.change_by(1)),
        current_input.set_to(tmp),
        needs_redraw.set_to(1)),
)

# Space
terminal.add_script(
    when_key_pressed("space"),
    current_input.set_to(join(current_input, " ")),
    needs_redraw.set_to(1),
)

# Alphanumeric
for char in "abcdefghijklmnopqrstuvwxyz0123456789":
    terminal.add_script(
        when_key_pressed(char),
        current_input.set_to(join(current_input, char)),
        needs_redraw.set_to(1),
    )

# Special keys
for key, ch in [(".", "."), ("-", "-"), ("/", "/")]:
    terminal.add_script(
        when_key_pressed(key),
        current_input.set_to(join(current_input, ch)),
        needs_redraw.set_to(1),
    )

# ============================================================
# CHAR SPRITE (renderer)
# ============================================================
char_sprite = Sprite("Char", costumes=char_costumes, x=0, y=0, size=100)

# Renderer variables
render_line_idx = Variable("rl_idx", 0)
render_char_idx = Variable("rc_idx", 0)
render_x = Variable("rx", 0)
render_y = Variable("ry", 0)
render_costume = Variable("rc", 0)
render_start = Variable("r_start", 0)
render_line = Variable("r_line", "")

for v in [render_line_idx, render_char_idx, render_x, render_y,
          render_costume, render_start, render_line]:
    char_sprite.add_variable(v)

# stamp_line: stamps one string at current render_y
stamp_line = Procedure("stamp_line", params=["text"], warp=True)
char_sprite.add_script(
    stamp_line.define(),
    render_x.set_to(SCREEN_LEFT_X),
    render_char_idx.set_to(1),
    repeat_until(render_char_idx > length_of(stamp_line.arg("text")),
        # find costume index for this character
        render_costume.set_to(1),
        repeat_until(or_(render_costume > length_of(font_chars),
                         letter_of(render_costume, font_chars) == letter_of(render_char_idx, stamp_line.arg("text"))),
            render_costume.change_by(1)),
        if_(render_costume <= length_of(font_chars),
            switch_costume_to(join("c_", letter_of(render_char_idx, stamp_line.arg("text")))),
            go_to(render_x, render_y),
            pen_stamp()),
        render_x.change_by(CHAR_WIDTH),
        render_char_idx.change_by(1)),
)

# redraw: full screen redraw
redraw = Procedure("redraw", warp=True)
char_sprite.add_script(
    redraw.define(),
    pen_clear(),
    hide(),
    set_size(100),
    # Compute start index: show last MAX_ROWS-1 lines from terminal_lines
    # (last line is reserved for the prompt)
    if_else(terminal_lines.length() > (MAX_ROWS - 1),
        [render_start.set_to(terminal_lines.length() - (MAX_ROWS - 1) + 1)],
        [render_start.set_to(1)]),
    render_y.set_to(SCREEN_TOP_Y),
    render_line_idx.set_to(render_start),
    repeat_until(or_(render_line_idx > terminal_lines.length(),
                     render_y < (0 - SCREEN_TOP_Y)),
        stamp_line.call(terminal_lines.item(render_line_idx)),
        render_y.change_by(0 - LINE_HEIGHT),
        render_line_idx.change_by(1)),
    # Stamp the prompt line: "$ " + current_input
    stamp_line.call(join("$ ", current_input)),
)

# React to redraw flag
char_sprite.add_script(
    when_flag_clicked(),
    hide(),
    forever(
        if_(needs_redraw > 0,
            needs_redraw.set_to(0),
            redraw.call()),
        wait(0.03),
    ),
)

# ============================================================
# BUILD
# ============================================================
project.add_sprite(terminal)
project.add_sprite(char_sprite)
project.save(os.path.join(os.path.dirname(os.path.abspath(__file__)), "scratch_terminal.sb3"))
print("Built: scratch_terminal.sb3")

