__author__ = 'Mark Boszko'

import re
from operator import itemgetter
from itertools import groupby
from django.core.exceptions import ValidationError
from django.db import models
from django.forms import CharField


class MultiRangeField(models.CharField):
    # default_validators = [validators.validate_comma_separated_integer_list]
    description = "A multi-range of integers (e.g. page numbers 30, 41, 51-57, 68)"

    __metaclass__ = models.SubfieldBase


    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 1000
        kwargs['help_text'] = "Comma-separated pages and page ranges."
        super(MultiRangeField, self).__init__(*args, **kwargs)


    def get_internal_type(self):
        return 'CharField'


    def to_python(self, value):
        """

        :type value: str
        """
        if not value:
            return ''
        # Validate
        if re.match("^[0-9, -]*$", value):
            return repack(depack(value))
        # else something's wrong.
        return value
        

    def get_prep_value(self, value):
        if not value:
            return ''
        return repack(depack(value))


    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return repack(depack(value))


    def formfield(self, **kwargs):
        defaults = {'form_class': MultiRangeFormField}
        defaults.update(kwargs)
        return super(MultiRangeField, self).formfield(**defaults)


    def clean(self, value, model_instance):
        if re.match("^[0-9, -]*$", value):
            return repack(depack(value))
        else:
            # Turn all other characters into commas, because it's probably a typo
            value = re.sub("[^0-9, -]", ",", value)
            return repack(depack(value))



class MultiRangeFormField(CharField):

    def validate(self, value):
        """
        Check if the value consts of valid page ranges,
        with only numbers, hyphens, commas, and spaces
        :param value:str
        :return:
        """
        if re.match("^[0-9, -]*$", value):
            return repack(depack(value))
        # Comment this out if you'd rather just have it auto-clean your entry
        else:
            raise ValidationError('Can only contain numbers, hyphens, commas, and spaces.')



def depack(value):
    """
    Unpacks a string representation of integers and ranges into a list of ints
    :type value: str
    """
    page_list = []

    # Strip out the spaces first, before we depack
    value = re.sub("[\s]", '', value)

    for part in value.split(','):
        if '-' in part:
            # It's a range
            a, b = part.split('-')
            a, b = int(a), int(b)
            page_list.extend(range(a, b + 1))
        else:
            # Make sure that it contains a number before we add it.
            if re.match("[0-9]+", part):
                a = int(part)
                page_list.append(a)
    return page_list

def repack(page_list):
    """
    Returns a string representation from integers in a list

    :type page_list: list
    :return: str
    """
    # Need to sort the list first, so that we can combine runs into ranges
    sorted_values = sorted(page_list, key=int)

    ranges = []
    for key, group in groupby(enumerate(sorted_values), lambda (index, item): index - item):
        group = map(itemgetter(1), group)
        if len(group) > 1:
           ranges.append(xrange(group[0], group[-1])) # under Python 3.x, switch to "range"
        else:
            ranges.append(group[0])

    ranges_strings = []
    for item in ranges:
        if isinstance(item, xrange): # This only works under Python 2.x - under 3.x, switch to "range"
            # 1-2
            range = "%d-%d" % (item[0], item[-1]+1)
            ranges_strings.append(range)
        else:
            ranges_strings.append(str(item))

    return ', '.join([unicode(s) for s in ranges_strings])