text: import { readInput, toDigitsArray } from "@util";
  
  export async function main() {
    const inputPath = `${__dirname}/input`;
    const integers = await readInput(inputPath, ",", true);
  
    /* Part 1 */
    // runProgram(integers, 1);
  
    runProgram(integers, 5);
  }
  
  const OPCODE = {
    ADD: 1,
    MULTIPLY: 2,
    INPUT: 3,
    OUTPUT: 4,
    JUMP_IF_TRUE: 5,
    JUMP_IF_FALSE: 6,
    LESS_THAN: 7,
    EQUALS: 8,
    EXIT: 99
  };
  
  const MODE = {
    POSITION: 0,
    IMMEDIATE: 1
  };
  
  const OPERATOR = {
    ADD: "+",
    MULTIPLY: "*"
  };
  
  const COMPARATOR = {
    LESS_THAN: "<",
    EQUAL: "="
  };
  
  function runProgram(rawIntegers, input) {
    const integers = [...rawIntegers];
    let i = 0;
    let j = 0;
  
    let instruction = extractInstruction(integers[i]);
  
    while (instruction && instruction.opcode !== OPCODE.EXIT) {
      const { opcode, modes } = instruction;
  
      switch (opcode) {
        case OPCODE.ADD:
          add(integers, i, modes);
          i += 4;
          break;
  
        case OPCODE.MULTIPLY:
          multiply(integers, i, modes);
          i += 4;
          break;
  
        case OPCODE.INPUT:
          save(integers, i, input);
          i += 2;
          break;
  
        case OPCODE.OUTPUT:
          print(integers, i, modes);
          i += 2;
          break;
  
        case OPCODE.JUMP_IF_TRUE:
          j = jumpIfTrue(integers, i, modes);
          if (j >= 0) {
            i = j;
          } else {
            i += 3;
          }
          break;
  
        case OPCODE.JUMP_IF_FALSE:
          j = jumpIfFalse(integers, i, modes);
          if (j >= 0) {
            i = j;
          } else {
            i += 3;
          }
          break;
  
        case OPCODE.LESS_THAN:
          lessThan(integers, i, modes);
          i += 4;
          break;
  
        case OPCODE.EQUALS:
          equals(integers, i, modes);
          i += 4;
          break;
  
        default:
          throw new Error(`Unexpected opcode ${opcode}`);
      }
  
      instruction = extractInstruction(integers[i]);
    }
  }
  
  function extractInstruction(instruction) {
    const digits = toDigitsArray(instruction);
  
    while (digits.length < 5) {
      digits.unshift(0);
    }
  
    const opcode = digits.pop() + digits.pop() * 10;
  
    return {
      opcode,
      modes: digits.reverse()
    };
  }
  
  function add(integers, index, modes) {
    calc(OPERATOR.ADD, integers, index, modes);
  }
  
  function multiply(integers, index, modes) {
    calc(OPERATOR.MULTIPLY, integers, index, modes);
  }
  
  function calc(operator, integers, index, modes) {
    const value1 = getValue(integers, ++index, modes[0]);
    const value2 = getValue(integers, ++index, modes[1]);
  
    switch (operator) {
      case OPERATOR.ADD:
        integers[integers[++index]] = value1 + value2;
        break;
  
      case OPERATOR.MULTIPLY:
        integers[integers[++index]] = value1 * value2;
        break;
  
      default:
        throw new Error(`Unexpected operator ${operator}`);
    }
  }
  
  function save(integers, index, input) {
    integers[integers[++index]] = input;
  }
  
  function print(integers, index, modes) {
    console.log(getValue(integers, ++index, modes[0]));
  }
  
  function jumpIfTrue(integers, index, modes) {
    return jumpIf(true, integers, index, modes);
  }
  
  function jumpIfFalse(integers, index, modes) {
    return jumpIf(false, integers, index, modes);
  }
  
  function jumpIf(condition, integers, index, modes) {
    const value1 = getValue(integers, ++index, modes[0]);
    const value2 = getValue(integers, ++index, modes[1]);
  
    if ((condition && value1 !== 0) || (!condition && value1 === 0)) {
      return value2;
    } else {
      return -1;
    }
  }
  
  function lessThan(integers, index, modes) {
    compare(COMPARATOR.LESS_THAN, integers, index, modes);
  }
  
  function equals(integers, index, modes) {
    compare(COMPARATOR.EQUAL, integers, index, modes);
  }
  
  function compare(comparator, integers, index, modes) {
    const value1 = getValue(integers, ++index, modes[0]);
    const value2 = getValue(integers, ++index, modes[1]);
  
    if (
      (comparator === COMPARATOR.LESS_THAN && value1 < value2) ||
      (comparator == COMPARATOR.EQUAL && value1 === value2)
    ) {
      integers[integers[++index]] = 1;
    } else {
      integers[integers[++index]] = 0;
    }
  }
  
  function getValue(integers, index, mode) {
    switch (mode) {
      case MODE.POSITION:
        return integers[integers[index]];
  
      case MODE.IMMEDIATE:
        return integers[index];
  
      default:
        throw new Error(`Unexpected mode ${mode}`);
    }
  }
  