/*
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License version 2 
	as published by the Free Software Foundation.

	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


	Copyright (C) 2006  Thierry Berger-Perrin <tbptbp@gmail.com>
*/
#ifndef SYS_CLOCK_H
#define SYS_CLOCK_H

#include "specifics.h"

//TODO: add a time of day driver
#ifdef WINDOWS
	#define USE_PERFCOUNTERS
#else
	#define USE_RDTSC
#endif

namespace sys {
	#ifdef __GNUC__
		#ifndef LINUX
			static FINLINE uint64_t rdtsc() {
				register uint64_t val;
				__asm__ __volatile__ ("rdtsc; " : "=A" (val));
				return val;
			}
		#else
			//FIXME: take this path only for 64bit

			// hmmk, the 'A' constraint only work for 32bit mode
			// have to do the stuff by hand in 64bit mode.
			// rip off from the kernel msr.h
			static FINLINE uint64_t rdtsc() {
				unsigned int __a,__d;
				asm volatile("rdtsc" : "=a" (__a), "=d" (__d));
				return ((unsigned long)__a) | (((unsigned long)__d)<<32);
			}
		#endif
	#elif defined __ICC__
		static FINLINE uint64_t rdtsc() { return _rdtsc(); }
	#else
		// MSVC
		static FINLINE uint64_t rdtsc() { __asm { rdtsc }; }
	#endif


	static FINLINE uint64_t get_timestamp() {
		#ifdef USE_PERFCOUNTERS
			LARGE_INTEGER c;
			QueryPerformanceCounter(&c);
			return c.QuadPart;
		#elif defined USE_RDTSC
			return rdtsc();
		#else
			#error not implemented.
		#endif
	}


	struct laps_t {
		static float64_t freq_inv;
		typedef uint64_t value_type;
		value_type start;


		FINLINE laps_t() : start(get()) {}
		FINLINE value_type elapsed() const { return get() - start; }
		FINLINE void restart() { start = get(); }

		// returns millisecs.
		static FINLINE float64_t to_time(const value_type val) { return float64_t(val) * freq_inv; }
		static FINLINE value_type get() { return sys::get_timestamp(); }

		static NOINLINE void bootstrap();
	};

	template <int num_stages>
	struct stage_clock_t {
		laps_t::value_type
			ref,
			samples[num_stages];
		uint_t stage;

		stage_clock_t(): ref(laps_t::get()), stage(0) {}
		void next() {
			samples[stage] = laps_t::get();
			++stage;
		}

		double time(const uint_t idx) { return laps_t::to_time(samples[idx]-ref); }
		double time_delta(const uint_t idx) { return laps_t::to_time(samples[idx] - (idx == 0 ? ref : samples[idx-1])); }
	};

	// lousy, low res & slow absolute time reference.
	// can even warp back... so no waranty here.
	namespace wallclock {
		// extern float64_t reference, granularity;

		NOINLINE float64_t get();
	}

	namespace clock {
		NOINLINE void bootstrap();
	}
}


#endif
