// File: /workspace/github_cpp_makefile_star2_O3/thanoskoutr[P]Chip-8-Emulator/chip8.cpp
// Function: Chip8::emulateCycle()
void Chip8::emulateCycle()
{
  /* Fetch next Opcode from memory */
  unsigned short opcode = memory[pc] << 8 | memory[pc + 1];  // opcode is 2 bytes, memory stores 1 byte

  /* Decode Opcode */
  switch (opcode & 0xF000)  // Switch on MSB
  {
    case 0x0000:
      switch (opcode & 0x000F)
      {
        case 0x0000:  // 00E0 - CLS: Clear the display.
          for (int i = 0; i < SCREEN_SIZE; i++)
            gfx[i] = 0x0;
          drawFlag = true;
          pc += 2;
        break;

        case 0x000E:  // 00EE - RET: Return from a subroutine.
          sp--;
          pc = stack[sp];
          pc += 2;
        break;

        default:
          printf ("Unknown opcode [0x0000]: 0x%X\n", opcode);
      }
    break;

    case 0x1000:  // 1nnn - JP addr: Jump to location nnn.
      pc = opcode & 0x0FFF;
    break;

    case 0x2000:  // 2nnn - CALL addr: Call subroutine at nnn.
      stack[sp] = pc;
      sp++;
      pc = opcode & 0x0FFF;
    break;

    case 0x3000:  // 3xkk - SE Vx, byte: Skip next instruction if Vx = kk.
      if (V[(opcode & 0x0F00) >> 8] == (opcode & 0x00FF))
        pc += 4;
      else
        pc += 2;
    break;

    case 0x4000:  // 4xkk - SNE Vx, byte: Skip next instruction if Vx != kk.
      if (V[(opcode & 0x0F00) >> 8] != (opcode & 0x00FF))
        pc += 4;
      else
        pc += 2;
    break;

    case 0x5000:  // 5xy0 - SE Vx, Vy: Skip next instruction if Vx = Vy.
      if (V[(opcode & 0x0F00) >> 8] == V[(opcode & 0x00F0) >> 4])
        pc += 4;
      else
        pc += 2;
    break;

    case 0x6000:  // 6xkk - LD Vx, byte: Set Vx = kk.
      V[(opcode & 0x0F00) >> 8] = opcode & 0x00FF;
      pc += 2;
    break;

    case 0x7000:  // 7xkk - ADD Vx, byte: Set Vx = Vx + kk.
      V[(opcode & 0x0F00) >> 8] += opcode & 0x00FF;
      pc += 2;
    break;

    case 0x8000:
      switch (opcode & 0x000F)
      {
        case 0x0000:  // 8xy0 - LD Vx, Vy: Set Vx = Vy.
          V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4];
          pc += 2;
        break;

        case 0x0001:  // 8xy1 - OR Vx, Vy: Set Vx = Vx OR Vy.
          V[(opcode & 0x0F00) >> 8] |= V[(opcode & 0x00F0) >> 4];
          pc += 2;
        break;

        case 0x0002:  // 8xy2 - AND Vx, Vy: Set Vx = Vx AND Vy.
          V[(opcode & 0x0F00) >> 8] &= V[(opcode & 0x00F0) >> 4];
          pc += 2;
        break;

        case 0x0003:  // 8xy3 - XOR Vx, Vy: Set Vx = Vx XOR Vy.
          V[(opcode & 0x0F00) >> 8] ^= V[(opcode & 0x00F0) >> 4];
          pc += 2;
        break;

        case 0x0004:  //  8xy4 - ADD Vx, Vy: Set Vx = Vx + Vy, set VF = carry.
          if (V[(opcode & 0x0F00) >> 8] + V[(opcode & 0x00F0) >> 4] > 0xFF) // Check for overflow
            V[0xF] = 1;
          else
            V[0xF] = 0;
          V[(opcode & 0x0F00) >> 8] += V[(opcode & 0x00F0) >> 4];
          pc += 2;
        break;

        case 0x0005:  //  8xy5 - SUB Vx, Vy: Set Vx = Vx - Vy, set VF = NOT borrow.
          if (V[(opcode & 0x0F00) >> 8] > V[(opcode & 0x00F0) >> 4])  // check Vx-Vy
            V[0xF] = 1;
          else
            V[0xF] = 0;
          V[(opcode & 0x0F00) >> 8] -= V[(opcode & 0x00F0) >> 4];
          pc += 2;
        break;

        case 0x0006:  //  8xy6 - SHR Vx {, Vy}: Set Vx = Vx SHR 1.
          if ((V[(opcode & 0x0F00) >> 8] & 0x1) == 1) // Check if LSB == 1
            V[0xF] = 1;
          else
            V[0xF] = 0;
          V[(opcode & 0x0F00) >> 8] >>= 1;  // divide by 2 == bitwise right shift with 1
          pc += 2;
        break;

        case 0x0007:  // 8xy7 - SUBN Vx, Vy: Set Vx = Vy - Vx, set VF = NOT borrow.
          if (V[(opcode & 0x0F00) >> 8] < V[(opcode & 0x00F0) >> 4])  // check Vy-Vx
            V[0xF] = 1;
          else
            V[0xF] = 0;
          V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4] - V[(opcode & 0x0F00) >> 8];
          pc += 2;
        break;

        case 0x000E:  // 8xyE - SHL Vx {, Vy}: Set Vx = Vx SHL 1.
          if (V[(opcode & 0x0F00) >> 8] >> 7 == 1) // Check if MSB == 1
            V[0xF] = 1;
          else
            V[0xF] = 0;
          V[(opcode & 0x0F00) >> 8] <<= 1;  // multiply by 2 == bitwise left shift with 1
          pc += 2;
        break;

        default:
          printf ("Unknown opcode [0x8000]: 0x%X\n", opcode);
      }
    break;

    case 0x9000:  // 9xy0 - SNE Vx, Vy: Skip next instruction if Vx != Vy.
      if (V[(opcode & 0x0F00) >> 8] != V[(opcode & 0x00F0) >> 4])
        pc += 4;
      else
        pc += 2;
    break;

    case 0xA000:  // Annn - LD I, addr: Set I = nnn.
      I = opcode & 0x0FFF;
      pc += 2;
    break;

    case 0xB000:  // Bnnn - JP V0, addr: Jump to location nnn + V0.
      pc = (opcode & 0x0FFF) + V[0];
    break;

    case 0xC000:  // Cxkk - RND Vx, byte: Set Vx = random byte AND kk.
      V[(opcode & 0x0F00) >> 8] = (rand() % 0xFF) & (opcode & 0x00FF);  // generate random number between 0-255
      pc += 2;
    break;

    case 0xD000:  // Dxyn - DRW Vx, Vy, nibble: Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
    {
      /* A sprite is a set of 8 pixels = 8 bits = 1 byte,
       * Each row of 8 pixels is read as bit-coded starting from memory location I;
       * I value doesn't change after the execution of this instruction.
       * VF is set to 1 if any screen pixels are flipped from set to unset when the sprite is drawn,
         and to 0 if that doesn't happen */

      unsigned short x = V[(opcode & 0x0F00) >> 8];
      unsigned short y = V[(opcode & 0x00F0) >> 4];
      unsigned short height = opcode & 0x000F;
      unsigned short sprite;

      V[0xF] = 0;
      for (int yLine = 0; yLine < height; yLine++)  // read for n-byte sprites
      {
        sprite = memory[I + yLine];  // sprite represents 1 hex value e.g. 0xF2 (= 1 byte = 8 bits = 8 pixels)
        for (int xLine = 0; xLine < 8; xLine++) // each row is 8 pixels
        {
          if ((sprite & (0x80 >> xLine)) != 0 ) // For each bit of the sprite that is 1, 0x80 = 1000 0000
          {
            if (gfx[((y + yLine) * SCREEN_WIDTH) + (x + xLine)] == 1) // Check if the pixel on the display is set to 1
              V[0xF] = 1;
            gfx[((y + yLine) * SCREEN_WIDTH) + (x + xLine)] ^= 1; // Set the pixel value by using XOR
          }
        }
      }
      drawFlag = true;
      pc += 2;
    }
    break;

    case 0xE000:
      switch (opcode & 0x00FF)
      {
        case 0x009E:  // Ex9E - SKP Vx: Skip next instruction if key with the value of Vx is pressed.
          if (key[V[(opcode & 0x0F00) >> 8]] != 0)
            pc += 4;
          else
            pc += 2;
        break;

        case 0x00A1:  // ExA1 - SKNP Vx: Skip next instruction if key with the value of Vx is not pressed.
          if (key[V[(opcode & 0x0F00) >> 8]] == 0)
            pc += 4;
          else
            pc += 2;
        break;

        default:
          printf ("Unknown opcode [0xE000]: 0x%X\n", opcode);
      }
    break;

    case 0xF000:
      switch (opcode & 0x00FF)
      {
        case 0x0007:  // Fx07 - LD Vx, DT: Set Vx = delay timer value.
          V[(opcode & 0x0F00) >> 8] = delay_timer;
          pc += 2;
        break;

        case 0x000A:  // Fx0A - LD Vx, K: Wait for a key press, store the value of the key in Vx.
        {
          bool isPressed = false;
          for (int i = 0; i < KEY_SIZE; i++)
          {
            if (key[i] != 0)
            {
              V[(opcode & 0x0F00) >> 8] = key[i]; // or Vx = i
              isPressed = true;
            }
          }
          if (!isPressed) // If no key pressed, skip this cycle and try again
            return;
          pc += 2;
        }
        break;

        case 0x0015:  // Fx15 - LD DT, Vx: Set delay timer = Vx.
          delay_timer = V[(opcode & 0x0F00) >> 8];
          pc += 2;
        break;

        case 0x0018:  // Fx18 - LD ST, Vx: ST is set equal to the value of Vx.
          sound_timer = V[(opcode & 0x0F00) >> 8];
          pc += 2;
        break;

        case 0x001E:  // Fx1E - ADD I, Vx: Set I = I + Vx.
          if (I + V[(opcode & 0x0F00) >> 8] > 0xFFF) // Check for overflow, undocumented feature
            V[0xF] = 1;
          else
            V[0xF] = 0;
          I += V[(opcode & 0x0F00) >> 8];
          pc += 2;
        break;

        case 0x0029:  // Fx29 - LD F, Vx: Set I = location of sprite for digit Vx.
          I = V[(opcode & 0x0F00) >> 8] * 0x5;  // Each character in font takes 5 bytes of memory
          pc += 2;
        break;

        case 0x0033:  // Fx33 - LD B, Vx: Store BCD representation of Vx in memory locations I, I+1, and I+2.
          memory[I]   = (V[(opcode & 0x0F00) >> 8] / 100);       // hundreds
          memory[I+1] = (V[(opcode & 0x0F00) >> 8] / 10) % 10;   // tens
          memory[I+2] = (V[(opcode & 0x0F00) >> 8]) % 10;        // ones
          pc += 2;
        break;

        case 0x0055:  // Fx55 - LD [I], Vx: Store registers V0 through Vx in memory starting at location I.
          for (int i = 0; i <= ((opcode & 0x0F00) >> 8); i++)
            memory[I + i] = V[i];
          /* On the original interpreter, when the operation is done, I = I + X + 1 (I is incremented).
             In SCHIP, I is left unmodified. */
					I += ((opcode & 0x0F00) >> 8) + 1;
          pc += 2;
        break;

        case 0x0065:  // Fx65 - LD Vx, [I]: Read registers V0 through Vx from memory starting at location I.
          for (int i = 0; i <= ((opcode & 0x0F00) >> 8); i++)
            V[i] = memory[I + i];
          /* On the original interpreter, when the operation is done, I = I + X + 1 (I is incremented).
             In SCHIP, I is left unmodified. */
					I += ((opcode & 0x0F00) >> 8) + 1;
          pc += 2;
        break;

        default:
          printf ("Unknown opcode [0xF000]: 0x%X\n", opcode);
      }
    break;

    default:
      printf ("Unknown opcode: 0x%X\n", opcode);
  }

  /* Update Timers */
  if (delay_timer > 0)
    delay_timer--;

  if (sound_timer > 0)
  {
    if (sound_timer == 1)
      sound_timer--;
  }

}

// Assembly:
pushq %rbp
pushq %r15
pushq %r14
pushq %rbx
pushq %rax
movq %rdi, %rbx
movzwl 0x1826(%rdi), %r14d
movzbl 0x814(%rdi,%r14), %r15d
movl %r15d, %esi
shll $0x8, %esi
movb 0x815(%rdi,%r14), %bpl
movzbl %bpl, %eax
orl %eax, %esi
movl %r15d, %ecx
shrl $0x4, %ecx
leaq 0x18e8(%rip), %rdx      # 0x4020
movslq (%rdx,%rcx,4), %rcx
addq %rdx, %rcx
jmpq *%rcx
andl $0xf, %eax
cmpl $0xe, %eax
je 0x2a72
testl %eax, %eax
jne 0x2a95
leaq 0x1(%rbx), %rdi
movl $0x800, %edx            # imm = 0x800
xorl %esi, %esi
callq 0x2110
movb $0x1, (%rbx)
jmp 0x2d79
cmpl $0xa1, %eax
je 0x2a42
cmpl $0x9e, %eax
jne 0x2a66
andl $0xf, %r15d
movzbl 0x1814(%rbx,%r15), %eax
cmpb $0x0, 0x801(%rbx,%rax)
jne 0x2a5d
jmp 0x2d79
andl $0xfff, %esi            # imm = 0xFFF
movzbl 0x1814(%rbx), %eax
addl %esi, %eax
jmp 0x2d7d
andl $0xf, %r15d
cmpb %bpl, 0x1814(%rbx,%r15)
je 0x2d79
jmp 0x2a5d
callq 0x20b0
cltq
imulq $-0x7f7f7f7f, %rax, %rcx # imm = 0x80808081
shrq $0x20, %rcx
addl %eax, %ecx
movl %ecx, %edx
shrl $0x1f, %edx
shrl $0x7, %ecx
addl %edx, %ecx
addl %eax, %ecx
andb %cl, %bpl
andl $0xf, %r15d
movb %bpl, 0x1814(%rbx,%r15)
addw $0x2, 0x1826(%rbx)
jmp 0x2d84
andl $0xf, %r15d
movb 0x1814(%rbx,%r15), %cl
shrl $0x4, %eax
cmpb 0x1814(%rbx,%rax), %cl
je 0x2d79
jmp 0x2a5d
movzwl 0x1848(%rbx), %eax
movw %r14w, 0x1828(%rbx,%rax,2)
incw 0x1848(%rbx)
andl $0xfff, %esi            # imm = 0xFFF
movw %si, 0x1826(%rbx)
jmp 0x2d84
cmpl $0x1d, %eax
jle 0x29ea
cmpl $0x32, %eax
jle 0x2ac6
cmpl $0x33, %eax
je 0x2b66
cmpl $0x55, %eax
je 0x2c99
cmpl $0x65, %eax
jne 0x2d5b
andl $0xf, %r15d
leaq 0x1814(%rbx), %rax
movq %r15, %rcx
incq %rcx
movzwl 0x1824(%rbx), %edx
movb -0x1000(%rax,%rdx), %dl
movb %dl, (%rax)
incq %rax
decq %rcx
jne 0x2892
jmp 0x2cc2
andl $0xf, %r15d
cmpb %bpl, 0x1814(%rbx,%r15)
je 0x2a5d
jmp 0x2d79
andl $0xf, %r15d
addb %bpl, 0x1814(%rbx,%r15)
jmp 0x2d79
andl $0xf, %r15d
movb 0x1814(%rbx,%r15), %cl
shrl $0x4, %eax
cmpb 0x1814(%rbx,%rax), %cl
jne 0x2d79
jmp 0x2a5d
andl $0xf, %r15d
movzbl 0x1814(%rbx,%r15), %ecx
movl %eax, %edx
shrl $0x4, %edx
movzbl 0x1814(%rbx,%rdx), %edx
movb $0x0, 0x1823(%rbx)
andq $0xf, %rax
je 0x2986
shll $0x6, %edx
addq %rcx, %rdx
addq %rbx, %rdx
incq %rdx
xorl %esi, %esi
movzwl 0x1824(%rbx), %ecx
addq %rsi, %rcx
movzbl 0x814(%rbx,%rcx), %edi
xorl %ecx, %ecx
movl %edi, %r8d
shll %cl, %r8d
testb %r8b, %r8b
jns 0x2969
movb (%rdx,%rcx), %r8b
cmpb $0x1, %r8b
jne 0x2961
movb $0x1, 0x1823(%rbx)
movb (%rdx,%rcx), %r8b
xorb $0x1, %r8b
movb %r8b, (%rdx,%rcx)
incq %rcx
cmpq $0x8, %rcx
jne 0x2941
incq %rsi
addq $0x40, %rdx
cmpq %rax, %rsi
jne 0x292d
movzwl 0x1826(%rbx), %r14d
movb $0x1, (%rbx)
addl $0x2, %r14d
movw %r14w, 0x1826(%rbx)
jmp 0x2d84
andl $0xf, %r15d
movb %bpl, 0x1814(%rbx,%r15)
jmp 0x2d79
andl $0xfff, %esi            # imm = 0xFFF
movw %si, 0x1824(%rbx)
jmp 0x2d79
movl %eax, %ecx
andl $0xf, %ecx
cmpl $0xe, %ecx
ja 0x2d64
leaq 0x168e(%rip), %rdx      # 0x4060
movslq (%rdx,%rcx,4), %rcx
addq %rdx, %rcx
jmpq *%rcx
shrl $0x4, %eax
movb 0x1814(%rbx,%rax), %al
jmp 0x2b15
cmpl $0x14, %eax
jg 0x2aa1
cmpl $0x7, %eax
je 0x2b0f
cmpl $0xa, %eax
jne 0x2d5b
andl $0xf, %r15d
xorl %eax, %eax
xorl %ecx, %ecx
addq $-0x10, %rax
movb 0x811(%rbx,%rax), %dl
testb %dl, %dl
jne 0x2a26
incq %rax
jne 0x2a11
jmp 0x2d74
movb %dl, 0x1814(%rbx,%r15)
leaq 0x11(%rax), %rdx
movb $0x1, %cl
cmpq $-0x1, %rax
movq %rdx, %rax
jne 0x2a0d
jmp 0x2d79
andl $0xf, %r15d
movzbl 0x1814(%rbx,%r15), %eax
cmpb $0x0, 0x801(%rbx,%rax)
jne 0x2d79
leal 0x4(%r14), %eax
jmp 0x2d7d
leaq 0x171c(%rip), %rdi      # 0x4189
jmp 0x2d6b
movzwl 0x1848(%rbx), %eax
decl %eax
movw %ax, 0x1848(%rbx)
movzwl %ax, %eax
movzwl 0x1828(%rbx,%rax,2), %eax
addl $0x2, %eax
jmp 0x2d7d
leaq 0x16af(%rip), %rdi      # 0x414b
jmp 0x2d6b
cmpl $0x15, %eax
je 0x2b1e
cmpl $0x18, %eax
jne 0x2d5b
andl $0xf, %r15d
movb 0x1814(%rbx,%r15), %al
movb %al, 0x184b(%rbx)
jmp 0x2d79
cmpl $0x1e, %eax
je 0x2bea
cmpl $0x29, %eax
jne 0x2d5b
andl $0xf, %r15d
movzbl 0x1814(%rbx,%r15), %eax
leal (%rax,%rax,4), %eax
movw %ax, 0x1824(%rbx)
jmp 0x2d79
shrl $0x4, %eax
movb 0x1814(%rbx,%rax), %al
andl $0xf, %r15d
andb %al, 0x1814(%rbx,%r15)
jmp 0x2d79
movb 0x184a(%rbx), %al
andl $0xf, %r15d
jmp 0x2c8c
andl $0xf, %r15d
movb 0x1814(%rbx,%r15), %al
movb %al, 0x184a(%rbx)
jmp 0x2d79
andl $0xf, %r15d
movb 0x1814(%rbx,%r15), %cl
shrl $0x4, %eax
cmpb 0x1814(%rbx,%rax), %cl
seta 0x1823(%rbx)
movb 0x1814(%rbx,%rax), %al
subb %al, 0x1814(%rbx,%r15)
jmp 0x2d79
andl $0xf, %r15d
movzbl 0x1814(%rbx,%r15), %eax
leal (%rax,%rax,4), %ecx
leal (%rax,%rcx,8), %eax
shrl $0xc, %eax
movzwl 0x1824(%rbx), %ecx
movb %al, 0x814(%rbx,%rcx)
movzbl 0x1814(%rbx,%r15), %eax
imull $0xcd, %eax, %eax
shrl $0xb, %eax
leal (%rax,%rax,4), %ecx
leal (%rcx,%rcx,4), %ecx
addl %eax, %ecx
shrl $0x7, %ecx
andl $0x6, %ecx
leal (%rcx,%rcx,4), %ecx
subb %cl, %al
movzwl 0x1824(%rbx), %ecx
movb %al, 0x815(%rbx,%rcx)
movzbl 0x1814(%rbx,%r15), %eax
imull $0xcd, %eax, %ecx
shrl $0xa, %ecx
andl $-0x2, %ecx
leal (%rcx,%rcx,4), %ecx
subb %cl, %al
movzwl 0x1824(%rbx), %ecx
movb %al, 0x816(%rbx,%rcx)
jmp 0x27fd
movzwl 0x1824(%rbx), %eax
andl $0xf, %r15d
movzbl 0x1814(%rbx,%r15), %ecx
addl %eax, %ecx
cmpl $0x1000, %ecx           # imm = 0x1000
setae 0x1823(%rbx)
movzbl 0x1814(%rbx,%r15), %ecx
addl %eax, %ecx
movw %cx, 0x1824(%rbx)
jmp 0x2d79
shrl $0x4, %eax
movb 0x1814(%rbx,%rax), %al
andl $0xf, %r15d
orb %al, 0x1814(%rbx,%r15)
jmp 0x2d79
andl $0xf, %r15d
movb 0x1814(%rbx,%r15), %al
andb $0x1, %al
movb %al, 0x1823(%rbx)
shrb 0x1814(%rbx,%r15)
jmp 0x2d79
andl $0xf, %r15d
movb 0x1814(%rbx,%r15), %cl
shrl $0x4, %eax
cmpb 0x1814(%rbx,%rax), %cl
setb 0x1823(%rbx)
movb 0x1814(%rbx,%rax), %al
subb 0x1814(%rbx,%r15), %al
movb %al, 0x1814(%rbx,%r15)
jmp 0x2d79
andl $0xf, %r15d
leaq 0x1814(%rbx), %rax
movq %r15, %rcx
incq %rcx
movb (%rax), %dl
movzwl 0x1824(%rbx), %esi
movb %dl, -0x1000(%rax,%rsi)
incq %rax
decq %rcx
jne 0x2caa
leal 0x1(%r15), %eax
movd 0x1824(%rbx), %xmm0
movdqa 0x133a(%rip), %xmm1     # 0x4010
pinsrw $0x0, %eax, %xmm1
paddw %xmm0, %xmm1
movd %xmm1, 0x1824(%rbx)
jmp 0x2d84
shrl $0x4, %eax
movb 0x1814(%rbx,%rax), %al
andl $0xf, %r15d
xorb %al, 0x1814(%rbx,%r15)
jmp 0x2d79
andl $0xf, %r15d
movzbl 0x1814(%rbx,%r15), %ecx
shrl $0x4, %eax
movzbl 0x1814(%rbx,%rax), %edx
addl %ecx, %edx
cmpl $0x100, %edx            # imm = 0x100
setae 0x1823(%rbx)
movb 0x1814(%rbx,%rax), %al
addb %al, 0x1814(%rbx,%r15)
jmp 0x2d79
andl $0xf, %r15d
movb 0x1814(%rbx,%r15), %al
shrb $0x7, %al
movb %al, 0x1823(%rbx)
shlb 0x1814(%rbx,%r15)
jmp 0x2d79
leaq 0x1446(%rip), %rdi      # 0x41a8
jmp 0x2d6b
leaq 0x13ff(%rip), %rdi      # 0x416a
xorl %eax, %eax
callq 0x2070
jmp 0x2d84
testb $0x1, %cl
je 0x2da6
leal 0x2(%r14), %eax
movw %ax, 0x1826(%rbx)
movb 0x184a(%rbx), %al
testb %al, %al
je 0x2d96
decb %al
movb %al, 0x184a(%rbx)
cmpb $0x1, 0x184b(%rbx)
jne 0x2da6
movb $0x0, 0x184b(%rbx)
addq $0x8, %rsp
popq %rbx
popq %r14
popq %r15
popq %rbp
retq
nopl (%rax)