# -*- python -*-
#
#       OpenAlea.Visualea: OpenAlea graphical user interface
#
#       Copyright 2006 INRIA - CIRAD - INRA  
#
#       File author(s): Samuel Dufour-Kowalski <samuel.dufour@sophia.inria.fr>
#                       Christophe Pradal <christophe.prada@cirad.fr>
#
#       Distributed under the CeCILL v2 License.
#       See accompanying file LICENSE.txt or copy at
#           http://www.cecill.info/licences/Licence_CeCILL_V2-en.html
# 
#       OpenAlea WebSite : http://openalea.gforge.inria.fr
#
# Fri Mar 15 15:10:58 IST 2013: This file was obtained from:
# http://obswww.unige.ch/~revaz/git/old.glups-4.0/glups/scishell.py -
# Subhasis Ray

__doc__="""
This module implements a QT4 python interpreter widget.
It is inspired bu PyCute : http://gerard.vermeulen.free.fr
"""

__license__= "CeCILL V2"
__revision__=" $Id: scishell.py 579 2007-06-13 10:27:57Z dufourko $"


import os, sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import Qt

from PyQt4.Qsci import QsciScintilla, QsciLexerPython, QsciAPIs

class History:
  """ Backup and restore history between sessions 
      Class added to initial files. Only 3 lines modified to original files.
      Look for "Added by vb" comments
      Beware: The backup file will grow without limit. You must clean it manually.
  """
  def __init__(self, file):
    if os.path.isfile(file): # Le fichier existe
      self.f = open(file, 'r+')
      self.history = self.f.readlines()
    else: # Le fichier existe pas
      self.f = open(file, 'w')
      self.history = []
      
  def load(self, trunk = None):
      r = [str[:-1] for str in self.history]
      if not trunk:
        return r
      else: 
        return r[-trunk:]
  
  def append(self, word):
    self.history.append(word)
    self.f.write(word + '\n')
    self.f.flush()
      
      

class MultipleRedirection:
    """ Dummy file which redirects stream to multiple file """

    def __init__(self, files):
        """ The stream is redirect to the file list 'files' """

        self.files = files

    def write(self, str):
        """ Emulate write function """

        for f in self.files:
            f.write(str)

class SciShell(QsciScintilla):
    """
    SciShell is a Python shell based in QScintilla.
    """
    
    def __init__(self, interpreter, message="", log='', parent=None):
        """Constructor.
        @param interpreter : InteractiveInterpreter in which
        the code will be executed

        @param message : welcome message string
        
        @param  'parent' : specifies the parent widget.
        If no parent widget has been specified, it is possible to
        exit the interpreter by Ctrl-D.
        """

        QsciScintilla.__init__(self, parent)
        self.interpreter = interpreter

        # user interface setup
        self.setAutoIndent(True)
        self.setAutoCompletionThreshold(4)
        self.setAutoCompletionSource(QsciScintilla.AcsDocument)
        # Lexer
        self.setLexer(QsciLexerPython(self))

        # Search
        self.incrementalSearchString = ""
        self.incrementalSearchActive = False
        self.inRawMode = False
        self.echoInput = True
            
        # Initialize history
        self.historyLists = {}
        self.maxHistoryEntries = 1000
        self.H = History("%s/.ghist"%os.environ['HOME']) # Added by vb
        self.history = self.H.load(self.maxHistoryEntries) # Added by vb
#        self.history = []
        self.histidx = -1
        
        # # capture all interactive input/output 
        sys.stdout   = self
        sys.stderr   = MultipleRedirection((sys.stderr, self))
        sys.stdin    = self

        self.reading = 0
        # interpreter prompt.
        try:
            sys.ps1
        except AttributeError:
            sys.ps1 = ">>> "
        try:
            sys.ps2
        except AttributeError:
            sys.ps2 = "... "

        #self.completionText = ""
        # Excecution Status
        self.more = False
        # Multi line execution Buffer
        self.execlines = []

        # interpreter banner
        self.write('The shell running Python %s on %s.\n' %
                   (sys.version, sys.platform))
        self.write('Type "copyright", "credits" or "license"'
                   ' for more information on Python.\n')
        self.write(message+'\n\n')
        self.write(sys.ps1)


        #self.standardCommands().clearKeys()
        self.keymap = {
            Qt.Key_Backspace : self.__QScintillaDeleteBack,
            Qt.Key_Delete : self.__QScintillaDelete,
            Qt.Key_Return : self.__QScintillaNewline,
            Qt.Key_Enter : self.__QScintillaNewline,
            Qt.Key_Tab : self.__QScintillaTab,
            Qt.Key_Left : self.__QScintillaCharLeft,
            Qt.Key_Right : self.__QScintillaCharRight,
            Qt.Key_Up : self.__QScintillaLineUp,
            Qt.Key_Down : self.__QScintillaLineDown,
            Qt.Key_Home : self.__QScintillaVCHome,
            Qt.Key_End : self.__QScintillaLineEnd,
            }

        self.connect(self, QtCore.SIGNAL('userListActivated(int, const QString)'),
                     self.__completionListSelected)

    def get_interpreter(self):
        """ Return the interpreter object """

        return self.interpreter
        

    def flush(self):
        """
        Simulate stdin, stdout, and stderr.
        """
        pass


    def isatty(self):
        """
        Simulate stdin, stdout, and stderr.
        """
        return 1
    

    def readline(self):
        """
        Simulate stdin, stdout, and stderr.
        """
        self.reading = 1
        line, col = self.__getEndPos()
        self.setCursorPosition(line, col)

        buf = ""
        
        if len(buf) == 0:
            return '\n'
        else:
            return buf


    def write(self, s):
        """
        Simulate stdin, stdout, and stderr.
        """

        line, col = self.__getEndPos()
        self.setCursorPosition(line, col)
        self.insert(s)

        line, col = self.__getEndPos()
        self.setCursorPosition(line, col)

        self.prline, self.prcol = self.getCursorPosition()

        self.ensureCursorVisible()
        self.ensureLineVisible(line)

        
    ###########################################

    def __getEndPos(self):
        """
        Private method to return the line and column of the last character.
        
        @return tuple of two values (int, int) giving the line and column
        """
        line = self.lines() - 1
        return (line, self.lineLength(line))

    
    def paste(self):
        """
        Reimplemented slot to handle the paste action.
        """
        lines = unicode(QApplication.clipboard().text())
        self.__executeLines(lines)
        
        
    def __middleMouseButton(self):
        """
        Private method to handle the middle mouse button press.
        """
        lines = unicode(QApplication.clipboard().text(QClipboard.Selection))
        self.__executeLines(lines)

        
    def __executeLines(self, lines):
        """
        Private method to execute a set of lines as multiple commands.
        @param lines multiple lines of text to be executed as single
            commands (string)
        """
        for line in lines.splitlines(True):
            if line.endswith("\r\n"):
                fullline = True
                cmd = line[:-2]
            elif line.endswith("\r") or line.endswith("\n"):
                fullline = True
                cmd = line[:-1]
            else:
                fullline = False
            
            self.__insertTextAtEnd(line)
            if fullline:
                self.__executeCommand(cmd)

                
    def __executeCommand(self, cmd):
        """
        Private slot to execute a command.
        
        @param cmd command to be executed by debug client (string)
        """

        if not cmd:  cmd = ''
        else:
            if len(self.history) == self.maxHistoryEntries:
                del self.history[0]
            self.history.append(QtCore.QString(cmd))
            self.H.append(QtCore.QString(cmd)) # Added by vb
            self.histidx = -1

        # Execute command
        self.execlines.append(str(cmd))
        source = '\n'.join(self.execlines)
        self.more = self.interpreter.runsource(source)

        if self.more:
            self.write(sys.ps2)
        else:
            self.write(sys.ps1)
            self.execlines = []
        
    
    def __insertText(self, s):
        """
        Insert text at the current cursor position.
        """

        line, col = self.getCursorPosition()
        self.insertAt(s, line, col)
        self.setCursorPosition(line, col + len(str(s)))


    def __insertTextAtEnd(self, s):
        """
        Private method to insert some text at the end of the command line.
        @param s text to be inserted (string or QString)
        """
        line, col = self.__getEndPos()
        self.setCursorPosition(line, col)
        self.insert(s)
        self.prline, self.prcol = self.__getEndPos()
        self.setCursorPosition(self.prline, self.prcol)

        
    def __isCursorOnLastLine(self):
        """
        Private method to check, if the cursor is on the last line.
        """
        cline, ccol = self.getCursorPosition()
        return cline == self.lines() - 1    


    def keyPressEvent(self, ev):
        """
        Re-implemented to handle the user input a key at a time.
        
        @param ev key event (QKeyEvent)
        """
        txt = ev.text()
        key = ev.key()
        
        ctrl = ev.modifiers() & Qt.ControlModifier

        if(ctrl):
            QsciScintilla.keyPressEvent(self, ev)

        elif(self.keymap.has_key(key)):
            self.keymap[key]()

        # See it is text to insert.
        elif self.__isCursorOnLastLine() and txt.length() :

            QsciScintilla.keyPressEvent(self, ev)
            self.incrementalSearchActive = True
            
            if(txt == '.'):
                self.__showDynCompletion()

        else:
            ev.ignore()


    def __QScintillaTab(self):
        """
        Private method to handle the Tab key.
        """
        if self.isListActive():
            self.SendScintilla(QsciScintilla.SCI_TAB)
        elif self.__isCursorOnLastLine():
            line, index = self.getCursorPosition()
            buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")
            if self.more and not buf[:index-len(sys.ps2)].strip():
                self.SendScintilla(QsciScintilla.SCI_TAB)
             
        
    def __QScintillaDeleteBack(self):
        """
        Private method to handle the Backspace key.
        """
        if self.__isCursorOnLastLine():
            line, col = self.getCursorPosition()
            ac = self.isListActive()
            oldLength = self.text(line).length()
            
            if self.text(line).startsWith(sys.ps1):
                if col > len(sys.ps1):
                    self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
                    
            elif self.text(line).startsWith(sys.ps2):
                if col > len(sys.ps2):
                    self.SendScintilla(QsciScintilla.SCI_DELETEBACK)

            elif col > 0:
                self.SendScintilla(QsciScintilla.SCI_DELETEBACK)

        
    def __QScintillaDelete(self):
        """
        Private method to handle the delete command.
        """
        if self.__isCursorOnLastLine():
            if self.hasSelectedText():
                lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
                if self.text(lineFrom).startsWith(sys.ps1):
                    if indexFrom >= len(sys.ps1):
                        self.SendScintilla(QsciScintilla.SCI_CLEAR)

                elif self.text(lineFrom).startsWith(sys.ps2):
                    if indexFrom >= len(sys.ps2):
                        self.SendScintilla(QsciScintilla.SCI_CLEAR)
                        
                elif indexFrom >= 0:
                    self.SendScintilla(QsciScintilla.SCI_CLEAR)
                    
                self.setSelection(lineTo, indexTo, lineTo, indexTo)
            else:
                self.SendScintilla(QsciScintilla.SCI_CLEAR)

        
        
    def __QScintillaNewline(self):
        """
        Private method to handle the Return key.
        """
        if self.__isCursorOnLastLine():
            if self.isListActive():
                self.SendScintilla(QsciScintilla.SCI_NEWLINE)
            elif self.reading:
                self.reading = 0
       
            else:
                self.incrementalSearchString = ""
                self.incrementalSearchActive = False
                line, col = self.__getEndPos()
                self.setCursorPosition(line,col)
                buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")
                self.insert('\n')
                self.__executeCommand(buf)

        # add and run selection
        else:
            s= self.selectedText()
            self.__insertTextAtEnd(s)

        
    def __QScintillaCharLeft(self, allLinesAllowed = False):
        """
        Private method to handle the Cursor Left command.
        """
        
        if self.__isCursorOnLastLine() or allLinesAllowed:
            line, col = self.getCursorPosition()
            if self.text(line).startsWith(sys.ps1):
                if col > len(sys.ps1):
                    self.SendScintilla(QsciScintilla.SCI_CHARLEFT)
                    
            elif self.text(line).startsWith(sys.ps2):
                if col > len(sys.ps2):
                    
                    self.SendScintilla(QsciScintilla.SCI_CHARLEFT)
            elif col > 0:
                
                self.SendScintilla(QsciScintilla.SCI_CHARLEFT)

        
    def __QScintillaCharRight(self):
        """
        Private method to handle the Cursor Right command.
        """
        if self.__isCursorOnLastLine():
            self.SendScintilla(QsciScintilla.SCI_CHARRIGHT)


    def __QScintillaVCHome(self):
        """
        Private method to handle the Home key.
        """
        if self.isListActive():
            self.SendScintilla(QsciScintilla.SCI_VCHOME)
        elif self.__isCursorOnLastLine():
            line, col = self.getCursorPosition()
            if self.text(line).startsWith(sys.ps1):
                col = len(sys.ps1)
            elif self.text(line).startsWith(sys.ps2):
                col = len(sys.ps2)
            else:
                col = 0
            self.setCursorPosition(line, col)

        
    def __QScintillaLineEnd(self):
        """
        Private method to handle the End key.
        """
        if self.isListActive():
            self.SendScintilla(QsciScintilla.SCI_LINEEND)
        elif self.__isCursorOnLastLine():
            self.SendScintilla(QsciScintilla.SCI_LINEEND)

        
    def __QScintillaLineUp(self):
        """
        Private method to handle the Up key.
        """
        if self.isListActive():
            self.SendScintilla(QsciScintilla.SCI_LINEUP)
        else:
            line, col = self.__getEndPos()
            buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")
            if buf and self.incrementalSearchActive:
                if self.incrementalSearchString:
                    idx = self.__rsearchHistory(self.incrementalSearchString, 
                                                self.histidx)
                    if idx >= 0:
                        self.histidx = idx
                        self.__useHistory()
                else:
                    idx = self.__rsearchHistory(buf)
                    if idx >= 0:
                        self.histidx = idx
                        self.incrementalSearchString = buf
                        self.__useHistory()
            else:
                if self.histidx < 0:
                    self.histidx = len(self.history)
                if self.histidx > 0:
                    self.histidx = self.histidx - 1
                    self.__useHistory()

        
    def __QScintillaLineDown(self):
        """
        Private method to handle the Down key.
        """
        if self.isListActive():
            self.SendScintilla(QsciScintilla.SCI_LINEDOWN)
        else:
            line, col = self.__getEndPos()
            buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")
            if buf and self.incrementalSearchActive:
                if self.incrementalSearchString:
                    idx = self.__searchHistory(self.incrementalSearchString, self.histidx)
                    if idx >= 0:
                        self.histidx = idx
                        self.__useHistory()
                else:
                    idx = self.__searchHistory(buf)
                    if idx >= 0:
                        self.histidx = idx
                        self.incrementalSearchString = buf
                        self.__useHistory()
            else:
                if self.histidx >= 0 and self.histidx < len(self.history):
                    self.histidx += 1
                    self.__useHistory()
  

    def __useHistory(self):
        """
        Private method to display a command from the history.
        """
        if self.histidx < len(self.history):
            cmd = self.history[self.histidx]
        else:
            cmd = QtCore.QString()
            self.incrementalSearchString = ""
            self.incrementalSearchActive = False

        self.setCursorPosition(self.prline, self.prcol + len(self.more and sys.ps1 or sys.ps2))
        self.setSelection(self.prline,self.prcol,\
                          self.prline,self.lineLength(self.prline))
        self.removeSelectedText()
        self.__insertText(cmd)

        
    def __searchHistory(self, txt, startIdx = -1):
        """
        Private method used to search the history.
        
        @param txt text to match at the beginning (string or QString)
        @param startIdx index to start search from (integer)
        @return index of 
        """
        if startIdx == -1:
            idx = 0
        else:
            idx = startIdx + 1
        while idx < len(self.history) and \
              not self.history[idx].startsWith(txt):
            idx += 1
        return idx
    
        
    def __rsearchHistory(self, txt, startIdx = -1):
        """
        Private method used to reverse search the history.
        
        @param txt text to match at the beginning (string or QString)
        @param startIdx index to start search from (integer)
        @return index of 
        """
        if startIdx == -1:
            idx = len(self.history) - 1
        else:
            idx = startIdx - 1
        while idx >= 0 and \
              not self.history[idx].startsWith(txt):
            idx -= 1
        return idx


    def focusNextPrevChild(self, next):
        """
        Reimplemented to stop Tab moving to the next window.
        
        While the user is entering a multi-line command, the movement to
        the next window by the Tab key being pressed is suppressed.
        
        @param next next window
        @return flag indicating the movement
        """
        if next and self.more:
            return False
    
        return QsciScintilla.focusNextPrevChild(self,next)


    def __showDynCompletion(self):
        """
        Display a completion list based on the last token
        """

        # get line
        line, col = self.__getEndPos()
        self.setCursorPosition(line,col)
        buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")

        text = buf.split()[-1][:-1]
        try:
            locals = self.interpreter.locals
            obj = eval(text, globals(), self.interpreter.locals)
            l = dir(obj)
            l = filter(lambda x : not x.startswith('__'), l)
            self.__showCompletions(l, text) 
        except : pass
        

    def __showCompletions(self, completions, text):
        """
        Private method to display the possible completions.
        """
        if len(completions) == 0:
            return
        
        if len(completions) > 1:
            completions.sort()
            comps = QtCore.QStringList()
            for comp in completions:
                comps.append(comp)
            self.showUserList(1, comps)
            #self.completionText = text
        else:
            txt = completions[0]
            if text != "":
                txt = txt.replace(text, "")
            self.__insertText(txt)
            #self.completionText = ""

        
    def __completionListSelected(self, id, txt):
        """
        Private slot to handle the selection from the completion list.
        
        @param id the ID of the user list (should be 1) (integer)
        @param txt the selected text (QString)
        """

         # Remove already written characters
        line, col = self.__getEndPos()
        self.setCursorPosition(line,col)
        buf = unicode(self.text(line))
        ind = len(buf) - buf.rfind(".") - 1

        if id == 1:
            txt = unicode(txt[ind:])
            #if self.completionText != "":
             #   txt = txt.replace(self.completionText, "")
            self.__insertText(txt)
            #self.completionText = ""