###############################################################################
# Name: completer.py                                                          #
# Purpose: Autcompleter interface base class.                                 #
# Author: Cody Precord <cprecord@editra.org>                                  #
# Copyright: (c) 2009 Cody Precord <staff@editra.org>                         #
# License: wxWindows License                                                  #
###############################################################################

"""
Base class for autocompletion providers to implement the completion interface.

@summary: Autocompleter base class

"""

__author__ = "Cody Precord <cprecord@editra.org>"
__svnid__ = "$Id: completer.py 70211 2011-12-31 02:23:54Z CJP $"
__revision__ = "$Revision: 70211 $"

__all__ = [ 'TYPE_FUNCTION', 'TYPE_METHOD', 'TYPE_CLASS', 'TYPE_ATTRIBUTE',
            'TYPE_VARIABLE', 'TYPE_ELEMENT', 'TYPE_PROPERTY', 'TYPE_UNKNOWN',
            'BaseCompleter', 'Symbol', 'CreateSymbols' ]

#--------------------------------------------------------------------------#
# Imports
import wx

#--------------------------------------------------------------------------#

# Image Type Ids
TYPE_FUNCTION, \
TYPE_METHOD, \
TYPE_CLASS, \
TYPE_ATTRIBUTE, \
TYPE_PROPERTY, \
TYPE_VARIABLE, \
TYPE_ELEMENT, \
TYPE_UNKNOWN = range(1, 9)

#--------------------------------------------------------------------------#
class Symbol(object):
    """ Defines a symbol as parsed by the autocompleter.
    Symbols with the same name and different type are EQUAL
    Symbol hash is based on symbol NAME
    
    """
    # we create lots of these so use slots as a performance tweak
    __slots__ = ('_name', '_type')

    def __init__(self, name, symtype):
        """ Constructor
        @param name: Symbol name
        @param symtype: Symbol type, one of the TYPE_FUNCTION ... TYPE_UNKNOWN range
        
        """
        super(Symbol, self).__init__()

        # Attributes
        self._name = unicode(name)
        self._type = symtype
    
    def __eq__(self, other):
        return (self.Name == other.Name)

    def __lt__(self, other):
        return (self.Name < other.Name)

    def __le__(self, other):
        return (self.Name <= other.Name)

    def __ne__(self, other):
        return (self.Name != other.Name)

    def __gt__(self, other):
        return (self.Name > other.Name)

    def __ge__(self, other):
        return (self.Name >= other.Name)

    # TODO: this task should probably be delegated to the ui
    def __str__(self):
        if self.Type != TYPE_UNKNOWN:
            return u'?'.join([self.Name, unicode(self.Type)])
        else:
            return self.Name

    def __hash__(self):
        return hash(self.Name)

    Name = property(lambda self: self._name,
                    lambda self, n: setattr(self, '_name', n))
    Type = property(lambda self: self._type,
                    lambda self, t: setattr(self, '_type', t))

#--------------------------------------------------------------------------#

def CreateSymbols(arglst, symtype=TYPE_UNKNOWN):
    """Convert a list of strings to a list of Symbol objects
    @param arglst: list of strings
    @keyword symtype: TYPE_FOO 
    @return: list of Symbols

    """
    return [ Symbol(obj, symtype) for obj in arglst ]

#--------------------------------------------------------------------------#

class BaseCompleter(object):
    """Base Autocomp provider class"""
    def __init__(self, parent):
        """Initializes the auto-completion service
        @param parent: parent of this service object

        """
        super(BaseCompleter, self).__init__()

        # Attributes
        self._buffer = parent
        self._log = wx.GetApp().GetLog()
        self._case_sensitive = False
        self._autocomp_after = False
        self._choose_single = True

        self._autocomp_keys = list()
        self._autocomp_stop = u''
        self._autocomp_fillup = u''
        self._calltip_keys = list()
        self._calltip_cancel = list()

    #--- Override in subclass ----#

    def GetAutoCompList(self, command):
        """Retrieves the sorted autocomplete list for a command
        @param command: command string to do lookup on
        @return: list of strings

        """
        return list()

    def GetCallTip(self, command):
        """Returns the calltip string for a command
        @param command: command to get calltip for (string)
        @return: string

        """
        return u''

    def OnCompletionInserted(self, pos, text):
        """Called by the buffer when an autocomp selection has been inserted.
        The completer can override this method to 
        @param pos: Position the caret was at before the insertion
        @param text: text that was inserted at pos

        """
        pass

    #--- End override in subclass ----#

    def GetCommandString(self, stc, line, col):
        """Get the command string found at the current location to use
        for lookups.
        @param stc: EditraStc
        @param line: line text
        @param col: position in line (column)

        """
        line = line.expandtabs(stc.GetTabWidth())
        line = line[:col]
        if line and ord(line[-1]) in (self.GetAutoCompKeys() + self.GetCallTipKeys()):
            line = line[:-1]
        cmd_lmt = list(self.GetAutoCompStops() + self.GetAutoCompFillups())
        for key in self.GetAutoCompKeys():
            kval = unichr(key)
            if kval in cmd_lmt:
                cmd_lmt.remove(kval)

        cmd = u''
        curr_pos = len(line) - 1
        while curr_pos > -1:
            cmd = line[curr_pos:]
            if len(cmd) and cmd[0] not in cmd_lmt:
                curr_pos -= 1
            else:
                break

        for char in cmd_lmt:
            cmd = cmd.replace(char, u'')

        return cmd.strip()

    def GetAutoCompKeys(self):
        """Returns the list of key codes for activating the auto-completion.
        @return: list of characters used for activating auto-completion

        """
        return self._autocomp_keys

    def SetAutoCompKeys(self, key_list):
        """Set the keys to provide completions on
        @param key_list: List of key codes

        """
        self._autocomp_keys = key_list

    def GetAutoCompStops(self):
        """Returns a string of characters that should cancel
        the auto-completion lookup.
        @return: string of characters that will hide the autocomp/calltip

        """
        return self._autocomp_stop

    def SetAutoCompStops(self, stops):
        """Set the keys to cancel auto-completions on.
        @param stops: string

        """
        self._autocomp_stop = stops

    def GetAutoCompFillups(self):
        """Get the list of characters to do a fillup on
        @return: string

        """
        return self._autocomp_fillup

    def SetAutoCompFillups(self, fillups):
        """Set the list of characters to do a fillup on
        @param fillups: string

        """
        self._autocomp_fillup = fillups

    def GetCallTipKeys(self):
        """Returns the list of keys to activate a calltip on
        @return: list of calltip activation keys

        """
        return self._calltip_keys

    def SetCallTipKeys(self, keys):
        """Set the list of keys to activate calltips on
        @return: list of calltip activation keys

        """
        self._calltip_keys = keys

    def GetCallTipCancel(self):
        """Get the list of key codes that should stop a calltip"""
        return self._calltip_cancel

    def SetCallTipCancel(self, key_list):
        """Set the list of key codes that should stop a calltip"""
        self._calltip_cancel = key_list

    def GetBuffer(self):
        """Get the reference to the buffer this autocomp object is owned by
        @return: EditraStc

        """
        return self._buffer

    def GetCaseSensitive(self):
        """Are commands case sensitive or not
        @return: bool

        """
        return self._case_sensitive

    def SetCaseSensitive(self, sensitive):
        """Set whether this completer is case sensitive or not
        @param sensitive: bool

        """
        self._case_sensitive = sensitive

    def GetChooseSingle(self):
        """Get whether the completer should automatically choose a selection
        when there is only one symbol in the completion list.
        @return: bool

        """
        return self._choose_single

    def SetChooseSingle(self, single):
        """Set whether the completer should automatically choose a selection
        when there is only one symbol in the completion list.
        @param single: bool

        """
        self._choose_single = single

    def ShouldCheck(self, cpos):
        """Should completions be attempted
        @param cpos: current buffer position
        @return: bool

        """
        buff = self.GetBuffer()
        rval = True
        if buff is not None:
            if buff.IsString(cpos) or buff.IsComment(cpos):
                rval = False
        return rval
