from collections import deque
from itertools import permutations

class Parameter:
    def __init__(self, addr, program):
        self.addr = addr
        self.program = program

    def _get_value(self):
        return self.program.registers[self.addr]

    def _set_value(self, v):
        self.program.registers[self.addr] = v

    v = property(_get_value, _set_value)

class Instruction:
    def execute():
        pass

    @classmethod
    def from_program(cls, program):
        _, opcode = next(program)
        instruction_type = opcode % 100
        parameter_modes = opcode // 100
        instruction_cls = INSTRUCTION_MAP[instruction_type]
        parameters = []
        for i in range(instruction_cls.parameter_count()):
            register_addr, register_content = next(program)
            mode = parameter_modes % 10
            parameter_modes = parameter_modes // 10
            if mode == 0:
                parameters.append(Parameter(register_content, program))
            elif mode == 1:
                parameters.append(Parameter(register_addr, program))
            elif mode == 2:
                parameters.append(Parameter(program.relative_base + register_content, program))
            else:
                raise ValueError("Unkown parameter mode")
        return instruction_cls(*parameters)
            

class Add(Instruction):
    def __init__(self, a, b, dest):
        self.a = a
        self.b = b
        self.dest = dest

    def __repr__(self):
        return f"Add {self.a} {self.b} {self.dest}"

    def execute(self, program):
        self.dest.v = self.a.v + self.b.v

    @classmethod
    def parameter_count(cls):
        return 3

class Multiply(Instruction):
    def __init__(self, a, b, dest):
        self.a = a
        self.b = b
        self.dest = dest

    def __repr__(self):
        return f"Multiply {self.a} {self.b} {self.dest}"

    def execute(self, program):
        self.dest.v = self.a.v * self.b.v

    @classmethod
    def parameter_count(cls):
        return 3

class Input(Instruction):
    def __init__(self, register):
        self.register = register

    def __repr__(self):
        return f"Input {self.register}"

    def execute(self, program):
        if program.input_gen is None:
            value = int(input("Gimme a value: "))
        else:
            value = next(program.input_gen)
        self.register.v = value

    @classmethod
    def parameter_count(cls):
        return 1

class Output(Instruction):
    def __init__(self, register):
        self.register = register

    def __repr__(self):
        return f"Output {self.register}"

    def execute(self, program):
        program.output(self.register.v)
    
    @classmethod
    def parameter_count(cls):
        return 1

class Halt(Instruction):
    def execute(self, program):
        program.halt()

    def __repr__(self):
        return f"Halt"

    @classmethod
    def parameter_count(cls):
        return 0

class JumpIfTrue(Instruction):
    def __init__(self, test, ip):
        self.test = test
        self.ip = ip

    def __repr__(self):
        return f"JumpIfTrue {self.test} {self.ip}"

    def execute(self, program):
        if self.test.v != 0:
            program.instruction_counter = self.ip.v
            
    @classmethod
    def parameter_count(cls):
        return 2

class JumpIfFalse(Instruction):
    def __init__(self, test, ip):
        self.test = test
        self.ip = ip

    def __repr__(self):
        return f"JumpIfFalse {self.test} {self.ip}"

    def execute(self, program):
        if self.test.v == 0:
            program.instruction_counter = self.ip.v
            
    @classmethod
    def parameter_count(cls):
        return 2

class LessThan(Instruction):
    def __init__(self, a, b, dest):
        self.a = a
        self.b = b
        self.dest = dest

    def __repr__(self):
        return f"LessThan {self.a} {self.b} {self.dest}"

    def execute(self, program):
        self.dest.v = int(self.a.v < self.b.v)

    @classmethod
    def parameter_count(cls):
        return 3

class Equal(Instruction):
    def __init__(self, a, b, dest):
        self.a = a
        self.b = b
        self.dest = dest

    def __repr__(self):
        return f"Equal {self.a} {self.b} {self.dest}"

    def execute(self, program):
        self.dest.v = int(self.a.v == self.b.v)

    @classmethod
    def parameter_count(cls):
        return 3

class RelativeBase(Instruction):
    def __init__(self, base):
        self.base = base

    def __repr__(self):
        return f"RelativeBase {self.base}"
    
    def execute(self, program):
        program.relative_base += self.base.v

    @classmethod
    def parameter_count(cls):
        return 1


class IntcodeProgram:
    def __init__(self, program):
        self.program = program
        self.reset()

        self.input_gen = None
        self.output_sink = None
        self.pause_on_output = False

    def output(self, value):
        if self.output_sink is not None:
            self.output_sink.append(value)
        self.outputs.append(value)
        print(value)
        if self.pause_on_output:
            self.running = False

    def __next__(self):
        ret_val = (self.instruction_counter, self.registers[self.instruction_counter])
        self.instruction_counter += 1
        return ret_val

    def halt(self):
        self.running = False
        self.halted = True

    def reset(self):
        self.registers = {i:v for i,v in enumerate(self.program.copy())}
        self.instruction_counter = 0
        self.relative_base = 0
        self.outputs = []
        self.halted = False
        self.running = False

    def run(self):
        self.running = True
        while self.instruction_counter < len(self.registers) and self.running:
            instruction = Instruction.from_program(self)
            instruction.execute(self)

class ProgramConnector:
    def __init__(self):
        self.values = deque()

    def append(self, value):
        self.values.append(value)

    def __next__(self):
        return self.values.popleft()

INSTRUCTION_MAP = {
        99: Halt,
        1: Add,
        2: Multiply,
        3: Input,
        4: Output,
        5: JumpIfTrue,
        6: JumpIfFalse,
        7: LessThan,
        8: Equal,
        9: RelativeBase,
}

program = IntcodeProgram([int(x) for x in open("input.txt","r").read().strip().split(',')])
program.run()

