## $Id: vocabularies.py 12839 2015-03-31 17:55:45Z 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, APPROVED
    )
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)


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

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


class RefereeSource(FactoredContextualSource):

      implements(IRefereeSource)


class RefereeSourceFactory(BasicContextualSourceFactory):
    """A referee source delivers all registered and approved customers.
    """

    source_class = RefereeSource

    def getValues(self, context):
        user_id = getattr(getattr(context, 'customer', None), 'user_id', None)
        catalog = getUtility(ICatalog, name='customers_catalog')
        results = catalog.searchResults(state=(APPROVED, APPROVED))
        return list(results)

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

    def getTitle(self, context, value):
        return "%s (%s)" % (value.display_fullname, value.customer_id[:9])
