"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/history.py $
$Id: history.py 23447 2004-02-06 17:36:45Z dbinger $
"""

from mx import DateTime
from dulcinea.base import DulcineaBase, DulcineaPersistent
from dulcinea.typeutils import typecheck
from dulcinea.user import DulcineaUser

class Event (DulcineaBase):
    """A single event in a history log.  A History instance is mainly a
    list of Event instances. Each event has a user ID, a timestamp, an
    "event code" (short string giving the general class of event) and a
    message giving details about the event.

    Instance attributes:
      timestamp : DateTime
      user : DulcineaUser
      event_code : string
      message : string

    Feel free to access these directly; do *not* modify them!  (The
    whole point of Event is to be immutable.)

    Also, Event objects should never be created by outside code:
    always go through the 'add_event()' method of History.
    """

    timestamp_is = DateTime.DateTimeType
    user_is = DulcineaUser
    event_code_is = str
    message_is = str

    def __init__(self, user, event_code, message, timestamp=None):
        typecheck(timestamp, DateTime.DateTimeType, allow_none=1)
        if timestamp is None:
            self.timestamp = DateTime.now()
        else:
            self.timestamp = timestamp
        typecheck(user, DulcineaUser, allow_none=1)
        self.user = user
        self.event_code = event_code
        self.message = message

    def __str__(self):
        return "%s %s" % (self.timestamp, self.event_code)

    def __cmp__(self, other):
        # DateTime objects can't be compared to None
        if self.timestamp is None and other.timestamp is not None:
            return -1
        if other.timestamp is None and self.timestamp is not None:
            return +1

        # Compare all attributes in turn, but stop as soon as cmp()
        # returns true (non-zero, ie. non-equal) for an attribute.
        return (cmp(self.timestamp, other.timestamp) or
                cmp(self.event_code, other.event_code) or
                cmp(self.user, other.user) or
                cmp(self.message, other.message))

    def get_timestamp(self):
        return self.timestamp

    def get_user(self):
        return self.user

    def get_event_code(self):
        return self.event_code

    def get_message(self):
        return self.message


class History (DulcineaPersistent):
    """A list of history events attached to a single object.

    Instance attributes:
      events : [Event]
        the list of events that comprise the object's history
    """

    events_is = [Event]

    def __init__(self):
        self.events = []

    def __str__(self):
        return "history (%d events)" % len(self.events)

    def __len__(self):
        return len(self.events)

    def __getitem__(self, i):
        return self.events[i]

    def insert_event(self, event):
        i = 0
        for e in self.events:
            if e > event:
                break
            i += 1
        self.events.insert(i, event)
        self._p_changed = 1

    def add_event(self, user, event_code, message, timestamp=None):
        """(user : DulcineaUser, event_code : string, message : string,
            timestamp : DateTime = None)

        Create a new Event instance. If timestamp is None (the default),
        the new event is stamped with the current time and appended to
        the end of the history list. If timestamp is supplied, it is
        used as the event's timestamp, and the event is inserted into
        the history list so that its ordering by timestamp is preserved.
        """
        new_event = Event(user, event_code, message, timestamp)
        if timestamp is None:
            self.events.append(new_event)
            self._p_changed = 1
        else:
            self.insert_event(new_event)

    def add_unique_event(self, user, event_code, message):
        """(user : DulcineaUser, event_code : string, message : string)

        Create a new Event instance. If the last event on the list had
        the same event code and user, replace the last event with the
        new one.
        """
        new_event = Event(user, event_code, message, timestamp=None)
        replace_event = False
        if self.events:
            last = self.events[-1]
            if (last.user is new_event.user and
                last.event_code == new_event.event_code):
                replace_event = True

        if replace_event:
            self.events[-1] = new_event
        else:
            self.events.append(new_event)
        self._p_changed = 1

    def find_event(self, user=None, event_code=None, event_codes=None,
                   latest=False, message=None):
        """(user : DulcineaUser = None,
            event_code : string = None,
            latest : bool = False,
            message : string = None) -> Event

        Find the earliest(or latest, if latest is true) event
        matching 'user' and/or 'event_code'.  If either search criterion
        is not supplied or None, it does not affect the search, thus the
        default behaviour is to return the first event in the history
        list.  Return None if no events match.
        """
        typecheck(user, DulcineaUser, allow_none=1)
        if latest:
            indices = xrange(len(self.events)-1, -1, -1)
        else:
            indices = xrange(len(self.events))
        for i in indices:
            event = self.events[i]
            if user is not None and event.user is not user:
                continue
            if event_code is not None and event.event_code != event_code:
                continue
            if event_codes is not None and event.event_code not in event_codes:
                continue
            if message is not None and event.message != message:
                continue
            return event
        else:
            return None

    def find_events_after(self, time, event_code=None):
        return [event
                for event in self.events
                if event.get_timestamp() > time and (
                    event_code is None or
                    event.get_event_code() == event_code)]

    def find_last_date(self, event_code):
        """(event_code:str) -> DateTime | None"""
        event = self.find_event(event_code=event_code, latest=True)
        return event and event.get_timestamp()

class Historical:
    """maintains a list of history events.

    Instance attributes:
      history : History
        the list of history events that comprise the object's history

    """

    history_is = History

    def __init__(self):
        self.history = History()

    def get_history(self):
        return self.history
