//-------------------------------------------------------------------------------
// <copyright file="LqrSMACTimer.cpp" 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>
// 	Implementation for Timer class, which represents a single timer.  When used
//  stand-alone, it can be polled; when used with a TimerCollection class, it can
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------

#include "LqrSMACTimer.h"
#include "LqrSMACTimerCollection.h"
#include "LqrSMACUptime.h"

using namespace std;

namespace LiquidRobotics {
namespace SMACFramework {

Timer::Timer(TimeUnits initialCounter, bool _retriggers, bool running, TimerCollectionBasePtr _timerColl)
: owner(_timerColl)
, startTime(running ? GetSystemUpTimeSeconds() : -1.0f)
, remainingCount(initialCounter)
, fullCount(remainingCount)
, retriggers(_retriggers)
{
}

Timer::Timer(Timer& rhs)
{
	*this = rhs;
}

Timer::~Timer()
{
}

Timer& Timer::operator=(const Timer& rhs)
{
	if (this != &rhs)
	{
		Lock rhlock(const_cast<Timer&>(rhs));
		const float aStart = rhs.startTime;
		const TimeUnits rem = rhs.remainingCount;
		const TimeUnits ful = rhs.fullCount;
		const bool ret = rhs.retriggers;
		rhlock.unlock();

		Lock lock(*this);
		startTime = aStart;
		remainingCount = rem;
		fullCount = ful;
		retriggers = ret;

		TimerCollectionBasePtr own = owner;
		lock.unlock();

		if (own)
			own->Update(*this);
	}

	return *this;
}

int Timer::Compare(const Timer& rhs) const
{
	if (this == &rhs)
		return 0;

	float aStart, bStart;
	TimeUnits aRemain, bRemain;
	TimeUnits aFull, bFull;
	bool aRetrig, bRetrig;

	{
		Lock lock(const_cast<Timer&>(*this));
		aStart = startTime;
		aRemain = remainingCount;
		aFull = fullCount;
		aRetrig = retriggers;
	}

	{
		Lock lock(const_cast<Timer&>(rhs));
		bStart = startTime;
		bRemain = remainingCount;
		bFull = fullCount;
		bRetrig = rhs.retriggers;
	}

	int status = 0;

	CompareT(status, aStart, bStart);
	CompareT(status, aRemain, bRemain);
	CompareT(status, aFull, bFull);
	CompareT(status, aRetrig, bRetrig);

	return status;
}

Timer::TimeUnits Timer::Elapsed(float __now) const
{
	Lock lock(const_cast<Timer&>(*this));
	TimeUnits result;

	if (startTime >= 0)
	{
		if (__now < 0)
			__now = GetSystemUpTimeSeconds();

		if (__now > startTime)
			result = (TimeUnits) floor(1000.0f*(__now-startTime));
		else
			result = 0;
	}
	else if (!fullCount)
		result = 0;
	else
		result = fullCount-remainingCount;

	return result;
}

bool Timer::Expired(float __now) const
{
	Lock lock(const_cast<Timer&>(*this));
	return !fullCount || (Elapsed(__now) >= fullCount);
}

bool Timer::FullCount() const
{
	return fullCount;
}

void Timer::FullCount(TimeUnits newValue)
{
	fullCount = newValue;
}

Timer::TimerCollectionBasePtr Timer::Owner() const
{
	return owner;
}

void Timer::Pause()
{
	Lock lock(*this);

	if ((startTime >= 0) && fullCount)
	{
		TimeUnits rem = Remaining();

		startTime = -1.0f;
		remainingCount = rem;

		TimerCollectionBasePtr own = owner;
		lock.unlock();

		if (own)
			own->Update(*this);
	}
}

Timer::TimeUnits Timer::Remaining(float __now) const
{
	Lock lock(const_cast<Timer&>(*this));
	TimeUnits result;

	if (!fullCount)
		result = 0;

	else
	{
		const TimeUnits elapsed = Elapsed();

		result = (fullCount < elapsed) ? 0 : (fullCount-elapsed);
	}

	return result;
}

void Timer::Reset()
{
	Lock lock(*this);
	const bool changed = (startTime >= 0);

	startTime = -1.0f;
	remainingCount = 0;

	TimerCollectionBasePtr own = owner;

	lock.unlock();

	if (changed && own)
		own->Update(*this);
}

void Timer::Restart(TimeUnits newValue, bool retrig, bool run)
{
	Lock lock(*this);
	const bool changed = (startTime >= 0) != !!run;

	fullCount = newValue;
	retriggers = retrig;

	if (run)
	{
		fullCount = newValue;
		startTime = GetSystemUpTimeSeconds();
	}
	else
	{
		startTime = -1.0f;
		remainingCount = 0;
	}

	if (changed || run)
	{
		TimerCollectionBasePtr own = owner;

		lock.unlock();

		if (own)
			own->Update(*this);
	}
}

void Timer::Resume()
{
	Lock lock(*this);

	if (startTime < 0)
	{
		const float now = GetSystemUpTimeSeconds();

		startTime = now - 0.001f*(fullCount-remainingCount);

		TimerCollectionBasePtr own = owner;
		lock.unlock();

		if (own)
			own->Update(*this);
	}
}

bool Timer::Retriggers() const
{
	return retriggers;
}

void Timer::Retriggers(bool newValue)
{
	retriggers = newValue;
}

bool Timer::Running() const
{
	Lock lock(const_cast<Timer&>(*this));

	return (startTime >= 0);
}

Timer::ScopedConnectionPtr Timer::SubscribeTimerExpired(TimerExpiredSignalType::slot_type slot)
{
	ScopedConnectionPtr result;
	result.reset( new ScopedConnection(timerExpiredSignal.connect(slot)) );
	return result;
}

bool Timer::UpdateAndFire(float __now)
{
	bool expired = false;

	Lock lock(*this);

	if (startTime >= 0)
	{
		if (__now < 0)
			__now = GetSystemUpTimeSeconds();

		const float elapsed = __now-startTime;
		const TimeUnits elapsed2 = floor(elapsed*1000.0f);

		if (elapsed2 >= fullCount)
		{
			expired = true;

			if (retriggers && fullCount)
			{
				const unsigned elapsedPeriods = elapsed/fullCount;
				const unsigned offsetPeriods = (elapsedPeriods > 2) ? (elapsedPeriods-2) : (elapsedPeriods-1);

				startTime += offsetPeriods*fullCount*0.001f;
			}
			else
			{
				startTime = -1.0f;
				remainingCount = 0;
			}
		}
	}

	if (expired)
	{
		lock.unlock();
		timerExpiredSignal(*this);
	}

	return expired;
}

}
}
