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

  \author Satofumi KAMIMURA

  $Id: CycleTimer.cpp 1811 2010-04-30 16:12:05Z 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();
}
