from decimal import Decimal as D

from django.utils.translation import ugettext_lazy as _

from oscar.apps.shipping.base import Base


class Free(Base):
    code = 'free-shipping'
    name = _('Free shipping')
    is_tax_known = True
    charge_incl_tax = charge_excl_tax = D('0.00')


class NoShippingRequired(Free):
    """
    This is a special shipping method that indicates that no shipping is
    actually required (eg for digital goods).
    """
    code = 'no-shipping-required'
    name = _('No shipping required')


class FixedPrice(Base):
    code = 'fixed-price-shipping'
    name = _('Fixed price shipping')

    def __init__(self, charge_excl_tax, charge_incl_tax=None):
        self.charge_excl_tax = charge_excl_tax
        if charge_incl_tax is not None:
            self.charge_incl_tax = charge_incl_tax
            self.is_tax_known = True


class OfferDiscount(Base):
    """
    Wrapper class that applies a discount to an existing shipping method's
    charges
    """

    def __init__(self, method, offer):
        self.method = method
        self.offer = offer

    @property
    def is_discounted(self):
        # We check to see if the discount is non-zero.  It is possible to have
        # zero shipping already in which case this the offer does not lead to
        # any further discount.
        return self.discount > 0

    @property
    def discount(self):
        return self.get_discount()['discount']

    @property
    def code(self):
        return self.method.code

    @property
    def name(self):
        return self.method.name

    @property
    def description(self):
        return self.method.description

    def get_discount(self):
        # Return a 'discount' dictionary in the same form as that used by the
        # OfferApplications class
        return {
            'offer': self.offer,
            'result': None,
            'name': self.offer.name,
            'description': '',
            'voucher': self.offer.get_voucher(),
            'freq': 1,
            'discount': self.effective_discount}

    @property
    def charge_incl_tax_before_discount(self):
        return self.method.charge_incl_tax

    @property
    def charge_excl_tax_before_discount(self):
        return self.method.charge_excl_tax

    @property
    def is_tax_known(self):
        return self.method.is_tax_known

    @property
    def effective_discount(self):
        """
        The discount value.
        """
        raise NotImplemented()

    @property
    def charge_excl_tax(self):
        raise NotImplemented()

    @property
    def charge_incl_tax(self):
        raise NotImplemented()


class TaxExclusiveOfferDiscount(OfferDiscount):

    @property
    def effective_discount(self):
        parent_charge = self.method.charge_excl_tax
        return self.offer.shipping_discount(parent_charge)

    @property
    def charge_excl_tax(self):
        parent_charge = self.method.charge_excl_tax
        discount = self.offer.shipping_discount(parent_charge)
        return parent_charge - discount

    @property
    def charge_incl_tax(self):
        """
        Tax needs to be assigned later on
        """
        return self.charge_excl_tax + self.tax


class TaxInclusiveOfferDiscount(OfferDiscount):

    @property
    def effective_discount(self):
        parent_charge = self.method.charge_incl_tax
        return self.offer.shipping_discount(parent_charge)

    @property
    def charge_incl_tax(self):
        parent_charge = self.method.charge_incl_tax
        discount = self.offer.shipping_discount(parent_charge)
        return parent_charge - discount

    @property
    def charge_excl_tax(self):
        # Adjust tax exclusive rate using the ratio of the two tax inclusive
        # charges.
        parent_charge_excl_tax = self.method.charge_excl_tax
        parent_charge_incl_tax = self.method.charge_incl_tax
        charge_incl_tax = self.charge_incl_tax
        if parent_charge_incl_tax == 0:
            return D('0.00')
        charge = parent_charge_excl_tax * (charge_incl_tax /
                                           parent_charge_incl_tax)
        return charge.quantize(D('0.01'))
