'''
A unified context menu builder for all address context clicks.
'''
from PyQt4 import QtGui,QtCore

import envi
import envi.bits as e_bits
import envi.qt.memcanvas as e_q_memcanvas
import vivisect.analysis.generic.codeblocks as vagc

from vqt.common import *
from envi.threads import *
from vivisect.const import *

def cmpoffset(x,y):
    return cmp(x[0], y[0])

@firethread
def printEmuState(vw, fva, va):
    vw.vprint('Running emulator to: 0x%.8x' % (va,))
    emu = vw.getEmulator()

    stack = emu.getStackCounter()
    emu.runFunction(fva, stopva=va, maxhit=2)
    dstack = emu.getStackCounter() - stack

    regs = emu.getRegisters()
    rnames = regs.keys()
    rnames.sort()

    vw.vprint("Showing Register/Magic State At: 0x%.8x" % va)
    vw.vprint('Stack Delta: %d' % dstack)

    # FIXME: this may not be as flexible as it could be, as we don't necessarily *have* to have a location... though we certainly should.
    loc = vw.getLocation(va)
    if loc == None:
        vw.vprint("ARG! can't find location info for 0x%x" % va)
        return

    lva, lsz, ltype, tinfo = loc
    op = vw.parseOpcode(va, arch=tinfo)
    vw.canvas.addVaText("0x%.8x: " % va, va)
    op.render(vw.canvas)
    vw.canvas.addText("\n")
    for i in xrange(len(op.opers)):
        o = op.opers[i]
        o.render(vw.canvas, op, i)
        vw.canvas.addText(" = ")
        oval = o.getOperValue(op, emu)
        taint = emu.getVivTaint(oval)
        base = "0x%.8x (%d)" % (oval,oval)
        if taint != None:
            base += '%s + %d' % ( emu.reprVivTaint(taint), oval - taint[0])

        vw.vprint(base)

def buildContextMenu(vw, va=None, expr=None, menu=None, parent=None, nav=None):
    '''
    Return (optionally construct) a menu to use for handling a context click
    at the given virtual address or envi expression.

    Arguments:
    va      - virtual address of the context click
    expr    - expression to parse instead of va
    menu    - existing menu to add items to
    parent  - Qt parent
    nav     - the "local" EnviNavMixin instance
    '''
    if va == None:
        va = vw.parseExpression(expr)

    if expr == None:
        expr = '0x%.8x' % va

    if menu == None:
        menu = QtGui.QMenu(parent=parent)

    menu.addAction('rename (n)', ACT(vw.getVivGui().setVaName, va))
    menu.addAction('comment (;)', ACT(vw.getVivGui().setVaComment, va))

    refsto = vw.getXrefsTo(va)
    refsfrom = vw.getXrefsFrom(va)

    if refsto:
        rtomenu = menu.addMenu('xrefs to')
        for fromva,tova,xrtype,xrflag in refsto:
            xloc = vw.getLocation(fromva)
            xexpr = '0x%.8x' % fromva
            xrepr = '0x%.8x: %s' % (fromva, vw.reprLocation(xloc))
            xfva = vw.getFunction(fromva)
            if xfva != None:
                xrepr = '%s (%s)' % (xrepr, vw.getName(xfva))
            xmenu = rtomenu.addMenu(xrepr)
            if nav:
                xmenu.addAction('(this window)', ACT(nav.enviNavGoto, xexpr))
            e_q_memcanvas.initMemSendtoMenu(xexpr, xmenu)

    if refsfrom:
        rfrmenu = menu.addMenu('xrefs from')
        for fromva,tova,xrtype,xrflag in refsfrom:
            xloc = vw.getLocation(tova)
            xexpr = '0x%.8x' % tova
            xrepr = '0x%.8x: %s' % (tova, vw.reprLocation(xloc))
            xfva = vw.getFunction(tova)
            if xfva != None:
                xrepr = '%s (%s)' % (xrepr, vw.getName(xfva))
            xmenu = rfrmenu.addMenu(xrepr)
            if nav:
                xmenu.addAction('(this window)', ACT(nav.enviNavGoto, xexpr))
            e_q_memcanvas.initMemSendtoMenu(xexpr, xmenu)

    fva = vw.getFunction(va)
    if fva != None:
        funcmenu = menu.addMenu('function')
        funcname = vw.getName(fva)
        if nav:
            funcmenu.addAction(funcname[:80], ACT(nav.enviNavGoto, funcname))

        rtype,rname,cconv,cname,cargs = vw.getFunctionApi(fva)
        if cargs:
            argmenu = funcmenu.addMenu('args')
            for i,(atype,aname) in enumerate(cargs):
                act = ACT(vw.getVivGui().setFuncArgName, fva, i, atype, aname)
                argmenu.addAction(aname, act)

        funclocals = vw.getFunctionLocals(fva)
        if funclocals:
            funclocals.sort()
            funclocals.reverse()

            locmenu = funcmenu.addMenu('locals')

            for _,spdelta,ltype,linfo in funclocals:
                if spdelta > 0: # FIXME perhaps make this flexable based on cconv?
                    continue

                # Make the workspace do the resolving for us
                typename, varname = vw.getFunctionLocal(fva,spdelta)
                act = ACT(vw.getVivGui().setFuncLocalName, fva, spdelta, typename, varname)
                locmenu.addAction(varname, act)

        if fva != va:
            funcmenu.addAction('show emulator state', ACT(printEmuState, vw, fva, va) )

        funcmenu.addAction('call graph', ACT(vw.getVivGui().showFuncCallGraph, fva))
        funcmenu.addAction('re-analyze codeblocks', ACT(vagc.analyzeFunction, vw, fva))

    loc = vw.getLocation(va)
    if loc == None:
        makemenu = menu.addMenu('make')
        makemenu.addAction('code (c)',      ACT(vw.makeCode,     va))
        makemenu.addAction('function (f)',  ACT(vw.makeFunction, va))
        makemenu.addAction('string (s)',    ACT(vw.makeString,   va))
        makemenu.addAction('pointer (p)',   ACT(vw.makePointer,  va))
        makemenu.addAction('unicode (u)',   ACT(vw.makeUnicode,  va))
        makemenu.addAction('structure (S)', ACT(vw.getVivGui().makeStruct, va))

        nummenu = makemenu.addMenu('number')
        for size in (1,2,4,8):
            nummenu.addAction("%d-bit (%d bytes)" % (size<<3, size), ACT(vw.makeNumber, va, size=size))

        archmenu = makemenu.addMenu('code (archs)')
        prevumenu = menu.addMenu('preview instruction')

        archs = [ (archname,archid) for (archid,archname) in envi.arch_names.items() ]
        archs.sort()
        for archname,archid in archs:
            if archname == 'default':
                continue
            archmenu.addAction(archname, ACT(vw.makeCode, va, arch=archid))
            prevumenu.addAction(archname, ACT(vw.previewCode, va, arch=archid))

    else:

        if loc[L_LTYPE] == LOC_OP:

            op = vw.parseOpcode(va, arch=loc[L_TINFO])
            for idx,oper in enumerate(op.opers):
                # Give the option to switch ('hint') that you want
                # the immediate operand displayed differently...
                if oper.isImmed():
                    val = oper.getOperValue(op)
                    hval = e_bits.hex(val)

                    cval = val
                    r = []
                    while cval:
                        r.append(chr(cval & 0xff))
                        cval = cval >> 8
                    cstr = repr(''.join(r))

                    immmenu = menu.addMenu('immediate')
                    # FIXME struct offsets?
                    immmenu.addAction('decimal (%d)' % val, ACT(vw.setSymHint, va, idx, str(val)))
                    immmenu.addAction('hex (%s)' % hval,    ACT(vw.setSymHint, va, idx, hval))
                    immmenu.addAction('chars (%s)' % cstr,  ACT(vw.setSymHint, va, idx, cstr))

                    names = vw.vsconsts.revLookup(val)
                    if names != None:
                        for name in names:
                            immmenu.addAction(name, ACT(vw.setSymHint, va, idx, name))
            menu.addAction('make code xref->', ACT(vw.getVivGui().addVaXref, va))

        menu.addAction('bookmark (B)',   ACT(vw.getVivGui().addBookmark, va))
        menu.addAction('undefine (U)',   ACT(vw.delLocation, va))

    e_q_memcanvas.initMemSendtoMenu(expr, menu)
    return menu


