from __future__ import print_function

import logging
import threading
import re
import struct
from six.moves.queue import Queue

from voltron.api import *
from voltron.plugin import *
from voltron.dbg import *

try:
    import gdb
    HAVE_GDB = True
except ImportError:
    HAVE_GDB = False

log = logging.getLogger('debugger')

if HAVE_GDB:

    def post_event(func):
        """
        Decorator to wrap a GDB adaptor method in a mechanism to run the method
        on the main thread at the next possible time.
        """
        def inner(self, *args, **kwargs):
            if self.use_post_event:
                # create ephemeral queue
                q = Queue()

                # create an invocation that calls the decorated function
                class Invocation(object):
                    def __call__(killme):
                        # when the invocation is called, we call the function and stick the result into the queue
                        try:
                            res = func(self, *args, **kwargs)
                        except Exception as e:
                            # if we got an exception, just queue that instead
                            res = e
                        q.put(res)

                # post this invocation to be called on the main thread at the next opportunity
                gdb.post_event(Invocation())

                # now we wait until there's something in the queue, which indicates that the invocation has run and return
                # the result that was pushed onto the queue by the invocation
                res = q.get()

                # if we got an exception back from the posted event, raise it
                if isinstance(res, Exception):
                    raise res

                return res
            else:
                return func(self, *args, **kwargs)

        return inner

    class GDBAdaptor(DebuggerAdaptor):
        archs = {
            'i386': 'x86', 'i386:intel': 'x86', 'i386:x64-32': 'x86', 'i386:x64-32:intel': 'x86', 'i8086': 'x86',
            'i386:x86-64': 'x86_64', 'i386:x86-64:intel': 'x86_64',
            'arm': 'arm', 'armv2': 'arm', 'armv2a': 'arm', 'armv3': 'arm', 'armv3m': 'arm', 'armv4': 'arm',
            'armv4t': 'arm', 'armv5': 'arm', 'armv5t': 'arm', 'armv5te': 'arm', 'armv7': 'arm', 'armv7s': 'arm',
            'powerpc:common': 'powerpc'
        }
        sizes = {
            'x86': 4,
            'x86_64': 8,
            'arm': 4,
            'powerpc': 4,
        }
        max_frame = 64
        max_string = 128
        use_post_event = True

        """
        The interface with an instance of GDB
        """
        def __init__(self, *args, **kwargs):
            self.listeners = []
            self.host_lock = threading.RLock()
            self.host = gdb
            self.busy = False

        def target_is_busy(self, target_id=0):
            """
            Returns True or False indicating if the inferior is busy.

            The busy flag is set by the stop and continue handlers registered
            in the debugger command class.
            """
            return self.busy

        @post_event
        def version(self):
            """
            Get the debugger's version.

            Returns a string containing the debugger's version
            (e.g. 'GNU gdb (GDB) 7.8')
            """
            output = gdb.execute('show version', to_string=True)
            try:
                version = output.split('\n')[0]
            except:
                version = None
            return version

        def _target(self, target_id=0):
            """
            Return information about the specified target.

            Returns data in the following structure:
            {
                "id":       0,         # ID that can be used in other funcs
                "file":     "/bin/ls", # target's binary file
                "arch":     "x86_64",  # target's architecture
                "state:     "stopped"  # state
            }
            """
            # get target
            target = gdb.selected_inferior()

            # get target properties
            d = {}
            d["id"] = 0
            d["num"] = target.num

            # get target state
            d["state"] = self._state()

            # get inferior file (doesn't seem to be available through the API)
            lines = list(filter(lambda x: x != '', gdb.execute('info inferiors', to_string=True).split('\n')))
            if len(lines) > 1:
                info = list(filter(lambda x: '*' in x[0], map(lambda x: x.split(), lines[1:])))
                d["file"] = info[0][-1]
            else:
                log.debug("No inferiors in `info inferiors`")
                raise NoSuchTargetException()

            # get arch
            d["arch"] = self.get_arch()
            d['byte_order'] = self.get_byte_order()
            d['addr_size'] = self.get_addr_size()

            return d

        @post_event
        def target(self, target_id=0):
            """
            Return information about the current inferior.

            GDB only supports querying the currently selected inferior, rather
            than an arbitrary target like LLDB, because the API kinda sucks.

            `target_id` is ignored.
            """
            return self._target()

        @post_event
        def targets(self, target_ids=None):
            """
            Return information about the debugger's current targets.

            `target_ids` is ignored. Only the current target is returned. This
            method is only implemented to maintain API compatibility with the
            LLDBAdaptor.
            """
            return [self._target()]

        @validate_target
        @post_event
        def state(self, target_id=0):
            """
            Get the state of a given target.
            """
            return self._state()

        @validate_busy
        @validate_target
        @post_event
        def registers(self, target_id=0, thread_id=None, registers=[]):
            """
            Get the register values for a given target/thread.
            """
            arch = self.get_arch()
            log.debug("xxx")

            # if we got 'sp' or 'pc' in registers, change it to whatever the right name is for the current arch
            if arch in self.reg_names:
                if 'pc' in registers:
                    registers.remove('pc')
                    registers.append(self.reg_names[arch]['pc'])
                if 'sp' in registers:
                    registers.remove('sp')
                    registers.append(self.reg_names[arch]['sp'])
            else:
                raise Exception("Unsupported architecture: {}".format(target['arch']))

            # get registers
            if registers != []:
                regs = {}
                for reg in registers:
                    regs[reg] = self.get_register(reg)
            else:
                log.debug('Getting registers for arch {}'.format(arch))
                if arch == "x86_64":
                    regs = self.get_registers_x86_64()
                elif arch == "x86":
                    regs = self.get_registers_x86()
                elif arch == "arm":
                    regs = self.get_registers_arm()
                elif arch == "powerpc":
                    regs = self.get_registers_powerpc()
                else:
                    raise UnknownArchitectureException()

            return regs

        @validate_busy
        @validate_target
        @post_event
        def stack_pointer(self, target_id=0, thread_id=None):
            """
            Get the value of the stack pointer register.
            """
            arch = self.get_arch()
            if arch in self.reg_names:
                sp_name = self.reg_names[arch]['sp']
                sp = self.get_register(sp_name)
            else:
                raise UnknownArchitectureException()

            return sp_name, sp

        @validate_busy
        @validate_target
        @post_event
        def program_counter(self, target_id=0, thread_id=None):
            """
            Get the value of the program counter register.
            """
            return self._program_counter(target_id, thread_id)

        def _program_counter(self, target_id=0, thread_id=None):
            """
            Implementation of getting PC to avoid recursive decorators
            """
            arch = self.get_arch()
            if arch in self.reg_names:
                pc_name = self.reg_names[arch]['pc']
                pc = self.get_register(pc_name)
            else:
                raise UnknownArchitectureException()

            return pc_name, pc

        @validate_busy
        @validate_target
        @post_event
        def memory(self, address, length, target_id=0):
            """
            Read memory from the inferior.

            `address` is the address at which to start reading
            `length` is the number of bytes to read
            """
            # read memory
            log.debug('Reading 0x{:x} bytes of memory at 0x{:x}'.format(length, address))
            memory = bytes(gdb.selected_inferior().read_memory(address, length))
            return memory

        @validate_busy
        @validate_target
        @post_event
        def write_memory(self, address, data, target_id=0):