/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief CPU warm-up utility, used to counteract CPU throttling.
 *//*--------------------------------------------------------------------*/

#include "tcuCPUWarmup.hpp"
#include "deDefs.hpp"
#include "deMath.h"
#include "deClock.h"

#include <algorithm>

namespace tcu
{

namespace warmupCPUInternal
{

volatile Dummy g_dummy;

};

template <typename T, int Size>
static inline float floatMedian (const T (&v)[Size])
{
	T temp[Size];
	for (int i = 0; i < Size; i++)
		temp[i] = v[i];

	std::sort(DE_ARRAY_BEGIN(temp), DE_ARRAY_END(temp));

	return Size % 2 == 0
		   ? 0.5f * ((float)temp[Size/2-1] + (float)temp[Size/2])
		   : (float)temp[Size/2];
}

template <typename T, int Size>
static inline float floatRelativeMedianAbsoluteDeviation (const T (&v)[Size])
{
	const float		median = floatMedian(v);
	float			absoluteDeviations[Size];

	for (int i = 0; i < Size; i++)
		absoluteDeviations[i] = deFloatAbs((float)v[i] - median);

	return floatMedian(absoluteDeviations) / median;
}

static inline float dummyComputation (float initial, int numIterations)
{
	float	a = initial;
	int		b = 123;

	for (int i = 0; i < numIterations; i++)
	{
		// Arbitrary computations.
		for (int j = 0; j < 4; j++)
		{
			a = deFloatCos(a + (float)b);
			b = (b + 63) % 107 + de::abs((int)(a*10.0f));
		}
	}

	return a + (float)b;
}

void warmupCPU (void)
{
	float	dummy				= *warmupCPUInternal::g_dummy.m_v;
	int		computationSize		= 1;

	// Do a rough calibration for computationSize to get dummyComputation's running time above a certain threshold.
	while (computationSize < 1<<30) // \note This condition is unlikely to be met. The "real" loop exit is the break below.
	{
		const float		singleMeasurementThreshold	= 10000.0f;
		const int		numMeasurements				= 3;
		deInt64			times[numMeasurements];

		for (int i = 0; i < numMeasurements; i++)
		{
			const deUint64 startTime = deGetMicroseconds();
			dummy = dummyComputation(dummy, computationSize);
			times[i] = (deInt64)(deGetMicroseconds() - startTime);
		}

		if (floatMedian(times) >= singleMeasurementThreshold)
			break;

		computationSize *= 2;
	}

	// Do dummyComputations until running time seems stable enough.
	{
		const int			maxNumMeasurements							= 50;
		const int			numConsecutiveMeasurementsRequired			= 5;
		const float			relativeMedianAbsoluteDeviationThreshold	= 0.05f;
		deInt64				latestTimes[numConsecutiveMeasurementsRequired];

		for (int measurementNdx = 0;

			 measurementNdx < maxNumMeasurements &&
			 (measurementNdx < numConsecutiveMeasurementsRequired ||
			  floatRelativeMedianAbsoluteDeviation(latestTimes) > relativeMedianAbsoluteDeviationThreshold);

			 measurementNdx++)
		{
			const deUint64 startTime = deGetMicroseconds();
			dummy = dummyComputation(dummy, computationSize);
			latestTimes[measurementNdx % numConsecutiveMeasurementsRequired] = (deInt64)(deGetMicroseconds() - startTime);
		}
	}

	*warmupCPUInternal::g_dummy.m_v = dummy;
}

} // tcu