text: from typing import Union, List, Tuple, Optional, Type
  
  from intcode.handlers.io.std import StdIOHandler
  from intcode.interfaces.base_op import BaseOp
  from intcode.interfaces.io_handler import BaseIOHandler
  from intcode.interpreter.opcode_factory import OpFactory
  from intcode.interpreter.state import MachineState
  
  
  class IntCodeMachine:
      def __init__(self,
                   program: Union[str, List[int]],
                   machine_state: Optional[MachineState] = None,
                   io_handler: Optional[BaseIOHandler] = None
                   ):
          self.machine_state = machine_state or MachineState(self._read_input_program(program))
          self._original_machine_state = self.machine_state.copy()
          self.io_handler = io_handler or StdIOHandler()
  
      def run(self, enable_pauses: bool = False, reset_machine_state: bool = False):
          if reset_machine_state:
              self.machine_state = self._original_machine_state.copy()
          self.machine_state.paused = False
          while not self.machine_state.halted or (enable_pauses and self.machine_state.paused):
              op, unresolved_params = self._next_op()
              self._execute_op(op, unresolved_params)
  
      def _next_op(self) -> Tuple[Type[BaseOp], List[Tuple[int, int]]]:
          raw_op_code = self.machine_state.get_op_codes().pop()
          fixed_op_code = self._fix_op_code(raw_op_code)
          op = self._get_op(fixed_op_code)
          raw_params = self.machine_state.get_op_codes(op.n_params + int(op.returns_value))
          unresolved_params = list(zip([int(access_method)
                                        for access_method in fixed_op_code[:-2][::-1]],
                                       raw_params))
          return op, unresolved_params
  
      def _fix_op_code(self, raw_op_code: int) -> str:
          fixed_op_code = str(raw_op_code).rjust(2, '0')
          op = self._get_op(fixed_op_code)
          padded_op_code = fixed_op_code.rjust(2 + op.n_params + int(op.returns_value), '0')
          return padded_op_code
  
      def _get_op(self, op_code: str) -> Type[BaseOp]:
          return OpFactory.get_op(op_code[-2:])
  
      def _resolve_op_params(self, unresolved_op_params: List[Tuple[int, int]]) -> List[int]:
          return [self.machine_state.get_registry_value(access_mode, pointer)
                  for access_mode, pointer in unresolved_op_params]
  
      def _execute_op(self, op: Type[BaseOp], unresolved_params: List[Tuple[int, int]]):
          resolved_params = self._resolve_op_params(unresolved_params)
          resolved_params.pop() if op.returns_value else None
          unresolved_destination = unresolved_params.pop() if op.returns_value else None
  
          result = op.execute(*resolved_params, **{k: getattr(self, k) for k in op.special_parameters})  # type: ignore
  
          if op.returns_value:
              self.machine_state.set_registry_value(unresolved_destination[0],  # type: ignore
                                                    unresolved_destination[1],  # type: ignore
                                                    result)  # type: ignore
          if op.pauses_execution:
              self.machine_state.paused = True
  
      @staticmethod
      def _read_input_program(program: Union[str, List[int]]) -> List[int]:
          if isinstance(program, str):
              program = [int(c.strip()) for c in program.split(',') if c.strip()]
          return program
  