// Filename: pStatGraph.cxx
// Created by:  drose (19Jul00)
//
////////////////////////////////////////////////////////////////////
//
// 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 "pStatGraph.h"
#include "pStatServer.h"
#include "pStatFrameData.h"
#include "pStatCollectorDef.h"
#include "string_utils.h"
#include "config_pstats.h"

#include <stdio.h>  // for sprintf

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::GuideBar::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
PStatGraph::GuideBar::
GuideBar(float height, const string &label, PStatGraph::GuideBarStyle style) :
  _height(height),
  _label(label),
  _style(style)
{
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::GuideBar::Copy Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
PStatGraph::GuideBar::
GuideBar(const PStatGraph::GuideBar &copy) :
  _height(copy._height),
  _label(copy._label),
  _style(copy._style)
{
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
PStatGraph::
PStatGraph(PStatMonitor *monitor, int xsize, int ysize) :
  _monitor(monitor),
  _xsize(xsize),
  _ysize(ysize)
{
  _target_frame_rate = pstats_target_frame_rate;
  _labels_changed = false;
  _guide_bars_changed = false;
  _guide_bar_units = GBU_ms;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::Destructor
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
PStatGraph::
~PStatGraph() {
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::get_num_guide_bars
//       Access: Public
//  Description: Returns the number of horizontal guide bars that
//               should be drawn, based on the indicated target frame
//               rate.  Not all of these may be visible; some may be
//               off the top of the chart because of the vertical
//               scale.
////////////////////////////////////////////////////////////////////
int PStatGraph::
get_num_guide_bars() const {
  return _guide_bars.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::get_guide_bar
//       Access: Public
//  Description: Returns the nth horizontal guide bar.  This should be
//               drawn as a horizontal line across the chart at the y
//               pixel location determined by height_to_pixel(bar._height).
//
//               It is possible that this bar will be off the top of
//               the chart.
////////////////////////////////////////////////////////////////////
const PStatGraph::GuideBar &PStatGraph::
get_guide_bar(int n) const {
#ifndef NDEBUG
  static GuideBar bogus_bar(0.0, "bogus", GBS_normal);
  nassertr(n >= 0 && n < (int)_guide_bars.size(), bogus_bar);
#endif
  return _guide_bars[n];
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::get_num_user_guide_bars
//       Access: Public
//  Description: Returns the current number of user-defined guide
//               bars.  Not all of these may be visible.
////////////////////////////////////////////////////////////////////
int PStatGraph::
get_num_user_guide_bars() const {
  return _monitor->get_server()->get_num_user_guide_bars();
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::get_user_guide_bar
//       Access: Public
//  Description: Returns the nth user-defined guide bar.
////////////////////////////////////////////////////////////////////
PStatGraph::GuideBar PStatGraph::
get_user_guide_bar(int n) const {
  float height = _monitor->get_server()->get_user_guide_bar_height(n);
  return make_guide_bar(height, GBS_user);
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::move_user_guide_bar
//       Access: Public
//  Description: Adjusts the height of the nth user-defined guide bar.
////////////////////////////////////////////////////////////////////
void PStatGraph::
move_user_guide_bar(int n, float height) {
  _monitor->get_server()->move_user_guide_bar(n, height);
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::add_user_guide_bar
//       Access: Public
//  Description: Creates a new user guide bar and returns its index
//               number.
////////////////////////////////////////////////////////////////////
int PStatGraph::
add_user_guide_bar(float height) {
  return _monitor->get_server()->add_user_guide_bar(height);
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::remove_user_guide_bar
//       Access: Public
//  Description: Removes the user guide bar with the indicated index
//               number.  All subsequent index numbers are adjusted
//               down one.
////////////////////////////////////////////////////////////////////
void PStatGraph::
remove_user_guide_bar(int n) {
  _monitor->get_server()->remove_user_guide_bar(n);
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::find_user_guide_bar
//       Access: Public
//  Description: Returns the index number of the first user guide bar
//               found whose height is within the indicated range, or
//               -1 if no user guide bars fall within the range.
////////////////////////////////////////////////////////////////////
int PStatGraph::
find_user_guide_bar(float from_height, float to_height) const {
  return _monitor->get_server()->find_user_guide_bar(from_height, to_height);
}


////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::format_number
//       Access: Public, Static
//  Description: Returns a string representing the value nicely
//               formatted for its range.
////////////////////////////////////////////////////////////////////
string PStatGraph::
format_number(float value) {
  char buffer[128];

  if (value < 0.01) {
    sprintf(buffer, "%0.4f", value);
  } else if (value < 0.1) {
    sprintf(buffer, "%0.3f", value);
  } else if (value < 1.0) {
    sprintf(buffer, "%0.2f", value);
  } else if (value < 10.0) {
    sprintf(buffer, "%0.1f", value);
  } else {
    sprintf(buffer, "%0.0f", value);
  }

  return buffer;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::format_number
//       Access: Public, Static
//  Description: Returns a string representing the value nicely
//               formatted for its range, including the units
//               as indicated.
////////////////////////////////////////////////////////////////////
string PStatGraph::
format_number(float value, int guide_bar_units, const string &unit_name) {
  string label;

  if ((guide_bar_units & GBU_named) != 0) {
    // Units are whatever is specified by unit_name, not a time unit
    // at all.
    label = format_number(value);
    if ((guide_bar_units & GBU_show_units) != 0 && !unit_name.empty()) {
      label += " ";
      label += unit_name;
    }

  } else {
    // Units are either milliseconds or hz, or both.
    if ((guide_bar_units & GBU_ms) != 0) {
      float ms = value * 1000.0;
      label += format_number(ms);
      if ((guide_bar_units & GBU_show_units) != 0) {
        label += " ms";
      }
    }

    if ((guide_bar_units & GBU_hz) != 0) {
      float hz = 1.0 / value;

      if ((guide_bar_units & GBU_ms) != 0) {
        label += " (";
      }
      label += format_number(hz);
      if ((guide_bar_units & GBU_show_units) != 0) {
        label += " Hz";
      }
      if ((guide_bar_units & GBU_ms) != 0) {
        label += ")";
      }
    }
  }

  return label;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::update_guide_bars
//       Access: Protected
//  Description: Resets the list of guide bars.
////////////////////////////////////////////////////////////////////
void PStatGraph::
update_guide_bars(int num_bars, float scale) {
  _guide_bars.clear();

  // We'd like to draw about num_bars bars on the chart.  But we also
  // want the bars to be harmonics of the target frame rate, so that
  // the bottom bar is at tfr/n or n * tfr, where n is an integer, and
  // the upper bars are even multiples of that.

  // Choose a suitable harmonic of the target frame rate near the
  // bottom part of the chart.

  float bottom = (float)num_bars / scale;

  float harmonic;
  if (_target_frame_rate < bottom) {
    // n * tfr
    harmonic = floor(bottom / _target_frame_rate + 0.5) * _target_frame_rate;

  } else {
    // tfr / n
    harmonic = _target_frame_rate / floor(_target_frame_rate / bottom + 0.5);
  }

  // Now, make a few bars at k / harmonic.
  for (int k = 1; k / harmonic <= scale; k++) {
    _guide_bars.push_back(make_guide_bar(k / harmonic));
  }

  _guide_bars_changed = true;
}

////////////////////////////////////////////////////////////////////
//     Function: PStatGraph::make_guide_bar
//       Access: Protected
//  Description: Makes a guide bar for the indicated elapsed time or
//               level units.
////////////////////////////////////////////////////////////////////
PStatGraph::GuideBar PStatGraph::
make_guide_bar(float value, PStatGraph::GuideBarStyle style) const {
  string label = format_number(value, _guide_bar_units, _unit_name);

  if ((style == GBS_normal) &&
      (_guide_bar_units & GBU_named) == 0) {
    // If it's a time unit, check to see if it matches our target
    // frame rate.
    float hz = 1.0 / value;
    if (IS_THRESHOLD_EQUAL(hz, _target_frame_rate, 0.001)) {
      style = GBS_target;
    }
  }

  return GuideBar(value, label, style);
}
