#!/usr/bin/env python
#-*- coding:utf-8 -*-
"""
Tag classes and utilities.

Basic usage should only require the use of the register function.
Simple customization can be achieved by explicit instanciation or subclassing 
of the Tag or TagManager classes.
"""
import re
import logging

from tie import processors
from tie import utils
from tie.exceptions import InvalidTagError

LOGGER = logging.getLogger(__name__)

@utils.implements_to_string
class Tag(object):
    """
    Custom, user defined template tag.
    Will detect matching placeholders in a given template and replace
    them with provided values.
    The user should not have to handle this process himself, but can
    provide callback functions to fine-tune a particular tag's processing and
    rendering.
    Altering the tag matching logic will require redefining the match method.
    """

    def __init__(self, pattern, flags=0, processor=processors.sub,
                 cached=False): 
        """
        Parameters:
        pattern:   Regular expression used for tag matching.
                   Can be either a string or an already compiled regex object.
        flags:     re module's flags for pattern compilation.
                   Pass them just as you would when using the re.compile 
                   function.
        processor: Tag processing callback. 
        """
        try:
            self.regexp = re.compile(pattern, flags=flags)
        except TypeError:
            raise InvalidTagError(
                    "Invalid tag pattern %s of type %s" % 
                    (pattern, type(pattern))
            )
        self.processor = processor

        self.cached = cached
        self.cache  = {}

    def __str__(self):
        """To string"""
        return "<%s %r>" % (self.__class__.__name__, self.regexp.pattern)

    def __repr__(self):
        """Instance representation"""
        return self.__str__()

    def match(self, template):
        """
        Find all matches in ``template`` and return them as an generator
        object. 
        """
        return self.regexp.finditer(template)

    def process(self, template, **context):
        """
        Scan the template string for all occurences of the Tag and process
        those using the instance's own processor function.
        Context args will be passed to the processor function.
        Return a dictionnary mapping the matched tags from the template with
        their corresponding values.
        """
        if self.cached and self.cache:
            return self.cache
        out = {}
        num_matches = 0
        for m in self.match(template):
            num_matches += 1
            src_tag = m.group(0)
            LOGGER.debug("%s matched %s", self, src_tag)
            if src_tag not in out:
                val = self.processor(m, **context)
                LOGGER.debug("Substituting %s for %s", val, src_tag)
                out[src_tag] = val
        LOGGER.debug("Found %i matches for %s", num_matches, self)
        if self.cached:
            self.cache = out
        return out

    def clear_cache(self):
        """Clear the tag's internal cache"""
        self.cache = {}

### Managers ###
################

class TagManager(object):
    """
    Tag Container.
    Iterating over it will yield each contained tag in order of their 
    insertion.

    Tags are stored in a simple list. Eventual subclasses will need to
    redefine their access and __iter__ method if they decide to use another
    data structure.
    """
    def __init__(self):
        self._tag_list = []

    def add(self, tag):
        """
        Register a new tag.
        Override this method to accomodate a different internal data strucutre.
        """
        self._tag_list.append(self._check_tag(tag))

    def clear(self):
        """
        Clear the internal tag list.
        Override to accomodate a different internal data strucutre.
        """
        self._tag_list = []

    def __iter__(self):
        """
        Yield contained tags.
        Override to accomodate a different internal data strucutre.
        """
        for tag in self._tag_list:
            yield tag
    
    def __len__(self):
        return len(self._tag_list)

    def clear_cache(self):
        """Clear the cache of all contained tags"""
        for tag in self:
            tag.clear_cache()

    @staticmethod
    def _check_tag(tag, cls=Tag):
        """
        Instances of cls (or a subclass of it) will be returned unchanged.
        Any other type will be sent to cls' constructor, which is responsible
        for type checking.
        cls defaults to Tag.
        """
        if not isinstance(tag, cls):
            return cls(tag)
        return tag


class PriorityTagManager(TagManager):
    """
    TagManager that keeps a priority value along its tags and yields them
    in that order.
    """
    def __init__(self):
        super(PriorityTagManager, self).__init__()
        self._tag_list = {}

    def add(self, tag):
        """
        Register a new tag. 
        ``tag`` should be a tupple ``(tag, priority)``. If not, ``priority``
        will default to 0.
        """
        try:
            tag_obj, priority = tag
        except TypeError:
            tag_obj, priority = tag, 0
        tag_obj = self._check_tag(tag_obj)
        self._tag_list.setdefault(priority, []).append(tag_obj)

    def clear(self):
        """Clear the internal tag list."""
        self._tag_list = {}

    def __iter__(self):
        """Yield contained tags."""
        for i in sorted(self._tag_list.keys()):
            for tag in self._tag_list[i]:
                yield tag

# "Global" manager instance.
_manager = TagManager()

### Module Level Utils ###
##########################

def get_manager():
    """Return the global TagManager"""
    return _manager

def set_manager(manager):
    """
    Set the global TagManager to ``manager``. Only useful to setup a custom
    manager.
    """
    global _manager
    LOGGER.info("New Tag manager: %s", manager)
    _manager = manager

def register(*tag_list):
    """Register a sequence of tags."""
    manager = get_manager()
    for tag in tag_list:
        manager.add(tag)
        LOGGER.info("Registered: %s", utils.unicode(tag))

