/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#include "glmTypes.h"

#if GLM_IS_PLATFORM_WINDOWS
	#include "glmWindows.h"
#elif GLM_IS_PLATFORM_LINUX
	#include <time.h>
#endif

namespace glm
{
	typedef uint64_t timetick_t;

	//TODO: if a "CPU processing only" time counter is needed, more info: http://nadeausoftware.com/articles/2012/03/c_c_tip_how_measure_cpu_time_benchmarking

	class GLM_CORE_API TimeCounter
	{
	public:

		TimeCounter() : _startTimeInTicks(0), _stopTimeInTicks(0) {}
		~TimeCounter();

		GLM_FORCE_INLINE void start()
		{
			_startTimeInTicks = getTimeInTicks();
		}

		GLM_FORCE_INLINE void stop() {
			_stopTimeInTicks = getTimeInTicks();
		}

		GLM_FORCE_INLINE timetick_t getStartTimeInTicks() const { return _startTimeInTicks; }
		GLM_FORCE_INLINE timetick_t getStopTimeInTicks() const { return _stopTimeInTicks; }

		//NOTE: using monotonic time, therefore should be used for timing purposes only
		// it can't be converted to a real-world date and time

		static GLM_FORCE_INLINE timetick_t getTimeInTicks()
		{
		#if GLM_IS_PLATFORM_WINDOWS
			LARGE_INTEGER now;
			QueryPerformanceCounter(&now);
			const timetick_t timeInTicks = static_cast<timetick_t>(now.QuadPart);
		#elif GLM_IS_PLATFORM_MACOSX
			const timetick_t timeInTicks = static_cast<timetick_t>(mach_absolute_time());
		#elif GLM_IS_PLATFORM_LINUX
			struct timespec tick;
			clock_gettime(CLOCK_MONOTONIC, &tick);
			const timetick_t timeInTicks = static_cast<timetick_t>(tick.tv_sec * 1000000000ull + tick.tv_nsec);
		#endif
			return timeInTicks;
		}

		static double getSecondsPerTick()
		{
		#if GLM_IS_PLATFORM_WINDOWS
			LARGE_INTEGER freq;
			QueryPerformanceFrequency(&freq);
			const double secondsPerTick = 1.0 / static_cast<double>(freq.QuadPart);
		#elif GLM_IS_PLATFORM_MACOSX
			mach_timebase_info_data_t info;
			mach_timebase_info(&info);
			const double secondsPerTick = 1e-9 * info.numer / info.denom;
		#elif GLM_IS_PLATFORM_LINUX
			const double secondsPerTick = 1e-9;
		#endif
			return secondsPerTick;
		}

	private:
		timetick_t _startTimeInTicks;
		timetick_t _stopTimeInTicks;
	};

	class GLM_CORE_API TimeResolver
	{
	public:
		TimeResolver() : _secondsPerTick(0.0), _startTimeInTicks(0){}
		~TimeResolver() {}

		void initialize() { _secondsPerTick = TimeCounter::getSecondsPerTick(); _startTimeInTicks = TimeCounter::getTimeInTicks(); }
		void initialize(double secondsPerTick, timetick_t startTimeInTicks) { _secondsPerTick = secondsPerTick; _startTimeInTicks = startTimeInTicks; }

		GLM_FORCE_INLINE double getElapsedTimeInSeconds(const TimeCounter& timeCounter) const
		{
			return getElapsedTimeInSeconds(timeCounter.getStartTimeInTicks(), timeCounter.getStopTimeInTicks(), _secondsPerTick);
		}

		GLM_FORCE_INLINE double getElapsedTimeInSeconds(timetick_t startTimeInTicks, timetick_t stopTimeInTicks) const
		{
			return getElapsedTimeInSeconds(startTimeInTicks, stopTimeInTicks, _secondsPerTick);
		}

		GLM_FORCE_INLINE double getElapsedTimeInSeconds(timetick_t stopTimeInTicks) const
		{
			return getElapsedTimeInSeconds(_startTimeInTicks, stopTimeInTicks, _secondsPerTick);
		}

		static GLM_FORCE_INLINE double getElapsedTimeInSeconds(timetick_t startTimeInTicks, timetick_t stopTimeInTicks, double secondsPerTick)
		{
			return secondsPerTick * static_cast<double>(stopTimeInTicks - startTimeInTicks);
		}

	private:
		GLM_DISALLOW_COPY_AND_ASSIGN(TimeResolver)

		double _secondsPerTick;
		timetick_t _startTimeInTicks;
	};
}
