##
## dictwidget.py
## Login : <uli@pu.smp.net>
## Started on  Sun Jul 18 03:05:27 2010 Uli Fouquet
## $Id$
## 
## Copyright (C) 2010 Uli Fouquet
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
## 
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import grok
from zope.browserpage import ViewPageTemplateFile
from zope.component import getMultiAdapter
from zope.formlib.i18n import _
from zope.formlib.interfaces import (
    ISimpleInputWidget, IInputWidget, IDisplayWidget)
from zope.formlib.sequencewidget import SequenceWidget
from zope.formlib.widget import DisplayWidget, renderElement
from zope.i18n import translate
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.schema.interfaces import IDict, IField

class DictWidget(SequenceWidget):
    _type = dict
    #grok.adapts(IDict, IField, IBrowserRequest)
    #grok.adapts(IDict, IBrowserRequest)
    #grok.provides(ISimpleInputWidget)
    template = ViewPageTemplateFile('dictwidget.pt')

    
    def __init__(self, context, request, subwidget=None):
        super(DictWidget, self).__init__(context, None, request)
        self.subwidget = subwidget

        # The subwidgets are cached in this dict if preserve_widgets is True.
        self._widgets = {}
        self.preserve_widgets = False

    def _getWidget(self, i):
        """Return a widget for the i-th number of the sequence.

        Normally this method creates a new widget each time, but when
        the ``preserve_widgets`` attribute is True, it starts caching
        widgets.  We need it so that the errors on the subwidgets
        would appear only if ``getInputValue`` was called.

        ``getInputValue`` on the subwidgets gets called on each
        request that has data.
        """
        if i not in self._widgets:
            key_field = self.context.key_type
            if self.subwidget is not None:
                key_widget = self.subwidget(key_field, self.request)
            else:
                key_widget = getMultiAdapter(
                    (key_field, self.request), IInputWidget)
            #key_widget.setPrefix('%s.key.%d.' % (self.name, i))
            key_widget.setPrefix('%s.key.%d.' % (self.name, i))
            
            value_field = self.context.value_type
            if self.subwidget is not None:
                value_widget = self.subwidget(value_field, self.request)
            else:
                value_widget = getMultiAdapter(
                    (value_field, self.request), IInputWidget)
            #value_widget.setPrefix('%s.value.%d.' % (self.name, i))
            value_widget.setPrefix('%s.value.%d.' % (self.name, i))
            #import pdb; pdb.set_trace()
            if not self.preserve_widgets:
                #return super(DictWidget, self)._getWidget(i)
                return (key_widget, value_widget)
            self._widgets[i] = (key_widget, value_widget)
        #return super(DictWidget, self)._getWidget(i)
        return self._widgets[i]
            
        if i not in self._widgets:
            field = self.context.value_type
            if self.subwidget is not None:
                widget = self.subwidget(field, self.request)
            else:
                widget = getMultiAdapter(
                    (field, self.request), IInputWidget)
            widget.setPrefix('%s.%d.' % (self.name, i))
            if not self.preserve_widgets:
                return widget
            self._widgets[i] = widget
        return self._widgets[i]

    def widgets(self):
        """Return a list of widgets to display"""
        sequence = self._getRenderedValue()
        result = []
        for i, value in enumerate(sequence.items()):
            widget = self._getWidget(i)
            widget[0].setRenderedValue(value[0])
            widget[1].setRenderedValue(value[1])
            result.append(widget)
        return result

    def _generateSequence(self):
        """Extract the values of the subwidgets from the request.

        Returns a list of key-value tuples.

        This can only be called if self.hasInput() returns true.
        """
        if self.context.value_type is None:
            # Why would this ever happen?
            return []
        if self.context.key_type is None:
            return []
        # the marker field tells how many individual items were
        # included in the input; we check for exactly that many input
        # widgets
        try:
            count = int(self.request.form[self.name + ".count"])
        except ValueError:
            # could not convert to int; the input was not generated
            # from the widget as implemented here
            raise WidgetInputError(self.context.__name__, self.context.title)

        # pre-populate
        sequence = [(None, None)] * count
        #sequence = [None] * count
        #sequence = dict()

        # now look through the request for interesting values
        # in reverse so that we can remove items as we go
        removing = self.name + ".remove" in self.request.form
        for i in reversed(range(count)):
            #widget = self._getWidget(i)
            key_widget, value_widget = self._getWidget(i)
            key = None
            value = None
            if key_widget.hasValidInput():
                # catch and set sequence widget errors to ``_error`` attribute
                try:
                    key = key_widget.getInputValue()
                    #sequence[i] = widget.getInputValue()
                except WidgetInputError, error:
                    self._error = error
                    raise self._error
            if value_widget.hasValidInput():
                # catch and set sequence widget errors to ``_error`` attribute
                try:
                    value = value_widget.getInputValue()
                    #sequence[i] = widget.getInputValue()
                except WidgetInputError, error:
                    self._error = error
                    raise self._error
            sequence[i] = (key, value)
            #if widget.hasValidInput():
            #    # catch and set sequence widget errors to ``_error`` attribute
            #    try:
            #        sequence[i] = widget.getInputValue()
            #    except WidgetInputError, error:
            #        self._error = error
            #        raise self._error

            remove_key = "%s.remove_%d" % (self.name, i)
            if remove_key in self.request.form and removing:
                del sequence[i]

        # add an entry to the list if the add button has been pressed
        if self.name + ".add" in self.request.form:
            # Should this be using self.context.value_type.missing_value
            # instead of None?
            sequence.append((None, None))

        return sequence

    
    def getInputValue(self):
        """Return converted and validated widget data.

        If there is no user input and the field is required, then a
        ``MissingInputError`` will be raised.

        If there is no user input and the field is not required, then
        the field default value will be returned.

        A ``WidgetInputError`` is raised in the case of one or more
        errors encountered, inputting, converting, or validating the data.
        """
        sequence = super(DictWidget, self).getInputValue()
        return sequence
        return self._type(sequence)

    def _getRenderedValue(self):
        """Returns a dict from the request or _data"""
        if self._renderedValueSet():
            if self._data is self.context.missing_value:
                #sequence = []
                sequence = dict()
            else:
                #sequence = list(self._data)
                sequence = dict(self._data)
        elif self.hasInput():
            #sequence = self._generateSequence()
            sequence = dict(self._generateSequence())
        elif self.context.default is not None:
            #sequence = self.context.default
            sequence = self.context.default
        else:
            #sequence = []
            seuence = dict()
        # ensure minimum number of items in the form
        #while len(sequence) < self.context.min_length:
        #    # Shouldn't this use self.field.value_type.missing_value,
        #    # instead of None?
        #    #sequence.append(None)
        #    pass
        return sequence

    # TODO: applyChanges isn't reporting "change" correctly (we're
    # re-generating the sequence with every edit, and need to be smarter)
    def applyChanges(self, content):
        field = self.context
        value = self.getInputValue()
        change = field.query(content, self) != value
        if change:
            print "SET: ", field, content, value
            field.set(content, value)
        else:
            print "NO SET: ", field, content, value
        return change

    
# Basic display widget

class DictDisplayWidget(DisplayWidget):

    _missingValueMessage = _("sequence-value-not-provided",
                             u"(no value available)")

    _emptySequenceMessage = _("sequence-value-is-empty",
                              u"(no values)")

    tag = "ol"
    itemTag = "li"
    cssClass = "sequenceWidget"
    extra = ""

    def __init__(self, context, request, subwidget=None):
        # def __init__(self, context, field, request, subwidget=None):
        super(DictDisplayWidget, self).__init__(context, request)
        self.subwidget = subwidget

    def __call__(self):
        # get the data to display:
        #import pdb; pdb.set_trace()
        if self._renderedValueSet():
            data = self._data
        else:
            data = self.context.get(self.context.context)

        # deal with special cases:
        if data == self.context.missing_value:
            return translate(self._missingValueMessage, self.request)
        #data = list(data)
        data = dict(data)
        if not data:
            return translate(self._emptySequenceMessage, self.request)

        parts = []
        # for i, item in enumerate(data):
        for i, item in enumerate(data.items()):
            #widget = self._getWidget(i)
            key_widget, value_widget = self._getWidget(i)
            key_widget.setRenderedValue(item[0])
            value_widget.setRenderedValue(item[1])
            #widget.setRenderedValue(item)
            #s = widget()
            s = "%s: %s" % (key_widget(), value_widget())
            if self.itemTag:
                s = "<%s>%s</%s>" % (self.itemTag, s, self.itemTag)
            parts.append(s)
        contents = "\n".join(parts)
        if self.tag:
            contents = "\n%s\n" % contents
            contents = renderElement(self.tag,
                                     cssClass=self.cssClass,
                                     extra=self.extra,
                                     contents=contents)
        return contents

    def _getWidget(self, i):
        """Get a tuple of key-value widgets for the i-th 'row' of dict.
        """
        key_field = self.context.key_type
        value_field = self.context.value_type
        if self.subwidget is not None:
            key_widget = self.subwidget(key_field, self.request)
            value_widget = self.subwidget(value_field, self.request)
        else:
            key_widget = getMultiAdapter(
                (key_field, self.request), IDisplayWidget)
            value_widget = getMultiAdapter(
                (value_field, self.request), IDisplayWidget)
        key_widget.setPrefix('%s.key.%d.' % (self.name, i))
        value_widget.setPrefix('%s.value.%d.' % (self.name, i))
        return (key_widget, value_widget)

