/*-------------------------------------------------------------------------
 * drawElements Thread Library
 * ---------------------------
 *
 * 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 Win32 implementation of thread management.
 *//*--------------------------------------------------------------------*/

#include "deThread.h"

#if (DE_OS == DE_OS_WIN32 || DE_OS == DE_OS_WINCE)

#include "deMemory.h"
#include "deInt32.h"

#define VC_EXTRALEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

/* Thread handle equals deThread in this implementation. */
DE_STATIC_ASSERT(sizeof(deThread) >= sizeof(HANDLE));

typedef struct ThreadEntry_s
{
	deThreadFunc	func;
	void*			arg;
} ThreadEntry;

static int mapPriority (deThreadPriority priority)
{
	switch (priority)
	{
		case DE_THREADPRIORITY_LOWEST:	return THREAD_PRIORITY_IDLE;
		case DE_THREADPRIORITY_LOW:		return THREAD_PRIORITY_LOWEST;
		case DE_THREADPRIORITY_NORMAL:	return THREAD_PRIORITY_NORMAL;
		case DE_THREADPRIORITY_HIGH:	return THREAD_PRIORITY_ABOVE_NORMAL;
		case DE_THREADPRIORITY_HIGHEST:	return THREAD_PRIORITY_HIGHEST;
		default:	DE_ASSERT(DE_FALSE);
	}
	return 0;
}

static DWORD __stdcall startThread (LPVOID entryPtr)
{
	ThreadEntry*	entry	= (ThreadEntry*)entryPtr;
	deThreadFunc	func	= entry->func;
	void*			arg		= entry->arg;

	deFree(entry);

	func(arg);

	return 0;
}

deThread deThread_create (deThreadFunc func, void* arg, const deThreadAttributes* attributes)
{
	ThreadEntry*	entry	= (ThreadEntry*)deMalloc(sizeof(ThreadEntry));
	HANDLE			thread	= 0;

	if (!entry)
		return 0;

	entry->func	= func;
	entry->arg	= arg;

	thread = CreateThread(DE_NULL, 0, startThread, entry, 0, DE_NULL);
	if (!thread)
	{
		deFree(entry);
		return 0;
	}

	if (attributes)
		SetThreadPriority(thread, mapPriority(attributes->priority));

	return (deThread)thread;
}

deBool deThread_join (deThread thread)
{
	HANDLE	handle		= (HANDLE)thread;
	WaitForSingleObject(handle, INFINITE);

	return DE_TRUE;
}

void deThread_destroy (deThread thread)
{
	HANDLE	handle		= (HANDLE)thread;
	CloseHandle(handle);
}

void deSleep (deUint32 milliseconds)
{
	Sleep((DWORD)milliseconds);
}

void deYield (void)
{
	SwitchToThread();
}

static SYSTEM_LOGICAL_PROCESSOR_INFORMATION* getWin32ProcessorInfo (deUint32* numBytes)
{
	deUint32								curSize	= (deUint32)sizeof(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)*8;
	SYSTEM_LOGICAL_PROCESSOR_INFORMATION*	info	= (SYSTEM_LOGICAL_PROCESSOR_INFORMATION*)deMalloc(curSize);

	for (;;)
	{
		DWORD	inOutLen	= curSize;
		DWORD	err;

		if (GetLogicalProcessorInformation(info, &inOutLen))
		{
			*numBytes = inOutLen;
			return info;
		}
		else
		{
			err = GetLastError();

			if (err == ERROR_INSUFFICIENT_BUFFER)
			{
				curSize <<= 1;
				info = deRealloc(info, curSize);
			}
			else
			{
				deFree(info);
				return DE_NULL;
			}
		}
	}
}

typedef struct ProcessorInfo_s
{
	deUint32	numPhysicalCores;
	deUint32	numLogicalCores;
} ProcessorInfo;

void parseWin32ProcessorInfo (ProcessorInfo* dst, const SYSTEM_LOGICAL_PROCESSOR_INFORMATION* src, deUint32 numBytes)
{
	const SYSTEM_LOGICAL_PROCESSOR_INFORMATION*	cur		= src;

	deMemset(dst, 0, sizeof(ProcessorInfo));

	while (((const deUint8*)cur - (const deUint8*)src) + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= numBytes)
	{
		if (cur->Relationship == RelationProcessorCore)
		{
			dst->numPhysicalCores	+= 1;
#if (DE_PTR_SIZE == 8)
			dst->numLogicalCores	+= dePop64(cur->ProcessorMask);
#else
			dst->numLogicalCores	+= dePop32(cur->ProcessorMask);
#endif
		}

		cur++;
	}
}

deBool getProcessorInfo (ProcessorInfo* info)
{
	deUint32								numBytes	= 0;
	SYSTEM_LOGICAL_PROCESSOR_INFORMATION*	rawInfo		= getWin32ProcessorInfo(&numBytes);

	if (!numBytes)
		return DE_FALSE;

	parseWin32ProcessorInfo(info, rawInfo, numBytes);
	deFree(rawInfo);

	return DE_TRUE;
}

deUint32 deGetNumTotalPhysicalCores (void)
{
	ProcessorInfo	info;

	if (!getProcessorInfo(&info))
		return 1u;

	return info.numPhysicalCores;
}

deUint32 deGetNumTotalLogicalCores (void)
{
	ProcessorInfo	info;

	if (!getProcessorInfo(&info))
		return 1u;

	return info.numLogicalCores;
}

deUint32 deGetNumAvailableLogicalCores (void)
{
	return deGetNumTotalLogicalCores();
}

#endif /* DE_OS */