# -*- python -*-

# This software was produced by NIST, an agency of the U.S. government,
# and by statute is not subject to copyright in the United States.
# Recipients of this software assume all responsibilities associated
# with its operation, modification and maintenance. However, to
# facilitate maintenance we ask that before distributing modified
# versions of this software, you first contact the authors at
# oof_manager@nist.gov.

# The Historian class is used by GUI code to record and revisit
# previously executed commands (or other historical objects, but we'll
# call them 'commands' here).  It does not actually contain any GUI
# code itself, but it provides callbacks to be installed in GUI
# objects.

# Outside the Historian, the GUI must have a way of displaying
# commands and choosing between them, eg, with a
# RegisteredClassFactory.  The widget that does this must have a
# callback function called when its state is changed.  That callback
# must call (or be) Historian.stateChangeCB.  The GUI must also have
# 'Next' and 'Prev' buttons (or the equivalent) used to move through
# the historical commands, and the callbacks for those buttons must
# call (or be) Historian.nextCB and Historian.prevCB.

# The constructor takes two callbacks as arguments.  Only the first is
# required:
#
#  setCB is called when a historical object should be displayed.  The
#  object to be displayed is its first argument.  setCBkwargs, if any,
#  are passed to it as well.  (If setCB is a RegisteredClassFactory's
#  set() function, then setCBkwargs should be {'interactive':1}.
#
#  sensitizeCB (optional) is called when something has happened to
#  change the availability of the Next and Prev operations.  It should
#  call a function that uses Historian.nextSensitive() and
#  Historian.prevSensitive() to sensitize or desensitize the Next and
#  Prev buttons.

# Historian.record() must be used to put new objects in the history.
# These are the same objects that will be passed to setCB() later.

# Historian.__len__() returns the number of objects stored, and
# Historian.current() returns the current object, if the history is
# being edited, and None otherwise.

# TODO: History entries can be invalidated, eg. if I've selected the
# members of a group, but the group is later deleted, then going
# "previous" to that entry will result in an invalid display.

from ooflib.common import debug
from ooflib.common import ringbuffer
from ooflib.common import mainthread

class Historian:
    def __init__(self, setCB, sensitizeCB=None, bufsiz=50, setCBkwargs={}):
        self.editingHistory = 0
        self.setCB = setCB
        self.setCBkwargs = setCBkwargs
        self.historyBuffer = ringbuffer.RingBuffer(bufsiz)
        self.sensitizeCB = sensitizeCB
        self.inhibit = 0

    ### Functions to examine/change the state of the Historian

    def record(self, obj):              # Add obj to the recorded history
        try:
            self.historyBuffer.last()
        except IndexError:
            pass
        self.historyBuffer.push(obj)
        self.editingHistory = 1

        # TODO LATER: When historians are changed programmatically,
        # from scripts for example, the widgets they refer to don't
        # reflect the last-issued command.  It's wrong to just always
        # revert the widgets here, because "record" may not be called
        # in a timely manner, and you might end up post-empting user
        # changes.  What should be done eventually is to detect
        # programmatic changes and update the widgets in that case,
        # and only in that case.
        # if self.setCB:
        #     self.setCB(obj, **self.setCBkwargs)

        if self.sensitizeCB:
            mainthread.runBlock(self.sensitizeCB)

    def nextSensitive(self):            # Is there a "next" history item?
        return not self.historyBuffer.atTop()

    def prevSensitive(self):            # Is there a "previous" history item?
        if self.editingHistory:
            return not self.historyBuffer.atBottom()
        return len(self.historyBuffer) > 0

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

    def current(self):
        # This used to return None if self.editingHistory was false.
        # That was wrong, because it prevented the user from changing
        # the selector and repeating. (For example, using a different
        # pixel selection method with old mouse coordinates.)  The
        # question is, why did this function ever check
        # editingHistory?
        return self.historyBuffer.current()

    # Find out what the next or previous value is, without actually
    # going to it.
    def nextVal(self):
        return self.historyBuffer.nextPeek()
    def prevVal(self):
        return self.historyBuffer.prevPeek()
        
    ### Callbacks to be installed in the GUI objects ###

    def stateChangeCB(self, *args):     # call this when the selector changes
        if self.inhibit:
            return
        self.editingHistory = 0
        try:
            self.historyBuffer.last()
        except IndexError:
            pass
        if self.sensitizeCB:
            mainthread.runBlock(self.sensitizeCB)

    def nextCB(self, *args):            # go to the next history item
        self.historyBuffer.next()
        self.inhibit = 1
        self.setCB(self.historyBuffer.current(), **self.setCBkwargs)
        self.inhibit = 0 
        self.editingHistory = 1
        if self.sensitizeCB:
            mainthread.runBlock(self.sensitizeCB)

    def prevCB(self, *args):            # go to the previous history item
        if self.editingHistory:
            self.historyBuffer.prev()
        self.inhibit = 1
        self.setCB(self.historyBuffer.current(), **self.setCBkwargs)
        self.inhibit = 0
        self.editingHistory = 1
        if self.sensitizeCB:
            mainthread.runBlock(self.sensitizeCB)

