// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "os.h"
#include "irrString.h"
#include "IrrCompileConfig.h"
#include "irrMath.h"

#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_)
	#include <SDL/SDL_endian.h>
	#define bswap_16(X) SDL_Swap16(X)
	#define bswap_32(X) SDL_Swap32(X)
	#define bswap_64(X) SDL_Swap64(X)
#elif defined(_IRR_WINDOWS_API_) && defined(_MSC_VER) && (_MSC_VER > 1298)
	#include <stdlib.h>
	#define bswap_16(X) _byteswap_ushort(X)
	#define bswap_32(X) _byteswap_ulong(X)
	#define bswap_64(X) _byteswap_uint64(X)
#if (_MSC_VER >= 1400)
	#define localtime _localtime_s
#endif
#elif defined(_IRR_OSX_PLATFORM_) || defined(_IRR_IOS_PLATFORM_)
	#include <libkern/OSByteOrder.h>
	#define bswap_16(X) OSReadSwapInt16(&X,0)
	#define bswap_32(X) OSReadSwapInt32(&X,0)
	#define bswap_64(X) OSReadSwapInt64(&X,0)
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
	#include <sys/endian.h>
	#define bswap_16(X) bswap16(X)
	#define bswap_32(X) bswap32(X)
	#define bswap_64(X) bswap64(X)
#elif !defined(_IRR_SOLARIS_PLATFORM_) && !defined(__PPC__) && !defined(_IRR_WINDOWS_API_)
	#include <byteswap.h>
#else
	#define bswap_16(X) ((((X)&0xFF) << 8) | (((X)&0xFF00) >> 8))
	#define bswap_32(X) ((((X)&0x000000FF) << 24) | (((X)&0xFF000000) >> 24) | (((X)&0x0000FF00) << 8) | (((X) &0x00FF0000) >> 8))
	#define bswap_64(X) ((((X)&0x00000000000000FF) << 56) | (((X)&0xFF00000000000000) >> 56) | (((X)&0x000000000000FF00) << 40) | (((X)&0x00FF000000000000) >> 40) | (((X)&0x0000000000FF0000) << 24) | (((X)&0x0000FF0000000000) >> 24) | (((X)&0x00000000FF000000) << 8) | (((X) &0x000000FF00000000) >> 8))
#endif

namespace irr
{
namespace os
{
	u16 Byteswap::byteswap(u16 num) {return bswap_16(num);}
	s16 Byteswap::byteswap(s16 num) {return bswap_16(num);}
	u32 Byteswap::byteswap(u32 num) {return bswap_32(num);}
	s32 Byteswap::byteswap(s32 num) {return bswap_32(num);}
	u64 Byteswap::byteswap(u64 num) {return bswap_64(num);}
	s64 Byteswap::byteswap(s64 num) {return bswap_64(num);}
	f32 Byteswap::byteswap(f32 num) {u32 tmp=IR(num); tmp=bswap_32(tmp); return (FR(tmp));}
	// prevent accidental byte swapping of chars
	u8  Byteswap::byteswap(u8 num)  {return num;}
	c8  Byteswap::byteswap(c8 num)  {return num;}
}
}

#if defined(_IRR_WINDOWS_API_)
// ----------------------------------------------------------------
// Windows specific functions
// ----------------------------------------------------------------

#ifdef _IRR_XBOX_PLATFORM_
#include <xtl.h>
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <time.h>
#endif

namespace irr
{
namespace os
{
	//! prints a debuginfo string
	void Printer::print(const c8* message, ELOG_LEVEL ll)
	{
#if defined (_WIN32_WCE )
		core::stringw tmp(message);
		tmp += L"\n";
		OutputDebugStringW(tmp.c_str());
#else
		core::stringc tmp(message);
		tmp += "\n";
		OutputDebugStringA(tmp.c_str());
		printf("%s", tmp.c_str());
#endif
	}

	static LARGE_INTEGER HighPerformanceFreq;
	static BOOL HighPerformanceTimerSupport = FALSE;
	static BOOL MultiCore = FALSE;

	void Timer::initTimer(bool usePerformanceTimer)
	{
#if !defined(_WIN32_WCE) && !defined (_IRR_XBOX_PLATFORM_)
		// workaround for hires timer on multiple core systems, bios bugs result in bad hires timers.
		SYSTEM_INFO sysinfo;
		GetSystemInfo(&sysinfo);
		MultiCore = (sysinfo.dwNumberOfProcessors > 1);
#endif
		if (usePerformanceTimer)
			HighPerformanceTimerSupport = QueryPerformanceFrequency(&HighPerformanceFreq);
		else
			HighPerformanceTimerSupport = FALSE;
		initVirtualTimer();
	}

	u32 Timer::getRealTime()
	{
		if (HighPerformanceTimerSupport)
		{
#if !defined(_WIN32_WCE) && !defined (_IRR_XBOX_PLATFORM_)
			// Avoid potential timing inaccuracies across multiple cores by
			// temporarily setting the affinity of this process to one core.
			DWORD_PTR affinityMask=0;
			if(MultiCore)
				affinityMask = SetThreadAffinityMask(GetCurrentThread(), 1);
#endif
			LARGE_INTEGER nTime;
			BOOL queriedOK = QueryPerformanceCounter(&nTime);

#if !defined(_WIN32_WCE)  && !defined (_IRR_XBOX_PLATFORM_)
			// Restore the true affinity.
			if(MultiCore)
				(void)SetThreadAffinityMask(GetCurrentThread(), affinityMask);
#endif
			if(queriedOK)
				return u32((nTime.QuadPart) * 1000 / HighPerformanceFreq.QuadPart);

		}

		return GetTickCount();
	}

} // end namespace os


#elif defined( _IRR_ANDROID_PLATFORM_ )

// ----------------------------------------------------------------
// Android version
// ----------------------------------------------------------------

#include <android/log.h>

namespace irr
{
namespace os
{

	//! prints a debuginfo string
	void Printer::print(const c8* message, ELOG_LEVEL ll)
	{
		android_LogPriority LogLevel = ANDROID_LOG_UNKNOWN;

		switch (ll)
		{
		case ELL_DEBUG:
			LogLevel = ANDROID_LOG_DEBUG;
			break;
		case ELL_INFORMATION:
			LogLevel = ANDROID_LOG_INFO;
			break;
		case ELL_WARNING:
			LogLevel = ANDROID_LOG_WARN;
			break;
		case ELL_ERROR:
			LogLevel = ANDROID_LOG_ERROR;
			break;
		default: // ELL_NONE
			LogLevel = ANDROID_LOG_VERBOSE;
			break;
		}

		// Android logcat restricts log-output and cuts the rest of the message away. But we want it all.
		// On my device max-len is 1023 (+ 0 byte). Some websites claim a limit of 4096 so maybe different numbers on different devices.
		const size_t maxLogLen = 1023;
		size_t msgLen = strlen(message);
		size_t start = 0;
		while ( msgLen-start > maxLogLen )
		{
			__android_log_print(LogLevel, "Irrlicht", "%.*s\n", maxLogLen, &message[start]);
			start += maxLogLen;
		}
		__android_log_print(LogLevel, "Irrlicht", "%s\n", &message[start]);
	}

	void Timer::initTimer(bool usePerformanceTimer)
	{
		initVirtualTimer();
	}

	u32 Timer::getRealTime()
	{
		timeval tv;
		gettimeofday(&tv, 0);
		return (u32)(tv.tv_sec * 1000) + (tv.tv_usec / 1000);
	}
} // end namespace os

#elif defined(_IRR_EMSCRIPTEN_PLATFORM_)

// ----------------------------------------------------------------
// emscripten version
// ----------------------------------------------------------------

#include <emscripten.h>
#include <time.h>
#include <sys/time.h>

namespace irr
{
namespace os
{

	//! prints a debuginfo string
	void Printer::print(const c8* message, ELOG_LEVEL ll)
	{
        int log_level;
		switch (ll)
		{
		case ELL_DEBUG:
            log_level=0;
		break;
		case ELL_INFORMATION:
           log_level=0;
		break;
		case ELL_WARNING:
            log_level=EM_LOG_WARN;
			break;
		case ELL_ERROR:
            log_level=EM_LOG_ERROR;
		break;
		default: // ELL_NONE
            log_level=0;
			break;
		}
        emscripten_log(log_level, "%s", message);	// Note: not adding \n as emscripten_log seems to do that already.
	}

	void Timer::initTimer(bool usePerformanceTimer)
	{
		initVirtualTimer();
	}

	u32 Timer::getRealTime()
	{
        double time = emscripten_get_now();
        return (u32)(time);
	}
} // end namespace os

#else

// ----------------------------------------------------------------
// linux/ansi version
// ----------------------------------------------------------------

#include <stdio.h>
#include <time.h>
#include <sys/time.h>

namespace irr
{
namespace os
{

	//! prints a debuginfo string
	void Printer::print(const c8* message, ELOG_LEVEL ll)
	{
		printf("%s\n", message);
	}

	void Timer::initTimer(bool usePerformanceTimer)
	{
		initVirtualTimer();
	}

	u32 Timer::getRealTime()
	{
		timeval tv;
		gettimeofday(&tv, 0);
		return (u32)(tv.tv_sec * 1000) + (tv.tv_usec / 1000);
	}
} // end namespace os

#endif // end linux / emscripten / android / windows

namespace os
{
	// The platform independent implementation of the printer
	ILogger* Printer::Logger = 0;

	void Printer::log(const c8* message, ELOG_LEVEL ll)
	{
		if (Logger)
			Logger->log(message, ll);
	}

	void Printer::log(const wchar_t* message, ELOG_LEVEL ll)
	{
		if (Logger)
			Logger->log(message, ll);
	}

	void Printer::log(const c8* message, const c8* hint, ELOG_LEVEL ll)
	{
		if (Logger)
			Logger->log(message, hint, ll);
	}

	void Printer::log(const c8* message, const io::path& hint, ELOG_LEVEL ll)
	{
		if (Logger)
			Logger->log(message, hint.c_str(), ll);
	}

	// our Randomizer is not really os specific, so we
	// code one for all, which should work on every platform the same,
	// which is desirable.

	s32 Randomizer::seed = 0x0f0f0f0f;

	//! generates a pseudo random number
	s32 Randomizer::rand()
	{
		// (a*seed)%m with Schrage's method
		seed = a * (seed%q) - r* (seed/q);
		if (seed<1)
			seed += m;

		return seed-1;	// -1 because we want it to start at 0
	}

	//! generates a pseudo random number
	f32 Randomizer::frand()
	{
		return rand()*(1.f/rMax);
	}

	s32 Randomizer::randMax()
	{
		return rMax;
	}

	//! resets the randomizer
	void Randomizer::reset(s32 value)
	{
		if (value<0)
			seed = value+m;
		else if ( value == 0 || value == m)
			seed = 1;
		else
			seed = value;
	}


	// ------------------------------------------------------
	// virtual timer implementation

	f32 Timer::VirtualTimerSpeed = 1.0f;
	s32 Timer::VirtualTimerStopCounter = 0;
	u32 Timer::LastVirtualTime = 0;
	u32 Timer::StartRealTime = 0;
	u32 Timer::StaticTime = 0;

	//! Get real time and date in calendar form
	ITimer::RealTimeDate Timer::getRealTimeAndDate()
	{
		time_t rawtime;
		time(&rawtime);

		struct tm * timeinfo;
		timeinfo = localtime(&rawtime);

		// init with all 0 to indicate error
		ITimer::RealTimeDate date;
		memset(&date, 0, sizeof(date));
		// at least Windows returns NULL on some illegal dates
		if (timeinfo)
		{
			// set useful values if succeeded
			date.Hour=(u32)timeinfo->tm_hour;
			date.Minute=(u32)timeinfo->tm_min;
			date.Second=(u32)timeinfo->tm_sec;
			date.Day=(u32)timeinfo->tm_mday;
			date.Month=(u32)timeinfo->tm_mon+1;
			date.Year=(u32)timeinfo->tm_year+1900;
			date.Weekday=(ITimer::EWeekday)timeinfo->tm_wday;
			date.Yearday=(u32)timeinfo->tm_yday+1;
			date.IsDST=timeinfo->tm_isdst != 0;
		}
		return date;
	}

	//! returns current virtual time
	u32 Timer::getTime()
	{
		if (isStopped())
			return LastVirtualTime;

		return LastVirtualTime + (u32)((StaticTime - StartRealTime) * VirtualTimerSpeed);
	}

	//! ticks, advances the virtual timer
	void Timer::tick()
	{
		StaticTime = getRealTime();
	}

	//! sets the current virtual time
	void Timer::setTime(u32 time)
	{
		StaticTime = getRealTime();
		LastVirtualTime = time;
		StartRealTime = StaticTime;
	}

	//! stops the virtual timer
	void Timer::stopTimer()
	{
		if (!isStopped())
		{
			// stop the virtual timer
			LastVirtualTime = getTime();
		}

		--VirtualTimerStopCounter;
	}

	//! starts the virtual timer
	void Timer::startTimer()
	{
		++VirtualTimerStopCounter;

		if (!isStopped())
		{
			// restart virtual timer
			setTime(LastVirtualTime);
		}
	}

	//! sets the speed of the virtual timer
	void Timer::setSpeed(f32 speed)
	{
		setTime(getTime());

		VirtualTimerSpeed = speed;
		if (VirtualTimerSpeed < 0.0f)
			VirtualTimerSpeed = 0.0f;
	}

	//! gets the speed of the virtual timer
	f32 Timer::getSpeed()
	{
		return VirtualTimerSpeed;
	}

	//! returns if the timer currently is stopped
	bool Timer::isStopped()
	{
		return VirtualTimerStopCounter < 0;
	}

	void Timer::initVirtualTimer()
	{
		StaticTime = getRealTime();
		StartRealTime = StaticTime;
	}

} // end namespace os
} // end namespace irr