"""Integrates Dojo with Django."""

import json
import uuid

# Used to convert Django Field attributes to Dojo attributes.
#
# Key is Django Field attribute name.
# Value is tuple.
# [0] == Dijit attribute name
# [1] == callable to convert Django value to Dijit value
default_attr_map = {
    'required': ('required', None),
    'min_length': ('minLength', None),
    'max_length': ('maxLength', None),
    'regex': ('regExp', lambda a: a.pattern)
}

def dojo_field(field, dojo_type, attr_map=None, dojo_attrs=None):
    """
    Instruments a Django form.Field with Dijit parameters.

    arguments
    ==========
    * field - Django form.Field to instrument.
    * dojo_type - str, qualified class name of dijit class to use.
    * attr_map - dict, optional mapping between Django and Dojo
                 attrs, see default_attr_map comments for format.
    * dojo_attrs - dict, Attributes to apply to Dijit.
    """

    # Specifies mapping between Django field attributes
    # and Dijit attributes
    if attr_map is not None:
        map = dict(default_attr_map)
        map.update(attr_map)
    else:
        map = default_attr_map

    # Get mapped attributes from Django field
    attrs = {}
    for attr, mapper in map.iteritems():
        val = getattr(field, attr, None)
        if val is None:
            continue

        if mapper[1] is not None:
            val = mapper[1](val)

        attrs[mapper[0]] = val

    # Convert Error message
    error_msgs = getattr(field, 'error_messages', None)
    if error_msgs is not None:
        invalid_msg = error_msgs.get('invalid', None)
        if invalid_msg is not None:
            # Django uses a
            # lazy proxy when this property is not
            # explicitly set. json encoder will choke
            # on the proxy object. Calling a method 
            # causes the proxy to be evaluated and
            # json encoder is happy.
            attrs['invalidMessage'] = invalid_msg.rstrip()

    # Add user defined attributes
    if dojo_attrs is not None:
        attrs.update(dojo_attrs)

    # Attach Dojo attributes to field
    field._dojo_type = dojo_type
    field._dojo_params = attrs

class Dojo(object):
    """Keeps track of Dojo properties."""

    def __init__(self, src, enabled=True, theme='tundra',
                 dj_config=None, form_function=None):
        self.enabled = enabled
        self.dj_config = dj_config
        self.form_function = form_function

        self._src = src
        self._stylesheets = ['/'.join((self._src, 'dojo', 'resources', 'dojo.css'))]
        self._modules = [] #required_modules
        self._aols = [] # addOnLoad functions
        self.theme = theme

    def get_src(self):
        return self._src
    src = property(get_src)

    def get_theme(self):
        return self._theme

    def set_theme(self, val):
        def _get_theme_css(theme):
            return '/'.join((self._src, 'dijit', 'themes',
                             theme, '%s.css' % theme))

        current_theme = getattr(self, '_theme', None)
        if current_theme is not None:
            theme_css = _get_theme_css(current_theme)
            if theme_css in self._stylesheets:
                self._stylesheets.remove(theme_css)

        self._theme = val
        theme_css = _get_theme_css(self._theme)
        self.append_stylesheet(theme_css)
    theme = property(get_theme, set_theme)

    def get_modules(self):
        return self._modules

    def set_modules(self, val):
        self._modules = val
    modules = property(get_modules, set_modules)

    def get_stylesheets(self):
        return self._stylesheets

    def set_stylesheets(self, val):
        self._stylesheets = val
    stylesheets = property(get_stylesheets, set_stylesheets)

    def get_aols(self):
        return self._aols

    def set_aols(self, val):
        self._aols = val
    aols = property(get_aols, set_aols)

    def set_module_path(self, module, path):
        """Short cut for setting module path in djConfig."""

        if 'modulePaths' not in self.dj_config:
            self.dj_config['modulePaths'] = {}
        self.dj_config['modulePaths'][module] = path

    def append_module(self, module):
        """Add a required module."""

        if module not in self._modules:
            self._modules.append(module)

    def append_stylesheet(self, stylesheet):
        """Add a stylesheet."""

        if stylesheet not in self._stylesheets:
            self._stylesheets.append(stylesheet)

    def append_aol(self, aol):
        """Add an addOnLoad function."""

        self._aols.append(aol)

    def prepend_aol(self, aol):
        self._aols.insert(0, aol)

    def dojo_form(self, form, form_function=None):
        """
        Generates Javascript to instrument a Dijit form.

        Adds an addOnLoad handler that instantiates a
        Dijit form when the page loads.

        Any fields that have been setup with dojo_field
        will also be instantiated as Dijits.

        """

        dijits = []
        for bound_field in form:
            field = bound_field.field
            dojo_type = getattr(field, '_dojo_type', None)
            if dojo_type is not None:
                # This is a dojo enabled widget
                params = getattr(field, '_dojo_params', {})
                params['dojoType'] = dojo_type
                self.append_module(dojo_type)
                dijits.append({'name': bound_field.name, 'params': params})

        json_dijits = json.dumps(dijits)

        self.append_module('dijit.form.Form')

        if getattr(form, 'id', None) is None:
            # Form must have a unique id for
            # JS form instrumentation to work
            # correctly.
            form.id = str(uuid.uuid1())

        if form_function is None:
            # Form function wasn't passed,
            # use form_function member var instead
            form_function = self.form_function

        if form_function is None:
            # Generate JS
            self.append_module('dojo.parser')
            self.append_aol(self._build_form_js(form.id, json_dijits))
        else:
            # Call existing JS function
            self.append_module(form_function[0])
            function_name = '.'.join(form_function)
            function_call = "function() {%s('%s', %s);}" % (function_name, form.id, json_dijits)
            self.append_aol(function_call)

    def _build_form_js(self, form_id, json_dijits):
        """Builds a JS function string used to dijitize a form."""

        return """
function () {
    var dijitForm = dojo.byId('%(form_id)s');
    if (dijitForm != null) {
        var elements = [];
        var dijits = %(json_dijits)s;
        dojo.forEach(dijits, function(dijit) {
            var n = dijitForm.elements[dijit.name];
            if (n != null) {
                dojo.attr(n, dijit.params);
                elements.push(n);
            }
        });

        dojo.attr(dijitForm, {dojoType: 'dijit.form.Form'}); 
        elements.push(dijitForm);
        dojo.parser.instantiate(elements);
    }
}
""" % {'form_id': form_id, 'json_dijits': json_dijits}