//
// This file is part of the aMule Project.
//
// Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
//
// Any parts of this program derived from the xMule, lMule or eMule project,
// or contributed by third-party developers are copyrighted by their
// respective authors.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
//

#include "Timer.h"		// Interface declaration
#include "GetTickCount.h"	// Needed for GetTickCountFullRes
#include "MuleThread.h"		// Needed for CMuleThread


//////////////////////// Timer Thread ////////////////////

class CTimerThread : public CMuleThread
{
public:
	CTimerThread()
		: CMuleThread(wxTHREAD_JOINABLE)
	{
	}

	void* Entry() {
		CTimerEvent evt(m_id);

		uint32 lastEvent = GetTickCountFullRes();
		do {
			// current time
			uint32 now = GetTickCountFullRes();
			// This is typically zero, because lastEvent was already incremented by one period. 
			sint32 delta = now - lastEvent;
			if (delta > 100 * m_period) {
				// We're way too far behind.  Probably what really happened is
				// the system time was adjusted backwards a bit.  So,
				// the calculation of delta has produced an absurd value.
				delta = 100 * m_period;
				lastEvent = now - delta;
			}

			// Wait one period (adjusted by the difference just calculated)
			sint32 timeout = ((m_period < delta) ? 0 : (m_period - delta));
			
			// In normal operation, we will never actually acquire the
			// semaphore; we will always timeout.  This is used to
			// implement a Sleep operation which the owning CTimer can
			// interrupt by posting to the semaphore.  So, it follows
			// that if we do acquire the semaphore it means the owner
			// wants us to exit.
			if (m_sleepSemaphore.WaitTimeout(timeout) == wxSEMA_TIMEOUT) {
				// Increment for one event only, so no events can be lost.
				lastEvent += m_period;
					
				wxPostEvent(m_owner, evt);
			} else {
				break;
			}
		} while (!m_oneShot);
		
		return NULL;
	}
	
	sint32			m_period;
	bool			m_oneShot;
	wxEvtHandler*	m_owner;
	int				m_id;
	wxSemaphore		m_sleepSemaphore;
};


////////////////////// CTimer ////////////////////////

CTimer::~CTimer()
{
	Stop();
}


CTimer::CTimer(wxEvtHandler* owner, int id)
{
	wxASSERT(owner);
	m_owner = owner;
	m_id = id;
	m_thread = NULL;
}


bool CTimer::IsRunning() const
{
	return (m_thread && m_thread->IsRunning());
}


bool CTimer::Start(int millisecs, bool oneShot)
{
	wxCHECK_MSG(m_id != -1, false, wxT("Invalid target-ID for timer-events."));
	
	// Since this class generally matches wxTimer, calling
	// start on a running timer stops and then restarts it.
	Stop();
	
	m_thread = new CTimerThread();
	m_thread->m_period	= millisecs;
	m_thread->m_oneShot	= oneShot;
	m_thread->m_owner	= m_owner;
	m_thread->m_id		= m_id;

	if (m_thread->Create() == wxTHREAD_NO_ERROR) {
		if (m_thread->Run() == wxTHREAD_NO_ERROR) {
			return true;
		}
	}

	// Something went wrong ...
	m_thread->Stop();
	delete m_thread;
	m_thread = NULL;

	return false;
}


void CTimer::Stop()
{
	if (m_thread) {
		m_thread->m_sleepSemaphore.Post();
		m_thread->Stop();
		delete m_thread;
		m_thread = NULL;
	}
}


DEFINE_LOCAL_EVENT_TYPE(MULE_EVT_TIMER)

CTimerEvent::CTimerEvent(int id)
	: wxEvent(id, MULE_EVT_TIMER)
{
}


wxEvent* CTimerEvent::Clone() const
{
	return new CTimerEvent(GetId());
}

// File_checked_for_headers
