import string
from django.core.paginator import InvalidPage, EmptyPage

class NamePaginator(object):
    """Pagination for string-based objects"""

    def __init__(self, queryset, paginate_by=25, orphans=0, allow_empty_first_page=True):
        # We ignore allow_empty_first_page and orphans, just here for compliance
        self.pages = []
        self.object_list = queryset
        self.count = len(self.object_list)

        # chunk up the objects so we don't need to iterate over the whole list for each letter
        chunks = {}

        # we sort them by the first model ordering key
        for obj in self.object_list:
            if queryset: obj_str = unicode(getattr(obj, obj._meta.ordering[0]))
            else: obj_str = unicode(obj)

            # some of my models had "first_name" "last_name" sorting, and some first_names
            # were empty so if it fails you can try sorting by the second ordering key
            # which worked for me but do your own thing
            try:
                letter = unicode.upper(obj_str[0])
            except:
                obj_str = unicode(getattr(obj, obj._meta.ordering[1]))
                letter = unicode.upper(obj_str[1])

            if letter not in chunks: chunks[letter] = []

            chunks[letter].append(obj)

        # the process for assigning objects to each page
        current_page = NamePage(self)

        for letter in string.ascii_uppercase:
            if letter not in chunks:
                current_page.add([], letter)
                continue

            sub_list = chunks[letter] # the items in object_list starting with this letter

            new_page_count = len(sub_list) + current_page.count
            # first, check to see if sub_list will fit or it needs to go onto a new page.
            # if assigning this list will cause the page to overflow...
            # and an underflow is closer to per_page than an overflow...
            # and the page isn't empty (which means len(sub_list) > per_page)...
            if new_page_count > paginate_by and \
                    abs(paginate_by - current_page.count) < abs(paginate_by - new_page_count) and \
                    current_page.count > 0:
                # make a new page
                self.pages.append(current_page)
                current_page = NamePage(self)

            current_page.add(sub_list, letter)

        # if we finished the for loop with a page that isn't empty, add it
        if current_page.count > 0: self.pages.append(current_page)

    def page(self, num):
        """Returns a Page object for the given 1-based page number."""
        if len(self.pages) == 0:
            return None
        elif num > 0 and num <= len(self.pages):
            return self.pages[num-1]
        else:
            raise InvalidPage

    @property
    def num_pages(self):
        """Returns the total number of pages"""
        return len(self.pages)

class NamePage(object):
    def __init__(self, paginator):
        self.paginator = paginator
        self.object_list = []
        self.letters = []

    @property
    def count(self):
        return len(self.object_list)

    @property
    def start_letter(self):
        if len(self.letters) > 0:
            self.letters.sort(key=str.upper)
            return self.letters[0]
        else: return None

    @property
    def end_letter(self):
        if len(self.letters) > 0:
            self.letters.sort(key=str.upper)
            return self.letters[-1]
        else: return None

    @property
    def number(self):
        return self.paginator.pages.index(self) + 1

    # just added the methods I needed to use in the templates
    # feel free to add the ones you need too
    def has_other_pages(self):
        return len(self.object_list) > 0

    def has_previous(self):
        return self.paginator.pages.index(self)

    def has_next(self):
        return self.paginator.pages.index(self) + 2

    def next_page_number(self):
        return self.paginator.pages.index(self) + 2

    def previous_page_number(self):
        return self.paginator.pages.index(self)

    def add(self, new_list, letter=None):
        if len(new_list) > 0: self.object_list = self.object_list + new_list
        if letter: self.letters.append(letter)

    def __repr__(self):
        if self.start_letter == self.end_letter:
            return self.start_letter
        else:
            return '%c-%c' % (self.start_letter, self.end_letter)