## $Id: converters.py 7709 2012-02-27 11:53:51Z henrik $
##
## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
## 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
##
"""Converters for zope.schema-based datatypes.
"""
import grok
from zope.component import createObject
from zope.formlib import form
from zope.formlib.boolwidgets import CheckBoxWidget
from zope.formlib.form import (
    _widgetKey, WidgetInputError, ValidationError, InputErrors, expandPrefix)
from zope.formlib.interfaces import IInputWidget, ISimpleInputWidget
from zope.interface import Interface
from zope.publisher.browser import TestRequest
from waeup.sirp.interfaces import IObjectConverter

class ExtendedCheckBoxWidget(CheckBoxWidget):
    """A checkbox widget that supports more input values as True/False
    markers.

    The default bool widget expects the string 'on' as only valid
    ``True`` value in HTML forms for bool fields.

    This widget also accepts '1', 'true' and 'yes' for that. Also all
    uppercase/lowecase combinations of these strings are accepted.

    The widget still renders ``True`` to ``'on'`` when a form is
    generated.
    """
    true_markers = ['1', 'true', 'on', 'yes']

    def _toFieldValue(self, input):
        """Convert from HTML presentation to Python bool."""
        if not isinstance(input, basestring):
            return False
        return input.lower() in self.true_markers

    def _getFormInput(self):
        """Returns the form input used by `_toFieldValue`.

        Return values:

          ``'on'``  checkbox is checked
          ``''``    checkbox is not checked
          ``None``  form input was not provided

        """
        value = self.request.get(self.name)
        if isinstance(value, basestring):
            value = value.lower()
        if value in self.true_markers:
            return 'on'
        elif self.name + '.used' in self.request:
            return ''
        else:
            return None

def getWidgetsData(widgets, form_prefix, data):
    """Get data and validation errors from `widgets` for `data`.

    Updates the dict in `data` with values from the widgets in
    `widgets`.

    Returns a list of tuples ``(<WIDGET_NAME>, <ERROR>)`` where
    ``<WIDGET_NAME>`` is a widget name (normally the same as the
    associated field name) and ``<ERROR>`` is the exception that
    happened for that widget/field.

    This is merely a copy from the same-named function in
    :mod:`zope.formlib.form`. The only difference is that we also
    store the fieldname for which a validation error happened in the
    returned error list (what the original does not do).

    """
    errors = []
    form_prefix = expandPrefix(form_prefix)

    for input, widget in widgets.__iter_input_and_widget__():
        if input and IInputWidget.providedBy(widget):
            name = _widgetKey(widget, form_prefix)

            if not widget.hasInput():
                continue

            try:
                data[name] = widget.getInputValue()
            except ValidationError, error:
                # convert field ValidationError to WidgetInputError
                error = WidgetInputError(widget.name, widget.label, error)
                errors.append((name, error))
            except InputErrors, error:
                errors.append((name, error))

    return errors


class DefaultObjectConverter(grok.Adapter):
    """Turn string values into real values.

    A converter can convert string values for objects that implement a
    certain interface into real values based on the given interface.
    """

    grok.context(Interface)
    grok.provides(IObjectConverter)

    def __init__(self, iface):
        self.iface = iface
        # Omit known dictionaries since there is no widget available
        # for dictionary schema fields
        self.default_form_fields = form.Fields(iface).omit('description_dict')
        return

    def fromStringDict(self, data_dict, context, form_fields=None):
        """Convert values in `data_dict`.

        Converts data in `data_dict` into real values based on
        `context` and `form_fields`.

        `data_dict` is a mapping (dict) from field names to values
        represented as strings.

        The fields (keys) to convert can be given in optional
        `form_fields`. If given, form_fields should be an instance of
        :class:`zope.formlib.form.Fields`. Suitable instances are for
        example created by :class:`grok.AutoFields`.

        If no `form_fields` are given, a default is computed from the
        associated interface.

        The `context` can be an existing object (implementing the
        associated interface) or a factory name. If it is a string, we
        try to create an object using
        :func:`zope.component.createObject`.

        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
        <DATA_DICT>)`` where

        ``<FIELD_ERRORS>``
           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
           error that happened when validating the input data in
           `data_dict`

        ``<INVARIANT_ERRORS>``
           is a list of invariant errors concerning several fields

        ``<DATA_DICT>``
           is a dict with the values from input dict converted.

        If errors happen, i.e. the error lists are not empty, always
        an empty ``<DATA_DICT>`` is returned.

        If ``<DATA_DICT>`` is non-empty, there were no errors.
        """
        if form_fields is None:
            form_fields = self.default_form_fields

        request = TestRequest(form={})
        for key, val in data_dict.items():
            request.form['form.%s' % key] = val

        obj = context
        if isinstance(context, basestring):
            obj = createObject(context)

        widgets = form.setUpInputWidgets(
            form_fields, 'form', obj, request)

        new_data = dict()
        errors = getWidgetsData(widgets, 'form', new_data)

        invariant_errors = form.checkInvariants(form_fields, new_data)
        if errors or invariant_errors:
            err_messages = [(key, err.args[0]) for key, err in errors]
            invariant_errors = [err.message for err in invariant_errors]
            return err_messages, invariant_errors, {}

        return errors, invariant_errors, new_data
