from django.forms.models import fields_for_model
from django.core.paginator import Paginator, EmptyPage, InvalidPage
from django.utils.datastructures import SortedDict


class ModelListItem(object):
    """ Item for a ModelList. It represent a model instance. """  
    
    def __init__(self, instance, fields = None, exclude=None):
        self.instance = instance
        self.fields = fields
        self.exclude = exclude    
    
    def as_table_row(self):
        """ 
        Returns the list item as a table row, where every field
        is a table cell. 
        """
        
        row = u"" 
        
        for field in self.as_field_list():
            row += u"<td>" + field + u"</td>"
        
        return row
    
    def as_field_list(self):
        """ Returns the list item as a list of pretty-printed values. """
        
        field_list = [] 
        
        for attr in fields_for_model(self.instance, self.fields, self.exclude).iterkeys():
            field_list.append(self.get_value(attr))
        
        return field_list
    
    def get_value(self, attr):
        """ 
        Returns a string representation of the value of the given field name.
        First tries to use a function get_<attr>_value, otherwise infers the
        value.
        Subclasses of this can define that function for specific display.
        """
        
        function_name = "get_" + attr + "_value"
        if hasattr(self, function_name):
            return getattr(self, function_name)(attr)
        
        #for choices
        choice_display = "get_" + attr + "_display" 
        if hasattr(self.instance, choice_display):
            return getattr(self.instance, choice_display)()
        
        value = getattr(self.instance, attr)
        if value is True:
            return u"Yes"
        elif value is False:
            return u"No"
        
        if value is None:
            return u""
        
        return unicode(value)
    

class ModelList(object):
    '''    
    An object representing a list of models to use in template rendering.
    It's analogous to the Django ModelForm.
    '''
    
    def __init__(self, instances=None, paginate_by=None, page=1, 
                 order_by=None):
        
        self.__complete_meta__()
        self.__override_parameters__(instances, paginate_by, order_by)
        
        self.items = [self.__new_item__(i) for i in self.Meta.instances]

        if self.Meta.order_by:
            self.__order__()
        
        if self.Meta.paginate_by:
            self.__paginate__(page)        
    
    
    def __new_item__(self, instance):
        """
        Given an instance to be added to the model list, a new model list item
        is created. Useful for overriding in subclasses to construct complex 
        list items.
        """
        
        return self.Meta.item_class(instance, self.Meta.fields, 
                                    self.Meta.exclude)
    
    def __complete_meta__(self):
        """
        This method ensures that the Meta class has all the needed attributes, 
        so only those to that change have to be overriden.
        """
        for field in dir(ModelList.Meta):
            if not hasattr(self.Meta, field):
                setattr(self.Meta, field, getattr(ModelList.Meta, field))
    
    def __override_parameters__(self, instances, paginate_by, order_by):
        if instances is not None:
            self.Meta.instances = instances
        
        if paginate_by is not None:
            self.Meta.paginate_by = paginate_by
        
        if order_by is not None:
            self.Meta.order_by = order_by
    
    def __paginate__(self, page):
        """
        Filters the queryset and returns the correct page object
        """
        paginator = Paginator(self.items, self.Meta.paginate_by)
        
        try:
            self.page_obj = paginator.page(page)
        except (EmptyPage, InvalidPage):
            self.page_obj = paginator.page(paginator.num_pages)
        
        self.items = self.page_obj.object_list
    
    def __order__(self):
        reverse = False
        
        if self.Meta.order_by.startswith('-'):
            reverse = True
            self.Meta.order_by = self.Meta.order_by[1:]
        
        try:
            self.items.sort(key=self.__key_function__, reverse=reverse)
        except AttributeError:
            #if the order_by is not a valid field, don't order
            pass
                   
    def __key_function__(self, item):
        
        value = item.get_value(self.Meta.order_by)
        
        if value.startswith(u'$'):
            value = value[1:]
        
        if value.endswith(u'%'):
            value = value[:len(value) - 1]
        
        try:
            return float(value)
        
        except ValueError:        
            return value.lower()
    
    def as_table_header(self):
        field_names = self.field_names()
        
        tds = [u'<td>' + field_names[field] + u'</td>' for field 
                                            in field_names.keys()]            
        return u''.join(tds)
    
    
    def field_names(self):
        model_fields = fields_for_model(self.Meta.model,
                                      self.Meta.fields, self.Meta.exclude)
        
        return SortedDict((field, self.get_name(field)) for field in model_fields)
    
    
    def get_name(self, field): 
        """
        Returns the name of the given field. First tries to use a function
        get_<field>_name, otherwise it uses the field label.
        """
        
        function_name = "get_" + field + "_name"
        if hasattr(self, function_name):
            return getattr(self, function_name)(field)        
        
        return field.capitalize()
    
        
    class Meta:
        model = None
        instances = []
        fields = None
        exclude = None 
        item_class = ModelListItem
        paginate_by = None
        order_by = None