from django.utils.encoding import smart_str
from django.core.urlresolvers import get_callable, RegexURLResolver, get_resolver
from django.template import Node, NodeList, TextNode, TemplateSyntaxError, Library, resolve_variable
from django.conf import settings

register = Library()

def do_ifactive(parser, token):
    """
    Defines a conditional block tag, "ifactive" that switches based on whether the active request 
    is being handled by a particular view (with optional args and kwargs).
    
    Has the form:
    
    {% ifactive request path.to.view %}
        [Block to render if path.to.view is the active view]
    {% else %}
        [Block to render if path.to.view is not the active view]
    {% endifactive %}

    'request' is a context variable expression which resolves to the HttpRequest object for the current
    request. (Additionally, the ActiveViewMiddleware must be installed for this to work.)

    'path.to.view' can be a string with a python import path (which must be mentioned in the urlconf),
    or a name of a urlpattern (i.e., same as the argument to the {% url %} tag).
    
    You can also pass arguments or keyword arguments in the same form as accepted by the {% url %} tag,
    e.g.:
    
    {% ifactive request path.to.view var1="bar",var2=var.prop %}...{% endifactive %}   
    
    or:
    
    {% ifactive request path.to.view "bar",var.prop %}...{% endifactive %}   
    
    The else block is optional.
    """

    end_tag = 'endifactive'

    active_nodes = parser.parse((end_tag,'else'))    
    end_token = parser.next_token()
    if end_token.contents == 'else':
        inactive_nodes = parser.parse((end_tag,))
        parser.delete_first_token()
    else:
        inactive_nodes = None

    tag_args = token.contents.split(' ')
    if len(tag_args) < 3:
        raise TemplateSyntaxError("'%s' takes at least two arguments"
                                  " (context variable with the request, and path to a view)" % tag_args[0])
    
    request_var = tag_args[1]
    view_name = tag_args[2]
    args, kwargs = _parse_url_args(parser, tag_args[3:])
    
    return ActiveNode(request_var, view_name, args, kwargs, active_nodes, inactive_nodes)
       
register.tag('ifactive', do_ifactive)

class ActiveNode(Node):
    
    def __init__(self, request_var, view_name, args, kwargs, active_nodes, inactive_nodes=None):
        self.request_var = request_var
        self.view_name = view_name
        self.args = args
        self.kwargs = kwargs
        self.active_nodes = active_nodes
        self.inactive_nodes = inactive_nodes
    
    def render(self, context):        

        request = resolve_variable(self.request_var, context)

        view, default_args = _get_view_and_default_args(self.view_name)        
                
        if getattr(request, '_view_func', None) is view:        
            
            resolved_args = [arg.resolve(context) for arg in self.args]                                               
            if request._view_args == resolved_args:
            
                resolved_kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()])
                resolved_kwargs.update(default_args)
                                               
                if request._view_kwargs == resolved_kwargs:
                    return self.active_nodes.render(context)
        
        if self.inactive_nodes is not None:    
            return self.inactive_nodes.render(context)
        else:
            return ''

def _get_patterns_map(resolver, default_args=None):
    """
    Recursively generates a map of 
    (pattern name or path to view function) -> (view function, default args)    
    """

    patterns_map = {}
    
    if default_args is None:
        default_args = {}

    for pattern in resolver.url_patterns:
    
        pattern_args = default_args.copy()
    
        if isinstance(pattern, RegexURLResolver):
            pattern_args.update(pattern.default_kwargs)
            patterns_map.update(_get_patterns_map(pattern, pattern_args))
        else:
            pattern_args.update(pattern.default_args)
        
            if pattern.name is not None:
                patterns_map[pattern.name] = (pattern.callback, pattern_args)
            
            # HACK: Accessing private attribute of RegexURLPattern
            callback_str = getattr(pattern, '_callback_str', None)
            if callback_str is not None:    
                patterns_map[pattern._callback_str] = (pattern.callback, pattern_args)
        
    return patterns_map    

_view_name_cache = None

def _get_view_and_default_args(view_name):
    """
    Given view_name (a path to a view or a name of a urlpattern,
    returns the view function and a dict containing any default kwargs
    that are specified in the urlconf for that view.
    """

    global _view_name_cache
    
    if _view_name_cache is None:
        _view_name_cache = _get_patterns_map(get_resolver(None))               
        
    try:
        return _view_name_cache[view_name]
    except KeyError:
        raise KeyError("%s does not match any urlpatterns" % view_name)   
    
def _parse_url_args(parser, bits):
    """
    Parses URL parameters in the same way as the {% url %} tag.    
    """

    args = []
    kwargs = {}
    
    for bit in bits:
        for arg in bit.split(","):
            if '=' in arg:
                k, v = arg.split('=', 1)
                k = k.strip()
                kwargs[smart_str(k,'ascii')] = parser.compile_filter(v)
            elif arg:
                args.append(parser.compile_filter(arg))
    
    return args, kwargs
                        

class ActiveViewMiddleware(object):
    def process_view(self, request, view_func, view_args, view_kwargs):
        """
        Records the view function used on this request and its *args and **kwargs.
        Needed by {% ifactive %} to determine if a particular view is currently active.
        """
        request._view_func = view_func
        request._view_args = list(view_args)
        request._view_kwargs = view_kwargs