## $Id: vocabularies.py 12770 2015-03-15 13:26:44Z henrik $
##
## Copyright (C) 2014 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
##
"""Vocabularies and sources for the customer section.
"""
import grok
from zope.component import getUtility, queryUtility
from zope.catalog.interfaces import ICatalog
from zope.interface import implements, directlyProvides
from zope.schema.interfaces import (
    ISource, IContextSourceBinder, ValidationError)
from zc.sourcefactory.basic import BasicSourceFactory
from zc.sourcefactory.contextual import BasicContextualSourceFactory
from zc.sourcefactory.source import FactoredContextualSource
from zc.sourcefactory.interfaces import IContextualSource
from waeup.ikoba.sourcefactory import SmartBasicContextualSourceFactory
from waeup.ikoba.interfaces import SimpleIkobaVocabulary, SUBMITTED, VERIFIED
from waeup.ikoba.interfaces import MessageFactory as _
from waeup.ikoba.utils.helpers import get_sorted_preferred
from waeup.ikoba.utils.countries import COUNTRIES
from waeup.ikoba.payments.currencies import ISO_4217_CURRENCIES

#: a tuple of tuples (<COUNTRY-NAME>, <ISO-CODE>) with Nigeria first.
COUNTRIES = get_sorted_preferred(COUNTRIES, ['NG'])
nats_vocab = SimpleIkobaVocabulary(*COUNTRIES)


class GenderSource(BasicSourceFactory):
    """A gender source delivers basically a mapping
       ``{'m': 'Male', 'f': 'Female'}``

       Using a source, we make sure that the tokens (which are
       stored/expected for instance from CSV files) are something one
       can expect and not cryptic IntIDs.
    """
    def getValues(self):
        return ['m', 'f']

    def getToken(self, value):
        return value[0].lower()

    def getTitle(self, value):
        if value == 'm':
            return _('male')
        if value == 'f':
            return _('female')


class RegNumNotInSource(ValidationError):
    """Registration number in use.
    """
    # The docstring of ValidationErrors is used as error description
    # by zope.formlib.
    pass

class EmailNotInSource(ValidationError):
    """Email address in use.
    """
    pass


class RegNumberSource(object):
    """A source that accepts any entry for a certain field if not used
    already.

    Using this kind of source means a way of setting an invariant.

    We accept a value iff:
    - the value cannot be found in catalog or
    - the value can be found as part of some item but the bound item
      is the context object itself.
    """
    implements(ISource)
    cat_name = 'customers_catalog'
    field_name = 'reg_number'
    validation_error = RegNumNotInSource
    comp_field = 'customer_id'

    def __init__(self, context):
        self.context = context
        return

    def __contains__(self, value):
        """We accept all values not already given to other customers.
        """
        cat = queryUtility(ICatalog, self.cat_name)
        if cat is None:
            return True
        kw = {self.field_name: (value, value)}
        results = cat.searchResults(**kw)
        for entry in results:
            if not hasattr(self.context, self.comp_field):
                # we have no context with comp_field (most probably
                # while adding a new object, where the container is
                # the context) which means that the value was given
                # already to another object (as _something_ was found in
                # the catalog with that value). Fail on first round.
                raise self.validation_error(value)
            if getattr(entry, self.comp_field) != getattr(
                self.context, self.comp_field):
                # An entry already given to another customer is not in our
                # range of acceptable values.
                raise self.validation_error(value)
                #return False
        return True


def contextual_reg_num_source(context):
    source = RegNumberSource(context)
    return source

directlyProvides(contextual_reg_num_source, IContextSourceBinder)

class EmailSource(RegNumberSource):
    field_name = 'email'
    validation_error = EmailNotInSource

def contextual_email_source(context):
    source = EmailSource(context)
    return source
directlyProvides(contextual_email_source, IContextSourceBinder)


class ConCatProductSource(BasicContextualSourceFactory):
    """A contract category product source delivers all products
    which belong to a certain contract_category.
    """

    def getValues(self, context):
        concat = getattr(context, 'contract_category', None)
        products = grok.getSite()['products'].values()
        if not concat:
            return products
        resultlist = [
            value for value in products if value.contract_category == concat]
        return resultlist

    def getToken(self, context, value):
        return value.product_id

    def getTitle(self, context, value):
        return "%s - %s" % (value.product_id, value.title)


class ConCatActiveProductSource(ConCatProductSource):
    """A contract category product source delivers all products
    which belong to a certain contract_category.
    """

    def getValues(self, context):
        concat = getattr(context, 'contract_category', None)
        products = grok.getSite()['products'].values()
        if not concat:
            return products
        
        resultlist = [
            value for value in products
            if value.contract_category == concat and value.active
            ]
        return resultlist


class CustomerDocumentSource(BasicContextualSourceFactory):
    """A customer document source delivers all submitted and verified documents
    of the context customer.
    """

    def getValues(self, context):
        # When checking conversion during import, contracts do not belong to
        # customers. Thus all portal documents must be returned.
        user_id = getattr(getattr(context, 'customer', None), 'user_id', None)
        catalog = getUtility(ICatalog, name='documents_catalog')
        results = catalog.searchResults(user_id=(user_id, user_id))
        resultlist = [
            value for value in results if value.state in (SUBMITTED, VERIFIED)]
        return resultlist

    def getToken(self, context, value):
        return value.document_id

    def getTitle(self, context, value):
        return "%s... - %s" % (value.document_id[:9], value.title)


class IProductOptionSource(IContextualSource):
    """A source operating in context.

    This is a marker interface needed for the registration of the
    IkobaSequenceWidget when using the List-Choice-ProductOptionSourceFactory
    field combination primarily in IContract as a replacement for
    the SourceOrderedMultiSelectWidget.
    """


class ProductOptionSource(FactoredContextualSource):

      implements(IProductOptionSource)


class ProductOptionSourceFactory(BasicContextualSourceFactory):
    """A product option source delivers all options belonging to
    a selected product.
    """

    source_class = ProductOptionSource

    def getValues(self, context):
        options = []
        recent_options = getattr(
            getattr(context, 'product_object', None), 'options', [])
        # Check and add stored options first before adding recent
        # product options.
        if context.product_options:
            for stored_option in context.product_options:
                if stored_option not in recent_options:
                    options.append(stored_option)
        options += recent_options
        return options

    def getToken(self, context, value):
        return "%s_%s_%s" % (value.title, value.fee, value.currency)

    def getTitle(self, context, value):
        currency = ISO_4217_CURRENCIES[value.currency][1]
        return "%s @ %s %s" % (value.title, value.fee, currency)
