from django import forms
from django.forms import ValidationError
from django.forms.formsets import BaseFormSet
from django.db.models.fields import BLANK_CHOICE_DASH
from django.contrib.contenttypes.models import ContentType

from appsearch.registry import OPERATOR_MAP

OPERATOR_CHOICES = [
    ("__lt", "<"),
    ("__gt", ">"),
    ("__iexact", "="),
    ('__icontains', "contains"),
    ('__range', "range" ),
    ('__isnull', "exists" ),
    ('__isnotnull', "doesn't exist" ),
]

class ModelSelectionForm(forms.Form):
    model = forms.ChoiceField(label="Search For")
    
    def __init__(self, registry, *args, **kwargs):
        self.registry = registry
        configurations = registry.get_configurations()
        super(ModelSelectionForm, self).__init__(*args, **kwargs)
        
        self.fields['model'].choices = BLANK_CHOICE_DASH + \
                [(c._content_type.id, c.verbose_name) for c in configurations]
    
    def clean_model(self):
        model = self.cleaned_data['model']
        try:
            model = ContentType.objects.get(id=model).model_class()
        except ContentType.DoesNotExist as e:
            raise ValidationError("Invalid choice")
        
        if model not in self.registry:
            raise ValidationError("Invalid choice")
        
        return model
    
    def get_selected_configuration(self):
        model_value = self.cleaned_data['model']
        return self.registry[model_value]
    
    def get_selected_model(self):
        """ Returns the select model's class. """
        
        return self.get_selected_configuration().model

class SearchForm(object):
    pass


class ConstraintForm(forms.Form):
    """
    Using an additional constructor parameter ``configuration``, an instance of
    ``registry.ModelSearch``, the form dynamically populates the choices for the ``field`` field.
    
    """
    
    type = forms.ChoiceField(label="Constraint", choices=[
        ('and', 'AND'),
        ('or', 'OR'),
    ])
    
    # Dynamically populated list of fields available for filtering
    field = forms.ChoiceField(label="Filter by", choices=[])
    
    # Dynamically populated list of valid operators for the chosen ``field``
    operator = forms.ChoiceField(label="Constraint type", choices=[])
    
    term = forms.CharField(label="Search term")
    end_term = forms.CharField(label="End term", required=False)
    
    def __init__(self, configuration, *args, **kwargs):
        """
        Receives the configuration for the model to represent (potentially ``None`` if the original
        ``ModelSelectionForm`` is blank or invalid).
        
        """
        
        super(ConstraintForm, self).__init__(*args, **kwargs)
        
        self.configuration = configuration
        if configuration:
            self.fields['field'].choices = configuration.get_searchable_field_choices()
    def clean_field(self):
        data = self.cleaned_data['field']
        try:
            data = self.configuration.reverse_field_hash(data)
        except ValueError:
            raise ValidationError("Invalid field")
        
        return data

class ConstraintFormset(BaseFormSet):
    """ Removes the first ``ConstraintForm``'s ``type`` field. """
    
    def __init__(self, configuration, *args, **kwargs):
        """ Stores the configuration until the forms are constructed. """
        self.configuration = configuration
        super(ConstraintFormset, self).__init__(*args, **kwargs)
    
    def _construct_form(self, i, **kwargs):
        """ Sends the specified model configuration to the form. """
        return super(ConstraintFormset, self)._construct_form(i, configuration=self.configuration, \
                **kwargs)
    
    # def add_fields(self, form, index):
    #     """ Removes the first form's ``type`` field. """
    #     super(ConstraintFormset, self).add_fields(form, index)
    #     if index == 0:
    #         del form.fields['type']

@@ -2893,32 +2893,83 @@
 an_field(self):%0A
+        %22%22%22 Convert field into ORM path tuple. %22%22%22%0A
         data = s
@@ -3144,44 +3144,657 @@
 lid 
-field%22)%0A        %0A        return data
+choice%22)%0A        %0A        return data%0A    %0A    def clean_operator(self):%0A        %22%22%22 Convert operator into ORM query type such as %22icontains%22 %22%22%22%0A        %0A        # The default cleaned_value will be the text from the UI, so the choices (which have been%0A        # reversed for default validation to accept the text as a valid choice) are converted to a%0A        # dictionary for accessing the intended ORM queryset language value.%0A        %0A        # Input: %22= equal%22%0A        # Output: %22iexact%22%0A        %0A        operator = self.cleaned_data%5B'operator'%5D%0A        operator = dict(self.fields%5B'operator'%5D.choices)%5Boperator%5D%0A        %0A        return operator
 %0A%0Acl

Add "clean" methods to constraint form