import gdb

from .cmd import SimpleCommand, AutoCompleteMixin
from .struct import enum, cstr, GdbStructMeta, ProgramCounter, TailQueue
from .utils import func_ret_addr, local_var, TextTable
from .ctx import Context


def sigpend(v):
    return '{:08x}'.format(int(v['sp_set']['__bits']) << 1)


def sigmask(v):
    return '{:08x}'.format(int(v['__bits']) << 1)


class Thread(metaclass=GdbStructMeta):
    __ctype__ = 'struct thread'
    __cast__ = {'td_waitpt': ProgramCounter,
                'td_tid': int,
                'td_state': enum,
                'td_prio': int,
                'td_name': cstr,
                'td_flags': str,
                'td_nctxsw': int,
                'td_sigpend': sigpend,
                'td_sigmask': sigmask}

    @staticmethod
    def current():
        return gdb.parse_and_eval('_pcpu_data->curthread')

    @classmethod
    def from_current(cls):
        return cls(Thread.current().dereference())

    @classmethod
    def from_pointer(cls, ptr):
        return cls(gdb.parse_and_eval('(struct thread *)' + ptr).dereference())

    @classmethod
    def list_all(cls):
        return map(cls, TailQueue(gdb.parse_and_eval('all_threads'), 'td_all'))

    @classmethod
    def find_by_name(cls, name):
        return [td for td in cls.list_all() if td.td_name == name]

    @classmethod
    def find_by_tid(cls, tid):
        for td in cls.list_all():
            if td.td_tid == tid:
                return td

    def __repr__(self):
        return 'thread{%s/%s}' % (self.td_name, self.td_tid)


class ThreadSwitchBP(gdb.Breakpoint):
    def __init__(self):
        super().__init__('ctx_switch')

    def stop(self):
        td_from = Thread.from_pointer('$a0')
        td_to = Thread.from_pointer('$a1')
        print('context switch from {} to {}'.format(td_from, td_to))


class ThreadCreateBP(gdb.Breakpoint):
    def __init__(self):
        super().__init__('*0x%x' % func_ret_addr('thread_create'))

    def stop(self):
        print('New', local_var('td').dereference(), 'in the system!')


class Kthread(SimpleCommand, AutoCompleteMixin):
    """dump info about threads

    Thread can be either specified by its identifier (td_tid) or by its name
    (td_name).

    Example:

    Given list of threads in system:
    id   name              state
    0    main              TDS_RUNNING
    1    kernel-thread-1   TDS_READY
    2    user-thread-1     TDS_READY
    3    user-thread-2     TDS_RUNNING

    User might print information about thread 1 by typing:
    $ kthread 1
    or
    $ kthread kernel-thread-1
    If there are more threads with the same name, gdb will print a warning.
    """

    def __init__(self):
        super().__init__('kthread')

    def find_by_name(self, name):
        found = Thread.find_by_name(name)
        if len(found) == 1:
            return found[0]
        if len(found) > 1:
            print('More than one thread with name %s"' % name)
        if len(found) == 0:
            print("Can't find thread with name='%s'!" % name)

    def find_by_tid(self, tid):
        found = Thread.find_by_tid(tid)
        if not found:
            print("Can't find thread with tid=%d!" % tid)
        return found

    def dump_all(self, backtrace=False):
        cur_td = Thread.from_current()
        table = TextTable(types='itttiti', align='rrrrrlr')
        table.header(['Id', 'Name', 'State', 'Flags', 'Priority',
                      'Waiting Point', '# of ctx switches'])
        for td in Thread.list_all():
            marker = '(*) ' if cur_td.td_tid == td.td_tid else ''
            table.add_row(['{}{}'.format(marker, td.td_tid), td.td_name,
                           td.td_state, td.td_flags, td.td_prio, td.td_waitpt,
                           td.td_nctxsw])
        print('(*) current thread marker')
        print(table)

        if backtrace:
            for td in Thread.list_all():
                state = str(td.td_state)
                if state not in ['TDS_INACTIVE', 'TDS_RUNNING', 'TDS_DEAD']:
                    self.dump_backtrace(td)

    def dump_backtrace(self, thread):
        # Don't switch context if this is the current thread.
        not_current = Thread.from_current().td_tid != thread.td_tid
        if not_current:
            ctx = Context.current()
            Context.load(Context.from_kctx(thread.td_kctx))
        print('\n>>> backtrace for %s' % thread)
        gdb.execute('backtrace')
        if not_current:
            Context.load(ctx)

    def dump_one(self, found):
        try:
            thread = self.find_by_tid(int(found))
        except ValueError:
            thread = self.find_by_name(found)
        if not thread:
            return
        print(thread.dump())
        self.dump_backtrace(thread)

    def __call__(self, arg):
        if arg and arg != '*':
            self.dump_one(arg)
        else:
            # give simplified view of all threads in the system
            self.dump_all(arg == '*')

    def options(self):
        threads = Thread.list_all()
        return sum([map(lambda td: td.td_name, threads),
                    map(lambda td: td.td_tid, threads)], [])


class CurrentThread(gdb.Function):
    """Return address of currently running thread."""

    def __init__(self):
        super().__init__('thread')

    def invoke(self):
        return Thread.current()
