#
# Copyright (c) 2020 Bitdefender
# SPDX-License-Identifier: Apache-2.0
#
import os
import sys

from pydis.disasm import *

try:
    from termcolor import colored
except:
    colored = None


_SPACES = [
    '',
    '  ',
    '    ',
    '      ',
    '        ',
    '          ',
    '            ',
    '              ',
    '                ',
    '                  ',
    '                    ',
    '                      ',
    '                        ',
    '                          ',
    '                            ',
    '                              ',
]


def print_internal(string, foreground=None, highlight=True):
    no_colors = (foreground is None) or (not highlight)

    if not colored or no_colors:
        sys.stdout.write(string)
    else:
        text = colored(string, foreground)
        sys.stdout.write(text)


def print_instruction(instruction, rip, highlight=False, ext_info=False):
    k = 0

    print_internal('%016x ' % rip)

    # prefixes
    for ibyte in range(0, instruction.PrefLength):
        print_internal('%02x' % instruction.InstructionBytes[ibyte])

    # opcodes
    k += instruction.PrefLength
    for ibyte in range(k, k + instruction.OpLength):
        print_internal('%02x' % instruction.InstructionBytes[ibyte], 'green', highlight)

    # modrm and sib
    k += instruction.OpLength
    for ibyte in range(k, k + instruction.HasModRm + instruction.HasSib):
        print_internal('%02x' % instruction.InstructionBytes[ibyte], 'yellow', highlight)

    # displacement
    k += instruction.HasModRm + instruction.HasSib
    for ibyte in range(k, k + instruction.DispLength):
        print_internal('%02x' % instruction.InstructionBytes[ibyte], 'blue', highlight)

    # relative offset/moffset/immediates
    rest = instruction.Imm1Length + instruction.Imm2Length + instruction.RelOffsLength + \
        instruction.MoffsetLength + instruction.HasSseImm + instruction.AddrLength

    k += instruction.DispLength
    for ibyte in range(k, k + rest):
        print_internal('%02x' % instruction.InstructionBytes[ibyte], 'red', highlight)

    # the rest of the bytes
    k += rest
    for ibyte in range(k, instruction.Length):
        print_internal('%02x' % instruction.InstructionBytes[ibyte])

    print_internal('%s' % _SPACES[16 - instruction.Length])
    print_internal('%s' % instruction.Text)

    if ext_info:
        print_internal('\n')
        print_internal('%r' % instruction)

    print_internal('\n')


def disassemble_file(filepath, offset=0, size=0, rip=0, arch=64,
                     highlight=True, vendor='any', ext_info=False):
    if not filepath:
        return

    with open(filepath, 'rb') as f:
        total = 0
        file_size = os.path.getsize(filepath)

        if not size:
            size = file_size

        while offset < file_size and total < size:
            to_read = file_size - offset
            if to_read > 15:
                to_read = 15

            f.seek(offset, 0)
            buff = f.read(to_read)

            current_rip = rip + total

            instr = nd_decode_ex2(buff, arch, arch, arch, vendor, current_rip)
            if instr:
                print_instruction(instr, current_rip, highlight, ext_info)
                offset += instr['Length']
                total += instr['Length']
            else:
                sys.stdout.write('%016x %02x %s db 0x%02x' % (current_rip, buff[0],
                                                              _SPACES[15], buff[0]))

                if str.isalpha(chr(buff[0])):
                    sys.stdout.write(str(buff[0]))

                sys.stdout.write('\n')

                offset += 1
                total += 1


def disassemble_hexstring(hexstring, offset=0, size=0, rip=0, arch=64,
                          highlight=True, vendor='any', ext_info=False):
    if not hexstring:
        return

    buff = bytes.fromhex(''.join(hexstring))

    total = 0
    if not size:
        size = len(buff)

    while total < size:
        current_rip = rip + total

        instr = nd_decode_ex2(buff[total:total+16], arch, arch, arch, vendor, current_rip)
        if instr:
            print_instruction(instr, current_rip, highlight, ext_info)
            offset += instr['Length']
            total += instr['Length']
        else:
            sys.stdout.write('%016x %02x %s db 0x%02x\n' % (current_rip, buff[offset],
                                                            _SPACES[15], buff[offset]))
            offset += 1
            total += 1
