const std = @import("std");

const opcode = @import("opcode.zig");

pub const Disassembler = struct {
    const InStream = std.io.InStream(std.os.File.ReadError);
    const OutStream = std.io.OutStream(std.os.File.WriteError);

    input: *InStream,
    output: *OutStream,
    buffer: ?u8,

    pub fn init(input: *InStream, output: *OutStream) Disassembler {
        return Disassembler{
            .input = input,
            .output = output,
            .buffer = null,
        };
    }

    pub fn disassemble(self: *Disassembler) !void {
        const byte = self.buffer orelse try self.input.readByte();
        self.buffer = null;
        switch (byte) {
            @enumToInt(opcode.Opcode.NOP) => try self.output.print("nop\n"),
            @enumToInt(opcode.Opcode.LD_A_n) => try self.output.print("ld a,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.LD_B_n) => try self.output.print("ld b,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.LD_C_n) => try self.output.print("ld c,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.LD_D_n) => try self.output.print("ld d,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.LD_E_n) => try self.output.print("ld e,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.LD_H_n) => try self.output.print("ld h,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.LD_L_n) => try self.output.print("ld l,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.LD_A_A) => try self.output.print("ld a,a\n"),
            @enumToInt(opcode.Opcode.LD_A_B) => try self.output.print("ld a,b\n"),
            @enumToInt(opcode.Opcode.LD_A_C) => try self.output.print("ld a,c\n"),
            @enumToInt(opcode.Opcode.LD_A_D) => try self.output.print("ld a,d\n"),
            @enumToInt(opcode.Opcode.LD_A_E) => try self.output.print("ld a,e\n"),
            @enumToInt(opcode.Opcode.LD_A_H) => try self.output.print("ld a,h\n"),
            @enumToInt(opcode.Opcode.LD_A_L) => try self.output.print("ld a,l\n"),
            @enumToInt(opcode.Opcode.LD_A_HL) => try self.output.print("ld a,(hl)\n"),
            @enumToInt(opcode.Opcode.LD_B_B) => try self.output.print("ld b,b\n"),
            @enumToInt(opcode.Opcode.LD_B_C) => try self.output.print("ld b,c\n"),
            @enumToInt(opcode.Opcode.LD_B_D) => try self.output.print("ld b,d\n"),
            @enumToInt(opcode.Opcode.LD_B_E) => try self.output.print("ld b,e\n"),
            @enumToInt(opcode.Opcode.LD_B_H) => try self.output.print("ld b,h\n"),
            @enumToInt(opcode.Opcode.LD_B_L) => try self.output.print("ld b,l\n"),
            @enumToInt(opcode.Opcode.LD_B_HL) => try self.output.print("ld b,(hl)\n"),
            @enumToInt(opcode.Opcode.LD_C_B) => try self.output.print("ld c,b\n"),
            @enumToInt(opcode.Opcode.LD_C_C) => try self.output.print("ld c,c\n"),
            @enumToInt(opcode.Opcode.LD_C_D) => try self.output.print("ld c,d\n"),
            @enumToInt(opcode.Opcode.LD_C_E) => try self.output.print("ld c,e\n"),
            @enumToInt(opcode.Opcode.LD_C_H) => try self.output.print("ld c,h\n"),
            @enumToInt(opcode.Opcode.LD_C_L) => try self.output.print("ld c,l\n"),
            @enumToInt(opcode.Opcode.LD_C_HL) => try self.output.print("ld c,(hl)\n"),
            @enumToInt(opcode.Opcode.LD_D_B) => try self.output.print("ld d,b\n"),
            @enumToInt(opcode.Opcode.LD_D_C) => try self.output.print("ld d,c\n"),
            @enumToInt(opcode.Opcode.LD_D_D) => try self.output.print("ld d,d\n"),
            @enumToInt(opcode.Opcode.LD_D_E) => try self.output.print("ld d,e\n"),
            @enumToInt(opcode.Opcode.LD_D_H) => try self.output.print("ld d,h\n"),
            @enumToInt(opcode.Opcode.LD_D_L) => try self.output.print("ld d,l\n"),
            @enumToInt(opcode.Opcode.LD_D_HL) => try self.output.print("ld d,(hl)\n"),
            @enumToInt(opcode.Opcode.LD_E_B) => try self.output.print("ld e,b\n"),
            @enumToInt(opcode.Opcode.LD_E_C) => try self.output.print("ld e,c\n"),
            @enumToInt(opcode.Opcode.LD_E_D) => try self.output.print("ld e,d\n"),
            @enumToInt(opcode.Opcode.LD_E_E) => try self.output.print("ld e,e\n"),
            @enumToInt(opcode.Opcode.LD_E_H) => try self.output.print("ld e,h\n"),
            @enumToInt(opcode.Opcode.LD_E_L) => try self.output.print("ld e,l\n"),
            @enumToInt(opcode.Opcode.LD_E_HL) => try self.output.print("ld e,(hl)\n"),
            @enumToInt(opcode.Opcode.LD_H_B) => try self.output.print("ld h,b\n"),
            @enumToInt(opcode.Opcode.LD_H_C) => try self.output.print("ld h,c\n"),
            @enumToInt(opcode.Opcode.LD_H_D) => try self.output.print("ld h,d\n"),
            @enumToInt(opcode.Opcode.LD_H_E) => try self.output.print("ld h,e\n"),
            @enumToInt(opcode.Opcode.LD_H_H) => try self.output.print("ld h,h\n"),
            @enumToInt(opcode.Opcode.LD_H_L) => try self.output.print("ld h,l\n"),
            @enumToInt(opcode.Opcode.LD_H_HL) => try self.output.print("ld h,(hl)\n"),
            @enumToInt(opcode.Opcode.LD_L_B) => try self.output.print("ld l,b\n"),
            @enumToInt(opcode.Opcode.LD_L_C) => try self.output.print("ld l,c\n"),
            @enumToInt(opcode.Opcode.LD_L_D) => try self.output.print("ld l,d\n"),
            @enumToInt(opcode.Opcode.LD_L_E) => try self.output.print("ld l,e\n"),
            @enumToInt(opcode.Opcode.LD_L_H) => try self.output.print("ld l,h\n"),
            @enumToInt(opcode.Opcode.LD_L_L) => try self.output.print("ld l,l\n"),
            @enumToInt(opcode.Opcode.LD_L_HL) => try self.output.print("ld l,(hl)\n"),
            @enumToInt(opcode.Opcode.LD_HL_B) => try self.output.print("ld (hl),b\n"),
            @enumToInt(opcode.Opcode.LD_HL_C) => try self.output.print("ld (hl),c\n"),
            @enumToInt(opcode.Opcode.LD_HL_D) => try self.output.print("ld (hl),d\n"),
            @enumToInt(opcode.Opcode.LD_HL_E) => try self.output.print("ld (hl),e\n"),
            @enumToInt(opcode.Opcode.LD_HL_H) => try self.output.print("ld (hl),h\n"),
            @enumToInt(opcode.Opcode.LD_HL_L) => try self.output.print("ld (hl),l\n"),
            @enumToInt(opcode.Opcode.LD_HL_n) => try self.output.print("ld (hl),${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.LD_A_BC) => try self.output.print("ld a,(bc)\n"),
            @enumToInt(opcode.Opcode.LD_A_DE) => try self.output.print("ld a,(de)\n"),
            @enumToInt(opcode.Opcode.LD_A_nn) => try self.output.print("ld a,(${x})\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.LD_B_A) => try self.output.print("ld b,a\n"),
            @enumToInt(opcode.Opcode.LD_C_A) => try self.output.print("ld c,a\n"),
            @enumToInt(opcode.Opcode.LD_D_A) => try self.output.print("ld d,a\n"),
            @enumToInt(opcode.Opcode.LD_E_A) => try self.output.print("ld e,a\n"),
            @enumToInt(opcode.Opcode.LD_H_A) => try self.output.print("ld h,a\n"),
            @enumToInt(opcode.Opcode.LD_L_A) => try self.output.print("ld l,a\n"),
            @enumToInt(opcode.Opcode.LD_BC_A) => try self.output.print("ld (bc),a\n"),
            @enumToInt(opcode.Opcode.LD_DE_A) => try self.output.print("ld (de),a\n"),
            @enumToInt(opcode.Opcode.LD_HL_A) => try self.output.print("ld (hl),a\n"),
            @enumToInt(opcode.Opcode.LD_nn_A) => try self.output.print("ld (${x}),a\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.LD_A_mem_C) => try self.output.print("ld a,($FF00 + c)\n"),
            @enumToInt(opcode.Opcode.LD_mem_C_A) => try self.output.print("ld ($FF00 + c),a\n"),
            @enumToInt(opcode.Opcode.LDD_A_HL) => try self.output.print("ldd a,(hl)\n"),
            @enumToInt(opcode.Opcode.LDD_HL_A) => try self.output.print("ldd (hl),a\n"),
            @enumToInt(opcode.Opcode.LDI_A_HL) => try self.output.print("ldi a,(hl)\n"),
            @enumToInt(opcode.Opcode.LDI_HL_A) => try self.output.print("ldi (hl),a\n"),
            @enumToInt(opcode.Opcode.LDH_n_A) => try self.output.print("ld ${x},a\n", u16(0xFF00) | try self.input.readByte()),
            @enumToInt(opcode.Opcode.LDH_A_n) => try self.output.print("ld a,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.LD_BC_nn) => try self.output.print("ld bc,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.LD_DE_nn) => try self.output.print("ld de,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.LD_HL_nn) => try self.output.print("ld hl,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.LD_SP_nn) => try self.output.print("ld sp,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.LD_SP_HL) => try self.output.print("ld sp,hl\n"),
            @enumToInt(opcode.Opcode.LDHL_SP_n) => try self.output.print("ld hl,sp+${x}\n", try self.input.readByteSigned()),
            @enumToInt(opcode.Opcode.LD_nn_SP) => try self.output.print("ld (${x}),sp\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.PUSH_AF) => try self.output.print("push af\n"),
            @enumToInt(opcode.Opcode.PUSH_BC) => try self.output.print("push bc\n"),
            @enumToInt(opcode.Opcode.PUSH_DE) => try self.output.print("push de\n"),
            @enumToInt(opcode.Opcode.PUSH_HL) => try self.output.print("push hl\n"),
            @enumToInt(opcode.Opcode.POP_AF) => try self.output.print("pop af\n"),
            @enumToInt(opcode.Opcode.POP_BC) => try self.output.print("pop bc\n"),
            @enumToInt(opcode.Opcode.POP_DE) => try self.output.print("pop de\n"),
            @enumToInt(opcode.Opcode.POP_HL) => try self.output.print("pop hl\n"),
            @enumToInt(opcode.Opcode.ADD_A_A) => try self.output.print("add a,a\n"),
            @enumToInt(opcode.Opcode.ADD_A_B) => try self.output.print("add a,b\n"),
            @enumToInt(opcode.Opcode.ADD_A_C) => try self.output.print("add a,c\n"),
            @enumToInt(opcode.Opcode.ADD_A_D) => try self.output.print("add a,d\n"),
            @enumToInt(opcode.Opcode.ADD_A_E) => try self.output.print("add a,e\n"),
            @enumToInt(opcode.Opcode.ADD_A_H) => try self.output.print("add a,h\n"),
            @enumToInt(opcode.Opcode.ADD_A_L) => try self.output.print("add a,l\n"),
            @enumToInt(opcode.Opcode.ADD_A_HL) => try self.output.print("add a,(hl)\n"),
            @enumToInt(opcode.Opcode.ADD_A_n) => try self.output.print("add a,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.ADC_A_A) => try self.output.print("adc a,a\n"),
            @enumToInt(opcode.Opcode.ADC_A_B) => try self.output.print("adc a,b\n"),
            @enumToInt(opcode.Opcode.ADC_A_C) => try self.output.print("adc a,c\n"),
            @enumToInt(opcode.Opcode.ADC_A_D) => try self.output.print("adc a,d\n"),
            @enumToInt(opcode.Opcode.ADC_A_E) => try self.output.print("adc a,e\n"),
            @enumToInt(opcode.Opcode.ADC_A_H) => try self.output.print("adc a,h\n"),
            @enumToInt(opcode.Opcode.ADC_A_L) => try self.output.print("adc a,l\n"),
            @enumToInt(opcode.Opcode.ADC_A_HL) => try self.output.print("adc a,(hl)\n"),
            @enumToInt(opcode.Opcode.ADC_A_n) => try self.output.print("adc a,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.SUB_A_A) => try self.output.print("sub a,a\n"),
            @enumToInt(opcode.Opcode.SUB_A_B) => try self.output.print("sub a,b\n"),
            @enumToInt(opcode.Opcode.SUB_A_C) => try self.output.print("sub a,c\n"),
            @enumToInt(opcode.Opcode.SUB_A_D) => try self.output.print("sub a,d\n"),
            @enumToInt(opcode.Opcode.SUB_A_E) => try self.output.print("sub a,e\n"),
            @enumToInt(opcode.Opcode.SUB_A_H) => try self.output.print("sub a,h\n"),
            @enumToInt(opcode.Opcode.SUB_A_L) => try self.output.print("sub a,l\n"),
            @enumToInt(opcode.Opcode.SUB_A_HL) => try self.output.print("sub a,(hl)\n"),
            @enumToInt(opcode.Opcode.SUB_A_n) => try self.output.print("sub a,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.SBC_A_A) => try self.output.print("sbc a,a\n"),
            @enumToInt(opcode.Opcode.SBC_A_B) => try self.output.print("sbc a,b\n"),
            @enumToInt(opcode.Opcode.SBC_A_C) => try self.output.print("sbc a,c\n"),
            @enumToInt(opcode.Opcode.SBC_A_D) => try self.output.print("sbc a,d\n"),
            @enumToInt(opcode.Opcode.SBC_A_E) => try self.output.print("sbc a,e\n"),
            @enumToInt(opcode.Opcode.SBC_A_H) => try self.output.print("sbc a,h\n"),
            @enumToInt(opcode.Opcode.SBC_A_L) => try self.output.print("sbc a,l\n"),
            @enumToInt(opcode.Opcode.SBC_A_HL) => try self.output.print("sbc a,(hl)\n"),
            @enumToInt(opcode.Opcode.SBC_A_n) => try self.output.print("sbc a,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.AND_A_A) => try self.output.print("and a,a\n"),
            @enumToInt(opcode.Opcode.AND_A_B) => try self.output.print("and a,b\n"),
            @enumToInt(opcode.Opcode.AND_A_C) => try self.output.print("and a,c\n"),
            @enumToInt(opcode.Opcode.AND_A_D) => try self.output.print("and a,d\n"),
            @enumToInt(opcode.Opcode.AND_A_E) => try self.output.print("and a,e\n"),
            @enumToInt(opcode.Opcode.AND_A_H) => try self.output.print("and a,h\n"),
            @enumToInt(opcode.Opcode.AND_A_L) => try self.output.print("and a,l\n"),
            @enumToInt(opcode.Opcode.AND_A_HL) => try self.output.print("and a,(hl)\n"),
            @enumToInt(opcode.Opcode.AND_A_n) => try self.output.print("and a,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.OR_A_A) => try self.output.print("or a,a\n"),
            @enumToInt(opcode.Opcode.OR_A_B) => try self.output.print("or a,b\n"),
            @enumToInt(opcode.Opcode.OR_A_C) => try self.output.print("or a,c\n"),
            @enumToInt(opcode.Opcode.OR_A_D) => try self.output.print("or a,d\n"),
            @enumToInt(opcode.Opcode.OR_A_E) => try self.output.print("or a,e\n"),
            @enumToInt(opcode.Opcode.OR_A_H) => try self.output.print("or a,h\n"),
            @enumToInt(opcode.Opcode.OR_A_L) => try self.output.print("or a,l\n"),
            @enumToInt(opcode.Opcode.OR_A_HL) => try self.output.print("or a,(hl)\n"),
            @enumToInt(opcode.Opcode.OR_A_n) => try self.output.print("or a,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.XOR_A_A) => try self.output.print("xor a,a\n"),
            @enumToInt(opcode.Opcode.XOR_A_B) => try self.output.print("xor a,b\n"),
            @enumToInt(opcode.Opcode.XOR_A_C) => try self.output.print("xor a,c\n"),
            @enumToInt(opcode.Opcode.XOR_A_D) => try self.output.print("xor a,d\n"),
            @enumToInt(opcode.Opcode.XOR_A_E) => try self.output.print("xor a,e\n"),
            @enumToInt(opcode.Opcode.XOR_A_H) => try self.output.print("xor a,h\n"),
            @enumToInt(opcode.Opcode.XOR_A_L) => try self.output.print("xor a,l\n"),
            @enumToInt(opcode.Opcode.XOR_A_HL) => try self.output.print("xor a,(hl)\n"),
            @enumToInt(opcode.Opcode.XOR_A_n) => try self.output.print("xor a,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.CP_A_A) => try self.output.print("cp a,a\n"),
            @enumToInt(opcode.Opcode.CP_A_B) => try self.output.print("cp a,b\n"),
            @enumToInt(opcode.Opcode.CP_A_C) => try self.output.print("cp a,c\n"),
            @enumToInt(opcode.Opcode.CP_A_D) => try self.output.print("cp a,d\n"),
            @enumToInt(opcode.Opcode.CP_A_E) => try self.output.print("cp a,e\n"),
            @enumToInt(opcode.Opcode.CP_A_H) => try self.output.print("cp a,h\n"),
            @enumToInt(opcode.Opcode.CP_A_L) => try self.output.print("cp a,l\n"),
            @enumToInt(opcode.Opcode.CP_A_HL) => try self.output.print("cp a,(hl)\n"),
            @enumToInt(opcode.Opcode.CP_A_n) => try self.output.print("cp a,${x}\n", try self.input.readByte()),
            @enumToInt(opcode.Opcode.INC_A) => try self.output.print("inc a\n"),
            @enumToInt(opcode.Opcode.INC_B) => try self.output.print("inc b\n"),
            @enumToInt(opcode.Opcode.INC_C) => try self.output.print("inc c\n"),
            @enumToInt(opcode.Opcode.INC_D) => try self.output.print("inc d\n"),
            @enumToInt(opcode.Opcode.INC_E) => try self.output.print("inc e\n"),
            @enumToInt(opcode.Opcode.INC_H) => try self.output.print("inc h\n"),
            @enumToInt(opcode.Opcode.INC_L) => try self.output.print("inc l\n"),
            @enumToInt(opcode.Opcode.INC_mem_HL) => try self.output.print("inc (hl)\n"),
            @enumToInt(opcode.Opcode.DEC_A) => try self.output.print("dec a\n"),
            @enumToInt(opcode.Opcode.DEC_B) => try self.output.print("dec b\n"),
            @enumToInt(opcode.Opcode.DEC_C) => try self.output.print("dec c\n"),
            @enumToInt(opcode.Opcode.DEC_D) => try self.output.print("dec d\n"),
            @enumToInt(opcode.Opcode.DEC_E) => try self.output.print("dec e\n"),
            @enumToInt(opcode.Opcode.DEC_H) => try self.output.print("dec h\n"),
            @enumToInt(opcode.Opcode.DEC_L) => try self.output.print("dec l\n"),
            @enumToInt(opcode.Opcode.DEC_mem_HL) => try self.output.print("dec (hl)\n"),
            @enumToInt(opcode.Opcode.ADD_HL_BC) => try self.output.print("add hl,bc\n"),
            @enumToInt(opcode.Opcode.ADD_HL_DE) => try self.output.print("add hl,de\n"),
            @enumToInt(opcode.Opcode.ADD_HL_HL) => try self.output.print("add hl,hl\n"),
            @enumToInt(opcode.Opcode.ADD_HL_SP) => try self.output.print("add hl,sp\n"),
            @enumToInt(opcode.Opcode.ADD_SP_n) => try self.output.print("add hl,${x}\n", self.input.readByteSigned()),
            @enumToInt(opcode.Opcode.INC_BC) => try self.output.print("inc bc\n"),
            @enumToInt(opcode.Opcode.INC_DE) => try self.output.print("inc de\n"),
            @enumToInt(opcode.Opcode.INC_HL) => try self.output.print("inc hl\n"),
            @enumToInt(opcode.Opcode.INC_SP) => try self.output.print("inc sp\n"),
            @enumToInt(opcode.Opcode.DEC_BC) => try self.output.print("dec bc\n"),
            @enumToInt(opcode.Opcode.DEC_DE) => try self.output.print("dec de\n"),
            @enumToInt(opcode.Opcode.DEC_HL) => try self.output.print("dec hl\n"),
            @enumToInt(opcode.Opcode.DEC_SP) => try self.output.print("dec sp\n"),
            @enumToInt(opcode.Opcode.DAA) => try self.output.print("daa\n"),
            @enumToInt(opcode.Opcode.CPL) => try self.output.print("cpl\n"),
            @enumToInt(opcode.Opcode.CCF) => try self.output.print("ccf\n"),
            @enumToInt(opcode.Opcode.SCF) => try self.output.print("scf\n"),
            @enumToInt(opcode.Opcode.HALT) => try self.output.print("halt\n"),
            @enumToInt(opcode.Opcode.STOP_FIRST_BYTE) => {
                const second_byte = try self.input.readByte();
                if (second_byte != 0) {
                    self.buffer = second_byte;
                    try self.output.print("${x}\n", @enumToInt(opcode.Opcode.STOP_FIRST_BYTE));
                } else {
                    try self.output.print("stop\n");
                }
            },
            @enumToInt(opcode.Opcode.DI) => try self.output.print("di\n"),
            @enumToInt(opcode.Opcode.EI) => try self.output.print("ei\n"),
            @enumToInt(opcode.Opcode.RLCA) => try self.output.print("rlca\n"),
            @enumToInt(opcode.Opcode.RLA) => try self.output.print("rla\n"),
            @enumToInt(opcode.Opcode.RRCA) => try self.output.print("rrca\n"),
            @enumToInt(opcode.Opcode.RRA) => try self.output.print("rra\n"),
            @enumToInt(opcode.Opcode.JP_nn) => try self.output.print("jp ${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.JP_NZ_nn) => try self.output.print("jp nz,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.JP_Z_nn) => try self.output.print("jp z,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.JP_NC_nn) => try self.output.print("jp nc,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.JP_C_nn) => try self.output.print("jp c,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.JP_HL) => try self.output.print("jp (HL)\n"),
            @enumToInt(opcode.Opcode.JR_n) => try self.output.print("jr ${x}\n", try self.input.readByteSigned()),
            @enumToInt(opcode.Opcode.JR_NZ_n) => try self.output.print("jr nz,${x}\n", try self.input.readByteSigned()),
            @enumToInt(opcode.Opcode.JR_Z_n) => try self.output.print("jr z,${x}\n", try self.input.readByteSigned()),
            @enumToInt(opcode.Opcode.JR_NC_n) => try self.output.print("jr nc,${x}\n", try self.input.readByteSigned()),
            @enumToInt(opcode.Opcode.JR_C_n) => try self.output.print("jr c,${x}\n", try self.input.readByteSigned()),
            @enumToInt(opcode.Opcode.CALL_nn) => try self.output.print("call ${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.CALL_NZ_nn) => try self.output.print("call nz,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.CALL_Z_nn) => try self.output.print("call z,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.CALL_NC_nn) => try self.output.print("call nc,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.CALL_C_nn) => try self.output.print("call c,${x}\n", try self.input.readIntLittle(u16)),
            @enumToInt(opcode.Opcode.RST_00) => try self.output.print("rst $00\n"),
            @enumToInt(opcode.Opcode.RST_08) => try self.output.print("rst $08\n"),
            @enumToInt(opcode.Opcode.RST_10) => try self.output.print("rst $10\n"),
            @enumToInt(opcode.Opcode.RST_18) => try self.output.print("rst $18\n"),
            @enumToInt(opcode.Opcode.RST_20) => try self.output.print("rst $20\n"),
            @enumToInt(opcode.Opcode.RST_28) => try self.output.print("rst $28\n"),
            @enumToInt(opcode.Opcode.RST_30) => try self.output.print("rst $30\n"),
            @enumToInt(opcode.Opcode.RST_38) => try self.output.print("rst $38\n"),
            @enumToInt(opcode.Opcode.RET) => try self.output.print("ret\n"),
            @enumToInt(opcode.Opcode.RET_NZ) => try self.output.print("ret nz\n"),
            @enumToInt(opcode.Opcode.RET_Z) => try self.output.print("ret z\n"),
            @enumToInt(opcode.Opcode.RET_NC) => try self.output.print("ret nc\n"),
            @enumToInt(opcode.Opcode.RET_C) => try self.output.print("ret c\n"),
            @enumToInt(opcode.Opcode.RETI) => try self.output.print("reti\n"),
            @enumToInt(opcode.Opcode.MISC) => {
                const next_byte = try self.input.readByte();
                switch (next_byte) {
                    @enumToInt(opcode.MiscOpcode.SWAP_A) => try self.output.print("swap a\n"),
                    @enumToInt(opcode.MiscOpcode.SWAP_B) => try self.output.print("swap b\n"),
                    @enumToInt(opcode.MiscOpcode.SWAP_C) => try self.output.print("swap c\n"),
                    @enumToInt(opcode.MiscOpcode.SWAP_D) => try self.output.print("swap d\n"),
                    @enumToInt(opcode.MiscOpcode.SWAP_E) => try self.output.print("swap e\n"),
                    @enumToInt(opcode.MiscOpcode.SWAP_H) => try self.output.print("swap h\n"),
                    @enumToInt(opcode.MiscOpcode.SWAP_L) => try self.output.print("swap l\n"),
                    @enumToInt(opcode.MiscOpcode.SWAP_HL) => try self.output.print("swap (hl)\n"),
                    @enumToInt(opcode.MiscOpcode.RLC_A) => try self.output.print("rlc a\n"),
                    @enumToInt(opcode.MiscOpcode.RLC_B) => try self.output.print("rlc b\n"),
                    @enumToInt(opcode.MiscOpcode.RLC_C) => try self.output.print("rlc c\n"),
                    @enumToInt(opcode.MiscOpcode.RLC_D) => try self.output.print("rlc d\n"),
                    @enumToInt(opcode.MiscOpcode.RLC_E) => try self.output.print("rlc e\n"),
                    @enumToInt(opcode.MiscOpcode.RLC_H) => try self.output.print("rlc h\n"),
                    @enumToInt(opcode.MiscOpcode.RLC_L) => try self.output.print("rlc l\n"),
                    @enumToInt(opcode.MiscOpcode.RLC_HL) => try self.output.print("rlc (hl)\n"),
                    @enumToInt(opcode.MiscOpcode.RL_A) => try self.output.print("rl a\n"),
                    @enumToInt(opcode.MiscOpcode.RL_B) => try self.output.print("rl b\n"),
                    @enumToInt(opcode.MiscOpcode.RL_C) => try self.output.print("rl c\n"),
                    @enumToInt(opcode.MiscOpcode.RL_D) => try self.output.print("rl d\n"),
                    @enumToInt(opcode.MiscOpcode.RL_E) => try self.output.print("rl e\n"),
                    @enumToInt(opcode.MiscOpcode.RL_H) => try self.output.print("rl h\n"),
                    @enumToInt(opcode.MiscOpcode.RL_L) => try self.output.print("rl l\n"),
                    @enumToInt(opcode.MiscOpcode.RL_HL) => try self.output.print("rl (hl)\n"),
                    @enumToInt(opcode.MiscOpcode.RRC_A) => try self.output.print("rrc a\n"),
                    @enumToInt(opcode.MiscOpcode.RRC_B) => try self.output.print("rrc b\n"),
                    @enumToInt(opcode.MiscOpcode.RRC_C) => try self.output.print("rrc c\n"),
                    @enumToInt(opcode.MiscOpcode.RRC_D) => try self.output.print("rrc d\n"),
                    @enumToInt(opcode.MiscOpcode.RRC_E) => try self.output.print("rrc e\n"),
                    @enumToInt(opcode.MiscOpcode.RRC_H) => try self.output.print("rrc h\n"),
                    @enumToInt(opcode.MiscOpcode.RRC_L) => try self.output.print("rrc l\n"),
                    @enumToInt(opcode.MiscOpcode.RRC_HL) => try self.output.print("rrc (hl)\n"),
                    @enumToInt(opcode.MiscOpcode.RR_A) => try self.output.print("rr a\n"),
                    @enumToInt(opcode.MiscOpcode.RR_B) => try self.output.print("rr b\n"),
                    @enumToInt(opcode.MiscOpcode.RR_C) => try self.output.print("rr c\n"),
                    @enumToInt(opcode.MiscOpcode.RR_D) => try self.output.print("rr d\n"),
                    @enumToInt(opcode.MiscOpcode.RR_E) => try self.output.print("rr e\n"),
                    @enumToInt(opcode.MiscOpcode.RR_H) => try self.output.print("rr h\n"),
                    @enumToInt(opcode.MiscOpcode.RR_L) => try self.output.print("rr l\n"),
                    @enumToInt(opcode.MiscOpcode.RR_HL) => try self.output.print("rr (hl)\n"),
                    @enumToInt(opcode.MiscOpcode.SLA_A) => try self.output.print("sla a\n"),
                    @enumToInt(opcode.MiscOpcode.SLA_B) => try self.output.print("sla b\n"),
                    @enumToInt(opcode.MiscOpcode.SLA_C) => try self.output.print("sla c\n"),
                    @enumToInt(opcode.MiscOpcode.SLA_D) => try self.output.print("sla d\n"),
                    @enumToInt(opcode.MiscOpcode.SLA_E) => try self.output.print("sla e\n"),
                    @enumToInt(opcode.MiscOpcode.SLA_H) => try self.output.print("sla h\n"),
                    @enumToInt(opcode.MiscOpcode.SLA_L) => try self.output.print("sla l\n"),
                    @enumToInt(opcode.MiscOpcode.SLA_HL) => try self.output.print("sla (hl)\n"),
                    @enumToInt(opcode.MiscOpcode.SRA_A) => try self.output.print("sra a\n"),
                    @enumToInt(opcode.MiscOpcode.SRA_B) => try self.output.print("sra b\n"),
                    @enumToInt(opcode.MiscOpcode.SRA_C) => try self.output.print("sra c\n"),
                    @enumToInt(opcode.MiscOpcode.SRA_D) => try self.output.print("sra d\n"),
                    @enumToInt(opcode.MiscOpcode.SRA_E) => try self.output.print("sra e\n"),
                    @enumToInt(opcode.MiscOpcode.SRA_H) => try self.output.print("sra h\n"),
                    @enumToInt(opcode.MiscOpcode.SRA_L) => try self.output.print("sra l\n"),
                    @enumToInt(opcode.MiscOpcode.SRA_HL) => try self.output.print("sra (hl)\n"),
                    @enumToInt(opcode.MiscOpcode.SRL_A) => try self.output.print("srl a\n"),
                    @enumToInt(opcode.MiscOpcode.SRL_B) => try self.output.print("srl b\n"),
                    @enumToInt(opcode.MiscOpcode.SRL_C) => try self.output.print("srl c\n"),
                    @enumToInt(opcode.MiscOpcode.SRL_D) => try self.output.print("srl d\n"),
                    @enumToInt(opcode.MiscOpcode.SRL_E) => try self.output.print("srl e\n"),
                    @enumToInt(opcode.MiscOpcode.SRL_H) => try self.output.print("srl h\n"),
                    @enumToInt(opcode.MiscOpcode.SRL_L) => try self.output.print("srl l\n"),
                    @enumToInt(opcode.MiscOpcode.SRL_HL) => try self.output.print("srl (hl)\n"),
                    @enumToInt(opcode.MiscOpcode.BIT_A) => try self.output.print("bit ${x},a\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.BIT_B) => try self.output.print("bit ${x},b\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.BIT_C) => try self.output.print("bit ${x},c\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.BIT_D) => try self.output.print("bit ${x},d\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.BIT_E) => try self.output.print("bit ${x},e\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.BIT_H) => try self.output.print("bit ${x},h\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.BIT_L) => try self.output.print("bit ${x},l\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.BIT_HL) => try self.output.print("bit ${x},(hl)\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.SET_A) => try self.output.print("set ${x},a\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.SET_B) => try self.output.print("set ${x},b\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.SET_C) => try self.output.print("set ${x},c\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.SET_D) => try self.output.print("set ${x},d\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.SET_E) => try self.output.print("set ${x},e\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.SET_H) => try self.output.print("set ${x},h\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.SET_L) => try self.output.print("set ${x},l\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.SET_HL) => try self.output.print("set ${x},(hl)\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.RES_A) => try self.output.print("res ${x},a\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.RES_B) => try self.output.print("res ${x},b\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.RES_C) => try self.output.print("res ${x},c\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.RES_D) => try self.output.print("res ${x},d\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.RES_E) => try self.output.print("res ${x},e\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.RES_H) => try self.output.print("res ${x},h\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.RES_L) => try self.output.print("res ${x},l\n", try self.input.readByte()),
                    @enumToInt(opcode.MiscOpcode.RES_HL) => try self.output.print("res ${x},(hl)\n", try self.input.readByte()),
                    else => try self.output.print("${x}\n", next_byte),
                }
            },
            else => try self.output.print("${x}\n", byte),
        }
    }
};

test "Disassembler" {
    var test_rom_file = try std.os.File.openRead("testdata/sprite_priority.gb");
    defer test_rom_file.close();
    var stdout = try std.io.getStdOut();
    var buffered_in_stream = std.io.BufferedInStream(std.os.File.ReadError).init(&test_rom_file.inStream().stream);
    var buffered_out_stream = std.io.BufferedOutStream(std.os.File.WriteError).init(&stdout.outStream().stream);
    var disassembler = Disassembler.init(&buffered_in_stream.stream, &buffered_out_stream.stream);
    while (true) {
        disassembler.disassemble() catch |err| {
            if (err == error.EndOfStream) {
                break;
            } else {
                return err;
            }
        };
    }
}
