/* Copyright (c) 2010 Wildfire Games
 *
 * 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.
 */

/*
 * Timer implementation using QueryPerformanceCounter
 */

#include "precompiled.h"
#include "lib/sysdep/os/win/whrt/qpc.h"

#include "lib/sysdep/os/win/whrt/counter.h"

#include "lib/sysdep/os_cpu.h"
#include "lib/sysdep/os/win/win.h"
#include "lib/sysdep/os/win/wutil.h"	// wutil_argv
#include "lib/sysdep/os/win/whrt/pit.h"	// PIT_FREQ
#include "lib/sysdep/os/win/whrt/pmt.h"	// PMT_FREQ


class CounterQPC : public ICounter
{
public:
	CounterQPC()
		: m_frequency(-1)
	{
	}

	virtual const wchar_t* Name() const
	{
		return L"QPC";
	}

	Status Activate()
	{
		// note: QPC is observed to be universally supported, but the API
		// provides for failure, so play it safe.

		LARGE_INTEGER qpcFreq, qpcValue;
		const BOOL ok1 = QueryPerformanceFrequency(&qpcFreq);
		const BOOL ok2 = QueryPerformanceCounter(&qpcValue);
		if(!ok1 || !ok2)
			WARN_RETURN(ERR::FAIL);
		if(!qpcFreq.QuadPart || !qpcValue.QuadPart)
			WARN_RETURN(ERR::FAIL);

		m_frequency = (i64)qpcFreq.QuadPart;
		return INFO::OK;
	}

	void Shutdown()
	{
	}

	bool IsSafe() const
	{
		// note: we have separate modules that directly access some of the
		// counters potentially used by QPC. disabling the redundant counters
		// would be ugly (increased coupling). instead, we'll make sure our
		// implementations could (if necessary) coexist with QPC, but it
		// shouldn't come to that since only one counter is needed/used.

		// the PIT is entirely safe (even if annoyingly slow to read)
		if(m_frequency == PIT_FREQ)
			return true;

		// the PMT is generally safe (see discussion in CounterPmt::IsSafe),
		// but older QPC implementations had problems with 24-bit rollover.
		// "System clock problem can inflate benchmark scores"
		// (http://www.lionbridge.com/bi/cont2000/200012/perfcnt.asp ; no longer
		// online, nor findable in Google Cache / archive.org) tells of
		// incorrect values every 4.6 seconds (i.e. 24 bits @ 3.57 MHz) unless
		// the timer is polled in the meantime. fortunately, this is guaranteed
		// by our periodic updates (which come at least that often).
		if(m_frequency == PMT_FREQ)
			return true;

		// the TSC has been known to be buggy (even mentioned in MSDN). it is
		// used on MP HAL systems and can be detected by comparing QPF with the
		// CPU clock. we consider it unsafe unless the user promises (via
		// command line) that it's patched and thus reliable on their system.
		bool usesTsc = IsSimilarMagnitude((double)m_frequency, os_cpu_ClockFrequency());
		// unconfirmed reports indicate QPC sometimes uses 1/3 of the
		// CPU clock frequency, so check that as well.
		usesTsc |= IsSimilarMagnitude((double)m_frequency, os_cpu_ClockFrequency()/3);
		if(usesTsc)
		{
			const bool isTscSafe = wutil_HasCommandLineArgument(L"-wQpcTscSafe");
			return isTscSafe;
		}

		// the HPET is reliable and used on Vista. it can't easily be recognized
		// since its frequency is variable (the spec says > 10 MHz; the master
		// 14.318 MHz oscillator is often used). considering frequencies in
		// [10, 100 MHz) to be a HPET would be dangerous because it may actually
		// be faster or RDTSC slower. we have to exclude all other cases and
		// assume it's a HPET - and thus safe - if we get here.
		return true;
	}

	u64 Counter() const
	{
		// fairly time-critical here, don't check the return value
		// (IsSupported made sure it succeeded initially)
		LARGE_INTEGER qpc_value;
		(void)QueryPerformanceCounter(&qpc_value);
		return qpc_value.QuadPart;
	}

	size_t CounterBits() const
	{
		// there are reports of incorrect rollover handling in the PMT
		// implementation of QPC (see CounterPMT::IsSafe). however, other
		// counters would be used on those systems, so it's irrelevant.
		// we'll report the full 64 bits.
		return 64;
	}

	double NominalFrequency() const
	{
		return (double)m_frequency;
	}

	double Resolution() const
	{
		return 1.0 / m_frequency;
	}

private:
	// used in several places and QPF is a bit slow+cumbersome.
	// (i64 allows easier conversion to double)
	i64 m_frequency;
};

ICounter* CreateCounterQPC(void* address, size_t size)
{
	ENSURE(sizeof(CounterQPC) <= size);
	return new(address) CounterQPC();
}
