## $Id: payment.py 12700 2015-03-09 04:45:52Z uli $
##
## 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
##
"""
These are the payment tickets.
"""
import decimal
import grok
import uuid
from datetime import datetime
from zope.catalog.interfaces import ICatalog
from zope.component import getUtilitiesFor, getUtility
from zope.event import notify
from waeup.ikoba.interfaces import MessageFactory as _
from waeup.ikoba.utils.helpers import attrs_to_fields
from waeup.ikoba.utils.logger import Logger
from waeup.ikoba.payments.interfaces import (
    IPayment, STATE_UNPAID, STATE_FAILED, STATE_PAID,
    IPaymentGatewayService, IPayer, IPaymentItem, IPayee,
    IPaymentGatewayServicesLister,
    )


def get_payment(payment_id):
    """Get payment by payment id.

    If no such payment can be found in catalog, return none.
    """
    cat = getUtility(ICatalog, name='payments_catalog')
    result_set = [x for x in cat.searchResults(
        payment_id = (payment_id, payment_id))]
    if len(result_set):
        return result_set[0]
    return None


def format_payment_item_values(payment_item_values, currency):
    """Format tuples (description, currency, amount) for output.

    `currency` passed in is the 'target' currency.

    Returns a list of formated values. Last item is total sum.
    XXX: we do not really respect currency. If different items
         have different currencies, we are choked.
    """
    result = []
    total = decimal.Decimal("0.00")
    for descr, item_currency, amount in payment_item_values:
        total += amount
        if item_currency != currency:
            raise ValueError(
                "Different currencies in payment items not supported.")
        result.append((descr, '%s %0.2f' % (item_currency, amount)))
    result.append((_('Total'), '%s %0.2f' % (currency, total)))
    return result


def get_payment_providers():
    """Get all payment providers registered.
    """
    return dict(
        getUtilitiesFor(IPaymentGatewayService)
    )


class PaymentGatewayServicesLister(grok.GlobalUtility):
    grok.implements(IPaymentGatewayServicesLister)

    def __call__(self):
        """Get all services of payment gateways registered.
        """
        return get_payment_providers()


class PaymentProviderServiceBase(grok.GlobalUtility):

    grok.baseclass()
    grok.implements(IPaymentGatewayService)

    title = u'Sample Credit Card Service'


@attrs_to_fields
class Payment(grok.Container, Logger):
    """This is a payment.
    """
    grok.implements(IPayment)
    grok.provides(IPayment)

    logger_name = 'waeup.ikoba.${sitename}.payments'
    logger_filename = 'payments.log'
    logger_format_str = '"%(asctime)s","%(user)s",%(message)s'

    @property
    def amount(self):
        """The amount of a payment.

        Equals the sum of items contained.
        """
        return sum(
            [item.amount for item in self.values()],
            decimal.Decimal("0.00")  # default value
        )

    def __init__(self):
        super(Payment, self).__init__()
        self.creation_date = datetime.utcnow()
        self.payment_date = None
        self.payment_id = u'PAY_' + unicode(uuid.uuid4().hex)
        self.state = STATE_UNPAID
        return

    def approve(self, payment_date=None):
        """A payment was approved.

        Successful ending; the payment is marked as payed.

        If `payment_date` is given, it must be a datetime object
        giving a datetime in UTC timezone.

        Raises ObjectModifiedEvent.
        """
        if payment_date is None:
            payment_date = datetime.utcnow()
        self.payment_date = payment_date
        self.state = STATE_PAID
        notify(grok.ObjectModifiedEvent(self))

    def mark_failed(self, reason=None):
        """Mark payment as failed.

        Raises ObjectModifiedEvent.
        """
        self.state = STATE_FAILED
        notify(grok.ObjectModifiedEvent(self))

    def add_payment_item(self, item):
        """Add `item`

        Returns the key under which the `item` was stored. Please do
        not make anby assumptions about the key. It will be a
        string. That is all we can tell.

        """
        cnt = 0
        while str(cnt) in self:
            cnt += 1
        self[str(cnt)] = item
        return str(cnt)


@attrs_to_fields
class Payer(object):
    """A Payment is for testing.

    It cannot be stored in ZODB.
    """
    grok.implements(IPayer)


@attrs_to_fields
class PaymentItem(grok.Model):

    grok.implements(IPaymentItem)

    def __init__(self):
        super(PaymentItem, self).__init__()


@attrs_to_fields
class Payee(object):
    """Someone being paid.

    This is for testing only and cannot be stored in ZODB.
    """
    grok.implements(IPayee)
