import os
import sys
import traceback
import types
import warnings
from importlib import import_module

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse, get_resolver, get_urlconf
from django.http import HttpResponse, HttpResponseServerError
from django.shortcuts import render
from django.template.backends.base import BaseEngine
from django.template.base import TemplateDoesNotExist
from django.template.context import RequestContext, Context, make_context
from django.template.engine import Engine, _dirs_undefined
from django.templatetags import static
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.translation import ugettext
from mako import exceptions
from mako.exceptions import TemplateLookupException
from mako.lookup import TemplateLookup
from mako.template import Template


def url(view_name, *args, **kwargs):
    try:
        return reverse(view_name, args=args, kwargs=kwargs)
    except:
        resolver = get_resolver(get_urlconf())
        for key in resolver.reverse_dict.keys():
            if isinstance(key, types.FunctionType):
                name = key.__module__ + '.' + key.__name__
            else:
                name = key
            
            if name.endswith(view_name):
                return reverse(name, args=args, kwargs=kwargs)
        
        raise
        

default_context = {
    'url' : url,
    'static': static.static,
    '_': ugettext,
}

default_charset = getattr(settings, 'DEFAULT_CHARSET', 'utf8')

app_template_dirs = []
fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()

for app in settings.INSTALLED_APPS:
    try:
        mod = import_module(app)
    except ImportError as e:
        raise ImproperlyConfigured('ImportError %s: %s' % (app, e.args[0]))
    template_dir = os.path.join(os.path.dirname(mod.__file__), 'templates')
    if os.path.isdir(template_dir):
        app_template_dirs.append(template_dir)


template_lookup = TemplateLookup(directories=app_template_dirs, 
                                 input_encoding=default_charset, 
                                 output_encoding=default_charset, 
                                 )

def render_to_response(filename, dictionary, context_instance=None):
    '''
    :param filename:
    :param dictionary:
    :param context_instance:
    :return: rendered django HttpResponse
    '''

    dictionary.update(default_context)

    if context_instance:
        return render(context_instance.request, filename, dictionary)

    if hasattr(settings, 'MAKO_DEFAULT_CONTEXT'):
        dictionary.update(settings.MAKO_DEFAULT_CONTEXT)

    try:
        template = template_lookup.get_template(filename)
        return HttpResponse(template.render(**dictionary))
    except exceptions.TopLevelLookupException:
        raise
    except:
        traceback.print_exc()
        return HttpResponseServerError(exceptions.html_error_template().render())


class MakoTemplateEngine(BaseEngine):

    app_dirname = 'templates'

    def __init__(self, params):
        params = params.copy()
        options = params.pop('OPTIONS').copy()
        options.setdefault('debug', settings.DEBUG)
        options.setdefault('file_charset', settings.FILE_CHARSET)
        super().__init__(params)
        self.apps = options.get('apps', None)
        self.app_dirs = []
        for app in self.apps:
            app_module = import_module(app)
            self.app_dirs.append(os.path.abspath(app_module.__path__[0]))

    def from_string(self, template_code):
        return MakoTemplateWrapper(Template(text=template_code))

    def get_template(self, template_name, dirs=_dirs_undefined):
        try:
            mt = template_lookup.get_template(template_name)

            if not [dir for dir in self.app_dirs if os.path.join(dir, 'templates', template_name) == mt.filename]:
                raise TemplateDoesNotExist("template does not exists in templates directories in specified apps: %s", str(self.apps))
        except TemplateLookupException as e:
            raise TemplateDoesNotExist(str(e))

        return MakoTemplateWrapper(mt)


class MakoTemplateWrapper(object):

    def __init__(self, template):
        self.template = template
        self.engine = Engine.get_default()

    @property
    def origin(self):
        # TODO: define the Origin API. For now simply forwarding to the
        #       underlying Template preserves backwards-compatibility.
        return self.template

    def render(self, context=None, request=None):
        # A deprecation path is required here to cover the following usage:
        # >>> from django.template import Context
        # >>> from django.template.loader import get_template
        # >>> template = get_template('hello.html')
        # >>> template.render(Context({'name': 'world'}))
        # In Django 1.7 get_template() returned a django.template.Template.
        # In Django 1.8 it returns a django.template.backends.django.Template.
        # In Django 2.0 the isinstance checks should be removed. If passing a
        # Context or a RequestContext works by accident, it won't be an issue
        # per se, but it won't be officially supported either.
        if isinstance(context, RequestContext):
            if request is not None and request is not context.request:
                raise ValueError(
                    "render() was called with a RequestContext and a request "
                    "argument which refer to different requests. Make sure "
                    "that the context argument is a dict or at least that "
                    "the two arguments refer to the same request.")
            warnings.warn(
                "render() must be called with a dict, not a RequestContext.",
                RemovedInDjango20Warning, stacklevel=2)

        elif isinstance(context, Context):
            warnings.warn(
                "render() must be called with a dict, not a Context.",
                RemovedInDjango20Warning, stacklevel=2)

        else:
            context = make_context(context, request)

        if hasattr(settings, 'MAKO_DEFAULT_CONTEXT'):
            context.dicts.append(settings.MAKO_DEFAULT_CONTEXT)

        context.dicts.append(default_context)
        try:
            with context.bind_template(self):
                return self.template.render(**context.flatten())
        except exceptions.TopLevelLookupException:
            raise
        except:
            traceback.print_exc()
            return exceptions.html_error_template().render()

