/*!
  \file
  \brief 周期タイマー

  \author Satofumi KAMIMURA

  $Id: CycleTimer.cpp 1053 2009-06-20 22:52:51Z satofumi $
*/

#include "CycleTimer.h"
#include "ticks.h"
#include <limits>

using namespace qrk;
using namespace std;


namespace
{
  size_t fps2msec(size_t fps)
  {
    if (fps == 0) {
      // 最大 fps の制限
      return numeric_limits<size_t>::max();
    }

    size_t msec = 1000 / fps;
    return max(msec, static_cast<size_t>(1));
  }


  size_t msec2fps(size_t msec)
  {
    // 1000 / msec となるので、fps2msec() の計算で済ませる
    return fps2msec(msec);
  }
}


struct CycleTimer::pImpl
{
  enum {
    InvalidTicks = -1,
  };

  size_t cycle_msec_;
  int prev_ticks_;
  bool strict_cycle_;


  pImpl(void)
    : cycle_msec_(fps2msec(DefaultFps)), prev_ticks_(InvalidTicks),
      strict_cycle_(false)
  {
  }


  size_t waitMsec(void)
  {
    if (prev_ticks_ == InvalidTicks) {
      // 最初の呼び出しのときは、特に待たない
      prev_ticks_ = ticks();
      return 0;
    }

    int to_next_cycle = toNextCycleMsec();
    prev_ticks_ += static_cast<int>(cycle_msec_);

    return (to_next_cycle > 0) ? to_next_cycle : 0;
  }


  void reset(void)
  {
    prev_ticks_ = ticks();
  }


  int toNextCycleMsec(void)
  {
    int next_ticks = prev_ticks_ + static_cast<int>(cycle_msec_);

    int current_ticks = ticks();
    int to_next_cycle = next_ticks - current_ticks;
    if (to_next_cycle > 0) {
      return to_next_cycle;
    }

    if (! strict_cycle_) {
      if (to_next_cycle >= 0) {
        to_next_cycle %= cycle_msec_;
      } else {
        prev_ticks_ = current_ticks;
      }
    }

    return to_next_cycle;
  }
};


CycleTimer::CycleTimer(void) : pimpl(new pImpl)
{
}


CycleTimer::~CycleTimer(void)
{
}


size_t CycleTimer::waitMsec(void)
{
  return pimpl->waitMsec();
}


void CycleTimer::reset(void)
{
  pimpl->reset();
}


void CycleTimer::setStrictCycle(bool on)
{
  pimpl->strict_cycle_ = on;
}


void CycleTimer::setCycleFps(size_t fps)
{
  pimpl->cycle_msec_ = fps2msec(fps);
}


size_t CycleTimer::cycleFps(void) const
{
  return msec2fps(pimpl->cycle_msec_);
}


void CycleTimer::setCycleMsec(size_t msec)
{
  pimpl->cycle_msec_ = msec;
}


size_t CycleTimer::cycleMsec(void) const
{
  return pimpl->cycle_msec_;
}


int CycleTimer::toNextCycleMsec(void)
{
  return pimpl->toNextCycleMsec();
}
