#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <limits.h>

#include "../../lib/aoc.h"

#define PROGRAM_LENGTH 16

typedef struct Computer {
  long a;
  long b;
  long c;
  long program[PROGRAM_LENGTH];
  int pc;
  long output[1000];
  int output_count;
} Computer;

long combo(Computer *c, int operand) {
  switch (operand) {
  case 0:
  case 1:
  case 2:
  case 3:
    return operand;
  case 4:
    return c->a;
  case 5:
    return c->b;
  case 6:
    return c->c;
  default:
    printf("Invalid operand %d detected\n", operand);
    exit(1);
  }
}

void step(Computer *c) {
  int instruction = c->program[c->pc++];
  int operand = c->program[c->pc++];
  // printf("%d|%d\n", instruction, operand);
  // printf("a: %d, b: %d, c: %d, pc: %d\n", c->a, c->b, c->c, c->pc);
  switch(instruction) {
  case 0:
    // printf("0: a = %d / %d = %d\n", c->a, 1<<combo(c, operand), c->a / (1<<combo(c, operand)));
    c->a = c->a / (1 << combo(c, operand));
    break;
  case 1:
    // printf("1: b = %d ^ %d = %d\n", c->b, operand, c->b ^ operand);
    c->b = operand ^ c->b;
    break;
  case 2:
    // printf("2: b = %d % 8 = %d\n", combo(c, operand), combo(c, operand) % 8);
    c->b = combo(c, operand) % 8;
    break;
  case 3:
    // printf("3: if %d then jump to %d\n", c->a, operand);
    if (c->a) c->pc = operand;
    break;
  case 4:
    // printf("4: b = %d ^ %d = %d\n", c->b, c->c, c->b ^ c->c);
    c->b = c->b ^ c->c;
    break;
  case 5:
    // printf("5: print %d\n", combo(c, operand) % 8);
    c->output[c->output_count++] = combo(c, operand) % 8;
    break;
  case 6:
    // printf("6: b = %d / %d = %d\n", c->b, 1<<combo(c, operand), c->b / (1<<combo(c, operand)));
    c->b = c->a / (1 << combo(c, operand));
    break;
  case 7:
    // printf("7: c = %d / %d = %d\n", c->a, 1<<combo(c, operand), c->a / (1<<combo(c, operand)));
    c->c = c->a / (1 << combo(c, operand));
    break;
  }
}

bool quine(Computer *c, long a) {
  if (a % 100000000 == 0) printf("%ld\n", a);
  c->a = a;
  c->b = 0;
  c->c = 0;
  c->pc = 0;
  c->output_count = 0;
  while (c->pc < PROGRAM_LENGTH) {
    step(c);
    if (c->output_count == 0) continue;
    if (c->output[c->output_count - 1] != c->program[c->output_count - 1]) return false;
  }
  if (c->output_count != PROGRAM_LENGTH) return false;
  for (int i = 0; i < c->output_count; c++) {
    if (c->program[i] != c->output[i]) return false;
  }
  return true;
}

int main() {
  printf("%s\n", aoc_read_input());
  Computer c;
  c.pc = 0;

  sscanf(aoc_read_line(), "Register A: %d", &(c.a));
  sscanf(aoc_read_line(), "Register B: %d", &(c.b));
  sscanf(aoc_read_line(), "Register C: %d", &(c.c));
  aoc_read_line();
  strtok(aoc_read_line(), " ");
  for (char *token = strtok(NULL, ","); token != NULL; token = strtok(NULL, ",")) {
    c.program[c.pc++] = atoi(token);
  }
  c.pc = 0;
  c.output_count = 0;

  while (c.pc < PROGRAM_LENGTH) {
    printf("a: %d, b: %d, c: %d, pc: %d\n", c.a, c.b, c.c, c.pc);
    step(&c);
    // printf("\n");
  }

  printf("Part 1: %d", c.output[0]);
  for (int i = 1; i < c.output_count; i++) {
    printf(",%d", c.output[i]);
  }
  printf("\n");

  //long a = 100000000000000;
  // long a = 8 ** 15;
  // long a = 2 ** 45;
  long a = 1l << 45l;
  for (; a < (1l << 48l); a++) {
    quine(&c, a);
    /*printf("%ld: ", a);
    for (int i = 0; i < c.output_count; i++) {
      printf("%ld,", c.output[i]);
    }
    printf("\n");*/
  }
  // while (!quine(&c, a)) a++;
  printf("Part 2: %ld", a);

  aoc_free();
  exit(0);
}