from ViewMode import *
from cemu import *
import TextSelection
from TextDecorators import *    

from PyQt5 import QtGui, QtCore, QtWidgets
import PyQt5
from time import time 
import sys
import threading
import string

class BinViewMode(ViewMode):
    def __init__(self, width, height, data, cursor, widget=None, plugin=None):
        super(BinViewMode, self).__init__()

        self.dataModel = data
        self.addHandler(self.dataModel)

        self.width = width
        self.height = height

        self.cursor = cursor
        self.widget = widget

        self.refresh = True

        self.selector = TextSelection.DefaultSelection(self)

        # background brush
        self.backgroundBrush = QtGui.QBrush(QtGui.QColor(0, 0, 128))

        # text font
        #self.font = QtGui.QFont('Terminus (TTF)', 12, QtGui.QFont.Light)
        #self.font.setStyleHint(QtGui.QFont.AnyStyle, QtGui.QFont.PreferBitmap)
        self.font = QtGui.QFont('Terminus', 11, QtGui.QFont.Light)

        # font metrics. assume font is monospaced
        self.font.setKerning(False)
        self.font.setFixedPitch(True)
        fm = QtGui.QFontMetrics(self.font)
        self._fontWidth  = fm.width('a')
        self._fontHeight = fm.height()


        
        self.textPen = QtGui.QPen(QtGui.QColor(192, 192, 192), 0, QtCore.Qt.SolidLine)
        self.resize(width, height)

        self.Paints = {}
        self.newPix = None
        self.Ops = []
        self.plugin = plugin

    @property
    def fontWidth(self):
        return self._fontWidth

    @property
    def fontHeight(self):
        return self._fontHeight

    def startCaching(self):
        # cache
        self.cache()
        #t = threading.Thread(target=self.cache)
        #t.start()

    def isInCache(self, page):
        if page in self.Paints:
            return True

    def cache(self):

        for i in [1,2]:
            #pix = self._getNewPixmap(self.width, self.height)
            if not self.isInCache(self.dataModel.getPageOffset(i)):
                pix = QtGui.QImage(self.width, self.height, QtGui.QImage.Format_ARGB32)
                self.scrollPages(1, cachePix=pix, pageOffset=i)
                self.Paints[self.dataModel.getPageOffset(i)] = pix
                #print 'cache'

    def _getNewPixmap(self, width, height):
        return QtGui.QPixmap(width, height)

    def setTransformationEngine(self, engine):
        self.transformationEngine = engine
        self.original_textdecorator = engine

    def computeTextArea(self):
        self.COLUMNS = self.width//self.fontWidth
        self.ROWS    = self.height//self.fontHeight
        self.notify(self.ROWS, self.COLUMNS)

    def drawAdditionals(self):
        self.newPix = self._getNewPixmap(self.width, self.height + self.SPACER)
        qp = QtGui.QPainter()
        qp.begin(self.newPix)
        qp.drawPixmap(0, 0, self.qpix)

        #self.transformationEngine.decorateText()

        # highlight selected text
        self.selector.highlightText()

        # draw other selections
        self.selector.drawSelections(qp)

        # draw our cursor
        self.drawCursor(qp)
        qp.end()

    def getPageOffset(self):
        return self.dataModel.getOffset()

    def getGeometry(self):
        return self.COLUMNS, self.ROWS

    def getColumnsbyRow(self, row):
        return self.COLUMNS

    def getDataModel(self):
        return self.dataModel

    def startSelection(self):
        self.selector.startSelection()

    def stopSelection(self):
        self.selector.stopSelection()

    def draw(self, refresh=False, row=0, howMany=0):
        if self.dataModel.getOffset() in self.Paints:
            self.refresh = False
            self.qpix = QtGui.QPixmap(self.Paints[self.dataModel.getOffset()])
            #print 'hit'
            self.drawAdditionals()
            return

        if self.refresh or refresh:
            qp = QtGui.QPainter()
            qp.begin(self.qpix)
            #start = time()
            if not howMany:
                howMany = self.ROWS

            self.drawTextMode(qp, row=row, howMany=howMany)
            #end = time() - start
            #print 'Time ' + str(end)
            self.refresh = False
            qp.end()

#        self.Paints[self.dataModel.getOffset()] = QtGui.QPixmap(self.qpix)
        self.drawAdditionals()


    def draw2(self, qp, refresh=False):
        if self.refresh or refresh:
            start = time()
            self.drawTextMode(qp, howMany=self.ROWS)
            end = time() - start
            #print 'Time ' + str(end)
            qp = QtGui.QPainter()
            qp.begin(self.qpix)

        self.drawAdditionals()

    def drawCursor(self, qp):
        cursorX, cursorY = self.cursor.getPosition()
        qp.setBrush(QtGui.QColor(255, 255, 0))

        if self.isInEditMode():
            qp.setBrush(QtGui.QColor(255, 102, 179))

        qp.setOpacity(0.8)
        qp.drawRect(cursorX*self.fontWidth, cursorY*self.fontHeight, self.fontWidth, self.fontHeight + 2)
        qp.setOpacity(1)        


    def scroll_h(self, dx):
        self.qpix.scroll(dx*self.fontWidth, 0, self.qpix.rect())

        qp = QtGui.QPainter()
        
        qp.begin(self.qpix)
        qp.setFont(self.font)
        qp.setPen(self.textPen)

        factor = abs(dx)
        if dx < 0:
            qp.fillRect((self.COLUMNS - 1*factor)*self.fontWidth, 0, factor * self.fontWidth, self.ROWS*self.fontHeight + self.SPACER, self.backgroundBrush)
        if dx > 0:
            qp.fillRect(0, 0, factor * self.fontWidth, self.ROWS*self.fontHeight + self.SPACER, self.backgroundBrush)

        cemu = ConsoleEmulator(qp, self.ROWS, self.COLUMNS)

        page = self.transformationEngine.decorate()
        # scriem pe fiecare coloana in parte
        for column in range(factor):
            # fiecare caracter de pe coloana
            for i in range(self.ROWS):

                if dx < 0:
                    # cu (column) selectam coloana
                    idx = (i+1)*(self.COLUMNS) - (column + 1)
                if dx > 0:
                    idx = (i)*(self.COLUMNS) + (column)

                #c = self.dataModel.getDisplayablePage()[idx]
                c = self.transformationEngine.getChar(idx)
                qp.setPen(self.transformationEngine.choosePen(idx))

                if self.transformationEngine.chooseBrush(idx) != None:
                    qp.setBackgroundMode(1)
                    qp.setBackground(self.transformationEngine.chooseBrush(idx))


#                self.decorate(qp, (idx, c), self.dataModel.getDisplayablePage())
                if dx < 0:
                    cemu.writeAt(self.COLUMNS - (column + 1), i, self.cp437(c))

                if dx > 0:
                    cemu.writeAt(column, i, self.cp437(c))

                qp.setBackgroundMode(0)
        qp.end()


    def scroll_v(self, dy, cachePix=None, pageOffset=None):
        start = time()        

#        if cachePix:
 #           print 'da'

        if not cachePix:
            self.qpix.scroll(0, dy*self.fontHeight, self.qpix.rect())

        qp = QtGui.QPainter()

        if cachePix:
            qp.begin(cachePix)
        else:
            qp.begin(self.qpix)

        #self.font.setStyleHint(QtGui.QFont.AnyStyle, QtGui.QFont.PreferAntialias)
        qp.setFont(self.font)
        qp.setPen(self.textPen)

        # TODO: while the characters are not all the same hight, when scrolling up on y axis, trails remains. We should also erase a Rect with SPACER in hight
        #       same problem when scrolling up.             
        factor = abs(dy)
        if dy < 0:
            qp.fillRect(0, (self.ROWS-factor)*self.fontHeight, self.fontWidth*self.COLUMNS, factor * self.fontHeight + self.SPACER, self.backgroundBrush)

        if dy > 0:
            qp.fillRect(0, 0, self.fontWidth*self.COLUMNS, factor * self.fontHeight, self.backgroundBrush)

        cemu = ConsoleEmulator(qp, self.ROWS, self.COLUMNS)

        #page = self.dataModel.getDisplayablePage()
        page = self.transformationEngine.decorate(pageOffset=pageOffset)


        lastPen = None
        lastBrush = None

        # cate linii desenam
        k = time()
        for row in range(factor):
            # desenam caracterele
            #cemu.writeAt(0, row, str(page[row*self.COLUMNS:row*self.COLUMNS+self.COLUMNS]))
            
            for i in range(self.COLUMNS):

                if dy < 0:
                    idx = (self.ROWS - (row + 1))*self.COLUMNS + i

                if dy > 0:
                    idx = i + (self.COLUMNS*row)

                c = self.transformationEngine.getChar(idx)

                
                nextPen = self.transformationEngine.choosePen(idx)
                if nextPen != lastPen:
                    qp.setPen(nextPen)
                    lastPen = nextPen

                
                qp.setBackgroundMode(0)
                nextBrush = self.transformationEngine.chooseBrush(idx)
                if nextBrush != None:
                    qp.setBackgroundMode(1)

                    if nextBrush != lastBrush:
                        qp.setBackground(nextBrush)
                        lastBrush = nextBrush

                
                if dy < 0:
                    cemu.writeAt_c(i, self.ROWS - 1 - row, self.cp437(c))

                if dy > 0:
                    cemu.writeAt_c(i, row, self.cp437(c))
                
           
        # TODO: text decorator is taking too much! print time() - k
        qp.end()

        end = time() - start
#        print end
#        sys.exit()


    def scroll(self, dx, dy, cachePix=None, pageOffset=None):
        if not cachePix:
            if self.dataModel.getOffset() in self.Paints:
                self.draw()
                return

        if dx != 0:
            if self.dataModel.inLimits((self.dataModel.getOffset() - dx)):
                self.dataModel.slide(-dx)
                self.scroll_h(dx)

        if dy != 0:
            if self.dataModel.inLimits((self.dataModel.getOffset() - dy*self.COLUMNS)):
                self.dataModel.slide(-dy*self.COLUMNS)
                #import time
                #k = time.time()
                self.scroll_v(dy, cachePix, pageOffset)
                #print time.time() - k
            else:
                if dy <= 0:
                    pass
                    #self.dataModel.slideToLastPage()
                else:
                    self.dataModel.slideToFirstPage()

                if not cachePix:
                    self.draw(refresh=True)

        if not cachePix:
            self.draw()


    def scrollPages(self, number, cachePix=None, pageOffset=None):
        self.scroll(0, -number*self.ROWS, cachePix=cachePix, pageOffset=pageOffset)

    def getPixmap(self):
        for t in self.Ops:
            if len(t) == 1:
                t[0]()

            else:
                t[0](*t[1:])

        self.Ops = []
        
        if not self.newPix:
            self.draw()

        return self.newPix

    def resize(self, width, height):
        self.width = width - width%self.fontWidth
        self.height = height - height%self.fontHeight
        self.computeTextArea()
        self.qpix = self._getNewPixmap(self.width, self.height + self.SPACER)
        self.refresh = True

    def drawTextMode(self, qp, row=0, howMany=1):
        # draw background
        qp.fillRect(0, row * self.fontHeight, self.COLUMNS * self.fontWidth,  howMany * self.fontHeight + self.SPACER, self.backgroundBrush)

        # set text pen&font
        qp.setFont(self.font)
        qp.setPen(self.textPen)
        
        cemu = ConsoleEmulator(qp, self.ROWS, self.COLUMNS)

        page = self.transformationEngine.decorate()

        cemu.gotoXY(0, row)

        for i, c in enumerate(page[row*self.COLUMNS:(row + howMany)*self.COLUMNS]):
            x = i + row*self.COLUMNS

            c = self.transformationEngine.getChar(x)
            
            qp.setPen(self.transformationEngine.choosePen(x))

            if self.transformationEngine.chooseBrush(x) != None:
                qp.setBackgroundMode(1)
                qp.setBackground(self.transformationEngine.chooseBrush(x))

            cemu.write(self.cp437(c))
            qp.setBackgroundMode(0)                        

    
    def getCursorAbsolutePosition(self):
        x, y = self.cursor.getPosition()
        return self.dataModel.getOffset() + y*self.COLUMNS + x

    def moveCursor(self, direction):
        cursorX, cursorY = self.cursor.getPosition()

        if direction == Directions.Left:
            if cursorX == 0:
                if cursorY == 0:
                    self.scroll(1, 0)
                else:
                    self.cursor.moveAbsolute(self.COLUMNS-1, cursorY - 1)
            else:
                self.cursor.move(-1, 0)


        if direction == Directions.Right:
            if self.getCursorAbsolutePosition() + 1 >= self.dataModel.getDataSize():
                return

            if cursorX == self.COLUMNS-1:
                if cursorY == self.ROWS-1:
                    self.scroll(-1, 0)
                else:
                    self.cursor.moveAbsolute(0, cursorY + 1)
            else:
                self.cursor.move(1, 0)


        if direction == Directions.Down:
            if self.getCursorAbsolutePosition() + self.COLUMNS >= self.dataModel.getDataSize():
                y, x = self.dataModel.getXYInPage(self.dataModel.getDataSize()-1)
                self.cursor.moveAbsolute(x, y)
                return

            if cursorY == self.ROWS-1:
                self.scroll(0, -1)
            else:
                self.cursor.move(0, 1)

        if direction == Directions.Up:
            if cursorY == 0:
                self.scroll(0, 1)
            else:
                self.cursor.move(0, -1)

        if direction == Directions.End:
            if self.dataModel.getDataSize() < self.getCursorAbsolutePosition() + self.ROWS * self.COLUMNS:
                y, x = self.dataModel.getXYInPage(self.dataModel.getDataSize()-1)
                self.cursor.moveAbsolute(x, y)

            else:
                self.cursor.moveAbsolute(self.COLUMNS-1, self.ROWS-1)

        if direction == Directions.Home:
            self.cursor.moveAbsolute(0, 0)

        if direction == Directions.CtrlHome:
            self.dataModel.slideToFirstPage()
            self.draw(refresh=True)
            self.cursor.moveAbsolute(0, 0)

        if direction == Directions.CtrlEnd:
            self.dataModel.slideToLastPage()
            self.draw(refresh=True)
            self.moveCursor(Directions.End)

    def keyFilter(self):
        return [
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Right),
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Left),
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Up),
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Down),
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_End),
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Home),


                (QtCore.Qt.NoModifier, QtCore.Qt.Key_Right),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_Left),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_Up),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_Down),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_End),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_Home),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_PageDown),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_PageUp)


                ]

    def anon(self, dx, dy):
        self.scroll(dx, dy)

        # scroll modifies datamodel offset, so we must do scroll and cursor
        # operations toghether

        y, x = self.dataModel.getXYInPage(self.dataModel.getDataSize() - 1)
        if self.getCursorAbsolutePosition() >= self.dataModel.getDataSize():
            y, x = self.dataModel.getXYInPage(self.dataModel.getDataSize() - 1)
            self.cursor.moveAbsolute(x, y)

        # we call draw() again because it was called before by scroll()
        # and the cursor is already painted but it's not in correct position
        # kinda hack, don't really like it
        self.draw()

    def handleEditMode(self, modifiers, key, event):
        if key in range(0, 256):
            offs = self.getCursorOffsetInPage()

            self.dataModel.setData_b(self.dataModel.getOffset() + offs, event.text().encode('cp437')[0])

            z = self.dataModel.getOffset() + offs                
            #TODO: sa nu se repete, tre original_transformengine
            self.transformationEngine = RangePen(self.original_textdecorator, z, z + 0, QtGui.QPen(QtGui.QColor(218, 94, 242), 0, QtCore.Qt.SolidLine), ignoreHighlights=True) 

            self.moveCursor(Directions.Right)
        
            x, y = self.cursor.getPosition()

            self.draw(refresh=True, row=y, howMany=1)


    def handleKeyEvent(self, modifiers, key, event=None):
        if event.type() == QtCore.QEvent.KeyRelease:
            if key == QtCore.Qt.Key_Shift:
                self.stopSelection()
                return True

        if event.type() == QtCore.QEvent.KeyPress:
            if modifiers & QtCore.Qt.ShiftModifier:
                keys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Down, QtCore.Qt.Key_Up, QtCore.Qt.Key_End, QtCore.Qt.Key_Home]
                if key in keys:
                    self.startSelection()


            if modifiers & QtCore.Qt.ControlModifier:
                if key == QtCore.Qt.Key_Right:
                    self.addop((self.anon, -1, 0))

                if key == QtCore.Qt.Key_Left:
                    self.addop((self.scroll, 1, 0))

                if key == QtCore.Qt.Key_Down:
                    self.addop((self.anon, 0, -1))

                if key == QtCore.Qt.Key_Up:
                    self.addop((self.scroll, 0, 1))

                if key == QtCore.Qt.Key_End:
                    self.moveCursor(Directions.CtrlEnd)
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_Home:
                    self.moveCursor(Directions.CtrlHome)
                    self.addop((self.draw,))


                return True

            else:#elif modifiers == QtCore.Qt.NoModifier or modifiers == QtCore.Qt.ShiftModifier::

                if key == QtCore.Qt.Key_Escape:
                    self.selector.resetSelections()
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_Left:
                    self.moveCursor(Directions.Left)
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_Right:
                    self.moveCursor(Directions.Right)
                    self.addop((self.draw,))
                    
                if key == QtCore.Qt.Key_Down:
                    self.moveCursor(Directions.Down)
                    self.addop((self.draw,))
                    
                if key == QtCore.Qt.Key_End:
                    self.moveCursor(Directions.End)
                    self.addop((self.draw,))
                    
                if key == QtCore.Qt.Key_Home:
                    self.moveCursor(Directions.Home)
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_Up:
                    self.moveCursor(Directions.Up)
                    self.addop((self.draw,))
                    
                if key == QtCore.Qt.Key_PageDown:
                    self.addop((self.scrollPages, 1))
        
                if key == QtCore.Qt.Key_PageUp:
                    self.addop((self.scrollPages, -1))

                if self.isInEditMode():
                    self.handleEditMode(modifiers, key, event)

                return True

        return False

    def isEditable(self):
        return True

    def setEditMode(self, mode):
        super(BinViewMode, self).setEditMode(mode)

        letters = string.ascii_letters + string.digits + ' .;\':;=\"?-!()/\\_'

        # disable/enable shortcuts
        if mode == False:
            self.transformationEngine = self.original_textdecorator
            self.transformationEngine.reset()
            self.draw(refresh=True)

            for shortcut in self.plugin.getShortcuts():
                if shortcut.key().toString() in list(letters):
                    shortcut.setEnabled(True)


        if mode == True:
            for shortcut in self.plugin.getShortcuts():
                if shortcut.key().toString() in list(letters):
                    shortcut.setEnabled(False)

    """
    def handleKeyPressEvent(self, modifier, key):
        if modifier == QtCore.Qt.ShiftModifier:
            #self.startSelection()
            return True

    def handleKeyReleaseEvent(self, modifier, key):
        if modifier == QtCore.Qt.ShiftModifier:
            self.stopSelection()
            return True
    """
    def addop(self, t):
        self.Ops.append(t)

    def getHeaderInfo(self):
        return 'Binary'