from typing import *
from aocpy import BaseChallenge

Instruction = Tuple[str, Optional[int]]


def parse(instr: str) -> List[Instruction]:
    res: List[Instruction] = []
    for line in instr.strip().splitlines():
        parts = line.split(" ")
        if len(parts) == 1:
            res.append((parts[0], None))
        elif len(parts) == 2:
            res.append((parts[0], int(parts[1])))
        else:
            raise ValueError(f"unexpected instruction length {len(parts)} for {line=}")
    return res


class MachineOne:
    ticks: int
    acc: int
    vals: List[int]

    def __init__(self):
        self.ticks = 0
        self.acc = 1
        self.vals = []

    def tick(self):
        self.ticks += 1

    def check_acc(self):
        tm = self.ticks % 40
        if tm == 20:
            self.vals.append(self.ticks * self.acc)

    def execute(self, ins: Instruction):
        (opcode, operand) = ins
        if opcode == "noop":
            self.tick()
            self.check_acc()
        elif opcode == "addx":
            self.tick()
            self.check_acc()
            self.tick()
            self.check_acc()
            self.acc += operand


class MachineTwo(MachineOne):
    output: str

    def __init__(self):
        super().__init__()
        self.output = ""

    def check_acc(self):
        tm = self.ticks % 40
        if tm == 0:
            self.output += "\n"

        if tm == 20:
            self.vals.append(self.ticks * self.acc)

        if self.acc - 1 <= tm <= self.acc + 1:
            self.output += "█"
        else:
            self.output += " "

    def execute(self, ins: Instruction):
        (opcode, operand) = ins
        if opcode == "noop":
            self.check_acc()
            self.tick()
        elif opcode == "addx":
            self.check_acc()
            self.tick()
            self.check_acc()
            self.tick()
            self.acc += operand


class Challenge(BaseChallenge):
    @staticmethod
    def one(instr: str) -> int:
        inp = parse(instr)

        machine = MachineOne()
        for ins in inp:
            machine.execute(ins)

        return sum(machine.vals)

    @staticmethod
    def two(instr: str):
        inp = parse(instr)

        machine = MachineTwo()
        for ins in inp:
            machine.execute(ins)

        return "\n" + machine.output.strip()
