#include <bits/types/FILE.h>
#include <cstdint>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

// Bit patterns for the 8086 OPCODES
enum class OPCODES {
  MOV = 0x88,
};

enum class OPMASKS {
  MOV = 0xfc,
};

enum class FLAGS {
  WORD = 0x01,
  REG_DEST = 0x02,
};

enum class MODE {
  MEM = 0x00,
  MEM8 = 0x40,
  MEM16 = 0x80,
  REG = 0xc0,
};

uint16_t decode_mode(uint16_t instruction);
void decode_register(uint16_t instruction, bool word, char *dest);

int main(int argc, char *argv[]) {
  if (argc < 2) {
    printf("Please provide a file to disassemble\n");
    return 1;
  }

  const char *filename = argv[1];

  FILE *fp = fopen(filename, "rb");

  if (fp) {
    uint16_t inst = 0;
    const char *op = "";

    char out_filename[4096] = {0};
    sprintf(out_filename, "%s_out.asm", filename);

    FILE *out = fopen(out_filename, "w");

    if (out) {
      fprintf(out, "; Disassembled by DASM\n\nbits 16\n\n");

      while (fread(&inst, sizeof(inst), 1, fp)) {
        if ((inst & (uint16_t)OPMASKS::MOV) == (uint16_t)OPCODES::MOV) {
          op = "mov";

          bool reg_dest =
              (inst & (uint16_t)FLAGS::REG_DEST) == (uint16_t)FLAGS::REG_DEST;
          bool word = (inst & (uint16_t)FLAGS::WORD) == (uint16_t)FLAGS::WORD;

          uint16_t operands_info = inst >> 8;

          if (decode_mode(operands_info) == (uint16_t)MODE::REG) {
            char rm[3] = {0};
            char reg[3] = {0};

            decode_register(operands_info, word, rm);
            decode_register(operands_info >> 3, word, reg);

            fprintf(out, "%s %s, %s\n", op, reg_dest ? reg : rm,
                    reg_dest ? rm : reg);
          }
        } else {
          printf("It's not a mov operation\n");
        }
      }

      fclose(out);
    } else {
      printf("Failed to open output file\n");
    }

    fclose(fp);
  } else {
    printf("Failed to open the selected file\n");
  }

  return 0;
}

uint16_t decode_mode(uint16_t instruction) {
  uint16_t mode_mask = 0xc0;

  return instruction & mode_mask;
}

void decode_register(uint16_t instruction, bool word, char *dest) {
  static uint16_t reg_mask = 0x07;

  // clang-format off
	static const char *table[16] = {
		"al", "ax",
		"cl", "cx",
		"dl", "dx",
		"bl", "bx",
		"ah", "sp",
		"ch", "bp",
		"dh", "si",
		"bh", "di"
	};
  // clang-format on

  static const uint16_t ROW_WIDTH = 2;

  uint16_t offset = instruction & reg_mask;

  // Multiply offset by 2 since each row has 2 columns
  strcpy(dest, table[offset * ROW_WIDTH + (uint16_t)word]);
}