"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/fm/lib/time_block.py $
$Id: time_block.py 22694 2003-10-06 15:57:51Z dbinger $
"""

from mx.DateTime import DateTimeType
from dulcinea.base import DulcineaPersistent
from dulcinea.keyed import Keyed, KeyedMap
from dulcinea.permission import PermissionManager
from dulcinea.sort import method_sort
from dulcinea.user import DulcineaUser as User
from dulcinea.spec import require

class Titled:
    """
    Instance attributes:
      title : str
      description : str

    """
    title_is = str
    description_is = str

    def __init__(self):
        self.title = None
        self.description = None

    def get_title(self):
        return self.title

    def set_title(self, title):
        require(title, self.title_is)
        self.title = title

    def get_description(self):
        return self.description

    def set_description(self, description):
        require(description, self.description_is)
        self.description = description


class Reservable(DulcineaPersistent, PermissionManager, Keyed, Titled):

    """
    Instance attributes:
      active : bool
    """

    active_is = bool

    valid_permissions = {
        'manage_permissions' : 'able to manage permissions of this item ',
        'control' : 'able to change any reservations of this item',
        'reserve' : 'able to make a reservation of this item',
        }

    def __init__(self):
        Keyed.__init__(self)
        Titled.__init__(self)
        PermissionManager.__init__(self)
        self.active = True

    def set_active(self, value):
        self.active = value

    def is_active(self):
        return self.active

    def allows_reserve(self, user):
        return self.grants('reserve', user)

    def allows_control(self, user):
        return self.grants('control', user)


class Resource (DulcineaPersistent, Reservable):
    pass


class Resources (KeyedMap):

    def __init__(self):
        KeyedMap.__init__(self, Resource)


class Reservation (DulcineaPersistent, Keyed, Titled):
    """
    Instance attributes:
      start_time : DateTime
      end_time : DateTime
      reserved: [ Reservable ]
      users : [ User ]
    """

    start_time_is = DateTimeType
    end_time_is = DateTimeType
    reserved_is = [Reservable]
    users_is = [User]

    def __init__(self):
        Keyed.__init__(self)
        Titled.__init__(self)
        self.start_time = None
        self.end_time = None
        self.reserved = None
        self.users = None

    def __str__ (self):
        return "%s: %s" % (self.key, self.start_time)

    def set_start_time (self, time):
        require(time, self.start_time_is)
        self.start_time = time

    def set_end_time (self, time):
        require(time, self.start_time_is)
        assert time >= self.start_time
        self.end_time = time

    def set_reserved (self, reserved):
        require(reserved, self.reserved_is)
        self.reserved = reserved

    def set_users (self, users):
        require(users, self.users_is)
        self.users = users

    def get_start_time (self):
        return self.start_time

    def get_end_time (self):
        return self.end_time

    def get_reserved (self):
        return self.reserved

    def get_users (self):
        return self.users

    def is_active(self, start, end):
        return (self.get_end_time() > start and
                self.get_start_time() < end)

    def can_modify (self, user):
        """(user: User) -> bool
        """
        if user in self.users:
            return True
        if user and user.is_admin():
            return True
        for resource in self.reserved:
            if resource.allows_control(user):
                return True
        return False


class Calendar (DulcineaPersistent, KeyedMap):

    """
    Instance attributes: none
    """

    def __init__(self):
        KeyedMap.__init__(self, Reservation)

    def get_reservations(self):
        return self.get_mapping().values()

    def get_table_map(self, resource, time_increment,
                      start_time, end_time,
                      start_time_of_day, end_time_of_day):
        """(resource:Resource,
            time_increment:DateTimeDelta,
            start_time:DateTime,
            end_time:DateTime,
            start_time_of_day:DateTimeDelta,
            end_time_of_day:DateTimeDelta) -> {
                (day:DateTime, time_of_day:DateTimeDelta) :
                (reservation:Reservation, rowspan:int) | None }

        This calculates information useful for displaying a tabular view
        of the calendar for one resource.
        The time_increment is amount of time for each row of the table.
        The start_time and end_time determine the columns (days) of the table.
        The start_time_of_day and end_time_of_day limit the rows that are shown.

        The function returns a dictionary with an entry for each time block
        that is not vacant.  For the first block of each day for a given
        reservation, the dictionary maps (day, time_of_day) to
        (reservation, rowspan), meaning that this reservation appears first
        on this day at this time of day, and that it extends across rowspan
        time increments.

        The cells of the table that are continuations of a reservation that
        has already appeared above have entries that map (day, time_of_day)
        to None.
        """

        reservations = method_sort(
            [reservation for reservation in self.get_reservations()
             if (reservation.get_start_time() < end_time and
                 reservation.get_end_time() > start_time and
                 resource in reservation.get_reserved())],
            'get_start_time')
        tableau = {}
        def gen_days():
            day = start_time
            while day < end_time:
                yield day
                day = day + 1

        for day in gen_days():
            day_reservations = [
                reservation for reservation in reservations
                if reservation.is_active(day + start_time_of_day,
                                         day + end_time_of_day)]
            time_of_day = start_time_of_day
            while time_of_day < end_time_of_day:
                if day_reservations:
                    reservation = day_reservations[0]
                    period_end = day + time_of_day + time_increment
                    if reservation.get_start_time() < period_end:
                        start = (day, time_of_day)
                        reservation_end = reservation.get_end_time()
                        rows = 1
                        next_time = time_of_day + time_increment
                        while (next_time < end_time_of_day and
                               day + next_time < reservation_end):
                            rows += 1
                            time_of_day = next_time
                            next_time = next_time + time_increment
                            tableau[(day, time_of_day)] = None # mark continuation
                        tableau[start] = (reservation, rows)
                        day_reservations = day_reservations[1:]
                time_of_day = time_of_day + time_increment
        return tableau


