// Filename: asyncTaskSequence.cxx
// Created by:  drose (04Oct08)
//
////////////////////////////////////////////////////////////////////
//
// 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 "asyncTaskSequence.h"

TypeHandle AsyncTaskSequence::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: AsyncTaskSequence::Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
AsyncTaskSequence::
AsyncTaskSequence(const string &name) :
  AsyncTask(name),
  _repeat_count(0),
  _task_index(0)
{
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTaskSequence::Destructor
//       Access: Published, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
AsyncTaskSequence::
~AsyncTaskSequence() {
  set_current_task(NULL, true);
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTaskSequence::is_runnable
//       Access: Protected, Virtual
//  Description: Override this function to return true if the task can
//               be successfully executed, false if it cannot.  Mainly
//               intended as a sanity check when attempting to add the
//               task to a task manager.
//
//               This function is called with the lock held.
////////////////////////////////////////////////////////////////////
bool AsyncTaskSequence::
is_runnable() {
  return (get_num_tasks() > 0);
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTaskSequence::do_task
//       Access: Protected, Virtual
//  Description: Override this function to do something useful for the
//               task.
//
//               This function is called with the lock *not* held.
////////////////////////////////////////////////////////////////////
AsyncTask::DoneStatus AsyncTaskSequence::
do_task() {
  // Clear the delay that might have been set from a previous wait.
  _delay = 0.0;
  _has_delay = false;

  if (_task_index >= get_num_tasks()) {
    // Ran off the end of the task list.
    set_current_task(NULL, true);
    _task_index = 0;
    if (_task_index >= get_num_tasks()) {
      return DS_done;
    }
    if (_repeat_count > 0) {
      --_repeat_count;
    }
    if (_repeat_count != 0) {
      return DS_cont;
    }
    return DS_done;
  }

  AsyncTask *task = get_task(_task_index);
  set_current_task(task, true);
  nassertr(_current_task != (AsyncTask *)NULL, DS_exit);

  DoneStatus result = _current_task->do_task();
  switch (result) {
  case DS_again:
  case DS_pause:
    // The task wants to sleep for a period of time.
    {
      double now = _manager->_clock->get_frame_time();
      _current_task->_start_time = now + _current_task->_delay;
      
      _delay = _current_task->_delay;
      _has_delay = _current_task->_has_delay;

      if (result == DS_pause) {
        // When it wakes up, move on to the next task.
        ++_task_index;
      }
    }
    return DS_again;

  case DS_done:
    // Go on to the next task.
    ++_task_index;
    return DS_cont;

  case DS_cont:
  case DS_pickup:
  case DS_exit:
  case DS_interrupt:
    // Just return these results through.
    return result;
  }

  // Shouldn't get here.
  nassertr(false, DS_exit);
  return DS_exit;
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTaskSequence::upon_birth
//       Access: Protected, Virtual
//  Description: Override this function to do something useful when the
//               task has been added to the active queue.
//
//               This function is called with the lock held.  You may
//               temporarily release if it necessary, but be sure to
//               return with it held.
////////////////////////////////////////////////////////////////////
void AsyncTaskSequence::
upon_birth(AsyncTaskManager *manager) {
  AsyncTask::upon_birth(manager);
  _task_index = 0;
  set_current_task(NULL, true);
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTaskSequence::upon_death
//       Access: Protected, Virtual
//  Description: Override this function to do something useful when the
//               task has been removed from the active queue.  The
//               parameter clean_exit is true if the task has been
//               removed because it exited normally (returning
//               DS_done), or false if it was removed for some other
//               reason (e.g. AsyncTaskManager::remove()).  By the
//               time this method is called, _manager has been
//               cleared, so the parameter manager indicates the
//               original AsyncTaskManager that owned this task.
//
//               The normal behavior is to throw the done_event only
//               if clean_exit is true.
//
//               This function is called with the lock held.  You may
//               temporarily release if it necessary, but be sure to
//               return with it held.
////////////////////////////////////////////////////////////////////
void AsyncTaskSequence::
upon_death(AsyncTaskManager *manager, bool clean_exit) {
  AsyncTask::upon_death(manager, clean_exit);
  set_current_task(NULL, clean_exit);
}

////////////////////////////////////////////////////////////////////
//     Function: AsyncTaskSequence::set_current_task
//       Access: Private
//  Description: Sets the current executing task.  If NULL, sets the
//               current task to NULL.
////////////////////////////////////////////////////////////////////
void AsyncTaskSequence::
set_current_task(AsyncTask *task, bool clean_exit) {
  if (_current_task == task) {
    return;
  }

  if (_current_task != (AsyncTask *)NULL) {
    nassertv(_current_task->_state == S_active_nested);
    nassertv(_current_task->_manager == _manager || _manager == NULL);
    _current_task->_state = S_inactive;
    _current_task->_manager = NULL;
    _current_task->upon_death(_manager, clean_exit);
  }

  _current_task = task;

  if (_current_task != (AsyncTask *)NULL) {
    nassertv(_current_task->_state == S_inactive);
    nassertv(_current_task->_manager == NULL);
    _current_task->upon_birth(_manager);
    nassertv(_current_task->_state == S_inactive);
    nassertv(_current_task->_manager == NULL);
    _current_task->_manager = _manager;
    _current_task->_state = S_active_nested;

    double now = _manager->_clock->get_frame_time();
    task->_start_time = now;
    task->_start_frame = _manager->_clock->get_frame_count();
  }
}
