from decimal import Decimal

from django import forms
from django.forms.formsets import BaseFormSet, DEFAULT_MAX_NUM
from django.utils.translation import pgettext, ugettext
from satchless.item import InsufficientStock


class QuantityField(forms.DecimalField):

    def __init__(self, *args, **kwargs):
        super(QuantityField, self).__init__(
            *args, max_value=None, min_value=Decimal(0), max_digits=10,
            decimal_places=4, initial=Decimal(1), **kwargs)


class AddToCartForm(forms.Form):
    '''
    Class use product and cart instance.
    '''
    quantity = QuantityField(label=pgettext('Form field', 'Quantity'))
    error_messages = {
        'empty-stock': ugettext(
            'Out of stock.'),
        'insufficient-stock': ugettext(
            'Only %(remaining)d remaining in stock.')}

    def __init__(self, *args, **kwargs):
        self.cart = kwargs.pop('cart')
        self.product = kwargs.pop('product')
        self.cart_line = self.cart.get_line(self.product)
        super(AddToCartForm, self).__init__(*args, **kwargs)

    def clean_quantity(self):
        quantity = self.cleaned_data['quantity']
        used_quantity = self.cart_line.quantity if self.cart_line else 0
        new_quantity = quantity + used_quantity
        try:
            self.cart.check_quantity(self.product, new_quantity, None)
        except InsufficientStock as e:
            remaining = e.item.stock - used_quantity
            if remaining:
                msg = self.error_messages['insufficient-stock']
            else:
                msg = self.error_messages['empty-stock']
            raise forms.ValidationError(msg % {'remaining': remaining})
        return quantity

    def save(self):
        '''
        Adds CartLine into the Cart instance.
        '''
        return self.cart.add(self.product, self.cleaned_data['quantity'])


class ReplaceCartLineForm(AddToCartForm):
    '''
    Replaces quantity in CartLine.
    '''
    def clean_quantity(self):
        quantity = self.cleaned_data['quantity']
        try:
            self.cart.check_quantity(self.product, quantity, None)
        except InsufficientStock as e:
            msg = self.error_messages['insufficient-stock']
            raise forms.ValidationError(msg % {'remaining': e.item.stock})
        return quantity

    def save(self):
        '''
        Replace quantity.
        '''
        return self.cart.add(self.product, self.cleaned_data['quantity'],
                             replace=True)


class ReplaceCartLineFormSet(BaseFormSet):
    '''
    Formset for all CartLines in the cart instance.
    '''
    absolute_max = 9999
    can_delete = False
    can_order = False
    extra = 0
    form = ReplaceCartLineForm
    max_num = DEFAULT_MAX_NUM
    validate_max = False

    def __init__(self, *args, **kwargs):
        self.cart = kwargs.pop('cart')
        kwargs['initial'] = [{'quantity': cart_line.get_quantity()}
                             for cart_line in self.cart
                             if cart_line.get_quantity()]
        super(ReplaceCartLineFormSet, self).__init__(*args, **kwargs)

    def _construct_form(self, i, **kwargs):
        kwargs['cart'] = self.cart
        kwargs['product'] = self.cart[i].product
        return super(ReplaceCartLineFormSet, self)._construct_form(i, **kwargs)

    def save(self):
        for form in self.forms:
            form.save()

@@ -47,16 +47,88 @@
 t forms%0A
+from django.core.exceptions import ObjectDoesNotExist, NON_FIELD_ERRORS%0A
 from dja
@@ -763,29 +763,16 @@
 gettext(
-%0A            
 'Out of 
@@ -776,16 +776,89 @@
 of stock
+.'),%0A        'variant-does-not-exists': ugettext('Variant does not exists
 .'),%0A   
@@ -1208,32 +1208,81 @@
 ef clean
-_quantity(self):
+(self):%0A        cleaned_data = super(AddToCartForm, self).clean()
 %0A       
@@ -1285,37 +1285,32 @@
      quantity = 
-self.
 cleaned_data%5B'qu
@@ -1297,33 +1297,37 @@
 y = cleaned_data
-%5B
+.get(
 'quantity'%5D%0A    
@@ -1312,34 +1312,71 @@
 a.get('quantity'
-%5D%0A
+)%0A        if quantity is not None:%0A    
         used_qua
@@ -1440,16 +1440,20 @@
         
+    
 new_quan
@@ -1484,32 +1484,36 @@
 uantity%0A        
+    
 try:%0A           
@@ -1517,44 +1517,346 @@
     
-self.cart.check_quantity(self.produc
+    product_variant = self.get_variant(cleaned_data)%0A            except ObjectDoesNotExist:%0A                msg = self.error_messages%5B'variant-does-not-exists'%5D%0A                self.add_error(NON_FIELD_ERRORS, msg)%0A            else:%0A                try:%0A                    self.cart.check_quantity(%0A                        product_varian
 t, n
@@ -1866,32 +1866,40 @@
 quantity, None)%0A
+        
         except I
@@ -1933,16 +1933,24 @@
         
+        
 remainin
@@ -1994,16 +1994,24 @@
         
+        
 if remai
@@ -2016,16 +2016,24 @@
 aining:%0A
+        
         
@@ -2092,38 +2092,53 @@
 k'%5D%0A            
-else:%0A
+        else:%0A       
                 
@@ -2129,32 +2129,33 @@
                 
+ 
 msg = self.error
@@ -2195,36 +2195,43 @@
     
-raise forms.ValidationError(
+        self.add_error('quantity', 
 msg 
@@ -2269,32 +2269,36 @@
      return 
-quantity
+cleaned_data
 %0A%0A    def sa
@@ -2389,72 +2389,335 @@
     
-return self.cart.add(self.product, self.cleaned_data%5B'quantity'%5D
+product_variant = self.get_variant(self.cleaned_data)%0A        return self.cart.add(product_variant, self.cleaned_data%5B'quantity'%5D)%0A%0A    def get_variant(self):%0A        raise NotImplementedError()%0A%0A    def add_error(self, name, value):%0A        errors = self.errors.setdefault(name, self.error_class())%0A        errors.append(value
 )%0A%0A%0A

Add variant functionality in cart