from datetime import timedelta, datetime
from sets import Set

ONLINE_MINUTES = 2
PURGE_MULTIPLE = 20

_users_seen_per_minute = {}
_last_purged = datetime.now()

def get_online_user_ids(): 
    """ 
    Returns a sets.Set of user_id's which have made requests in the last ONLINE_MINUTES. 
    Includes partial minutes (i.e. 2 minutes would count up to users seen in 2:59, due to minute rollover).
    """
    current_minute = _get_minute()
    user_set = Set()
    for count_minute in [_get_minute(current_minute, -1 * i) for i in range(0, ONLINE_MINUTES+1)]:
        try:
            user_set.union_update(_users_seen_per_minute[count_minute])
        except KeyError: 
            pass  #perhaps no requests that minute?
    return user_set

def _get_minute(base_datetime=None, offset_minutes=0):
    if base_datetime is None:
        base_datetime = datetime.now()    
    if offset_minutes != 0:
        offset = timedelta(minutes=offset_minutes)
        time = base_datetime + offset
    else:
        time = base_datetime
    return datetime(time.year, time.month, time.day, time.hour, time.minute)                                                                                
        
def _purge_old_minutes():
    global _last_purged
    
    now = datetime.now()
    purge_at = _last_purged + timedelta(minutes=PURGE_MULTIPLE*ONLINE_MINUTES) 
    if purge_at < now:
        purge_older_than = now - timedelta(minutes=ONLINE_MINUTES+1)        
        for seen_minute in _users_seen_per_minute.keys():
            if seen_minute < purge_older_than:
                del _users_seen_per_minute[seen_minute]
        _last_purged = now
    else:
        pass #not time to purge yet
    
class OnlineUsers(object): 
    def process_request(self, request): #assumes auth middleware earlier in the request chain
        assert hasattr(request, 'user'), "The OnlineUsers middleware requires auth middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.auth.middleware.AuthenticationMiddleware'."
    
        if request.user.is_anonymous(): #assuming most users are anonymous, this is a fast path for them
            return                      #but move down -after- purge if you expect << 1 non-anonymous request per minute.

        _purge_old_minutes()

        current_minute = _get_minute()        
        try:
            _users_seen_per_minute[current_minute].add(request.user.id)
        except KeyError: #request is first in new minute
            _users_seen_per_minute[current_minute] = Set([request.user.id])
