// Filename: pStatThreadData.cxx
// Created by:  drose (09Jul00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "pStatThreadData.h"

#include "pStatFrameData.h"
#include "pStatCollectorDef.h"
#include "config_pstats.h"


PStatFrameData PStatThreadData::_null_frame;

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
PStatThreadData::
PStatThreadData(const PStatClientData *client_data) :
  _client_data(client_data)
{
  _first_frame_number = 0;
  _history = pstats_history;
  _computed_elapsed_frames = false;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::Destructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
PStatThreadData::
~PStatThreadData() {
}


////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::is_empty
//       Access: Public
//  Description: Returns true if the structure contains no frames,
//               false otherwise.
////////////////////////////////////////////////////////////////////
bool PStatThreadData::
is_empty() const {
  return _frames.empty();
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_latest_frame_number
//       Access: Public
//  Description: Returns the frame number of the most recent frame
//               stored in the data.
////////////////////////////////////////////////////////////////////
int PStatThreadData::
get_latest_frame_number() const {
  nassertr(!_frames.empty(), 0);
  return _first_frame_number + _frames.size() - 1;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_oldest_frame_number
//       Access: Public
//  Description: Returns the frame number of the oldest frame still
//               stored in the data.
////////////////////////////////////////////////////////////////////
int PStatThreadData::
get_oldest_frame_number() const {
  nassertr(!_frames.empty(), 0);
  return _first_frame_number;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::has_frame
//       Access: Public
//  Description: Returns true if we have received data for the
//               indicated frame number from the client and we still
//               have it stored, or false otherwise.
////////////////////////////////////////////////////////////////////
bool PStatThreadData::
has_frame(int frame_number) const {
  int rel_frame = frame_number - _first_frame_number;

  return (rel_frame >= 0 && rel_frame < (int)_frames.size() &&
          _frames[rel_frame] != (PStatFrameData *)NULL);
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_frame
//       Access: Public
//  Description: Returns a FrameData structure associated with the
//               indicated frame number.  If the frame data has not
//               yet been received from the client, returns the newest
//               frame older than the requested frame.
////////////////////////////////////////////////////////////////////
const PStatFrameData &PStatThreadData::
get_frame(int frame_number) const {
  int rel_frame = frame_number - _first_frame_number;
  int num_frames = _frames.size();
  if (rel_frame >= num_frames) {
    rel_frame = num_frames - 1;
  }

  while (rel_frame >= 0 && _frames[rel_frame] == (PStatFrameData *)NULL) {
    rel_frame--;
  }
  if (rel_frame < 0) {
    // No frame data that old.  Return the oldest frame we've got.
    rel_frame = 0;
    while (rel_frame < num_frames &&
           _frames[rel_frame] == (PStatFrameData *)NULL) {
      rel_frame++;
    }
  }

  if (rel_frame >= 0 && rel_frame < num_frames) {
    PStatFrameData *frame = _frames[rel_frame];
    nassertr(frame != (PStatFrameData *)NULL, _null_frame);
    nassertr(frame->get_start() >= 0.0, _null_frame);
    return *frame;
  }

  nassertr(_null_frame.get_start() >= 0.0, _null_frame);
  return _null_frame;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_latest_time
//       Access: Public
//  Description: Returns the timestamp (in seconds elapsed since
//               connection) of the latest available frame.
////////////////////////////////////////////////////////////////////
float PStatThreadData::
get_latest_time() const {
  nassertr(!_frames.empty(), 0.0);
  return _frames.back()->get_start();
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_oldest_time
//       Access: Public
//  Description: Returns the timestamp (in seconds elapsed since
//               connection) of the oldest available frame.
////////////////////////////////////////////////////////////////////
float PStatThreadData::
get_oldest_time() const {
  nassertr(!_frames.empty(), 0.0);
  return _frames.front()->get_start();
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_frame_at_time
//       Access: Public
//  Description: Returns the FrameData structure associated with the
//               latest frame not later than the indicated time.
////////////////////////////////////////////////////////////////////
const PStatFrameData &PStatThreadData::
get_frame_at_time(float time) const {
  return get_frame(get_frame_number_at_time(time));
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_frame_number_at_time
//       Access: Public
//  Description: Returns the frame number of the latest frame not
//               later than the indicated time.
//
//               If the hint is nonnegative, it represents a frame
//               number that we believe the correct answer to be near,
//               which may speed the search for the frame.
////////////////////////////////////////////////////////////////////
int PStatThreadData::
get_frame_number_at_time(float time, int hint) const {
  hint -= _first_frame_number;
  if (hint >= 0 && hint < (int)_frames.size()) {
    if (_frames[hint] != (PStatFrameData *)NULL &&
        _frames[hint]->get_start() <= time) {
      // The hint might be right.  Scan forward from there.
      int i = hint + 1;
      while (i < (int)_frames.size() &&
             (_frames[i] == (PStatFrameData *)NULL ||
              _frames[i]->get_start() <= time)) {
        if (_frames[i] != (PStatFrameData *)NULL) {
          hint = i;
        }
        ++i;
      }
      return _first_frame_number + hint;
    }
  }

  // The hint is totally wrong.  Start from the end and work
  // backwards.

  int i = _frames.size() - 1;
  while (i >= 0) {
    const PStatFrameData *frame = _frames[i];
    if (frame != (PStatFrameData *)NULL && frame->get_start() <= time) {
      break;
    }
    --i;
  }

  return _first_frame_number + i;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_latest_frame
//       Access: Public
//  Description: Returns the FrameData associated with the most recent
//               frame.
////////////////////////////////////////////////////////////////////
const PStatFrameData &PStatThreadData::
get_latest_frame() const {
  nassertr(!_frames.empty(), _null_frame);
  return *_frames.back();
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_elapsed_frames
//       Access: Public
//  Description: Computes the oldest frame number not older than
//               pstats_average_time seconds, and the newest frame
//               number.  Handy for computing average frame rate over
//               a time.  Returns true if there is any data in that
//               range, false otherwise.
////////////////////////////////////////////////////////////////////
bool PStatThreadData::
get_elapsed_frames(int &then_i, int &now_i) const {
  if (!_computed_elapsed_frames) {
    ((PStatThreadData *)this)->compute_elapsed_frames();
  }

  now_i = _now_i;
  then_i = _then_i;
  return _got_elapsed_frames;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_frame_rate
//       Access: Public
//  Description: Computes the average frame rate over the past
//               pstats_average_time seconds, by counting up the
//               number of frames elapsed in that time interval.
////////////////////////////////////////////////////////////////////
float PStatThreadData::
get_frame_rate() const {
  int then_i, now_i;
  if (!get_elapsed_frames(then_i, now_i)) {
    return 0.0f;
  }

  int num_frames = now_i - then_i + 1;
  float now = _frames[now_i - _first_frame_number]->get_end();
  float elapsed_time = (now - _frames[then_i - _first_frame_number]->get_start());
  return (float)num_frames / elapsed_time;
}


////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::set_history
//       Access: Public
//  Description: Sets the number of seconds worth of frames that will
//               be retained by the ThreadData structure as each new
//               frame is added.  This affects how old the oldest
//               frame that may be queried is.
////////////////////////////////////////////////////////////////////
void PStatThreadData::
set_history(float time) {
  _history = time;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::get_history
//       Access: Public
//  Description: Returns the number of seconds worth of frames that
//               will be retained by the ThreadData structure as each
//               new frame is added.  This affects how old the oldest
//               frame that may be queried is.
////////////////////////////////////////////////////////////////////
float PStatThreadData::
get_history() const {
  return _history;
}


////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::record_new_frame
//       Access: Public
//  Description: Makes room for and stores a new frame's worth of
//               data.  Calling this function may cause old frame data
//               to be discarded to make room, according to the amount
//               of time set up via set_history().
//
//               The pointer will become owned by the PStatThreadData
//               object and will be freed on destruction.
////////////////////////////////////////////////////////////////////
void PStatThreadData::
record_new_frame(int frame_number, PStatFrameData *frame_data) {
  nassertv(frame_data != (PStatFrameData *)NULL);
  nassertv(!frame_data->is_empty());
  float time = frame_data->get_start();

  // First, remove all the old frames that fall outside of our
  // history window.
  float oldest_allowable_time = time - _history;
  while (!_frames.empty() &&
         (_frames.front() == (PStatFrameData *)NULL ||
          _frames.front()->is_empty() ||
          _frames.front()->get_start() < oldest_allowable_time)) {
    if (_frames.front() != (PStatFrameData *)NULL) {
      delete _frames.front();
    }
    _frames.pop_front();
    _first_frame_number++;
  }

  // Now, add enough empty frame definitions to account for the latest
  // frame number.  This might involve some skips, since we don't
  // guarantee that we get all the frames in order or even at all.
  if (_frames.empty()) {
    _first_frame_number = frame_number;
    _frames.push_back(NULL);

  } else {
    while (_first_frame_number + (int)_frames.size() <= frame_number) {
      _frames.push_back(NULL);
    }
  }

  int index = frame_number - _first_frame_number;
  nassertv(index >= 0 && index < (int)_frames.size());

  if (_frames[index] != (PStatFrameData *)NULL) {
    nout << "Got repeated frame data for frame " << frame_number << "\n";
    delete _frames[index];
  }

  _frames[index] = frame_data;
  _computed_elapsed_frames = false;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatThreadData::compute_elapsed_frames
//       Access: Private
//  Description: Computes the frame numbers returned by
//               get_elapsed_frames().  This is non-const, but only
//               updates cached values, so may safely be called from a
//               const method.
////////////////////////////////////////////////////////////////////
void PStatThreadData::
compute_elapsed_frames() {
  if (_frames.empty()) {
    // No frames in the data at all.
    _got_elapsed_frames = false;
    
  } else {
    _now_i = _frames.size() - 1;
    while (_now_i > 0 && _frames[_now_i] == (PStatFrameData *)NULL) {
      _now_i--;
    }
    if (_now_i < 0) {
      // No frames have any real data.
      _got_elapsed_frames = false;
      
    } else {
      nassertv(_frames[_now_i] != (PStatFrameData *)NULL);
      
      float now = _frames[_now_i]->get_end();
      float then = now - pstats_average_time;
      
      int old_i = _now_i;
      _then_i = _now_i;
      
      while (old_i >= 0) {
        const PStatFrameData *frame = _frames[old_i];
        if (frame != (PStatFrameData *)NULL) {
          if (frame->get_start() > then) {
            _then_i = old_i;
          } else {
            break;
          }
        }
        old_i--;
      }
      
      nassertv(_then_i >= 0);
      nassertv(_frames[_then_i] != (PStatFrameData *)NULL);
      _got_elapsed_frames = true;

      _now_i += _first_frame_number;
      _then_i += _first_frame_number;
    }
  }
  
  _computed_elapsed_frames = true;
}
