//------------------------------------------------------------------------
// Project     : SDK Base
// Version     : 1.0
// 
// Category    : Helpers
// Filename    : base/source/timer.cpp
// Created by  : Steinberg, 05/2006
// Description : Timer class for receiving triggers at regular intervals
// 
//-----------------------------------------------------------------------------
// LICENSE
// (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved
//-----------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
//   * Redistributions of source code must retain the above copyright notice, 
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation 
//     and/or other materials provided with the distribution.
//   * Neither the name of the Steinberg Media Technologies nor the names of its
//     contributors may be used to endorse or promote products derived from this 
//     software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

#include "base/source/timer.h"

namespace Steinberg {
static bool timersEnabled = true;

//------------------------------------------------------------------------
DisableDispatchingTimers::DisableDispatchingTimers ()
{
	oldState = timersEnabled;
	timersEnabled = false;
}

//------------------------------------------------------------------------
DisableDispatchingTimers::~DisableDispatchingTimers ()
{
	timersEnabled = oldState;
}

//------------------------------------------------------------------------
namespace SystemTime {

//------------------------------------------------------------------------
struct ZeroStartTicks
{
	static const uint64 startTicks;

	static int32 getTicks32 ()
	{
		return static_cast<int32> (SystemTime::getTicks64 () - startTicks);
	}
};
const uint64 ZeroStartTicks::startTicks = SystemTime::getTicks64 ();

//------------------------------------------------------------------------
int32 getTicks ()
{
	return ZeroStartTicks::getTicks32 ();
}

//------------------------------------------------------------------------
} // namespace SystemTime
} // namespace Steinberg


#if SMTG_OS_MACOS
#include <CoreFoundation/CoreFoundation.h>
#include <mach/mach_time.h>

#ifdef verify
#undef verify
#endif

namespace Steinberg {
namespace SystemTime {

//------------------------------------------------------------------------
struct MachTimeBase
{
private:
	struct mach_timebase_info timebaseInfo;

	MachTimeBase () { mach_timebase_info (&timebaseInfo); }

	static const MachTimeBase& instance ()
	{
		static MachTimeBase gInstance;
		return gInstance;
	}

public:
	static double getTimeNanos ()
	{
		const MachTimeBase& timeBase = instance ();
		double absTime = static_cast<double> (mach_absolute_time ());
		// nano seconds
		double d = (absTime / timeBase.timebaseInfo.denom) * timeBase.timebaseInfo.numer;
		return d;
	}
};

/*
	@return the current system time in milliseconds
*/
uint64 getTicks64 ()
{
	return static_cast<uint64> (MachTimeBase::getTimeNanos () / 1000000.);
}
//------------------------------------------------------------------------
} // namespace SystemTime

//------------------------------------------------------------------------
class MacPlatformTimer : public Timer
{
public:
	MacPlatformTimer (ITimerCallback* callback, uint32 milliseconds);
	~MacPlatformTimer ();

	void stop () override;
	bool verify () const { return platformTimer != nullptr; }

	static void timerCallback (CFRunLoopTimerRef timer, void* info);

protected:
	CFRunLoopTimerRef platformTimer;
	ITimerCallback* callback;
};

//------------------------------------------------------------------------
MacPlatformTimer::MacPlatformTimer (ITimerCallback* callback, uint32 milliseconds)
: platformTimer (nullptr), callback (callback)
{
	if (callback)
	{
		CFRunLoopTimerContext timerContext = {};
		timerContext.info = this;
		platformTimer = CFRunLoopTimerCreate (
		    kCFAllocatorDefault, CFAbsoluteTimeGetCurrent () + milliseconds * 0.001,
		    milliseconds * 0.001f, 0, 0, timerCallback, &timerContext);
		if (platformTimer)
			CFRunLoopAddTimer (CFRunLoopGetMain (), platformTimer, kCFRunLoopCommonModes);
	}
}

//------------------------------------------------------------------------
MacPlatformTimer::~MacPlatformTimer ()
{
	stop ();
}

//------------------------------------------------------------------------
void MacPlatformTimer::stop ()
{
	if (platformTimer)
	{
		CFRunLoopRemoveTimer (CFRunLoopGetMain (), platformTimer, kCFRunLoopCommonModes);
		CFRelease (platformTimer);
		platformTimer = nullptr;
	}
}

//------------------------------------------------------------------------
void MacPlatformTimer::timerCallback (CFRunLoopTimerRef, void* info)
{
	if (timersEnabled)
	{
		if (auto timer = (MacPlatformTimer*)info)
			timer->callback->onTimer (timer);
	}
}

//------------------------------------------------------------------------
Timer* Timer::create (ITimerCallback* callback, uint32 milliseconds)
{
	auto timer = NEW MacPlatformTimer (callback, milliseconds);
	if (timer->verify ())
		return timer;
	timer->release ();
	return nullptr;
}
//------------------------------------------------------------------------
} // namespace Steinberg

#elif SMTG_OS_WINDOWS

#include <windows.h>
#include <algorithm>
#include <list>

namespace Steinberg {
namespace SystemTime {

//------------------------------------------------------------------------
/*
    @return the current system time in milliseconds
*/
uint64 getTicks64 ()
{
#if defined(__MINGW32__)
	return GetTickCount ();
#else
	return GetTickCount64 ();
#endif
} // namespace SystemTime
} // namespace Steinberg

class WinPlatformTimer;
using WinPlatformTimerList = std::list<WinPlatformTimer*>;

//------------------------------------------------------------------------
// WinPlatformTimer
//------------------------------------------------------------------------
class WinPlatformTimer : public Timer
{
public:
//------------------------------------------------------------------------
	WinPlatformTimer (ITimerCallback* callback, uint32 milliseconds);
	~WinPlatformTimer ();

	void stop () override;
	bool verify () const { return id != 0; }

//------------------------------------------------------------------------
private:
	UINT_PTR id;
	ITimerCallback* callback;

	static void addTimer (WinPlatformTimer* t);
	static void removeTimer (WinPlatformTimer* t);

	static void CALLBACK TimerProc (HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
	static WinPlatformTimerList* timers;
};

//------------------------------------------------------------------------
WinPlatformTimerList* WinPlatformTimer::timers = nullptr;

//------------------------------------------------------------------------
WinPlatformTimer::WinPlatformTimer (ITimerCallback* callback, uint32 milliseconds)
: callback (callback)
{
	id = SetTimer (nullptr, 0, milliseconds, TimerProc);
	if (id)
		addTimer (this);
}

//------------------------------------------------------------------------
WinPlatformTimer::~WinPlatformTimer ()
{
	stop ();
}

//------------------------------------------------------------------------
void WinPlatformTimer::addTimer (WinPlatformTimer* t)
{
	if (timers == nullptr)
		timers = NEW WinPlatformTimerList;
	timers->push_back (t);
}

//------------------------------------------------------------------------
void WinPlatformTimer::removeTimer (WinPlatformTimer* t)
{
	if (!timers)
		return;

	WinPlatformTimerList::iterator it = std::find (timers->begin (), timers->end (), t);
	if (it != timers->end ())
		timers->erase (it);
	if (timers->empty ())
	{
		delete timers;
		timers = nullptr;
	}
}

//------------------------------------------------------------------------
void WinPlatformTimer::stop ()
{
	if (!id)
		return;

	KillTimer (nullptr, id);
	removeTimer (this);
	id = 0;
}

//------------------------------------------------------------------------
void CALLBACK WinPlatformTimer::TimerProc (HWND /*hwnd*/, UINT /*uMsg*/, UINT_PTR idEvent,
                                           DWORD /*dwTime*/)
{
	if (timersEnabled && timers)
	{
		WinPlatformTimerList::const_iterator it = timers->cbegin ();
		while (it != timers->cend ())
		{
			WinPlatformTimer* timer = *it;
			if (timer->id == idEvent)
			{
				if (timer->callback)
					timer->callback->onTimer (timer);
				return;
			}
			++it;
		}
	}
}

//------------------------------------------------------------------------
Timer* Timer::create (ITimerCallback* callback, uint32 milliseconds)
{
	auto* platformTimer = NEW WinPlatformTimer (callback, milliseconds);
	if (platformTimer->verify ())
		return platformTimer;
	platformTimer->release ();
	return nullptr;
}

//------------------------------------------------------------------------
} // namespace Steinberg

#elif SMTG_OS_LINUX

#include <cassert>
#include <time.h>

namespace Steinberg {
namespace SystemTime {

//------------------------------------------------------------------------
/*
    @return the current system time in milliseconds
*/
uint64 getTicks64 ()
{
	struct timespec ts;
	clock_gettime (CLOCK_MONOTONIC, &ts);
	return static_cast<uint64> (ts.tv_sec) * 1000 + static_cast<uint64> (ts.tv_nsec) / 1000000;
}
//------------------------------------------------------------------------
} // namespace SystemTime

//------------------------------------------------------------------------
Timer* Timer::create (ITimerCallback* callback, uint32 milliseconds)
{
#warning DEPRECATED No Linux implementation
	assert (false && "DEPRECATED No Linux implementation");
	return nullptr;
}

//------------------------------------------------------------------------
} // namespace Steinberg

#endif
