from django.conf import settings
from django.utils.translation import ugettext as _

from datetime import datetime, timedelta

from .signals import willEvaluateAvailabilityForRange as rangeSignal


def strfdate(date):
    return date.strftime(settings.TIMESLOTS_DATE_FORMAT)

def strftime(time):
    return time.strftime(settings.TIMESLOTS_TIME_FORMAT)

def strpdate(date):
    return datetime.strptime(date, settings.TIMESLOTS_DATE_FORMAT).date()   

def strptime(time):
    return datetime.strptime(time, settings.TIMESLOTS_TIME_FORMAT).time()


WEEKDAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']


def __get_timeslots(date, definition):
    # Return timeslots for date given definition
    if date < definition.valid:
        raise ValueError('out of bounds')
    if definition.until and date > definition.until:
        raise ValueError('out of bounds')

    from copy import copy
    return copy(definition.timeslots[WEEKDAYS[date.weekday()]])
    
def __timeslots_generator(lbound, ubound, constraint):
    
    definitions = constraint.definitions.filter(enabled=True)
    
    # Select all definitions in the date rage
    future_set = definitions.filter(valid__range=(lbound, ubound))
    current = definitions.filter(valid__lt=lbound).order_by('-valid')[:1]

    # Union of the current definition and all future definitions in the date
    # range. The union operator | may set me up for all sorts of associativity
    # problems. writing (current | future_set) throws AssertionError: Cannot
    # combine queries once a slice has been taken
    
    # There's no risk of getting duplicate rows, so I should be able to drop the
    # call to distinct('id'). only PostgreSQL supports DISTINCT ON

    # definitions = (future_set | current).distinct('id').order_by('valid')
    definitions = (future_set | current).order_by('valid')
    
    # Raise warnings if definitions is over- or underspecified
    
    def timeslots(date):
        final = None
        for definition in definitions:
            if definition.valid <= date:
                final = definition
            else:
                break
        return __get_timeslots(date, final) if final else None
        
    return timeslots
    
def availability(lbound, ubound, constraint):
    # I can load the entire queryset in one fould sweep here...
    generator = __timeslots_generator(lbound, ubound, constraint)
    
    # Take note of holidays
    holidays = constraint.holidays.filter(date__range=(lbound, ubound))

    # Allow signal listeners to register callback function
    callbacks = rangeSignal.send(__name__, lbound=lbound, ubound=ubound, constraint=constraint)
    
    for n in range((ubound-lbound).days):
        date = lbound + timedelta(n)
        # If date is a holiday, bail here
        
        if holidays.filter(date=date).count() > 0:
            data = {
                    'available' : False,
                    'status'    : 'holiday',
                    'code'      : 3,
                     # Replace with holiday message
                    'msg'       : _("holiday"),
                    'timeslots' : [],
                }
            
        else:
            timeslots = generator(date)
            if timeslots:
                # Allow signal handlers to update timeslots            
                for (receiver, callback) in callbacks:
                    try: 
                        callback(date, timeslots)
                    except Exception as e:
                        import logging
                        l = logging.getLogger(__name__)
                        l.warning("willEvaluateAvailabilityForRange callback threw an exception.", extra=e)
                # If final is empty then the date is fully booked...
                final = [strftime(k) for (k, v) in timeslots.items() if v > 0]
                data =  {
                    'available' : True if final else False,
                    'status'    : 'available' if final else 'fully booked',
                    'code'      : 0 if final else 2,
                    'msg'       : _("available") if final else _("fully booked"), 
                    'timeslots' : final,
                }
    
            else:
                data =  {
                    'available' : False,
                    'status'    : 'unavailable',
                    'code'      : 1,
                    'msg'       : _("unavailable"), 
                    'timeslots' : [],
                }

        yield date, data

    
def is_available(constraint, date, time):
    # Not available if date is a holiday
    if constraint.holidays.filter(date=date).count() > 0:
        return False

    # Create a timeslots generator for date only
    generator = __timeslots_generator(date, date, constraint)
    timeslots = generator(date)
    if not timeslots:
        return False

    # I can reduce the size of timeslots to time (but my callback may throw a warning)
    if time not in timeslots:
        return False
    timeslots = {time: timeslots[time]}
    
    # Allow signal listeners to register callback function
    callbacks = rangeSignal.send(__name__, lbound=date, ubound=date, constraint=constraint)
    for (receiver, callback) in callbacks:
        try:
            callback(date, timeslots, supress_warnings=True)
        except Exception as e:
            import logging
            l = logging.getLogger(__name__)
            l.warning("willEvaluateAvailabilityForRange callback threw an exception.", extra=e)
    
    if timeslots[time] < 1:
        return False
    
    return True
from django.conf import settings
from django.utils.translation import ugettext as _

from datetime import datetime, timedelta

from .signals import willEvaluateAvailabilityForRange as rangeSignal


def strfdate(date):
    return date.strftime(settings.TIMESLOTS_DATE_FORMAT)

def strftime(time):
    return time.strftime(settings.TIMESLOTS_TIME_FORMAT)

def strpdate(date):
    return datetime.strptime(date, settings.TIMESLOTS_DATE_FORMAT).date()   

def strptime(time):
    return datetime.strptime(time, settings.TIMESLOTS_TIME_FORMAT).time()


WEEKDAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']


def __get_timeslots(date, definition):
    # Return timeslots for date given definition
    if date < definition.valid:
        raise ValueError('out of bounds')
    if definition.until and date > definition.until:
        raise ValueError('out of bounds')

    from copy import copy
    return copy(definition.timeslots[WEEKDAYS[date.weekday()]])
    
def __timeslots_generator(lbound, ubound, constraint):
    
    definitions = constraint.definitions.filter(enabled=True)
    
    # Select all definitions in the date rage
    future_set = definitions.filter(valid__range=(lbound, ubound))
    current = definitions.filter(valid__lt=lbound).order_by('-valid')[:1]

    # Union of the current definition and all future definitions in the date
    # range. The union operator | may set me up for all sorts of associativity
    # problems. writing (current | future_set) throws AssertionError: Cannot
    # combine queries once a slice has been taken
    
    # There's no risk of getting duplicate rows, so I should be able to drop the
    # call to distinct('id'). only PostgreSQL supports DISTINCT ON

    # definitions = (future_set | current).distinct('id').order_by('valid')
    definitions = (future_set | current).order_by('valid')
    
    # Raise warnings if definitions is over- or underspecified
    
    def timeslots(date):
        final = None
        for definition in definitions:
            if definition.valid <= date:
                final = definition
            else:
                break
        return __get_timeslots(date, final) if final else None
        
    return timeslots
    
def availability(lbound, ubound, constraint):
    # I can load the entire queryset in one fould sweep here...
    generator = __timeslots_generator(lbound, ubound, constraint)
    
    # Take note of holidays
    holidays = constraint.holidays.filter(date__range=(lbound, ubound))

    # Allow signal listeners to register callback function
    callbacks = rangeSignal.send(__name__, lbound=lbound, ubound=ubound, constraint=constraint)
    
    for n in range((ubound-lbound).days):
        date = lbound + timedelta(n)
        # If date is a holiday, bail here
        
        if holidays.filter(date=date).count() > 0:
            data = {
                    'available' : False,
                    'status'    : 'holiday',
                    'code'      : 3,
                     # Replace with holiday message
                    'msg'       : _("holiday"),
                    'timeslots' : [],
                }
            
        else:
            timeslots = generator(date)
            if timeslots:
                # Allow signal handlers to update timeslots            
                for (receiver, callback) in callbacks:
                    try: 
                        callback(date, timeslots)
                    except Exception as e:
                        import logging
                        l = logging.getLogger(__name__)
                        l.warning("willEvaluateAvailabilityForRange callback threw an exception.", extra=e)
                # If final is empty then the date is fully booked...
                final = [strftime(k) for (k, v) in timeslots.items() if v > 0]
                data =  {
                    'available' : True if final else False,
                    'status'    : 'available' if final else 'fully booked',
                    'code'      : 0 if final else 2,
                    'msg'       : _("available") if final else _("fully booked"), 
                    'timeslots' : final,
                }
    
            else:
                data =  {
                    'available' : False,
                    'status'    : 'unavailable',
                    'code'      : 1,
                    'msg'       : _("unavailable"), 
                    'timeslots' : [],
                }

        yield date, data

    
def is_available(constraint, date, time):
    # Not available if date is a holiday
    if constraint.holidays.filter(date=date).count() > 0:
        return False

    # Create a timeslots generator for date only
    generator = __timeslots_generator(date, date, constraint)
    timeslots = generator(date)
    if not timeslots:
        return False

    # I can reduce the size of timeslots to time (but my callback may throw a warning)
    if time not in timeslots:
        return False
    timeslots = {time: timeslots[time]}
    
    # Allow signal listeners to register callback function
    callbacks = rangeSignal.send(__name__, lbound=date, ubound=date, constraint=constraint)
    for (receiver, callback) in callbacks:
        try:
            callback(date, timeslots, supress_warnings=True)
        except Exception as e:
            import logging
            l = logging.getLogger(__name__)
            l.warning("willEvaluateAvailabilityForRange callback threw an exception.", extra=e)
    
    if timeslots[time] < 1:
        return False
    
    return True
# Return timeslots for date given definition # Select all definitions in the date rage # Union of the current definition and all future definitions in the date # range. The union operator | may set me up for all sorts of associativity # problems. writing (current | future_set) throws AssertionError: Cannot # combine queries once a slice has been taken # There's no risk of getting duplicate rows, so I should be able to drop the # call to distinct('id'). only PostgreSQL supports DISTINCT ON # definitions = (future_set | current).distinct('id').order_by('valid') # Raise warnings if definitions is over- or underspecified # I can load the entire queryset in one fould sweep here... # Take note of holidays # Allow signal listeners to register callback function # If date is a holiday, bail here # Replace with holiday message # Allow signal handlers to update timeslots # If final is empty then the date is fully booked... # Not available if date is a holiday # Create a timeslots generator for date only # I can reduce the size of timeslots to time (but my callback may throw a warning) # Allow signal listeners to register callback function