//-------------------------------------------------------------------------------
// <copyright file="LqrSMACTimerCollection.h" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
// 	Declarations for TimerCollection class, which monitors a collection of timers
//  and triggers events for them.
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------

#ifndef __LqrSMACTimerCollection_H__
#define __LqrSMACTimerCollection_H__

#include "LqrSMACSharedLib.h"
#include "LqrSMACQueueWorker.h"
#include "LqrSMACTimer.h"
#include "LqrSMACUptime.h"
#include <map>
#include <stdexcept>
#include <boost/shared_ptr.hpp>
#include <boost/thread/locks.hpp>
#include <boost/thread/recursive_mutex.hpp>

namespace LiquidRobotics {
namespace SMACFramework {

class TimerCollectionBase
{
public:

	typedef boost::shared_ptr<Timer> TimerPtr;

	virtual ~TimerCollectionBase() { }

	virtual TimerPtr InsertTimer(Timer* pTimer) = 0;
	virtual void RemoveAll() = 0;
	virtual void RemoveTimer(TimerPtr timer) = 0;
	virtual void RemoveTimer(Timer* timerp) = 0;
	virtual void Update(Timer& timer) = 0;
};

typedef boost::shared_ptr<TimerCollectionBase> TimerCollectionBasePtr;

template<typename QELEM = void*>
class TimerCollection : public QueueWorker<QELEM>, public TimerCollectionBase
{
public:

	typedef QueueWorkerBase::Lock Lock;
	typedef QueueWorker<QELEM> PBASE;
	typedef boost::shared_ptr<TimerCollection> Ptr;

	TimerCollection(int queueCapacity = 0)
	: QueueWorker<QELEM>(queueCapacity)
	{
	}

	TimerPtr InsertTimer(Timer* pTimer)
	{
		using namespace std;

		if (!pTimer)
			throw invalid_argument("(TimerCollection) InsertTimer pTimer==NULL");

		try
		{
			TimerPtr result;
			result.reset(pTimer);
			pTimer = NULL;

			Lock lock(*this);
			timers[result.get()] = result;
			SendWakeup();
			return result;
		}
		catch(...)
		{
			if (pTimer)
				delete pTimer;

			throw;
		}
	}

	void RemoveAll()
	{
		Lock lock(*this);
		timers.clear();
		SendWakeup();
	}

	void RemoveTimer(TimerPtr timer)
	{
		RemoveTimer(timer.get());
	}

	void RemoveTimer(Timer* timerp)
	{
		{
			MutexLock lock(timersMutex);
			TimerMap::iterator it = timers.find(timerp);

			if (it != timers.end())
				timers.erase(it);
		}

		SendWakeup();
	}

	void Update(Timer& timer)
	{
		SendWakeup();
	}

protected:

	void OnAnnounceTimeout()
	{
		Lock lock(*this);
		const float now = GetSystemUpTimeSeconds();

		TimerMap temp;

		{
			MutexLock mutLock(timersMutex);

			for (TimerMap::iterator it = timers.begin(); it != timers.end(); ++it)
				temp[it->first] = it->second;
		}

		for (TimerMap::iterator it = temp.begin(); it != temp.end(); ++it)
			it->second->UpdateAndFire(now);

		PBASE::OnAnnounceTimeout();
	}

	int DetermineReadTimeout()
	{
		int numMillisec = -1;
		Lock lock(*this);
		const float now = GetSystemUpTimeSeconds();

		{
			MutexLock mutLock(timersMutex);

			for (TimerMap::iterator it = timers.begin(); it != timers.end(); ++it)
			{
				TimerPtr timer = it->second;

				if (timer->Running())
				{
					Timer::TimeUnits remaining = timer->Remaining(now);

					if ((numMillisec < 0) || (numMillisec > (int) remaining))
						numMillisec = (int) remaining;
				}
			}
		}

		return numMillisec;
	}

private:

	typedef boost::recursive_mutex Mutex;
	typedef boost::unique_lock<Mutex> MutexLock;
	typedef std::map<Timer*, TimerPtr> TimerMap;

	void SendWakeup()
	{
		PBASE::WriteQueueEntry(PBASE::ET_WAKEUP);
	}

	Mutex mutex;
	Mutex timersMutex;
	TimerMap timers;
};

}
}

#endif

