# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.


from elisa.core.log import Loggable
import re
import sre_constants
from bisect import insort_right

from twisted.python import reflect

"""
Find the object that has a matching regular expression for a given string
"""

class ResolverError(Exception):
    pass

class PatternNotFound(ResolverError):
    pass

class InvalidPattern(ResolverError):
    pass

class MatchNotFound(ResolverError):
    pass

class Pattern(object):
    """
    A pattern object contains a regular expression and the corresponding
    object.

    @ivar pattern:  the compiled pattern
    @type pattern:  L{_sre.SRE_PATTERN}
    @ivar regex:    the uncompiled pattern
    @type regex:    str
    @ivar obj:      the object to match
    """
    def __init__(self, regex, obj):
        """
        @raise InvalidPatter: when compiling the L{regex} fails
        """
        self.regex = regex
        self.obj = obj

        try:
            self.pattern = re.compile(regex)
        except sre_constants.error, e:
            raise InvalidPattern(regex, str(e))

    def __repr__(self):
        return '<%s regex: %s obj: %s>' % (reflect.qual(self.__class__),
                self.regex, self.obj)

    def __cmp__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented

        return cmp(self.regex, other.regex)

class PatternMatcher(Loggable):
    """
    Object used to map strings matching regex patterns to python objects.
    """
    patternFactory = Pattern

    def __init__(self):
        self.patterns = []

    def add_pattern(self, regex, obj):
        """
        Assign an object to a regex pattern.
        
        @param regex: regular expression
        @type regex: C{str}
        @param obj: the object to assign to strings matching C{regex}
        @type obj: any python object
        """
        pattern = self.patternFactory(regex, obj)
        insort_right(self.patterns, pattern)
        self.debug('added pattern %s: %s' % (regex, obj))

    def remove_pattern(self, regex):
        """
        Remove a regex pattern.

        @param regex: regular expression
        @type regex: C{str}

        @raise PatternNotFound: when the pattern is not found
        """
        for i, pattern in enumerate(reversed(self.patterns)):
            if pattern.regex == regex:
                break
        else:
            raise PatternNotFound(regex)

        del self.patterns[-i - 1]
        self.debug('removed pattern %s' % regex)

    def match(self, string, all=False):
        """
        Match C{string} to the list of patterns in the resolver and return the
        associated object.

        This tries to match the pattern against the whole string, and not only
        the beginning of the string as C{re.match()} does. Append '.*' to your
        regular expression if you want the same behaviour as C{re}.

        @param string: string to match
        @type string: C{str}
        @param all: whether to return all the matches or only the first one
        @type all: C{bool}
        @raise MatchNotFound:   there was no match for the given string
        """
        matches = []

        for pattern in reversed(self.patterns):
            compiled_pattern = pattern.pattern
            match = compiled_pattern.match(string)
            if match is None or match.end(0) != len(string):
                continue

            if not all:
                return pattern.obj

            matches.append(pattern.obj)

        if len(matches):
            return matches

        raise MatchNotFound(string)

class UriPattern(Pattern):
    """
    Pattern object used to sort URI regex patterns based on the number of path
    components in the regex.

    Take the following example:
    >>> uri_a = UriPattern('http://first_level/.*')
    >>> uri_b = UriPattern('http://first_level/second_level/.*')
    >>> uri_a < uri_b
    True

    This is needed by UriPatternMatcher to keep URI patterns sorted in order to
    match strings starting by the most specific pattern first.
    """
    def __init__(self, regex, obj):
        super(UriPattern, self).__init__(regex, obj)
        self.slash_count = regex.count('/')

    def __cmp__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented

        return cmp(self.slash_count, other.slash_count)

class UriPatternMatcher(PatternMatcher):
    patternFactory = UriPattern

