# -*- coding: utf-8 -*-

"""
    {% lucidTag ... %}
    ~~~~~~~~~~~~~~~~~~

    the special PyLucid tag who starts a plugin with the plugin manager.
    e.g.: {% lucidTag plugin_name.method_name key1="value1" key2="value2" %}

    registered in: ./PyLucid/defaulttags/__init__.py

    Last commit info:
    ~~~~~~~~~~~~~~~~~
    $LastChangedDate$
    $Rev$
    $Author$

    :copyleft: 2007 by the PyLucid team, see AUTHORS for more details.
    :license: GNU GPL v3 or above, see LICENSE for more details.
"""

import re
import sys
import shlex
import traceback

if __name__ == "__main__":
    # For doctest only
    import os
    os.environ["DJANGO_SETTINGS_MODULE"] = "django.conf.global_settings"

from django import template
from django.conf import settings
from django.contrib import messages
from django.http import HttpResponse
from django.template import mark_safe

#from pylucid_project.apps.pylucid.system import pylucid_plugin
from pylucid_project.system.pylucid_plugins import PYLUCID_PLUGINS


# FIXME: The re should be more fault-tolerant:
KWARGS_REGEX = re.compile('''(\w*?)\=['"](.*?)['"]''')


# For str2dict()
KEYWORD_MAP = {
    "True": True,
    "False": False,
    "None": None,
}

def str2dict(raw_content):
    """
    convert a string into a dictionary. e.g.:

    >>> str2dict('key1="value1" key2="value2"')
    {'key2': 'value2', 'key1': 'value1'}

    >>> str2dict('A="B" C=1 D=1.1 E=True F=False G=None')
    {'A': 'B', 'C': 1, 'E': True, 'D': '1.1', 'G': None, 'F': False}
    
    >>> str2dict('''key1="'1'" key2='"2"' key3="""'3'""" ''')
    {'key3': 3, 'key2': 2, 'key1': 1}

    >>> str2dict(u'unicode=True')
    {'unicode': True}
    """
    if isinstance(raw_content, unicode):
        # shlex.split doesn't work with unicode?!?
        raw_content = raw_content.encode(settings.DEFAULT_CHARSET)

    parts = shlex.split(raw_content)

    result = {}
    for part in parts:
        key, value = part.split("=", 1)

        if value in KEYWORD_MAP:
            # True False or None
            value = KEYWORD_MAP[value]
        else:
            # A number?
            try:
                value = int(value.strip("'\""))
            except ValueError:
                pass

        result[key] = value

    return result



class lucidTagNodeError(template.Node):
    """
    display a error messages in the cms page for a wrong lucidTag.
    """
    def __init__(self, plugin_name, method_name, msg):
        self.plugin_name = plugin_name
        self.method_name = method_name
        self.msg = msg

    def render(self, context):
        return u"[lucidTag %s.%s syntax error: %s]" % (self.plugin_name, self.method_name, self.msg)


class lucidTagNode(template.Node):
    def __init__(self, raw_content, plugin_name, method_name, method_kwargs):
        self.raw_content = raw_content
        self.plugin_name = plugin_name
        self.method_name = method_name
        self.method_kwargs = method_kwargs

    def __repr__(self):
        return "<lucidTag node ('%s.%s' kwargs:%s)>" % (
            self.plugin_name, self.method_name, self.method_kwargs)

    def render(self, context):
        """ Call the plugin view an return his reponse """
        try:
            request = context["request"]
        except KeyError:
            raise KeyError("request object not in context! You must add it into the template context!")

        plugin_name = self.plugin_name
        method_name = self.method_name
        method_kwargs = self.method_kwargs

        try:
            plugin_instance = PYLUCID_PLUGINS[plugin_name]
        except KeyError:
            return u"[PyLucid Plugin %s unknown]" % plugin_name

        try:
            response = plugin_instance.call_plugin_view(request, "views", method_name, method_kwargs)
        except Exception, err:
            pkg = "%s.views.%s" % (plugin_name, method_name)
            # Base error message for all users:
            msg = u"Error call PyLucid plugin view %s" % pkg

            if settings.DEBUG:
                # insert more information into the traceback and re-raise the original error
                etype, evalue, etb = sys.exc_info()
                evalue = etype('%s (%r): %s' % (msg, self.raw_content, evalue))
                raise etype, evalue, etb

            if request.user.is_staff:
                # add more info for staff members
                msg += u" (%s)" % err

            if request.user.is_superuser:
                # put the full traceback into page_msg, but only for superusers
                messages.info(request, mark_safe("%s:<pre>%s</pre>" % (msg, traceback.format_exc())))

            return u"[%s]" % msg

        # FIXME: Witch error should we raised here?
        if response == None:
            return u""
        elif isinstance(response, basestring):
            return response
        elif isinstance(response, HttpResponse):
            status_code = response.status_code
            assert status_code == 200, "Response status code %r != 200 ???" % status_code
            return response.content

        raise RuntimeError("pylucid plugins must return None, a basestring or a HttpResponse instance!")





def lucidTag(parser, token):
    """
    Parse the lucidTags.

    syntax e.g.:
        {% lucidTag PluginName %}
        {% lucidTag PluginName kwarg1="value1" %}
        {% lucidTag PluginName kwarg1="value1" kwarg2="value2" %}
    """
    raw_content = token.contents
    content = raw_content.split(" ", 2)[1:]
    plugin_name = content.pop(0)

    if "." in plugin_name:
        plugin_name, method_name = plugin_name.split(".", 1)
    else:
        method_name = "lucidTag"

    if content:
        raw_kwargs = content[0]
        try:
            method_kwargs = str2dict(raw_kwargs)
        except Exception, err:
            if settings.DEBUG:
                raise
            return lucidTagNodeError(plugin_name, method_name, msg="Wrong tag parameter")
    else:
        method_kwargs = {}

    return lucidTagNode(raw_content, plugin_name, method_name, method_kwargs)



if __name__ == "__main__":
    import doctest
    doctest.testmod(
#        verbose=True
        verbose=False
    )
    print "DocTest end."
