# By Dang Hoang Vu <danghvu@gmail.com>, 2014

cimport pyx.ccapstone as cc
import capstone, ctypes
from . import arm, x86, mips, ppc, arm64, sparc, systemz, xcore, CsError

_diet = cc.cs_support(capstone.CS_SUPPORT_DIET)


class CsDetail(object):

    def __init__(self, arch, raw_detail = None):
        if not raw_detail:
            return
        detail = ctypes.cast(raw_detail, ctypes.POINTER(capstone._cs_detail)).contents

        self.regs_read = detail.regs_read
        self.regs_read_count = detail.regs_read_count
        self.regs_write = detail.regs_write
        self.regs_write_count = detail.regs_write_count
        self.groups = detail.groups
        self.groups_count = detail.groups_count

        if arch == capstone.CS_ARCH_ARM:
            (self.usermode, self.vector_size, self.vector_data, self.cps_mode, self.cps_flag, \
                self.cc, self.update_flags, self.writeback, self.operands) = \
                arm.get_arch_info(detail.arch.arm)
        elif arch == capstone.CS_ARCH_ARM64:
            (self.cc, self.update_flags, self.writeback, self.operands) = \
                arm64.get_arch_info(detail.arch.arm64)
        elif arch == capstone.CS_ARCH_X86:
            (self.prefix, self.opcode, self.rex, self.addr_size, \
                self.modrm, self.sib, self.disp, \
                self.sib_index, self.sib_scale, self.sib_base, \
                self.sse_cc, self.avx_cc, self.avx_sae, self.avx_rm, \
                self.operands) = x86.get_arch_info(detail.arch.x86)
        elif arch == capstone.CS_ARCH_MIPS:
                self.operands = mips.get_arch_info(detail.arch.mips)
        elif arch == capstone.CS_ARCH_PPC:
            (self.bc, self.bh, self.update_cr0, self.operands) = \
                ppc.get_arch_info(detail.arch.ppc)
        elif arch == capstone.CS_ARCH_SPARC:
            (self.cc, self.hint, self.operands) = sparc.get_arch_info(detail.arch.sparc)
        elif arch == capstone.CS_ARCH_SYSZ:
            (self.cc, self.operands) = systemz.get_arch_info(detail.arch.sysz)
        elif arch == capstone.CS_ARCH_XCORE:
                self.operands = xcore.get_arch_info(detail.arch.xcore)


cdef class CsInsn(object):

    cdef cc.cs_insn _raw
    cdef cc.csh _csh
    cdef object _detail

    def __cinit__(self, _detail):
        self._detail = _detail

    # defer to CsDetail structure for everything else.
    def __getattr__(self, name):
        _detail = self._detail
        if not _detail:
            raise CsError(capstone.CS_ERR_DETAIL)
        return getattr(_detail, name)

    # return instruction's operands.
    @property
    def operands(self):
        return self._detail.operands

    # return instruction's ID.
    @property
    def id(self):
        return self._raw.id

    # return instruction's address.
    @property
    def address(self):
        return self._raw.address

    # return instruction's size.
    @property
    def size(self):
        return self._raw.size

    # return instruction's machine bytes (which should have @size bytes).
    @property
    def bytes(self):
        return bytearray(self._raw.bytes)[:self._raw.size]

    # return instruction's mnemonic.
    @property
    def mnemonic(self):
        if _diet:
            # Diet engine cannot provide @mnemonic & @op_str
            raise CsError(capstone.CS_ERR_DIET)

        return self._raw.mnemonic

    # return instruction's operands (in string).
    @property
    def op_str(self):
        if _diet:
            # Diet engine cannot provide @mnemonic & @op_str
            raise CsError(capstone.CS_ERR_DIET)

        return self._raw.op_str

    # return list of all implicit registers being read.
    @property
    def regs_read(self):
        if self._raw.id == 0:
            raise CsError(capstone.CS_ERR_SKIPDATA)

        if _diet:
            # Diet engine cannot provide @regs_read
            raise CsError(capstone.CS_ERR_DIET)

        if self._detail:
            detail = self._detail
            return detail.regs_read[:detail.regs_read_count]

        raise CsError(capstone.CS_ERR_DETAIL)

    # return list of all implicit registers being modified
    @property
    def regs_write(self):
        if self._raw.id == 0:
            raise CsError(capstone.CS_ERR_SKIPDATA)

        if _diet:
            # Diet engine cannot provide @regs_write
            raise CsError(capstone.CS_ERR_DIET)

        if self._detail:
            detail = self._detail
            return detail.regs_write[:detail.regs_write_count]

        raise CsError(capstone.CS_ERR_DETAIL)

    # return list of semantic groups this instruction belongs to.
    @property
    def groups(self):
        if self._raw.id == 0:
            raise CsError(capstone.CS_ERR_SKIPDATA)

        if _diet:
            # Diet engine cannot provide @groups
            raise CsError(capstone.CS_ERR_DIET)

        if self._detail:
            detail = self._detail
            return detail.groups[:detail.groups_count]

        raise CsError(capstone.CS_ERR_DETAIL)

    # get the last error code
    def errno(self):
        return cc.cs_errno(self._csh)

    # get the register name, given the register ID
    def reg_name(self, reg_id):
        if self._raw.id == 0:
            raise CsError(capstone.CS_ERR_SKIPDATA)

        if _diet:
            # Diet engine cannot provide register's name
            raise CsError(capstone.CS_ERR_DIET)

        return cc.cs_reg_name(self._csh, reg_id)

    # get the instruction string
    def insn_name(self):
        if _diet:
            # Diet engine cannot provide instruction's name
            raise CsError(capstone.CS_ERR_DIET)

        return cc.cs_insn_name(self._csh, self.id)

    # get the group string
    def group_name(self, group_id):
        if _diet:
            # Diet engine cannot provide group's name
            raise CsError(capstone.CS_ERR_DIET)

        return cc.cs_group_name(self._csh, group_id)

    # verify if this insn belong to group with id as @group_id
    def group(self, group_id):
        if self._raw.id == 0:
            raise CsError(capstone.CS_ERR_SKIPDATA)

        if _diet:
            # Diet engine cannot provide @groups
            raise CsError(capstone.CS_ERR_DIET)

        return group_id in self.groups

    # verify if this instruction implicitly read register @reg_id
    def reg_read(self, reg_id):
        if self._raw.id == 0:
            raise CsError(capstone.CS_ERR_SKIPDATA)

        if _diet:
            # Diet engine cannot provide @regs_read
            raise CsError(capstone.CS_ERR_DIET)

        return reg_id in self.regs_read

    # verify if this instruction implicitly modified register @reg_id
    def reg_write(self, reg_id):
        if self._raw.id == 0:
            raise CsError(capstone.CS_ERR_SKIPDATA)

        if _diet:
            # Diet engine cannot provide @regs_write
            raise CsError(capstone.CS_ERR_DIET)

        return reg_id in self.regs_write

    # return number of operands having same operand type @op_type
    def op_count(self, op_type):
        if self._raw.id == 0:
            raise CsError(capstone.CS_ERR_SKIPDATA)

        c = 0
        for op in self._detail.operands:
            if op.type == op_type:
                c += 1
        return c

    # get the operand at position @position of all operands having the same type @op_type
    def op_find(self, op_type, position):
        if self._raw.id == 0:
            raise CsError(capstone.CS_ERR_SKIPDATA)

        c = 0
        for op in self._detail.operands:
            if op.type == op_type:
                c += 1
            if c == position:
                return op


cdef class Cs(object):

    cdef cc.csh csh
    cdef object _cs

    def __cinit__(self, _cs):
        cdef version = cc.cs_version(NULL, NULL)
        if (version != (capstone.CS_API_MAJOR << 8) + capstone.CS_API_MINOR):
            # our binding version is different from the core's API version
            raise CsError(capstone.CS_ERR_VERSION)

        self.csh = <cc.csh> _cs.csh.value
        self._cs = _cs


    # destructor to be called automatically when object is destroyed.
    def __dealloc__(self):
        if self.csh:
            status = cc.cs_close(&self.csh)
            if status != capstone.CS_ERR_OK:
                raise CsError(status)


    # Disassemble binary & return disassembled instructions in CsInsn objects
    def disasm(self, code, addr, count=0):
        cdef cc.cs_insn *allinsn

        cdef res = cc.cs_disasm(self.csh, code, len(code), addr, count, &allinsn)
        detail = self._cs.detail
        arch = self._cs.arch

        for i from 0 <= i < res:
            if detail:
                dummy = CsInsn(CsDetail(arch, <size_t>allinsn[i].detail))
            else:
                dummy = CsInsn(None)

            dummy._raw = allinsn[i]
            dummy._csh = self.csh
            yield dummy

        cc.cs_free(allinsn, res)


    # Light function to disassemble binary. This is about 20% faster than disasm() because
    # unlike disasm(), disasm_lite() only return tuples of (address, size, mnemonic, op_str),
    # rather than CsInsn objects.
    def disasm_lite(self, code, addr, count=0):
        # TODO: dont need detail, so we might turn off detail, then turn on again when done
        cdef cc.cs_insn *allinsn

        if _diet:
            # Diet engine cannot provide @mnemonic & @op_str
            raise CsError(capstone.CS_ERR_DIET)

        cdef res = cc.cs_disasm(self.csh, code, len(code), addr, count, &allinsn)

        for i from 0 <= i < res:
            insn = allinsn[i]
            yield (insn.address, insn.size, insn.mnemonic, insn.op_str)

        cc.cs_free(allinsn, res)


# print out debugging info
def debug():
    if cc.cs_support(capstone.CS_SUPPORT_DIET):
        diet = "diet"
    else:
        diet = "standard"

    archs = { "arm": capstone.CS_ARCH_ARM, "arm64": capstone.CS_ARCH_ARM64, \
        "mips": capstone.CS_ARCH_MIPS, "ppc": capstone.CS_ARCH_PPC, \
        "sparc": capstone.CS_ARCH_SPARC, "sysz": capstone.CS_ARCH_SYSZ }

    all_archs = ""
    keys = archs.keys()
    keys.sort()
    for k in keys:
        if cc.cs_support(archs[k]):
            all_archs += "-%s" %k

    if cc.cs_support(capstone.CS_ARCH_X86):
        all_archs += "-x86"
        if cc.cs_support(capstone.CS_SUPPORT_X86_REDUCE):
            all_archs += "_reduce"

    (major, minor, _combined) = capstone.cs_version()

    return "Cython-%s%s-c%u.%u-b%u.%u" %(diet, all_archs, major, minor, capstone.CS_API_MAJOR, capstone.CS_API_MINOR)

