/**
   * Looks in the current set of triggers for the trigger with the latest (most recent) trigger
   * time. Assumes that the trigger collection has been sorted and are currently stored in ascending
   * trigger timestamp sorted order. Latest time will be the last one in the list.
   */
  long getLatestCurrentTriggerTime() {
    // Method implementation comments go here ...

    BeliefUpdateTrigger last_trigger;

    try {
      last_trigger = (BeliefUpdateTrigger) _current_triggers.get(_current_triggers.size() - 1);
    } catch (IndexOutOfBoundsException ioobe) {
      if (_logger.isInfoEnabled()) _logger.info("Cannot get latest trigger time from empty list.");
      return System.currentTimeMillis();
    }

    return last_trigger.getTriggerTimestamp();
  } // method getLatestCurrentTriggerTime
  /**
   * Add a trigger to this current history.
   *
   * @param trigger The new trigger to be added.
   * @return False if the trigger is not added. This happens if the timestamp of the trigger
   *     precedes the last computed belief state timestamp.
   */
  boolean add(BeliefUpdateTrigger trigger) {
    // Method implementation comments go here ...

    // I am pretty sure having an inequality here is better.  I
    // originally had "<=", which caused some problems for
    // simultaneous actions/diagnosis.  Anyway, if you are
    // debugging a problem and happen to look here, this might be
    // useful information for you.
    //
    if ((_last_computed_belief != null)
        && (trigger.getTriggerTimestamp() < _last_computed_belief.getTimestamp())) {
      if (_logger.isDetailEnabled()) _logger.detail("Found late arriving trigger.");

      if (!shouldAddLateArrivingTrigger(trigger)) {
        return false;
      }

      if (_logger.isDetailEnabled()) _logger.detail("Decided to add late arriving trigger.");
    } // if late arriving trigger

    // This will simply append it, though we will sort it by
    // timestamp later.
    //
    _current_triggers.add(trigger);

    // BelievabilityDiagnosis objects require some special
    // handling as far as ensuring that our data structures stay
    // current.
    //
    if (trigger instanceof BelievabilityDiagnosis) {
      BelievabilityDiagnosis diag = (BelievabilityDiagnosis) trigger;

      // Must track the last explicit diagnosis for sensors in
      // case we find that we need to add implicit diagnoses for
      // them.
      //
      _last_explicit_diagnosis.put(diag.getSensorName(), trigger);

      // An explict diagnosis should clear out the last implict
      // diagosis if there was any.
      //
      _last_implicit_diagnosis_time.remove(diag.getSensorName());

      // Also need to track all the sensor names, so that we can
      // handle the case of having received diagnoses from all
      // sensors before the sensor latency window alarm expires.
      //
      _current_sensors.add(diag.getSensorName());
    } // If this is a diagnosis trigger

    return true;
  } // method add
    public int compare(Object o1, Object o2) {
      if (!(o1 instanceof BeliefUpdateTrigger) || !(o2 instanceof BeliefUpdateTrigger))
        throw new ClassCastException("Cannot compare objects. Not BeliefUpdateTriggers.");

      BeliefUpdateTrigger t1 = (BeliefUpdateTrigger) o1;
      BeliefUpdateTrigger t2 = (BeliefUpdateTrigger) o2;

      if (t1.getTriggerTimestamp() < t2.getTriggerTimestamp()) return -1;

      if (t1.getTriggerTimestamp() > t2.getTriggerTimestamp()) return 1;

      // If the timestamps are the same, then we define an action to
      // preceed a diagnosis.
      //
      if ((o1 instanceof BelievabilityAction) && (o2 instanceof DiagnosisTrigger)) return -1;

      // Equality case is zero return value.
      //
      return 0;
    } // method compare
  /**
   * This is the main crank to turn on this history. It will factor in everything it knows about the
   * current triggers and produce a new belief state.
   */
  void updateBeliefState() throws BelievabilityException {
    // Iterate over the collection of triggers and compute a
    // series of belief states until we get to the end of the
    // list.  This final belief state is the one we want.
    //

    // This 'no trigger' case should not really hapen, since we
    // should call this only after adding at least one trigger.
    // However, better to explicitly check for this and deal with
    // it gracefully then have the system bomb when the
    // assumptions are violated.  Here we simply do not update the
    // belief state.
    //
    if (_current_triggers.size() < 1) {
      if (_logger.isInfoEnabled()) _logger.info("Ignoring belief update due to zero triggers.");
      return;
    }

    if (_logger.isInfoEnabled()) _logger.info("Belief Update Starting for: " + _asset_id);

    // We should first sort this trigger collection, as
    // everything else here will require it to be sorted.
    //
    Collections.sort(_current_triggers, new TriggerComparator());

    if (_logger.isDetailEnabled())
      _logger.detail("Explicit trigger count is " + _current_triggers.size() + " for " + _asset_id);

    // Before updating the belief state, we need to check whether
    // or not we will need to add any implicit diagnoses.  Some
    // sensors do not report infomration when it is the same for
    // efficiency reasons, so we need to make sure we treat this
    // case correctly.  This works by simply adding extra triggers
    // to the list at the end.
    //
    addImplicitDiagnoses(getLatestCurrentTriggerTime());

    if (_logger.isDetailEnabled())
      _logger.detail(
          "Updating belief based on " + _current_triggers.size() + " triggers for " + _asset_id);

    // Handle case where we might be computing the first belief
    // state.
    //
    if (_last_computed_belief == null) {
      // The default belief to use when we have no record of a
      // previous belief is defined in the constructor, based on
      // the state of the plugin.
      //
      _last_computed_belief = _default_belief;

      _last_computed_belief.setTimestamp(getEarliestCurrentTriggerTime());
    }

    BeliefState latest_belief = _last_computed_belief;

    if (_logger.isDetailEnabled())
      _logger.detail("Initial belief before trigger list: " + latest_belief.toString());

    // This iterator will return items in ascending order (by
    // trigger time).
    //
    Iterator trigger_iter = _current_triggers.iterator();
    while (trigger_iter.hasNext()) {
      BeliefUpdateTrigger trigger = (BeliefUpdateTrigger) trigger_iter.next();

      if (_logger.isDetailEnabled()) _logger.detail("Processing trigger: " + trigger.toString());

      latest_belief = _model_manager.updateBeliefState(latest_belief, trigger);

      if (_logger.isDetailEnabled())
        _logger.detail("Belief after trigger: " + latest_belief.toString());
    } // while trigger_iter

    // Once we have incorporated all the triggers into the
    // computed belief state, we should clear them out, along with
    // anything else that is associated with this latency window
    // we have maintained.
    //
    clearTriggerHistory();

    _last_computed_belief = latest_belief;

    // Note that we choose not to worry about what to do with this
    // belief state here.  The decision whether to publish or not
    // should be made by whatever routine is actually calling this
    // update method.
    //

    if (_logger.isInfoEnabled()) _logger.info("Belief Update Ended for: " + _asset_id);
  } // method updateBeliefState
  /**
   * Main method for handling the arrival of a belief update trigger.
   *
   * @param trigger The newly arriving trigger t be handled.
   */
  synchronized void handleBeliefTrigger(BeliefUpdateTrigger trigger) throws BelievabilityException {
    // Method implementation comments go here ...

    if (_logger.isDetailEnabled()) _logger.detail("Handling trigger: " + trigger.toString());

    // There are two events that can happen which impact the
    // details about how to deal with triggers: rehydration and
    // unleashing.  In both cases, these effect what we use for
    // the initial belief state (the first belief update after
    // these events.)  The model manager has the status of whether
    // or not rehydration or unleashing is happening, so we use
    // this opportunity to check those and arrange our internal
    // variables so that the right thing will happen when we do a
    // belief update.

    handleSpecialEvents();

    // First step is to add the trigger (no matter what type of
    // trigger this is, we add it).
    //
    if (!add(trigger)) {
      if (_logger.isDebugEnabled())
        _logger.debug("Trigger not added to history " + _asset_id + " (happened in the past?)");
      return;
    }

    // Now we run through the possible conditions under which we
    // may want to compute a new belief state, predominantly based
    // upon the type of trigger we are seeing..

    // We only publish in response to a timer/alarm going off.
    // However, there are four types of possible alarms that
    // could trigger publishing: PublishDelayTimeTrigger,
    // PublishIntervalTimeTrigger, ForceUpdateTimeTrigger and
    // SensorLatencyTimeTrigger.
    //
    if (trigger instanceof TimeUpdateTrigger) {
      if (_logger.isDetailEnabled())
        _logger.detail(
            "Time-based update trigger "
                + trigger.getClass().getName()
                + ". Updating and publishing for asset "
                + _asset_id);

      // When we are forcing an update, we want to ensure that
      // any alarams started to delay the belief computation are
      // cancelled.
      //
      if (trigger instanceof ForceUpdateTimeTrigger) {
        cancelAlarm(_delay_alarm);
        _delay_alarm = null;
        cancelAlarm(_latency_alarm);
        _latency_alarm = null;

        // Note that we do not want to cancel the publish
        // interval alarm, since we are not sure if this
        // updated belief will actually be published.  If it
        // will be published, then the code that will publish
        // it will handle cancleling any outstanding alarm for
        // that.
      }

      updateBeliefState();
      publishLatestBelief();
      return;
    } // if an alarm trigger type occurs

    // On actions, we do not want to release a state estimation
    // until we have waited long enough for the sensors to give a
    // diagnosis for the new state that could result from this
    // action having successfully completed.  Thus, we want to
    // start a sensor latency timer when we see this so that we
    // wait at least this long.  The main complication, is that we
    // may already have some diagnoses queued up in the trigger
    // history.  The solution here is to bring the belief state
    // up-to-date through this current action, but without
    // publishing it.  We then start a sensor latency timer which
    // will wait  a while before publishing a new state
    // estimation, in hopes we will get diagnoses from the asset's
    // sensors.
    //
    if (trigger instanceof BelievabilityAction) {

      // We always want to start with a fresh latency timer for
      // an action, because we relay on the sensors to give us
      // the feedback on the results of actions.  Thus, we
      // always want to wait for all sensors to report after an
      // action completes before we publish a new state
      // estimation.
      //
      cancelAlarm(_latency_alarm);
      _latency_alarm = null;

      if (_logger.isDetailEnabled())
        _logger.detail("Action trigger handling: internal update for: " + _asset_id);

      // We bring the belief sate up-to-date for any and all
      // existing triggers in the history (clearing out the
      // history as well).  Do not publish though.
      //
      updateBeliefState();

      // Now force a state estiation publication in the future.
      //
      if (_logger.isDetailEnabled())
        _logger.detail("Action trigger handling: latency timer for: " + _asset_id);

      startSensorLatencyTimer();

      return;
    } // if trigger instanceof BelievabilityAction

    // If we have seen a diagnosis from all sensors, then we
    // should not wait until the end of the max sensor latency
    // period. Note that if there is only one sensor, this wil
    // return true on the first diagnosis addition and thus cause
    // an immediate belief computation.
    //
    if (seenAllSensors()) {
      cancelAlarm(_latency_alarm);
      _latency_alarm = null;

      if (USE_DELAY_TIMER) {
        if (_logger.isDetailEnabled())
          _logger.detail("Seen all sensors. " + "Starting publish delay timer for: " + _asset_id);
        startPublishDelayTimer();
      } else {
        if (_logger.isDetailEnabled())
          _logger.detail("Seen all sensors. " + "Updating and publishing for asset " + _asset_id);
        updateBeliefState();
        publishLatestBelief();
      }

      return;
    } // if seen all sensors

    // If this is the first sensor we are seeing (and there are
    // more than one for this asset), then we need to start a
    // timer to wait for the maximum latency time before actually
    // generating a new belief state.
    //
    if (trigger instanceof BelievabilityDiagnosis) {

      // If we receive a diagnoses, *and* there is no latency
      // alarm, then this must be the first diagnoses we are
      // adding to the set of current triggers.
      //
      // However, the converse is not true: it is possible for
      // the sensor latency timer to exist even though this is
      // the first diagnosis being added.  This case happens
      // after the arrival of a SuccessfulAction object, where
      // we immediate start a time rot wait for all new sensor
      // values that should occur after the action takes place.
      //
      if (_latency_alarm == null) {
        if (_logger.isDetailEnabled())
          _logger.detail("First, diagnosis, starting latency timer for: " + _asset_id);

        startSensorLatencyTimer();
        return;
      } else {
        if (_logger.isDetailEnabled())
          _logger.detail("Deferring belief update (diagnosis) for: " + _asset_id);
        return;
      }
    } // if BelievabilityDiagnosis
    else {
      if (_logger.isDebugEnabled()) _logger.debug("unknown trigger for: " + _asset_id);
    }
  } // method handleBeliefTrigger