#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <capstone.h>

struct platform {
  cs_arch arch;
  cs_mode mode;
  char *comment;
};

int main(int argc, char **argv)
{
  if (argc != 2) {
    printf("Usage: %s <testcase>\n", argv[0]);
    return 1;
  }

  struct platform platforms[] = {
    {
      CS_ARCH_X86,
      CS_MODE_32,
      "X86 32 (Intel syntax)"
    },
    {
      CS_ARCH_X86,
      CS_MODE_64,
      "X86 64 (Intel syntax)"
    },
    {
      CS_ARCH_ARM,
      CS_MODE_ARM,
      "ARM"
    },
    {
      CS_ARCH_ARM,
      CS_MODE_THUMB,
      "THUMB-2"
    },
    {
      CS_ARCH_ARM,
      CS_MODE_ARM,
      "ARM: Cortex-A15 + NEON"
    },
    {
      CS_ARCH_ARM,
      CS_MODE_THUMB,
      "THUMB"
    },
    {
      CS_ARCH_ARM,
      (cs_mode)(CS_MODE_THUMB + CS_MODE_MCLASS),
      "Thumb-MClass"
    },
    {
      CS_ARCH_ARM,
      (cs_mode)(CS_MODE_ARM + CS_MODE_V8),
      "Arm-V8"
    },
    {
      CS_ARCH_MIPS,
      (cs_mode)(CS_MODE_MIPS32 + CS_MODE_BIG_ENDIAN),
      "MIPS-32 (Big-endian)"
    },
    {
      CS_ARCH_MIPS,
      (cs_mode)(CS_MODE_MIPS64 + CS_MODE_LITTLE_ENDIAN),
      "MIPS-64-EL (Little-endian)"
    },
    {
      CS_ARCH_MIPS,
      (cs_mode)(CS_MODE_MIPS32R6 + CS_MODE_MICRO + CS_MODE_BIG_ENDIAN),
      "MIPS-32R6 | Micro (Big-endian)"
    },
    {
      CS_ARCH_MIPS,
      (cs_mode)(CS_MODE_MIPS32R6 + CS_MODE_BIG_ENDIAN),
      "MIPS-32R6 (Big-endian)"
    },
    {
      CS_ARCH_ARM64,
      CS_MODE_ARM,
      "ARM-64"
    },
    {
      CS_ARCH_PPC,
      CS_MODE_BIG_ENDIAN,
      "PPC-64"
    },
    {
      CS_ARCH_SPARC,
      CS_MODE_BIG_ENDIAN,
      "Sparc"
    },
    {
      CS_ARCH_SPARC,
      (cs_mode)(CS_MODE_BIG_ENDIAN + CS_MODE_V9),
      "SparcV9"
    },
    {
      CS_ARCH_SYSZ,
      (cs_mode)0,
      "SystemZ"
    },
    {
      CS_ARCH_XCORE,
      (cs_mode)0,
      "XCore"
    },
  };

  // Read input
  long bufsize = 0;
  unsigned char *buf = NULL;
  FILE *fp = fopen(argv[1], "r");

  if (fp == NULL) return 1;

  if (fseek(fp, 0L, SEEK_END) == 0) {
    bufsize = ftell(fp);

    if (bufsize == -1) return 1;

    buf = malloc(bufsize + 1);

    if (buf == NULL) return 1;
    if (fseek(fp, 0L, SEEK_SET) != 0) return 1;

    size_t len = fread(buf, sizeof(char), bufsize, fp);

    if (len == 0) return 2;
  }
  fclose(fp);

  // Disassemble
  csh handle;
  cs_insn *all_insn;
  cs_detail *detail;
  cs_err err;

  if (bufsize < 3) return 0;

  int platforms_len = sizeof(platforms)/sizeof(platforms[0]);
  int i = (int)buf[0] % platforms_len;

  unsigned char *buf_ptr = buf + 1;
  long buf_ptr_size = bufsize - 1;

  printf("Platform: %s (0x%.2x of 0x%.2x)\n", platforms[i].comment, i, platforms_len);

  err = cs_open(platforms[i].arch, platforms[i].mode, &handle);
  if (err) {
    printf("Failed on cs_open() with error returned: %u\n", err);
    return 1;
  }

  cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);

  uint64_t address = 0x1000;
  size_t count = cs_disasm(handle, buf_ptr, buf_ptr_size, address, 0, &all_insn);

  if (count) {
    size_t j;
    int n;

    printf("Disasm:\n");

    for (j = 0; j < count; j++) {
      cs_insn *i = &(all_insn[j]);
      printf("0x%"PRIx64":\t%s\t\t%s // insn-ID: %u, insn-mnem: %s\n",
          i->address, i->mnemonic, i->op_str,
          i->id, cs_insn_name(handle, i->id));

      detail = i->detail;

      if (detail->regs_read_count > 0) {
        printf("\tImplicit registers read: ");
        for (n = 0; n < detail->regs_read_count; n++) {
          printf("%s ", cs_reg_name(handle, detail->regs_read[n]));
        }
        printf("\n");
      }

      if (detail->regs_write_count > 0) {
        printf("\tImplicit registers modified: ");
        for (n = 0; n < detail->regs_write_count; n++) {
          printf("%s ", cs_reg_name(handle, detail->regs_write[n]));
        }
        printf("\n");
      }

      if (detail->groups_count > 0) {
        printf("\tThis instruction belongs to groups: ");
        for (n = 0; n < detail->groups_count; n++) {
          printf("%s ", cs_group_name(handle, detail->groups[n]));
        }
        printf("\n");
      }
    }
    printf("0x%"PRIx64":\n", all_insn[j-1].address + all_insn[j-1].size);
    cs_free(all_insn, count);
  } else {
    printf("ERROR: Failed to disasm given code!\n");
  }

  printf("\n");

  free(buf);
  cs_close(&handle);

  return 0;
}
