from __future__ import absolute_import, division, unicode_literals
from future.builtins import int, open, str

from hashlib import md5
import os
try:
    from urllib.parse import quote, unquote
except ImportError:
    from urllib import quote, unquote

from django.apps import apps
from django.contrib import admin
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.sites.models import Site
from django.core.exceptions import ObjectDoesNotExist
from django.core.files import File
from django.core.files.storage import default_storage
from django.urls import reverse, resolve, NoReverseMatch
from django.db.models import Model
from django.template import Node, Template, TemplateSyntaxError
from django.template.base import (TOKEN_BLOCK, TOKEN_COMMENT,
                                  TOKEN_TEXT, TOKEN_VAR, TextNode)
from django.template.defaultfilters import escape
from django.template.loader import get_template
from django.utils import translation
from django.utils.html import strip_tags
from django.utils.text import capfirst
from django.utils.safestring import SafeText, mark_safe

from mezzanine.conf import settings
from mezzanine.core.fields import RichTextField
from mezzanine.core.forms import get_edit_form
from mezzanine.utils.cache import nevercache_token, cache_installed
from mezzanine.utils.html import decode_entities
from mezzanine.utils.importing import import_dotted_path
from mezzanine.utils.sites import current_site_id, has_site_permission
from mezzanine.utils.urls import admin_url, home_slug
from mezzanine.utils.views import is_editable
from mezzanine import template


register = template.Library()


if "compressor" in settings.INSTALLED_APPS:
    @register.tag
    def compress(parser, token):
        """
        Shadows django-compressor's compress tag so it can be
        loaded from ``mezzanine_tags``, allowing us to provide
        a dummy version when django-compressor isn't installed.
        """
        from compressor.templatetags.compress import compress
        return compress(parser, token)
else:
    @register.to_end_tag
    def compress(parsed, context, token):
        """
        Dummy tag for fallback when django-compressor isn't installed.
        """
        return parsed


def initialize_nevercache():
    if cache_installed():
        @register.tag
        def nevercache(parser, token):
            """
            Tag for two phased rendering. Converts enclosed template
            code and content into text, which gets rendered separately
            in ``mezzanine.core.middleware.UpdateCacheMiddleware``.
            This is to bypass caching for the enclosed code and content.
            """
            text = []
            end_tag = "endnevercache"
            tag_mapping = {
                TOKEN_TEXT: ("", ""),
                TOKEN_VAR: ("{{", "}}"),
                TOKEN_BLOCK: ("{%", "%}"),
                TOKEN_COMMENT: ("{#", "#}"),
            }
            delimiter = nevercache_token()
            while parser.tokens:
                token = parser.next_token()
                token_type = token.token_type
                if token_type == TOKEN_BLOCK and token.contents == end_tag:
                    return TextNode(delimiter + "".join(text) + delimiter)
                start, end = tag_mapping[token_type]
                text.append("%s%s%s" % (start, token.contents, end))
            parser.unclosed_block_tag(end_tag)
    else:
        @register.to_end_tag
        def nevercache(parsed, context, token):
            """
            Dummy fallback ``nevercache`` for when caching is not
            configured.
            """
            return parsed


initialize_nevercache()


@register.simple_tag(takes_context=True)
def fields_for(context, form, template="includes/form_fields.html"):
    """
    Renders fields for a form with an optional template choice.
    """
    context["form_for_fields"] = form
    return get_template(template).render(context.flatten())


@register.inclusion_tag("includes/form_errors.html")
def errors_for(form):
    """
    Renders an alert if the form has any errors.
    """
    return {"form": form}


@register.filter
def sort_by(items, attr):
    """
    General sort filter - sorts by either attribute or key.
    """
    def key_func(item):
        try:
            return getattr(item, attr)
        except AttributeError:
            try:
                return item[attr]
            except TypeError:
                getattr(item, attr)  # Reraise AttributeError
    return sorted(items, key=key_func)


@register.filter
def is_installed(app_name):
    """
    Returns ``True`` if the given app name is in the
    ``INSTALLED_APPS`` setting.
    """
    from warnings import warn
    warn("The is_installed filter is deprecated. Please use the tag "
         "{% ifinstalled appname %}{% endifinstalled %}")
    return app_name in settings.INSTALLED_APPS


@register.tag
def ifinstalled(parser, token):
    """
    Old-style ``if`` tag that renders contents if the given app is
    installed. The main use case is:

    {% ifinstalled app_name %}
    {% include "app_name/template.html" %}
    {% endifinstalled %}

    so we need to manually pull out all tokens if the app isn't
    installed, since if we used a normal ``if`` tag with a False arg,
    the include tag will still try and find the template to include.
    """
    try:
        tag, app = token.split_contents()
    except ValueError:
        raise TemplateSyntaxError("ifinstalled should be in the form: "
                                  "{% ifinstalled app_name %}"
                                  "{% endifinstalled %}")

    end_tag = "end" + tag
    unmatched_end_tag = 1
    if app.strip("\"'") not in settings.INSTALLED_APPS:
        while unmatched_end_tag:
            token = parser.tokens.pop(0)
            if token.token_type == TOKEN_BLOCK:
                block_name = token.contents.split()[0]
                if block_name == tag:
                    unmatched_end_tag += 1
                if block_name == end_tag:
                    unmatched_end_tag -= 1
        parser.tokens.insert(0, token)
    nodelist = parser.parse((end_tag,))
    parser.delete_first_token()

    class IfInstalledNode(Node):
        def render(self, context):
            return nodelist.render(context)

    return IfInstalledNode()


@register.render_tag
def set_short_url_for(context, token):
    """
    Sets the ``short_url`` attribute of the given model for share
    links in the template.
    """
    obj = context[token.split_contents()[1]]
    obj.set_short_url()
    return ""


@register.simple_tag
def gravatar_url(email, size=32):
    """
    Return the full URL for a Gravatar given an email hash.
    """
    bits = (md5(email.lower().encode("utf-8")).hexdigest(), size)
    return "//www.gravatar.com/avatar/%s?s=%s&d=identicon&r=PG" % bits


@register.to_end_tag
def metablock(parsed):
    """
    Remove HTML tags, entities and superfluous characters from
    meta blocks.
    """
    parsed = " ".join(parsed.replace("\n", "").split()).replace(" ,", ",")
    return escape(strip_tags(decode_entities(parsed)))


@register.inclusion_tag("includes/pagination.html", takes_context=True)