// Timer.h: An object that sends timer messages to an implementation of TimerSink

#ifndef TIMER_H
#define TIMER_H

#include "InvisibleWindow.h"
#include <assert.h>

struct TimerSink
{
    virtual void OnTimer() =0;
};

class Timer : private InvisibleWindowSink
{
public:
    Timer(TimerSink* pSink);
    ~Timer();

    // nElapse = milliseconds to set timer for
    // nMinDelta = minimum change of milliseconds since last timer notification
    // e.g. nElapse = 250, nMinDelta = 1000 means check every
    // 250ms for a change of 1000ms or more.
    bool Start(UINT nElapse, UINT nMinDelta = 0);
    bool Running();
    void Stop();

private:
    UINT            m_nTimer;    
    TimerSink*      m_pSink;
    DWORD           m_nLastTickCount;
    UINT            m_nMinDelta;
    InvisibleWindow m_wnd;

    // Handle WM_TIMER messages by forwarding them to the TimerSink
    virtual LRESULT OnWindowMessage(HWND hwnd, UINT nMsg, WPARAM wparam, LPARAM lparam);
};

// Inline implementations

inline
Timer::Timer(TimerSink* pSink)
:
    m_nTimer(0),
    m_pSink(pSink)
{
    assert(m_pSink && "Must have an implementation of TimerSink");
}

inline
Timer::~Timer()
{
    Stop();
}

inline
bool Timer::Start(UINT nElapse, UINT nMinDelta)
{
    // Create the window
    if( m_wnd.GetHwnd() || m_wnd.Create(this) )
    {
        // Set the timer
        if( m_nTimer )
        {
            // Reset currently running Timer
            m_nTimer = SetTimer(m_wnd.GetHwnd(), m_nTimer, nElapse, 0);
        }
        else
        {
            // Start a stopped timer
            m_nTimer = SetTimer(m_wnd.GetHwnd(), 1, nElapse, 0);
        }

        // Cache the current tick count and the minimum delta
        if( m_nTimer )
        {
            m_nMinDelta = nMinDelta;
            m_nLastTickCount = GetTickCount();
            return true;
        }
    }

    return false;
}

inline
bool Timer::Running()
{
    return m_nTimer != 0;
}

inline
void Timer::Stop()
{
    if( Running() )
    {
        KillTimer(m_wnd.GetHwnd(), m_nTimer);
        m_nTimer = 0;
    }
}

inline
LRESULT Timer::OnWindowMessage(HWND hwnd, UINT nMsg, WPARAM wparam, LPARAM lparam)
{
    if( nMsg == WM_TIMER )
    {
        // Check for minimum delta
        DWORD   nTickCount = GetTickCount();
        if( nTickCount - m_nLastTickCount >=  m_nMinDelta )
        {
            m_nLastTickCount = nTickCount;
            m_pSink->OnTimer();
        }

        return 0L;
    }
    else
    {
        return DefWindowProc(hwnd, nMsg, wparam, lparam);
    }
}

#endif  // TIMER_H
