from py65.utils.addressing import AddressParser


class Disassembler:
    def __init__(self, mpu, address_parser=None):
        if address_parser is None:
            address_parser = AddressParser()

        self._mpu = mpu
        self._address_parser = address_parser

        self.addrWidth = mpu.ADDR_WIDTH
        self.byteWidth = mpu.BYTE_WIDTH
        self.addrFmt = mpu.ADDR_FORMAT
        self.byteFmt = mpu.BYTE_FORMAT
        self.addrMask = mpu.addrMask
        self.byteMask = mpu.byteMask

    def instruction_at(self, pc):
        """ Disassemble the instruction at PC and return a tuple
        containing (instruction byte count, human readable text)
        """

        instruction = self._mpu.ByteAt(pc)
        disasm, addressing = self._mpu.disassemble[instruction]

        if addressing == 'acc':
            disasm += ' A'
            length = 1

        elif addressing == 'abs':
            address = self._mpu.WordAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                address, '$' + self.addrFmt % address)
            disasm += ' ' + address_or_label
            length = 3

        elif addressing == 'abx':
            address = self._mpu.WordAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                address, '$' + self.addrFmt % address)
            disasm += ' %s,X' % address_or_label
            length = 3

        elif addressing == 'aby':
            address = self._mpu.WordAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                address, '$' + self.addrFmt % address)
            disasm += ' %s,Y' % address_or_label
            length = 3

        elif addressing == 'imm':
            byte = self._mpu.ByteAt(pc + 1)
            disasm += ' #$' + self.byteFmt % byte
            length = 2

        elif addressing == 'imp':
            length = 1

        elif addressing == 'ind':
            address = self._mpu.WordAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                address, '$' + self.addrFmt % address)
            disasm += ' (%s)' % address_or_label
            length = 3

        elif addressing == 'iny':
            zp_address = self._mpu.ByteAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                zp_address, '$' + self.byteFmt % zp_address)
            disasm += ' (%s),Y' % address_or_label
            length = 2

        elif addressing == 'inx':
            zp_address = self._mpu.ByteAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                zp_address, '$' + self.byteFmt % zp_address)
            disasm += ' (%s,X)' % address_or_label
            length = 2

        elif addressing == 'iax':
            address = self._mpu.WordAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                address, '$' + self.addrFmt % address)
            disasm += ' (%s,X)' % address_or_label
            length = 3

        elif addressing == 'rel':
            opv = self._mpu.ByteAt(pc + 1)
            targ = pc + 2
            if opv & (1 << (self.byteWidth - 1)):
                targ -= (opv ^ self.byteMask) + 1
            else:
                targ += opv
            targ &= self.addrMask

            address_or_label = self._address_parser.label_for(
                targ, '$' + self.addrFmt % targ)
            disasm += ' ' + address_or_label
            length = 2

        elif addressing == 'zpi':
            zp_address = self._mpu.ByteAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                zp_address, '($' + self.byteFmt % zp_address + ')')
            disasm += ' %s' % address_or_label
            length = 2

        elif addressing == 'zpg':
            zp_address = self._mpu.ByteAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                zp_address, '$' + self.byteFmt % zp_address)
            disasm += ' %s' % address_or_label
            length = 2

        elif addressing == 'zpx':
            zp_address = self._mpu.ByteAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                zp_address, '$' + self.byteFmt % zp_address)
            disasm += ' %s,X' % address_or_label
            length = 2

        elif addressing == 'zpy':
            zp_address = self._mpu.ByteAt(pc + 1)
            address_or_label = self._address_parser.label_for(
                zp_address, '$' + self.byteFmt % zp_address)
            disasm += ' %s,Y' % address_or_label
            length = 2

        else:
            msg = "Addressing mode: %r" % addressing
            raise NotImplementedError(msg)

        return (length, disasm)
