// Filename: cMetaInterval.cxx
// Created by:  drose (27Aug02)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "cMetaInterval.h"
#include "waitInterval.h"
#include "config_interval.h"
#include "indirectLess.h"
#include "indent.h"

#include <algorithm>
#include <math.h>   // for log10()
#include <stdio.h>  // for sprintf()

TypeHandle CMetaInterval::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::Constructor
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
CMetaInterval::
CMetaInterval(const string &name) :
  CInterval(name, 0.0, true)
{
  _precision = interval_precision;
  _current_nesting_level = 0;
  _next_event_index = 0;
  _processing_events = false;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::Destructor
//       Access: Published, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
CMetaInterval::
~CMetaInterval() {
  clear_intervals();
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::clear_intervals
//       Access: Published
//  Description: Resets the list of intervals and prepares for
//               receiving a new list.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
clear_intervals() {
  // Better not do this unless you have serviced all of the
  // outstanding events!
  bool lost_events = false;
  if (!_event_queue.empty()) {
    interval_cat.warning()
      << "Losing outstanding events for " << *this << "\n";
    _event_queue.clear();
    lost_events = true;
  }

  clear_events();

  // Go through all of our nested intervals and remove ourselves as
  // their parent.
  Defs::iterator di;
  for (di = _defs.begin(); di != _defs.end(); ++di) {
    IntervalDef &def = (*di);
    if (def._c_interval != (CInterval *)NULL) {
      CInterval::Parents::iterator pi = 
        find(def._c_interval->_parents.begin(),
             def._c_interval->_parents.end(),
             this);
      nassertv(pi != def._c_interval->_parents.end());
      def._c_interval->_parents.erase(pi);
    }
  }
  _defs.clear();

  _current_nesting_level = 0;
  _next_event_index = 0;

#ifndef NDEBUG
  if (verify_intervals) {
    nassertv(!lost_events);
  }
#endif
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::push_level
//       Access: Published
//  Description: Marks the beginning of a nested level of child
//               intervals.  Within the nested level, a RelativeStart
//               time of RS_level_begin refers to the start of the
//               level, and the first interval added within the level
//               is always relative to the start of the level.
//
//               The return value is the index of the def entry
//               created by this push.
////////////////////////////////////////////////////////////////////
int CMetaInterval::
push_level(const string &name, double rel_time, RelativeStart rel_to) {
  nassertr(_event_queue.empty() && !_processing_events, -1);

  _defs.push_back(IntervalDef());
  IntervalDef &def = _defs.back();
  def._type = DT_push_level;
  def._ext_name = name;
  def._rel_time = rel_time;
  def._rel_to = rel_to;
  _current_nesting_level++;
  mark_dirty();

  return (int)_defs.size() - 1;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::add_c_interval
//       Access: Published
//  Description: Adds a new CInterval to the list.  The interval will
//               be played when the indicated time (relative to the
//               given point) has been reached.
//
//               The return value is the index of the def entry
//               representing the new interval.
////////////////////////////////////////////////////////////////////
int CMetaInterval::
add_c_interval(CInterval *c_interval, 
               double rel_time, RelativeStart rel_to) {
  nassertr(_event_queue.empty() && !_processing_events, -1);
  nassertr(c_interval != (CInterval *)NULL, -1);

  c_interval->_parents.push_back(this);
  c_interval->_ival_pcollector = PStatCollector(_ival_pcollector, c_interval->_pname);
  _defs.push_back(IntervalDef());
  IntervalDef &def = _defs.back();
  def._type = DT_c_interval;
  def._c_interval = c_interval;
  def._rel_time = rel_time;
  def._rel_to = rel_to;
  mark_dirty();

  return (int)_defs.size() - 1;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::add_ext_index
//       Access: Published
//  Description: Adds a new external interval to the list.  This
//               represents some object in the external scripting
//               language that has properties similar to a CInterval
//               (for instance, a Python Interval object).
//
//               The CMetaInterval object cannot play this external
//               interval directly, but it records a placeholder for
//               it and will ask the scripting language to play it
//               when it is time, via is_event_ready() and related
//               methods.
//
//               The ext_index number itself is simply a handle that
//               the scripting language makes up and associates with
//               its interval object somehow.  The CMetaInterval
//               object does not attempt to interpret this value.
//
//               The return value is the index of the def entry
//               representing the new interval.
////////////////////////////////////////////////////////////////////
int CMetaInterval::
add_ext_index(int ext_index, const string &name, double duration,
              bool open_ended,
              double rel_time, RelativeStart rel_to) {
  nassertr(_event_queue.empty() && !_processing_events, -1);

  _defs.push_back(IntervalDef());
  IntervalDef &def = _defs.back();
  def._type = DT_ext_index;
  def._ext_index = ext_index;
  def._ext_name = name;
  def._ext_duration = duration;
  def._ext_open_ended = open_ended;
  def._rel_time = rel_time;
  def._rel_to = rel_to;
  mark_dirty();

  return (int)_defs.size() - 1;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::pop_level
//       Access: Published
//  Description: Finishes a level marked by a previous call to
//               push_level(), and returns to the previous level.
//
//               If the duration is not negative, it represents a
//               phony duration to assign to the level, for the
//               purposes of sequencing later intervals.  Otherwise,
//               the level's duration is computed based on the
//               intervals within the level.
////////////////////////////////////////////////////////////////////
int CMetaInterval::
pop_level(double duration) {
  nassertr(_event_queue.empty() && !_processing_events, -1);
  nassertr(_current_nesting_level > 0, -1);

  _defs.push_back(IntervalDef());
  IntervalDef &def = _defs.back();
  def._type = DT_pop_level;
  def._ext_duration = duration;
  _current_nesting_level--;
  mark_dirty();

  return (int)_defs.size() - 1;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::set_interval_start_time
//       Access: Published
//  Description: Adjusts the start time of the child interval with the
//               given name, if found.  This may be either a C++
//               interval added via add_c_interval(), or an external
//               interval added via add_ext_index(); the name must
//               match exactly.
//
//               If the interval is found, its start time is adjusted,
//               and all subsequent intervals are adjusting
//               accordingly, and true is returned.  If a matching
//               interval is not found, nothing is changed and false
//               is returned.
////////////////////////////////////////////////////////////////////
bool CMetaInterval::
set_interval_start_time(const string &name, double rel_time,
                        CMetaInterval::RelativeStart rel_to) {
  nassertr(_event_queue.empty() && !_processing_events, false);
  Defs::iterator di;
  for (di = _defs.begin(); di != _defs.end(); ++di) {
    IntervalDef &def = (*di);

    bool match = false;
    switch (def._type) {
    case DT_c_interval:
      match = (def._c_interval->get_name() == name);
      break;

    case DT_ext_index:
      match = (def._ext_name == name);
      break;

    default:
      break;
    }
    if (match) {
      // Here's the interval.
      def._rel_time = rel_time;
      def._rel_to = rel_to;
      mark_dirty();
      return true;
    }
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::get_interval_start_time
//       Access: Published
//  Description: Returns the actual start time, relative to the
//               beginning of the interval, of the child interval with
//               the given name, if found, or -1 if the interval is
//               not found.
////////////////////////////////////////////////////////////////////
double CMetaInterval::
get_interval_start_time(const string &name) const {
  recompute();
  Defs::const_iterator di;
  for (di = _defs.begin(); di != _defs.end(); ++di) {
    const IntervalDef &def = (*di);

    bool match = false;
    switch (def._type) {
    case DT_c_interval:
      match = (def._c_interval->get_name() == name);
      break;

    case DT_ext_index:
      match = (def._ext_name == name);
      break;

    default:
      break;
    }
    if (match) {
      // Here's the interval.
      return int_to_double_time(def._actual_begin_time);
    }
  }

  return -1.0;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::get_interval_end_time
//       Access: Published
//  Description: Returns the actual end time, relative to the
//               beginning of the interval, of the child interval with
//               the given name, if found, or -1 if the interval is
//               not found.
////////////////////////////////////////////////////////////////////
double CMetaInterval::
get_interval_end_time(const string &name) const {
  recompute();
  Defs::const_iterator di;
  for (di = _defs.begin(); di != _defs.end(); ++di) {
    const IntervalDef &def = (*di);

    bool match = false;
    double duration = 0.0;
    switch (def._type) {
    case DT_c_interval:
      duration = def._c_interval->get_duration();
      match = (def._c_interval->get_name() == name);
      break;

    case DT_ext_index:
      duration = def._ext_duration;
      match = (def._ext_name == name);
      break;

    default:
      break;
    }
    if (match) {
      // Here's the interval.
      return int_to_double_time(def._actual_begin_time) + duration;
    }
  }

  return -1.0;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::initialize
//       Access: Published, Virtual
//  Description: This replaces the first call to priv_step(), and indicates
//               that the interval has just begun.  This may be
//               overridden by derived classes that need to do some
//               explicit initialization on the first call.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
priv_initialize(double t) {
  if (_processing_events) {
    enqueue_self_event(ET_initialize, t);
    return;
  }

  check_stopped(get_class_type(), "priv_initialize");
  // It may be tempting to flush the event_queue here, but don't do
  // it.  Those are events that must still be serviced from some
  // previous interval operation.  Throwing them away would be a
  // mistake.

  recompute();
  _next_event_index = 0;
  _active.clear();

  int now = double_to_int_time(t);

  /*
  // One special case: if we step to t == 0.0, it really means to the
  // very beginning of the interval, *before* any events that occurred
  // at time 0.  (Most of the time, stepping to a particular time
  // means *after* any events that occurred at that time.)
  if (t == 0.0) {
    now = -1;
  }
  */

  // Now look for events from the beginning up to the current time.
  _processing_events = true;
  ActiveEvents new_active;
  while (_next_event_index < _events.size() &&
         _events[_next_event_index]->_time <= now) {
    PlaybackEvent *event = _events[_next_event_index];
    _next_event_index++;
    
    // Do the indicated event.
    do_event_forward(event, new_active, true);
  }
  finish_events_forward(now, new_active);
  _processing_events = false;

  _curr_t = t;
  _state = S_started;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::instant
//       Access: Published, Virtual
//  Description: This is called in lieu of priv_initialize() .. priv_step()
//               .. priv_finalize(), when everything is to happen within
//               one frame.  The interval should initialize itself,
//               then leave itself in the final state.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
priv_instant() {
  if (_processing_events) {
    enqueue_self_event(ET_instant);
    return;
  }

  check_stopped(get_class_type(), "priv_instant");
  recompute();
  _active.clear();

  // Apply all of the events.  This just means we invoke "instant" for
  // any end or instant event, ignoring the begin events.
  _processing_events = true;
  PlaybackEvents::iterator ei;
  for (ei = _events.begin(); ei != _events.end(); ++ei) {
    PlaybackEvent *event = (*ei);
    if (event->_type != PET_begin) {
      enqueue_event(event->_n, ET_instant, true, 0);
    }
  }
  _processing_events = false;

  _next_event_index = _events.size();
  _curr_t = get_duration();
  _state = S_final;

  if (_event_queue.empty()) {
    interval_done();
  } else {
    enqueue_done_event();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::step
//       Access: Published, Virtual
//  Description: Advances the time on the interval.  The time may
//               either increase (the normal case) or decrease
//               (e.g. if the interval is being played by a slider).
////////////////////////////////////////////////////////////////////
void CMetaInterval::
priv_step(double t) {
  if (_processing_events) {
    enqueue_self_event(ET_step, t);
    return;
  }

  check_started(get_class_type(), "priv_step");
  int now = double_to_int_time(t);

  /*
  // One special case: if we step to t == 0.0, it really means to the
  // very beginning of the interval, *before* any events that occurred
  // at time 0.  (Most of the time, stepping to a particular time
  // means *after* any events that occurred at that time.)
  if (t == 0.0) {
    now = -1;
  }
  */

  // Now look for events between the last time we ran and the current
  // time.

  _processing_events = true;
  if (_next_event_index < _events.size() &&
      _events[_next_event_index]->_time <= now) {
    // The normal case: time is increasing.
    ActiveEvents new_active;
    while (_next_event_index < _events.size() &&
           _events[_next_event_index]->_time <= now) {
      PlaybackEvent *event = _events[_next_event_index];
      _next_event_index++;

      // Do the indicated event.
      do_event_forward(event, new_active, false);
    }

    finish_events_forward(now, new_active);

  } else {
    // A less usual case: time is decreasing.
    ActiveEvents new_active;
    while (_next_event_index > 0 && 
           _events[_next_event_index - 1]->_time > now) {
      _next_event_index--;
      PlaybackEvent *event = _events[_next_event_index];

      do_event_reverse(event, new_active, false);
    }

    finish_events_reverse(now, new_active);
  }
  _processing_events = false;

  _curr_t = t;
  _state = S_started;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::finalize
//       Access: Published, Virtual
//  Description: This is called when an interval is interrupted.  It
//               should advance the time as if priv_step() were called, and
//               also perform whatever cleanup might be required.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
priv_finalize() {
  if (_processing_events) {
    enqueue_self_event(ET_finalize);
    return;
  }

  double duration = get_duration();
  if (_state == S_initial) {
    priv_initialize(duration);
  }

  // Do all remaining events.
  _processing_events = true;
  ActiveEvents new_active;
  while (_next_event_index < _events.size()) {
    PlaybackEvent *event = _events[_next_event_index];
    _next_event_index++;

    // Do the indicated event.
    do_event_forward(event, new_active, true);
  }
  finish_events_forward(double_to_int_time(duration), new_active);
  _processing_events = false;

  _curr_t = duration;
  _state = S_final;

  if (_event_queue.empty()) {
    interval_done();
  } else {
    enqueue_done_event();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::reverse_initialize
//       Access: Published, Virtual
//  Description: Similar to priv_initialize(), but this is called when the
//               interval is being played backwards; it indicates that
//               the interval should start at the finishing state and
//               undo any intervening intervals.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
priv_reverse_initialize(double t) {
  if (_processing_events) {
    enqueue_self_event(ET_reverse_initialize, t);
    return;
  }

  check_stopped(get_class_type(), "priv_reverse_initialize");
  // It may be tempting to flush the event_queue here, but don't do
  // it.  Those are events that must still be serviced from some
  // previous interval operation.  Throwing them away would be a
  // mistake.

  recompute();
  _next_event_index = _events.size();
  _active.clear();

  int now = double_to_int_time(t);

  /*
  // One special case: if we step to t == 0.0, it really means to the
  // very beginning of the interval, *before* any events that occurred
  // at time 0.  (Most of the time, stepping to a particular time
  // means *after* any events that occurred at that time.)
  if (t == 0.0) {
    now = -1;
  }
  */

  // Now look for events from the end down to the current time.
  _processing_events = true;
  ActiveEvents new_active;
  while (_next_event_index > 0 && 
         _events[_next_event_index - 1]->_time > now) {
    _next_event_index--;
    PlaybackEvent *event = _events[_next_event_index];
    
    // Do the indicated event.
    do_event_reverse(event, new_active, true);
  }
  finish_events_reverse(now, new_active);
  _processing_events = false;

  _curr_t = t;
  _state = S_started;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::reverse_instant
//       Access: Published, Virtual
//  Description: This is called in lieu of priv_reverse_initialize()
//               .. priv_step() .. priv_reverse_finalize(), when everything is
//               to happen within one frame.  The interval should
//               initialize itself, then leave itself in the initial
//               state.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
priv_reverse_instant() {
  if (_processing_events) {
    enqueue_self_event(ET_reverse_instant);
    return;
  }

  check_stopped(get_class_type(), "priv_reverse_instant");
  recompute();
  _active.clear();

  // Apply all of the events.  This just means we invoke "instant" for
  // any end or instant event, ignoring the begin events.
  _processing_events = true;
  PlaybackEvents::reverse_iterator ei;
  for (ei = _events.rbegin(); ei != _events.rend(); ++ei) {
    PlaybackEvent *event = (*ei);
    if (event->_type != PET_begin) {
      enqueue_event(event->_n, ET_reverse_instant, true, 0);
    }
  }
  _processing_events = false;

  _next_event_index = 0;
  _curr_t = 0.0;
  _state = S_initial;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::reverse_finalize
//       Access: Published, Virtual
//  Description: Called generally following a priv_reverse_initialize(),
//               this indicates the interval should set itself to the
//               initial state.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
priv_reverse_finalize() {
  if (_processing_events) {
    enqueue_self_event(ET_reverse_finalize);
    return;
  }

  if (_state == S_initial) {
    priv_initialize(0.0);
  }

  // Do all remaining events at the beginning.
  _processing_events = true;
  ActiveEvents new_active;

  while (_next_event_index > 0) {
    _next_event_index--;
    PlaybackEvent *event = _events[_next_event_index];

    do_event_reverse(event, new_active, true);
  }
  finish_events_reverse(0, new_active);
  _processing_events = false;

  _curr_t = 0.0;
  _state = S_initial;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::interrupt
//       Access: Published, Virtual
//  Description: This is called while the interval is playing to
//               indicate that it is about to be interrupted; that is,
//               priv_step() will not be called for a length of time.  But
//               the interval should remain in its current state in
//               anticipation of being eventually restarted when the
//               calls to priv_step() eventually resume.
//
//               The purpose of this function is to allow self-running
//               intervals like sound intervals to stop the actual
//               sound playback during the pause.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
priv_interrupt() {
  if (_processing_events) {
    enqueue_self_event(ET_interrupt);
    return;
  }

  _processing_events = true;
  ActiveEvents::iterator ai;
  for (ai = _active.begin(); ai != _active.end(); ++ai) {
    PlaybackEvent *event = (*ai);
    enqueue_event(event->_n, ET_interrupt, false);
  }
  _processing_events = false;

  if (_state == S_started) {
    _state = S_paused;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::pop_event
//       Access: Published
//  Description: Acknowledges that the external interval on the top of
//               the queue has been extracted, and is about to be
//               serviced by the scripting language.  This prepares
//               the interval so the next call to is_event_ready()
//               will return information about the next external
//               interval on the queue, if any.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
pop_event() {
#ifndef NDEBUG
  nassertv(!_event_queue.empty());
  const EventQueueEntry &entry = _event_queue.front();
  const IntervalDef &def = _defs[entry._n];
  nassertv(def._type == DT_ext_index);
#endif
  _event_queue.pop_front();
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::write
//       Access: Published, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void CMetaInterval::
write(ostream &out, int indent_level) const {
  recompute();

  // How many digits of precision should we output for time?
  int num_decimals = (int)ceil(log10(_precision));
  int total_digits = num_decimals + 4;
  static const int max_digits = 32;  // totally arbitrary
  nassertv(total_digits <= max_digits);
  char format_str[12];
  sprintf(format_str, "%%%d.%df", total_digits, num_decimals);

  indent(out, indent_level) << get_name() << ":\n";

  int extra_indent_level = 1;
  Defs::const_iterator di;
  for (di = _defs.begin(); di != _defs.end(); ++di) {
    const IntervalDef &def = (*di);
    char time_str[max_digits + 1];
    sprintf(time_str, format_str, int_to_double_time(def._actual_begin_time));
    indent(out, indent_level) << time_str;

    write_event_desc(out, def, extra_indent_level);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::timeline
//       Access: Published
//  Description: Outputs a list of all events in the order in which
//               they occur.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
timeline(ostream &out) const {
  recompute();

  // How many digits of precision should we output for time?
  int num_decimals = (int)ceil(log10(_precision));
  int total_digits = num_decimals + 4;
  static const int max_digits = 32;  // totally arbitrary
  nassertv(total_digits <= max_digits);
  char format_str[12];
  sprintf(format_str, "%%%d.%df", total_digits, num_decimals);

  int extra_indent_level = 0;
  PlaybackEvents::const_iterator ei;
  for (ei = _events.begin(); ei != _events.end(); ++ei) {
    const PlaybackEvent *event = (*ei);

    char time_str[max_digits + 1];
    sprintf(time_str, format_str, int_to_double_time(event->_time));
    out << time_str;

    switch (event->_type) {
    case PET_begin:
      out << " begin   ";
      break;
    case PET_end:
      out << " end     ";
      break;
    case PET_instant:
      out << " instant ";
      break;
    }

    int n = event->_n;
    nassertv(n >= 0 && n < (int)_defs.size());
    const IntervalDef &def = _defs[n];

    write_event_desc(out, def, extra_indent_level);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::do_recompute
//       Access: Protected, Virtual
//  Description: Recomputes all of the events (and the duration)
//               according to the set of interval defs.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
do_recompute() {
  _dirty = false;
  clear_events();

  int n = recompute_level(0, 0, _end_time);

  if (n != (int)_defs.size()) {
    interval_cat.warning()
      << "CMetaInterval pushes don't match pops.\n";
  }

  // We do a stable_sort() to guarantee ordering of events that have
  // the same start time.  These must be invoked in the order in which
  // they appear.
  stable_sort(_events.begin(), _events.end(), IndirectLess<PlaybackEvent>());
  _duration = int_to_double_time(_end_time);
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::clear_events
//       Access: Private
//  Description: Removes all entries from the _events list.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
clear_events() {
  PlaybackEvents::iterator ei;
  for (ei = _events.begin(); ei != _events.end(); ++ei) {
    PlaybackEvent *event = (*ei);
    delete event;
  }
  _events.clear();
  _active.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::do_event_forward
//       Access: Private
//  Description: Process a single event in the interval, moving
//               forwards in time.  If the event represents a new
//               begin, adds it to the new_active list; if it is an
//               end, finalizes it.
//
//               If is_initial is true, it is as if we are in
//               initialize or finalize: instant events will be
//               invoked only if they are marked open_ended.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
do_event_forward(CMetaInterval::PlaybackEvent *event, 
                 CMetaInterval::ActiveEvents &new_active, bool is_initial) {
  switch (event->_type) {
  case PET_begin:
    nassertv(event->_begin_event == event);
    new_active.push_back(event);
    break;
    
  case PET_end:
    {
      // Erase the event from either the new active or the current
      // active lists.
      ActiveEvents::iterator ai;
      ai = find(new_active.begin(), new_active.end(), event->_begin_event);
      if (ai != new_active.end()) {
        new_active.erase(ai);
        // This interval was new this frame; we must invoke it as
        // an instant event.
        enqueue_event(event->_n, ET_instant, is_initial);

      } else {
        ai = find(_active.begin(), _active.end(), event->_begin_event);
        if (ai != _active.end()) {
          _active.erase(ai);
          enqueue_event(event->_n, ET_finalize, is_initial);

        } else {
          // Hmm, this event wasn't on either list.  Maybe there was a
          // start event on the list whose time was less than 0.
          interval_cat.error()
            << "Event " << event->_begin_event->_n << " not on active list.\n";
          nassertv(false);
        }
      }
    }
    break;
    
  case PET_instant:
    nassertv(event->_begin_event == event);
    enqueue_event(event->_n, ET_instant, is_initial);
    break;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::finish_events_forward
//       Access: Private
//  Description: After walking through the event list and adding a
//               bunch of new events to new_active, finished up by
//               calling priv_step() on all of the events still in _active
//               and priv_initialize() on all the events in new_active,
//               then copying the events from new_active to active.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
finish_events_forward(int now, CMetaInterval::ActiveEvents &new_active) {
  // Do whatever's still active.
  ActiveEvents::iterator ai;
  for (ai = _active.begin(); ai != _active.end(); ++ai) {
    PlaybackEvent *event = (*ai);
    enqueue_event(event->_n, ET_step, false, now - event->_time);
  }
  
  // Initialize whatever new intervals we came across.
  for (ai = new_active.begin(); ai != new_active.end(); ++ai) {
    PlaybackEvent *event = (*ai);
    enqueue_event(event->_n, ET_initialize, false, now - event->_time);
    _active.push_back(event);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::do_event_reverse
//       Access: Private
//  Description: Process a single event in the interval, moving
//               backwards in time.  This undoes the indicated event.
//               If the event represents a new begin, adds it to the
//               new_active list; if it is an end, finalizes it.
//
//               If is_initial is true, it is as if we are in
//               reverse_initialize or reverse_finalize: instant
//               events will be invoked only if they are marked
//               open_ended.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
do_event_reverse(CMetaInterval::PlaybackEvent *event, 
                 CMetaInterval::ActiveEvents &new_active, bool is_initial) {
  // Undo the indicated event.
  switch (event->_type) {
  case PET_begin:
    {
      nassertv(event->_begin_event == event);
      // Erase the event from either the new active or the current
      // active lists.
      ActiveEvents::iterator ai;
      ai = find(new_active.begin(), new_active.end(), event);
      if (ai != new_active.end()) {
        new_active.erase(ai);
        // This interval was new this frame; we invoke it as an
        // instant event.
        enqueue_event(event->_n, ET_reverse_instant, is_initial);

      } else {
        ai = find(_active.begin(), _active.end(), event);
        if (ai != _active.end()) {
          _active.erase(ai);
          enqueue_event(event->_n, ET_reverse_finalize, is_initial);

        } else {
          // Hmm, this event wasn't on either list.  Maybe there was a
          // stop event on the list whose time was greater than the
          // total, somehow. 
          interval_cat.error()
            << "Event " << event->_n << " not on active list.\n";
          nassertv(false);
        }
      }
    }
    break;
    
  case PET_end:
    new_active.push_front(event->_begin_event);
    break;
    
  case PET_instant:
    nassertv(event->_begin_event == event);
    enqueue_event(event->_n, ET_reverse_instant, is_initial);
    break;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::finish_events_reverse
//       Access: Private
//  Description: After walking through the event list and adding a
//               bunch of new events to new_active, finishes up by
//               calling priv_step() on all of the events still in _active
//               and priv_reverse_initialize() on all the events in
//               new_active, then copying the events from new_active
//               to active.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
finish_events_reverse(int now, CMetaInterval::ActiveEvents &new_active) {
  // Do whatever's still active.
  ActiveEvents::iterator ai;
  for (ai = _active.begin(); ai != _active.end(); ++ai) {
    PlaybackEvent *event = (*ai);
    enqueue_event(event->_n, ET_step, false, now - event->_time);
  }
  
  // Initialize whatever new intervals we came across.
  for (ai = new_active.begin(); ai != new_active.end(); ++ai) {
    PlaybackEvent *event = (*ai);
    enqueue_event(event->_n, ET_reverse_initialize, false, now - event->_time);
    _active.push_front(event);
  }
}
  
////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::enqueue_event
//       Access: Private
//  Description: Enqueues the indicated interval for invocation after
//               we have finished scanning for events that need
//               processing this frame.
//
//               is_initial is only relevant for event types
//               ET_instant or ET_reverse_instant, and indicates
//               whether we are in the priv_initialize() (or
//               priv_reverse_initialize()) call, and should therefore only
//               invoke open-ended intervals.
//
//               time is only relevant for ET_initialize,
//               ET_reverse_initialize, and ET_step.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
enqueue_event(int n, CInterval::EventType event_type, bool is_initial, int time) {
  nassertv(n >= 0 && n < (int)_defs.size());
  const IntervalDef &def = _defs[n];
  switch (def._type) {
  case DT_c_interval:
    if (is_initial &&
        (event_type == ET_instant || event_type == ET_reverse_instant) &&
        !def._c_interval->get_open_ended()) {
      // Ignore a non-open-ended interval that we skipped completely
      // past on priv_initialize().
      return;
    } else {
      if (_event_queue.empty()) {
        // if the event queue is empty, we can process this C++
        // interval immediately.  We only need to defer it if there
        // are external (e.g. Python) intervals in the queue that need
        // to be processed first.
        def._c_interval->priv_do_event(int_to_double_time(time), event_type);
        return;
      }
    }
    break;

  case DT_ext_index:
    if (is_initial &&
        (event_type == ET_instant || event_type == ET_reverse_instant) &&
        !def._ext_open_ended) {
      // Ignore a non-open-ended interval that we skipped completely
      // past on priv_initialize().
      return;
    }
    break;

  default:
    nassertv(false);
    return;
  }

  _event_queue.push_back(EventQueueEntry(n, event_type, time));
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::enqueue_self_event
//       Access: Private
//  Description: Enqueues a reference to *this* interval.  This is
//               called only when the interval is recursively
//               re-entered; the request will be serviced when the
//               current request is done processing.
//
//               time is only relevant for ET_initialize,
//               ET_reverse_initialize, and ET_step.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
enqueue_self_event(CInterval::EventType event_type, double t) {
  interval_cat.info()
    << "Recursive reentry detected into " << *this << "\n";
  int time = double_to_int_time(t);
  _event_queue.push_back(EventQueueEntry(-1, event_type, time));
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::enqueue_done_event
//       Access: Private
//  Description: Enqueues a special "event" that simply marks the end
//               of processing of the interval; the interval's done
//               event should be thrown now, if it is defined.
////////////////////////////////////////////////////////////////////
void CMetaInterval::
enqueue_done_event() {
  _event_queue.push_back(EventQueueEntry(-2, ET_finalize, 0));
}
  
////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::service_event_queue
//       Access: Private
//  Description: Invokes whatever C++ intervals might be at the head
//               of the queue, and prepares for passing an external
//               interval to the scripting language.
//
//               The return value is true if there remains at least
//               one external event to be serviced, false if all
//               events are handled.
////////////////////////////////////////////////////////////////////
bool CMetaInterval::
service_event_queue() {
  while (!_event_queue.empty()) {
    nassertr(!_processing_events, true);
    const EventQueueEntry &entry = _event_queue.front();
    if (entry._n == -1) {
      // Index -1 is a special code for *this* interval.
      priv_do_event(int_to_double_time(entry._time), entry._event_type);

    } else if (entry._n == -2) {
      // Index -2 is a special code to indicate the interval is now
      // done, and its done event should be thrown.
      interval_done();

    } else {
      nassertr(entry._n >= 0 && entry._n < (int)_defs.size(), false);
      const IntervalDef &def = _defs[entry._n];
      switch (def._type) {
      case DT_c_interval:
        // Handle the C++ event.
        def._c_interval->priv_do_event(int_to_double_time(entry._time), entry._event_type);
        break;
        
      case DT_ext_index:
        // Here's an external event; leave it there and return.
        return true;
        
      default:
        nassertr(false, false);
        return false;
      }
    }
    _event_queue.pop_front();
  }

  // No more events on the queue.
  nassertr(!_processing_events, false);
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::recompute_level
//       Access: Private
//  Description: Recursively recomputes a complete level (delimited by
//               push/pop definitions).
//
//               The value n on entry refers to the first entry after
//               the push; the return value will reference the
//               matching pop, or an index greater than the last
//               element in the array if there was no matching pop.
//
//               The level_begin value indicates the begin time of
//               this level.  On return, level_end is filled with the
//               end time of this level.
////////////////////////////////////////////////////////////////////
int CMetaInterval::
recompute_level(int n, int level_begin, int &level_end) {
  level_end = level_begin;
  int previous_begin = level_begin;
  int previous_end = level_begin;

  while (n < (int)_defs.size() && _defs[n]._type != DT_pop_level) {
    IntervalDef &def = _defs[n];
    int begin_time = previous_begin;
    int end_time = previous_end;
    switch (def._type) {
    case DT_c_interval:
      begin_time = get_begin_time(def, level_begin, previous_begin, previous_end);
      def._actual_begin_time = begin_time;
      end_time = begin_time + double_to_int_time(def._c_interval->get_duration());

      if (def._c_interval->is_exact_type(WaitInterval::get_class_type())) {
        // Don't bother enqueuing events for WaitIntervals; they're
        // just there to fill up time.

      } else {
        if (begin_time == end_time) {
          _events.push_back(new PlaybackEvent(begin_time, n, PET_instant));
        } else {
          PlaybackEvent *begin = new PlaybackEvent(begin_time, n, PET_begin);
          PlaybackEvent *end = new PlaybackEvent(end_time, n, PET_end);
          end->_begin_event = begin;
          _events.push_back(begin);
          _events.push_back(end);
        }
      }
      break;

    case DT_ext_index:
      begin_time = get_begin_time(def, level_begin, previous_begin, previous_end);
      def._actual_begin_time = begin_time;
      end_time = begin_time + double_to_int_time(def._ext_duration);
      if (begin_time == end_time) {
        _events.push_back(new PlaybackEvent(begin_time, n, PET_instant));
      } else {
        PlaybackEvent *begin = new PlaybackEvent(begin_time, n, PET_begin);
        PlaybackEvent *end = new PlaybackEvent(end_time, n, PET_end);
        end->_begin_event = begin;
        _events.push_back(begin);
        _events.push_back(end);
      }
      break;

    case DT_push_level:
      begin_time = get_begin_time(def, level_begin, previous_begin, previous_end);
      def._actual_begin_time = begin_time;
      n = recompute_level(n + 1, begin_time, end_time);
      break;

    case DT_pop_level:
      nassertr(false, _defs.size());
      break;
    }

    previous_begin = begin_time;
    previous_end = end_time;
    level_end = max(level_end, end_time);
    n++;
  }

  if (n < (int)_defs.size()) {
    IntervalDef &def = _defs[n];
    // If we have a pop record, check it for a phony duration.
    if (def._ext_duration >= 0.0) {
      level_end = level_begin + double_to_int_time(def._ext_duration);
    }

    // The final pop "begins" at the level end time, just for clarity
    // on output.
    def._actual_begin_time = level_end;
  }

  return n;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::get_begin_time
//       Access: Private
//  Description: Returns the integer begin time indicated by the given
//               IntervalDef, given the indicated level begin,
//               previous begin, and previous end times.
////////////////////////////////////////////////////////////////////
int CMetaInterval::
get_begin_time(const CMetaInterval::IntervalDef &def, int level_begin,
               int previous_begin, int previous_end) {
  switch (def._rel_to) {
  case RS_previous_end:
    return previous_end + double_to_int_time(def._rel_time);

  case RS_previous_begin:
    return previous_begin + double_to_int_time(def._rel_time);

  case RS_level_begin:
    return level_begin + double_to_int_time(def._rel_time);
  }

  nassertr(false, previous_end);
  return previous_end;
}

////////////////////////////////////////////////////////////////////
//     Function: CMetaInterval::write_event_desc
//       Access: Private
//  Description: Formats an event for output, for write() or
//               timeline().
////////////////////////////////////////////////////////////////////
void CMetaInterval::
write_event_desc(ostream &out, const CMetaInterval::IntervalDef &def, 
                 int &extra_indent_level) const {
  switch (def._type) {
  case DT_c_interval:
    indent(out, extra_indent_level)
      << *def._c_interval;
    if (!def._c_interval->get_open_ended()) {
      out << " (!oe)";
    }
    out << "\n";
    break;
    
  case DT_ext_index:
    indent(out, extra_indent_level)
      << "*" << def._ext_name;
    if (def._ext_duration != 0.0) {
      out << " dur " << def._ext_duration;
    }
    if (!def._ext_open_ended) {
      out << " (!oe)";
    }
    out<< "\n";
    break;
    
  case DT_push_level:
    indent(out, extra_indent_level)
      << def._ext_name << " {\n";
    extra_indent_level += 2;
    break;
    
  case DT_pop_level:
    extra_indent_level -= 2;
    indent(out, extra_indent_level)
      << "}\n";
    break;
  }
}
