import re
import traceback
from collections import deque

from PyQt4 import QtCore, QtGui

import envi
import envi.memcanvas as e_memcanvas
import envi.qt.memcanvas as e_memcanvas_qt
import envi.memcanvas.renderers as e_render

import vqt.main as vq_main
import vqt.tree as vq_tree
import vqt.colors as vq_colors
import vqt.hotkeys as vq_hotkey
import vqt.saveable as vq_save
import vqt.menubuilder as vqt_menu

from vqt.main import *
from vqt.common import *

class EnviNavMixin:
    '''
    Classes may inerhit from this mixin to help out with envi
    nav events.

    Implement enviNavGoto() which will only be called when your
    envi nav name (setEnviNavName()) matches the nav event.
    '''

    def __init__(self):
        self._envi_navname = 'mem'
        vqtconnect(self.enviNavGetnames, 'envi:nav:getnames')
        vqtconnect(self.enviNavExpr, 'envi:nav:expr')
        self.setAcceptDrops(True)

    def enviNavFini(self):
        '''
        Called when it's time to explicitly cleanup our event
        subscriptions.

        NOTE: this should be called by closeEvent handlers.
        '''
        vqtdisconnect(self.enviNavGetnames, 'envi:nav:getnames')
        vqtdisconnect(self.enviNavExpr, 'envi:nav:expr')

    def enviNavGoto(self, expr, sizeexpr=None, rend=None):
        pass

    def enviNavExpr(self, event, einfo):
        name,expr,sizeexpr = einfo
        if self._envi_navname == name:
            self.enviNavGoto(expr,sizeexpr=sizeexpr)

    def enviNavGetnames(self, event, einfo):
        einfo.append( self._envi_navname )

    def setEnviNavName(self, name):
        self._envi_navname = name

    def getEnviNavName(self):
        return self._envi_navname

    def dragEnterEvent(self, e):
        mdata = e.mimeData()
        if mdata.hasFormat('envi/expression'):
            e.accept()
            return
        e.ignore()

    def dropEvent(self, e):
        mdata = e.mimeData()
        if mdata.hasFormat('envi/expression'):
            expr = str(mdata.data('envi/expression').data())
            e.setDropAction(QtCore.Qt.CopyAction)
            e.accept()
            self.enviNavGoto(expr)
            return
        e.ignore()

class EnviNavModel(vq_tree.VQTreeModel):

    dragable = True

    def __init__(self, navcol, parent=None, columns=None):
        vq_tree.VQTreeModel.__init__(self, parent=parent, columns=columns)
        self.navcol = navcol

    def mimeData(self, idx):
        pnode = idx[0].internalPointer()
        expr = pnode.rowdata[self.navcol]
        mdata = QtCore.QMimeData()
        mdata.setData('envi/expression',expr)
        return mdata

class VQMemoryWindow(vq_hotkey.HotKeyMixin, EnviNavMixin, vq_save.SaveableWidget, QtGui.QWidget):

    def __init__(self, memobj, syms=None, parent=None, mwname='mem'):

        QtGui.QWidget.__init__(self, parent=parent)
        vq_hotkey.HotKeyMixin.__init__(self)
        vq_save.SaveableWidget.__init__(self)
        EnviNavMixin.__init__(self)
        self.setEnviNavName(mwname)

        self._mem_obj = memobj
        self.mwname = mwname
        self.mwlocked = False

        self.top_box = QtGui.QWidget(parent=self)
        hbox = QtGui.QHBoxLayout(self.top_box)
        hbox.setMargin(2)
        hbox.setSpacing(4)

        self.histmenu = QtGui.QMenu(parent=self)
        self.histmenu.aboutToShow.connect( self.setupMemHistMenu )

        self.hist_button = QtGui.QPushButton('History', parent=self.top_box)
        self.hist_button.setMenu(self.histmenu)

        self.addr_entry  = QtGui.QLineEdit(parent=self.top_box)
        self.size_entry  = QtGui.QLineEdit(parent=self.top_box)
        self.size_entry.setText('256')
        self.rend_select = QtGui.QComboBox(parent=self.top_box)

        self.rend_tools = QtGui.QPushButton('Opts', parent=self.top_box)
        self.rend_tools.setMenu( self.getRendToolsMenu() )

        self.mem_history = deque()
        self.mem_canvas = self.initMemoryCanvas(memobj, syms=syms)
        self.mem_canvas.setNavCallback(self.enviNavGoto)

        self.addHotKey('esc', 'mem:histback')
        self.addHotKeyTarget('mem:histback', self._hotkey_histback)

        self.loadDefaultRenderers()
        self.loadRendSelect()

        self.addr_entry.returnPressed.connect(self._renderMemory)
        self.size_entry.returnPressed.connect(self._renderMemory)

        self.connect(self.rend_select, QtCore.SIGNAL('currentIndexChanged(QString)'), self._renderMemory)

        hbox.addWidget(self.hist_button)
        hbox.addWidget(self.addr_entry)
        hbox.addWidget(self.size_entry)
        hbox.addWidget(self.rend_select)
        hbox.addWidget(self.rend_tools)

        vbox = QtGui.QVBoxLayout(self)
        vbox.setMargin(4)
        vbox.setSpacing(4)
        vbox.addWidget(self.top_box)
        vbox.addWidget(self.mem_canvas, stretch=100)

        self.top_box.setLayout(hbox)

        self.setLayout(vbox)
        self.setWindowTitle('%s: None' % self.mwname)

    def closeEvent(self, event):
        self.enviNavFini()

    def initMemoryCanvas(self, memobj, syms=None):
        return e_memcanvas_qt.VQMemoryCanvas(memobj, syms=syms, parent=self)

    def setMemWindowName(self, mwname):
        '''
        Set the memory window name/title prefix to the given string.
        '''
        self.mwname = mwname
        self.setEnviNavName(mwname)
        self.updateMemWindowTitle()

    def getExprTitle(self):
        return str(self.addr_entry.text())

    def updateMemWindowTitle(self):
        expr = self.getExprTitle()

        title = '%s: %s' % (self.mwname, expr)
        if self.mwlocked:
            title += ' (locked)'

        self.setWindowTitle(title)

    def getRendToolsMenu(self):
        menu = QtGui.QMenu(parent=self.rend_tools)
        menu.addAction('set name', self.rendToolsSetName)

        lockact = QtGui.QAction('locked', menu, checkable=True)
        lockact.setChecked(self.mwlocked)

        def lockToggle():
            self.mwlocked = not self.mwlocked
            self.updateMemWindowTitle()

        lockact.toggled.connect(lockToggle)
        menu.addAction(lockact)

        return menu

    def rendToolsSetName(self):
        mwname, ok = QtGui.QInputDialog.getText(self, 'Set Mem Window Name', 'Name')
        if ok:
            self.setMemWindowName(str(mwname))

    def rendToolsMenu(self, event):
        menu = self.getRendToolsMenu()
        menu.exec_(self.mapToGlobal(self.rend_tools.pos()))

    #def setRendererByName(self, rname):
        # FIXME implement...

    def _hotkey_histback(self):
        if len(self.mem_history) >= 2:
            hinfo = self.mem_history.popleft()
            hinfo = self.mem_history.popleft()
            self._histSelected( hinfo )

    def _histSelected(self, hinfo):
        addrexpr, sizeexpr, rendname = hinfo
        self.addr_entry.setText(addrexpr)
        self.size_entry.setText(sizeexpr)
        self.mem_canvas.setRenderer(rendname)
        self._renderMemory()

    def setupMemHistMenu(self):
        self.histmenu.clear()

        for hinfo in self.mem_history:
            addrexpr, sizeexpr, rendname = hinfo
            addr = self._mem_obj.parseExpression(addrexpr)
            menustr = '0x%.8x' % addr
            sym = self._mem_obj.getSymByAddr(addr)
            if sym != None:
                menustr += ' - %s' % repr(sym)

            self.histmenu.addAction(menustr, ACT(self._histSelected, hinfo))

    @idlethread
    def enviNavGoto(self, expr, sizeexpr='256', rend=''):

        if self.mwlocked:
            return

        # Used by nav event generators to make us render
        self.addr_entry.setText(expr)
        if sizeexpr != None:
            self.size_entry.setText(sizeexpr)

        if rend != None:
            idx = self.rend_select.findText(str(rend))
            if idx >= 0:
                self.rend_select.setCurrentIndex(idx)

        self._renderMemory()

    def loadRendSelect(self):
        self.rend_select.clear()
        for name in self.mem_canvas.getRendererNames():
            self.rend_select.addItem(name)

    def loadDefaultRenderers(self):
        self.mem_canvas.addRenderer("bytes",    e_render.ByteRend())
        self.mem_canvas.addRenderer("u_int_16", e_render.ShortRend())
        self.mem_canvas.addRenderer("u_int_32", e_render.LongRend())
        self.mem_canvas.addRenderer("u_int_64", e_render.QuadRend())

    def _getRenderVaSize(self):

        expr = str(self.addr_entry.text())
        sizeexpr = str(self.size_entry.text())

        if not expr:
            return None, None

        if not sizeexpr:
            return None, None

        try:
            addr = self._mem_obj.parseExpression(expr)
        except Exception, e:
            #import traceback;traceback.print_exc()
            self.mem_canvas.addText('Invalid Address: %s (%s)' % (expr, e))
            return None, None

        try:
            size = self._mem_obj.parseExpression(sizeexpr)
        except Exception, e:
            self.mem_canvas.addText('Invalid Size: %s (%s)' % (expr, e))
            return None, None

        self.updateMemWindowTitle()
        return addr, size

    @idlethread
    def _renderMemory(self, *args, **kwargs):

        self.clearText()

        addr, size = self._getRenderVaSize()
        if addr == None:
            return

        expr = str(self.addr_entry.text())
        rname = str(self.rend_select.currentText())
        sizeexpr = str(self.size_entry.text())

        mhist = (expr, sizeexpr, rname)
        if mhist not in self.mem_history:
            self.mem_history.appendleft( mhist )
            while len(self.mem_history) > 100:
                self.mem_history.pop()

        self.mem_canvas.setRenderer(rname)
        try:
            self.mem_canvas.renderMemory(addr, size)
        except Exception, e:
            self.mem_canvas.addText('Render Exception: %s (%s)' % (hex(addr), e))

    def clearText(self):
        self.mem_canvas.clearCanvas()

    def vqGetSaveState(self):
        state = {}
        state['addr_entry'] = str(self.addr_entry.text())
        state['rend_select'] = str(self.rend_select.currentText())
        state['size_entry'] = str(self.size_entry.text())
        state['name'] = self.mwname
        return state

    def vqSetSaveState(self, state):
        
        self.addr_entry.setText(state.get('addr_entry',''))
        self.size_entry.setText(state.get('size_entry',''))
        self.setMemWindowName( str(state.get('name','mem')) )

        rendname = state.get('rend_select')
        if rendname:
            index = self.rend_select.findText(rendname)
            self.rend_select.setCurrentIndex(index)

