/*****************************************************************************
*                                                                            *
*  PrimeSense PSCommon Library                                               *
*  Copyright (C) 2012 PrimeSense Ltd.                                        *
*                                                                            *
*  This file is part of PSCommon.                                            *
*                                                                            *
*  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.                                            *
*                                                                            *
*****************************************************************************/
//---------------------------------------------------------------------------
// Includes
//---------------------------------------------------------------------------
#include <XnLog.h>
#include <XnStringsHash.h>
#include <stdarg.h>
#include <XnList.h>
#include <XnArray.h>
#include <XnOSCpp.h>

#include "XnLogConsoleWriter.h"
#include "XnLogFileWriter.h"
#if XN_PLATFORM == XN_PLATFORM_ANDROID_ARM
#include "XnLogAndroidWriter.h"
#endif

//---------------------------------------------------------------------------
// Defines
//---------------------------------------------------------------------------
#define XN_LOG_MASKS_DELIM			";"
#define XN_LOG_MAX_MESSAGE_LENGTH	2048

//---------------------------------------------------------------------------
// Types
//---------------------------------------------------------------------------
typedef xnl::List<XnLogWriter*> XnLogWritersList;

typedef XnStringsHashT<XnLogger> XnLogMasksHash;

class XnBufferedLogEntry : public XnLogEntry
{
public:
	XnBufferedLogEntry()
	{
		m_strBuffer[0] = '\0';
		this->strMessage = m_strBuffer;
	}

	XnChar* Buffer() { return m_strBuffer; }
	XnUInt32 MaxBufferSize() const { return XN_LOG_MAX_MESSAGE_LENGTH; }

private:
	XnChar m_strBuffer[XN_LOG_MAX_MESSAGE_LENGTH];
};

class LogData
{
public:
	static LogData& GetInstance()
	{
		static LogData data;
		return data;
	}

	~LogData()
	{
		// This is important. During process shutdown all static objects are destroyed, including log objects
		// (like writers list). But the order can't be controlled, so some objects might be destroyed *after*
		// log has. Those objects might write down to the log during destruction which will cause access violation.
		// So, when the log is destroyed, we're turning it off, so that no writing will take place.
		Reset();
	}

	void Reset()
	{
		SetMinSeverityGlobally(XN_LOG_SEVERITY_NONE);
		this->strLogDir[0] = '\0';
		this->strSessionTimestamp[0] = '\0';
	}

	void SetMinSeverityGlobally(XnLogSeverity severity)
	{
		this->defaultMinSeverity = severity;
		for (XnLogMasksHash::Iterator it = this->pMasksHash->Begin(); it != this->pMasksHash->End(); ++it)
		{
			it->Value().nMinSeverity = severity;
		}
	}

	XnChar strLogDir[XN_FILE_MAX_PATH];
	XnLogMasksHash* pMasksHash;
	XnLogSeverity defaultMinSeverity;
	XnLogWritersList writers;
	XnBool anyWriters;
	XnChar strSessionTimestamp[25];
	XN_CRITICAL_SECTION_HANDLE hLock;

	// Writers
	XnLogConsoleWriter consoleWriter;
	XnLogFileWriter fileWriter;
#if XN_PLATFORM == XN_PLATFORM_ANDROID_ARM
	XnLogAndroidWriter AndroidWriter;
#endif

private:
	LogData()
	{
		// NOTE: as our log implementation returns a pointer directly into this hash, we can't
		// free this memory in our dtor (static objects dtors are called in unknown order. modules
		// might still access this memory after our dtor is called).
		// As in any case, this is a static object which will only be destroyed when the process goes 
		// down - we can allow this.
		this->pMasksHash = XN_NEW(XnLogMasksHash);
		XN_ASSERT(this->pMasksHash != NULL);

		// We need a critical section to guard the writers list - it is accessed with every log entry written
		// and also by the application (mainly, when calling xnStartNewLogFile()).
		XnStatus nRetVal = xnOSCreateCriticalSection(&this->hLock);
		XN_ASSERT(nRetVal == XN_STATUS_OK);
		XN_REFERENCE_VARIABLE(nRetVal);

		this->anyWriters = FALSE;

		Reset();
	}
};

//---------------------------------------------------------------------------
// Globals
//---------------------------------------------------------------------------
XnLogger* XN_LOGGER_RETVAL_CHECKS = xnLoggerOpen(XN_MASK_RETVAL_CHECKS);

//---------------------------------------------------------------------------
// Forward-Declaration
//---------------------------------------------------------------------------
static XnStatus xnLogBCSetMaskState(const XnChar* strMask, XnBool bEnabled);
static XnStatus xnLogBCSetSeverityFilter(XnLogSeverity nMinSeverity);

//---------------------------------------------------------------------------
// Code
//---------------------------------------------------------------------------
static const XnChar* xnLogGetSeverityString(XnLogSeverity nSeverity)
{
	switch (nSeverity)
	{
	case XN_LOG_VERBOSE:
		return "VERBOSE";
	case XN_LOG_INFO:
		return "INFO";
	case XN_LOG_WARNING:
		return "WARNING";
	case XN_LOG_ERROR:
		return "ERROR";
	case XN_LOG_SEVERITY_NONE:
		return "NONE";
	default:
		return "UNKNOWN";
	}
}

static void xnLogCreateEntryV(XnBufferedLogEntry* pEntry, const XnChar* csLogMask, XnLogSeverity nSeverity, const XnChar* csFile, XnUInt32 nLine, const XnChar* csFormat, va_list args)
{
	// format message
	XnUInt32 nChars;
	xnOSStrFormatV(pEntry->Buffer(), pEntry->MaxBufferSize(), &nChars, csFormat, args);

	// create log entry
	xnOSGetHighResTimeStamp(&pEntry->nTimestamp);
	pEntry->nSeverity = nSeverity;
	pEntry->strSeverity = xnLogGetSeverityString(nSeverity);
	pEntry->strMask = csLogMask;
	pEntry->strFile = csFile;
	pEntry->nLine = nLine;
}

static void xnLogCreateEntry(XnBufferedLogEntry* pEntry, const XnChar* csLogMask, XnLogSeverity nSeverity, const XnChar* csFile, XnUInt32 nLine, const XnChar* csFormat, ...)
{
	va_list args;
	va_start(args, csFormat);
	xnLogCreateEntryV(pEntry, csLogMask, nSeverity, csFile, nLine, csFormat, args);
	va_end(args);
}

static void xnLogWriteEntry(XnLogEntry* pEntry)
{
	LogData& logData = LogData::GetInstance();
	xnl::AutoCSLocker locker(logData.hLock);
	for (XnLogWritersList::ConstIterator it = logData.writers.Begin(); it != logData.writers.End(); ++it)
	{
		const XnLogWriter* pWriter = *it;
		pWriter->WriteEntry(pEntry, pWriter->pCookie);
	}
}

static void xnLogWriteImplV(const XnChar* csLogMask, XnLogSeverity nSeverity, const XnChar* csFile, XnUInt32 nLine, const XnChar* csFormat, va_list args)
{
	// check if there are any writers registered
	LogData& logData = LogData::GetInstance();

	// optimization: check if any writer is registered *before* locking.
	if (!logData.anyWriters)
	{
		// don't waste time formatting anything.
		return;
	}

	XnBufferedLogEntry entry;
	xnLogCreateEntryV(&entry, csLogMask, nSeverity, csFile, nLine, csFormat, args);

	// write it down
	xnLogWriteEntry(&entry);
}

static void xnLogWriteImpl(const XnChar* csLogMask, XnLogSeverity nSeverity, const XnChar* csFile, XnUInt32 nLine, const XnChar* csFormat, ...)
{
	va_list args;
	va_start(args, csFormat);
	xnLogWriteImplV(csLogMask, nSeverity, csFile, nLine, csFormat, args);
	va_end(args);
}

XN_C_API XnStatus XN_C_DECL xnLogCreateNewFile(const XnChar* strName, XnBool bSessionBased, XnChar* csFullPath, XnUInt32 nPathBufferSize, XN_FILE_HANDLE* phFile)
{
	XnStatus nRetVal = XN_STATUS_OK;

	LogData& logData = LogData::GetInstance();

	// set log directory
	if (logData.strLogDir[0] == '\0')
	{
		nRetVal = xnLogSetOutputFolder(XN_LOG_DIR_NAME);
		XN_IS_STATUS_OK(nRetVal);
	}

	if (logData.strSessionTimestamp[0] == '\0')
	{
		time_t currtime;
		time(&currtime);
		strftime(logData.strSessionTimestamp, sizeof(logData.strSessionTimestamp)-1, "%Y_%m_%d__%H_%M_%S", localtime(&currtime)); 
	}

	XN_PROCESS_ID nProcID = 0;
	xnOSGetCurrentProcessID(&nProcID);

	// create full path file name - add process start time and process ID
	XnUInt32 nPathSize = 0;
	XnUInt32 nCharsWritten = 0;
	nRetVal = xnOSStrFormat(csFullPath, nPathBufferSize - nPathSize, &nCharsWritten, "%s", logData.strLogDir);
	XN_IS_STATUS_OK(nRetVal);
	nPathSize += nCharsWritten;

	if (bSessionBased)
	{
		nRetVal = xnOSStrFormat(csFullPath + nPathSize, nPathBufferSize - nPathSize, &nCharsWritten, "%s_%u.", logData.strSessionTimestamp, nProcID);
		XN_IS_STATUS_OK(nRetVal);
		nPathSize += nCharsWritten;
	}

	nRetVal = xnOSStrFormat(csFullPath + nPathSize, nPathBufferSize - nPathSize, &nCharsWritten, "%s", strName);
	XN_IS_STATUS_OK(nRetVal);
	nPathSize += nCharsWritten;

	// and open the file
	nRetVal = xnOSOpenFile(csFullPath, XN_OS_FILE_WRITE | XN_OS_FILE_TRUNCATE, phFile);
	XN_IS_STATUS_OK(nRetVal);

	return (XN_STATUS_OK);
}

static void xnLogCreateFilterChangedMessage(XnBufferedLogEntry* pEntry)
{
	XnStatus nRetVal = XN_STATUS_OK;
	XN_REFERENCE_VARIABLE(nRetVal);
	
	LogData& logData = LogData::GetInstance();

	XnChar strConfigMessage[XN_LOG_MAX_MESSAGE_LENGTH];
	XnUInt32 nCharsWritten = 0;
	XnUInt32 nMessageLength = 0;
	nRetVal = xnOSStrFormat(strConfigMessage, XN_LOG_MAX_MESSAGE_LENGTH, &nCharsWritten, "--- Filter Info --- Minimum Severity: %s", xnLogGetSeverityString(logData.defaultMinSeverity));
	XN_ASSERT(nRetVal == XN_STATUS_OK);
	nMessageLength += nCharsWritten;

	XnBool bOverrides = FALSE;

	// go over masks, and see if there are any overrides
	for (XnLogMasksHash::ConstIterator it = logData.pMasksHash->Begin(); it != logData.pMasksHash->End(); ++it)
	{
		XnLogSeverity maskSeverity = it->Value().nMinSeverity;
		if (maskSeverity != logData.defaultMinSeverity)
		{
			if (!bOverrides)
			{
				nRetVal = xnOSStrFormat(strConfigMessage + nMessageLength, sizeof(strConfigMessage) - nMessageLength, &nCharsWritten, ". Overriding Masks - ");
				XN_ASSERT(nRetVal == XN_STATUS_OK);
				bOverrides = TRUE;
				nMessageLength += nCharsWritten;
			}
			else
			{
				nRetVal = xnOSStrFormat(strConfigMessage + nMessageLength, sizeof(strConfigMessage) - nMessageLength, &nCharsWritten, ", ");
				XN_ASSERT(nRetVal == XN_STATUS_OK);
				nMessageLength += nCharsWritten;
			}

			nRetVal = xnOSStrFormat(strConfigMessage + nMessageLength, sizeof(strConfigMessage) - nMessageLength, &nCharsWritten, "'%s': %s", it->Key(), xnLogGetSeverityString(maskSeverity));
			XN_ASSERT(nRetVal == XN_STATUS_OK);
			nMessageLength += nCharsWritten;
		}
	}

	xnLogCreateEntry(pEntry, XN_MASK_LOG, XN_LOG_INFO, __FILE__, __LINE__, "%s", strConfigMessage);
}

static void xnLogFilterChanged()
{
	XnBufferedLogEntry entry;
	xnLogCreateFilterChangedMessage(&entry);
	xnLogWriteEntry(&entry);

	LogData& logData = LogData::GetInstance();
	xnl::AutoCSLocker locker(logData.hLock);
	for (XnLogWritersList::ConstIterator it = logData.writers.Begin(); it != logData.writers.End(); ++it)
	{
		const XnLogWriter* pWriter = *it;
		pWriter->OnConfigurationChanged(pWriter->pCookie);
	}
}

XN_C_API void xnLogWriteBanner(const XnLogWriter* pWriter)
{
	XnBufferedLogEntry entry;

	// write system time
	time_t currtime;
	time(&currtime);
	XnChar strTime[200];
	strftime(strTime, sizeof(strTime) - 1, "%Y-%m-%d %H:%M:%S", localtime(&currtime)); 
	xnLogCreateEntry(&entry, XN_MASK_LOG, XN_LOG_INFO, __FILE__, __LINE__, "New log started on %s", strTime);
	pWriter->WriteEntry(&entry, pWriter->pCookie);

	// write filters
	xnLogCreateFilterChangedMessage(&entry);
	pWriter->WriteEntry(&entry, pWriter->pCookie);
}

typedef XnStatus (*XnSetMaskFunc)(const XnChar* csMask, XnBool bEnabled);

static XnStatus xnLogSetMasks(XnChar* csMasks, XnSetMaskFunc pSetMaskFunc)
{
	XnStatus nRetVal = XN_STATUS_OK;

	nRetVal = xnLogBCSetMaskState(XN_LOG_MASK_ALL, FALSE);
	XN_IS_STATUS_OK(nRetVal);

	XnChar* pMask = strtok(csMasks, XN_LOG_MASKS_DELIM);
	while (pMask != NULL)
	{
		nRetVal = pSetMaskFunc(pMask, TRUE);
		XN_IS_STATUS_OK(nRetVal);

		// get next
		pMask = strtok(NULL, XN_LOG_MASKS_DELIM);
	}

	return (XN_STATUS_OK);
}

static XnStatus xnLogReadMasksFromINI(const XnChar* cpINIFileName, const XnChar* cpSectionName, const XnChar* cpKey, XnSetMaskFunc pSetMaskFunc)
{
	XnStatus nRetVal = XN_STATUS_OK;
	XnChar csTemp[XN_INI_MAX_LEN] = "";

	nRetVal = xnOSReadStringFromINI(cpINIFileName, cpSectionName, cpKey, csTemp, XN_INI_MAX_LEN);
	if (nRetVal == XN_STATUS_OK)
	{
		nRetVal = xnLogSetMasks(csTemp, pSetMaskFunc);
		XN_IS_STATUS_OK(nRetVal);
	}

	return (XN_STATUS_OK);
}

XN_C_API XnStatus xnLogInitFromINIFile(const XnChar* cpINIFileName, const XnChar* cpSectionName)
{
	XnStatus nRetVal = XN_STATUS_OK;
	XnInt32 nTemp;

	// read filters
	xnLogReadMasksFromINI(cpINIFileName, cpSectionName, "LogMasks", xnLogBCSetMaskState);
	xnLogReadMasksFromINI(cpINIFileName, cpSectionName, "DumpMasks", xnDumpSetMaskState);

	LogData::GetInstance().SetMinSeverityGlobally(XN_LOG_SEVERITY_NONE);

	nRetVal = xnOSReadIntFromINI(cpINIFileName, cpSectionName, "LogLevel", &nTemp);
	if (nRetVal == XN_STATUS_OK)
	{
		nRetVal = xnLogBCSetSeverityFilter((XnLogSeverity)nTemp);
		XN_IS_STATUS_OK(nRetVal);
	}

	// configure writers
	nRetVal = xnOSReadIntFromINI(cpINIFileName, cpSectionName, "LogWriteToConsole", &nTemp);
	if (nRetVal == XN_STATUS_OK)
	{
		nRetVal = xnLogSetConsoleOutput(nTemp);
		XN_IS_STATUS_OK(nRetVal);
	}

	nRetVal = xnOSReadIntFromINI(cpINIFileName, cpSectionName, "LogWriteToFile", &nTemp);
	if (nRetVal == XN_STATUS_OK)
	{
		nRetVal = xnLogSetFileOutput(nTemp);
		XN_IS_STATUS_OK(nRetVal);
	}

#if XN_PLATFORM == XN_PLATFORM_ANDROID_ARM
	nRetVal = xnOSReadIntFromINI(cpINIFileName, cpSectionName, "LogWriteToAndroidLog", &nTemp);
	if (nRetVal == XN_STATUS_OK)
	{
		nRetVal = xnLogSetAndroidOutput(nTemp);
		XN_IS_STATUS_OK(nRetVal);
	}
#endif

	nRetVal = xnOSReadIntFromINI(cpINIFileName, cpSectionName, "LogWriteLineInfo", &nTemp);
	if (nRetVal == XN_STATUS_OK)
	{
		nRetVal = xnLogSetLineInfo(nTemp);
		XN_IS_STATUS_OK(nRetVal);
	}

	return XN_STATUS_OK;
}

XN_C_API XnStatus xnLogRegisterLogWriter(XnLogWriter* pWriter)
{
	XnStatus nRetVal = XN_STATUS_OK;
	
	LogData& logData = LogData::GetInstance();

	{
		xnl::AutoCSLocker locker(logData.hLock);
		nRetVal = logData.writers.AddLast(pWriter);
	}
	XN_IS_STATUS_OK(nRetVal);

	logData.anyWriters = TRUE;

	xnLogWriteBanner(pWriter);
	
	return (XN_STATUS_OK);
}

XN_C_API void xnLogUnregisterLogWriter(XnLogWriter* pWriter)
{
	XnStatus nRetVal = XN_STATUS_OK;
	XN_REFERENCE_VARIABLE(nRetVal);
	
	LogData& logData = LogData::GetInstance();

	xnl::AutoCSLocker locker(logData.hLock);
	nRetVal = logData.writers.Remove(pWriter);

	logData.anyWriters = !logData.writers.IsEmpty();
}

XN_C_API XnStatus xnLogStartNewFile()
{
	LogData& logData = LogData::GetInstance();
	if (!logData.fileWriter.IsRegistered())
	{
		return XN_STATUS_INVALID_OPERATION;
	}

	logData.fileWriter.Unregister();

	// reset our session timestamp, a new one will be created
	logData.strSessionTimestamp[0] = '\0';

	return logData.fileWriter.Register();
}

XN_C_API XnStatus xnLogClose()
{
	// notify all writers (while allowing them to unregister themselves)
	LogData& logData = LogData::GetInstance();

	xnl::AutoCSLocker locker(logData.hLock);
	XnLogWritersList::ConstIterator it = logData.writers.Begin();
	while (it != logData.writers.End())
	{
		XnLogWritersList::ConstIterator curr = it;
		++it;

		const XnLogWriter* pWriter = *curr;
		pWriter->OnClosing(pWriter->pCookie);
	}

	logData.strLogDir[0] = '\0';
	logData.strSessionTimestamp[0] = '\0';
	logData.SetMinSeverityGlobally(XN_LOG_SEVERITY_NONE);

	// turn off all dumps
	xnDumpSetMaskState(XN_LOG_MASK_ALL, FALSE);

	return XN_STATUS_OK;
}

XN_C_API XnStatus xnLogSetConsoleOutput(XnBool bConsoleOutput)
{
	XnStatus nRetVal = XN_STATUS_OK;

	LogData& logData = LogData::GetInstance();
	if (bConsoleOutput)
	{
		nRetVal = logData.consoleWriter.Register();
		XN_IS_STATUS_OK(nRetVal);
	}
	else
	{
		logData.consoleWriter.Unregister();
	}

	return XN_STATUS_OK;
}

XN_C_API XnStatus xnLogSetFileOutput(XnBool bFileOutput)
{
	XnStatus nRetVal = XN_STATUS_OK;

	LogData& logData = LogData::GetInstance();
	if (bFileOutput)
	{
		nRetVal = logData.fileWriter.Register();
		XN_IS_STATUS_OK(nRetVal);
	}
	else
	{
		logData.fileWriter.Unregister();
	}

	return XN_STATUS_OK;
}

#if XN_PLATFORM == XN_PLATFORM_ANDROID_ARM
XN_C_API XnStatus xnLogSetAndroidOutput(XnBool bAndroidOutput)
{
	XnStatus nRetVal = XN_STATUS_OK;

	LogData& logData = LogData::GetInstance();
	if (bAndroidOutput)
	{
		nRetVal = logData.AndroidWriter.Register();
		XN_IS_STATUS_OK(nRetVal);
	}
	else
	{
		logData.AndroidWriter.Unregister();
	}

	return XN_STATUS_OK;
}
#endif

XN_C_API XnStatus xnLogSetLineInfo(XnBool bLineInfo)
{
	LogData& logData = LogData::GetInstance();
	logData.fileWriter.SetLineInfo(bLineInfo);
	return (XN_STATUS_OK);
}

XN_C_API XnStatus xnLogSetOutputFolder(const XnChar* strOutputFolder)
{
	XnStatus nRetVal = XN_STATUS_OK;
	
	// check if folder exists
	XnBool bDirExists = FALSE;
	nRetVal = xnOSDoesDirectoryExist(strOutputFolder, &bDirExists);
	XN_IS_STATUS_OK(nRetVal);

	if (!bDirExists)
	{
		// create it
		nRetVal = xnOSCreateDirectory(strOutputFolder);
		XN_IS_STATUS_OK(nRetVal);
	}
	
	// place it in a temp buffer, just to make sure everything succeeds
	XnChar strDirName[XN_FILE_MAX_PATH];
	nRetVal = xnOSGetFullPathName(strOutputFolder, strDirName, XN_FILE_MAX_PATH);
	XN_IS_STATUS_OK(nRetVal);

	nRetVal = xnOSStrAppend(strDirName, XN_FILE_DIR_SEP, XN_FILE_MAX_PATH);
	XN_IS_STATUS_OK(nRetVal);

	// OK. replace
	LogData& logData = LogData::GetInstance();
	xnOSStrCopy(logData.strLogDir, strDirName, XN_FILE_MAX_PATH);

	// restart file writer
	xnLogStartNewFile();

	return (XN_STATUS_OK);
}

XN_C_API XnStatus XN_C_DECL xnLogGetFileName(XnChar* strFileName, XnUInt32 nBufferSize)
{
	LogData& logData = LogData::GetInstance();
	if (!logData.fileWriter.IsRegistered())
	{
		return XN_STATUS_INVALID_OPERATION;
	}

	return xnOSStrCopy(strFileName, logData.fileWriter.GetFileName(), nBufferSize);
}

XnLogger* xnLogGetLoggerForMask(const XnChar* csLogMask, XnBool bCreate)
{
	XnLogger* pLogger = NULL;
	LogData& logData = LogData::GetInstance();
	if (XN_STATUS_OK == logData.pMasksHash->Get(csLogMask, pLogger))
	{
		return pLogger;
	}
	else if (bCreate)
	{
		XnLogger logger;
		logger.nMinSeverity = logData.defaultMinSeverity;

		// first of all, add it to the map
		if (XN_STATUS_OK != logData.pMasksHash->Set(csLogMask, logger))
		{
			// failed to add it to the map
			XN_ASSERT(FALSE);
			return NULL;
		}

		// now find it in the map
		XnLogMasksHash::Iterator it = logData.pMasksHash->Find(csLogMask);
		if (it == logData.pMasksHash->End())
		{
			XN_ASSERT(FALSE);
			return NULL;
		}

		it->Value().pInternal = (void*)it->Key();

		return &it->Value();
	}
	else
	{
		return NULL;
	}
}

void xnLogWriteNoEntryImplV(const XnChar* csFormat, va_list args)
{
	const XnUInt32 nMaxMessageSize = 1024;
	XnChar csMessage[nMaxMessageSize+1];
	XnUInt32 nChars;
	xnOSStrFormatV(csMessage, nMaxMessageSize, &nChars, csFormat, args);

	LogData& logData = LogData::GetInstance();
	xnl::AutoCSLocker locker(logData.hLock);
	for (XnLogWritersList::ConstIterator it = logData.writers.Begin(); it != logData.writers.End(); ++it)
	{
		const XnLogWriter* pWriter = *it;
		pWriter->WriteUnformatted(csMessage, pWriter->pCookie);
	}
}

void xnLogWriteBinaryDataImplV(const XnChar* strMask, XnLogSeverity nSeverity, const XnChar* csFile, XnUInt32 nLine, XnUChar* pBinData, XnUInt32 nDataSize, const XnChar* csFormat, va_list args)
{
	// first write preceding message:
	xnLogWriteImplV(strMask, nSeverity, csFile, nLine, csFormat, args);

	// now write binary data (in lines of 16 bytes)
	XnChar csLine[256];
	XnUInt32 pos = 0;

	for (XnUInt32 i = 0; i < nDataSize; ++i)
	{
		if ((i % 16) == 0) // first byte in line
		{
			// start a new line
			pos = sprintf(csLine, "%6u: ", i);
		}

		pos += sprintf(csLine + pos, "%02x ", pBinData[i]);

		if ((i % 16) == 15 || (i == nDataSize-1)) // last byte in line
		{
			xnLogWriteImpl(strMask, nSeverity, csFile, nLine, "%s", csLine);
		}
	}
}

XN_C_API XnStatus XN_C_DECL xnLogSetMaskMinSeverity(const XnChar* strMask, XnLogSeverity minSeverity)
{
	LogData& logData = LogData::GetInstance();
	if (strcmp(strMask, XN_LOG_MASK_ALL) == 0)
	{
		logData.SetMinSeverityGlobally(minSeverity);
	}
	else
	{
		XnLogger* pLogger = xnLogGetLoggerForMask(strMask, TRUE);
		if (pLogger == NULL)
		{
			XN_ASSERT(FALSE);
			return XN_STATUS_ERROR;
		}

		pLogger->nMinSeverity = minSeverity;
	}

	return (XN_STATUS_OK);
}

XN_C_API XnLogSeverity XN_C_DECL xnLogGetMaskMinSeverity(const XnChar* strMask)
{
	XnLogger* pLogger = xnLogGetLoggerForMask(strMask, FALSE);
	if (pLogger == NULL)
	{
		LogData& logData = LogData::GetInstance();
		return logData.defaultMinSeverity;
	}
	else
	{
		return pLogger->nMinSeverity;
	}
}

XN_C_API XnLogger* XN_C_DECL xnLoggerOpen(const XnChar* strMask)
{
	return xnLogGetLoggerForMask(strMask, TRUE);
}

XN_C_API void XN_C_DECL xnLoggerWrite(XnLogger* pLogger, XnLogSeverity nSeverity, const XnChar* csFile, XnUInt32 nLine, const XnChar* csFormat, ...)
{
	if (!xnLoggerIsEnabled(pLogger, nSeverity))
		return;

	const XnChar* strMask = (const XnChar*)pLogger->pInternal;

	// write message
	va_list args;
	va_start(args, csFormat);
	xnLogWriteImplV(strMask, nSeverity, csFile, nLine, csFormat, args);
	va_end(args);
}

XN_C_API void XN_C_DECL xnLoggerWriteNoEntry(XnLogger* pLogger, XnLogSeverity nSeverity, const XnChar* csFormat, ...)
{
	if (!xnLoggerIsEnabled(pLogger, nSeverity))
		return;

	va_list args;
	va_start(args, csFormat);
	xnLogWriteNoEntryImplV(csFormat, args);
	va_end(args);
}

XN_C_API void XN_C_DECL xnLoggerWriteBinaryData(XnLogger* pLogger, XnLogSeverity nSeverity, const XnChar* csFile, XnUInt32 nLine, XnUChar* pBinData, XnUInt32 nDataSize, const XnChar* csFormat, ...)
{
	if (!xnLoggerIsEnabled(pLogger, nSeverity))
		return;

	const XnChar* strMask = (const XnChar*)pLogger->pInternal;

	va_list args;
	va_start(args, csFormat);
	xnLogWriteBinaryDataImplV(strMask, nSeverity, csFile, nLine, pBinData, nDataSize, csFormat, args);
	va_end(args);
}

XN_C_API void XN_C_DECL _xnLoggerClose(XnLogger* pLogger)
{
	if (pLogger == NULL)
	{
		return;
	}

	const XnChar* strMask = (const XnChar*)pLogger->pInternal;
	LogData& logData = LogData::GetInstance();
	logData.pMasksHash->Remove(strMask);
}

XN_C_API XnBool XN_C_DECL xnLoggerIsEnabled(XnLogger* pLogger, XnLogSeverity severity)
{
	if (pLogger == NULL)
	{
		return FALSE;
	}
	else
	{
		return (severity >= pLogger->nMinSeverity);
	}
}

#ifndef __XN_NO_BC__

//---------------------------------------------------------------------------
// Backwards-Compatibility Code
//---------------------------------------------------------------------------

// Note: in old log implementation, you could first set global min severity, and
// then turn on/off masks. To achieve the same behavior, a BC "turned-off" mask
// will have the MSB turned on, so that it has a very high min severity, and nothing
// will be output.
#define XN_LOG_BC_MASK_OFF_FLAG		(1 << 30)

static XnLogSeverity xnLogBCCalcSeverityByState(XnLogSeverity severity, XnBool bEnabled)
{
	if (bEnabled)
	{
		return XnLogSeverity(severity & ~XN_LOG_BC_MASK_OFF_FLAG);
	}
	else
	{
		return XnLogSeverity(severity | XN_LOG_BC_MASK_OFF_FLAG);
	}
}

static XnStatus xnLogBCSetMaskState(const XnChar* strMask, XnBool bEnabled)
{
	LogData& logData = LogData::GetInstance();
	if (strcmp(strMask, XN_LOG_MASK_ALL) == 0)
	{
		XnLogSeverity newGlobalSeverity = xnLogBCCalcSeverityByState(logData.defaultMinSeverity, bEnabled);
		logData.SetMinSeverityGlobally(newGlobalSeverity);
	}
	else
	{
		XnLogger* pLogger = xnLogGetLoggerForMask(strMask, TRUE);
		if (pLogger == NULL)
		{
			XN_ASSERT(FALSE);
			return XN_STATUS_ERROR;
		}

		pLogger->nMinSeverity = xnLogBCCalcSeverityByState(pLogger->nMinSeverity, bEnabled);
	}

	return (XN_STATUS_OK);
}

static XnStatus xnLogBCSetSeverityFilter(XnLogSeverity nMinSeverity)
{
	LogData& logData = LogData::GetInstance();
	if ((logData.defaultMinSeverity & XN_LOG_BC_MASK_OFF_FLAG) != 0)
	{
		nMinSeverity = XnLogSeverity(nMinSeverity | XN_LOG_BC_MASK_OFF_FLAG);
	}

	logData.SetMinSeverityGlobally(nMinSeverity);
	xnLogFilterChanged();
	return (XN_STATUS_OK);
}

XN_C_API XnBool xnLogIsEnabled(const XnChar* csLogMask, XnLogSeverity nSeverity)
{
	XnLogger* pLogger = xnLogGetLoggerForMask(csLogMask, FALSE);
	if (pLogger == NULL)
	{
		// no such logger exists. Mask uses default severity
		LogData& logData = LogData::GetInstance();
		return (nSeverity >= logData.defaultMinSeverity);
	}
	else
	{
		return (nSeverity >= pLogger->nMinSeverity);
	}
}

XN_C_API void xnLogWrite(const XnChar* csLogMask, XnLogSeverity nSeverity, const XnChar* csFile, XnUInt32 nLine, const XnChar* csFormat, ...)
{
	if (!xnLogIsEnabled(csLogMask, nSeverity))
		return;

	// write message
	va_list args;
	va_start(args, csFormat);
	xnLogWriteImplV(csLogMask, nSeverity, csFile, nLine, csFormat, args);
	va_end(args);
}

XN_C_API void xnLogWriteNoEntry(const XnChar* csLogMask, XnLogSeverity nSeverity, const XnChar* csFormat, ...)
{
	if (!xnLogIsEnabled(csLogMask, nSeverity))
		return;

	va_list args;
	va_start(args, csFormat);

	const XnUInt32 nMaxMessageSize = 1024;
	XnChar csMessage[nMaxMessageSize+1];
	XnUInt32 nChars;
	xnOSStrFormatV(csMessage, nMaxMessageSize, &nChars, csFormat, args);

	va_end(args);

	LogData& logData = LogData::GetInstance();
	xnl::AutoCSLocker locker(logData.hLock);
	for (XnLogWritersList::ConstIterator it = logData.writers.Begin(); it != logData.writers.End(); ++it)
	{
		const XnLogWriter* pWriter = *it;
		pWriter->WriteUnformatted(csMessage, pWriter->pCookie);
	}
}

XN_C_API void xnLogWriteBinaryData(const XnChar* csLogMask, XnLogSeverity nSeverity, const XnChar* csFile, XnUInt32 nLine, XnUChar* pBinData, XnUInt32 nDataSize, const XnChar* csFormat, ...)
{
	if (!xnLogIsEnabled(csLogMask, nSeverity))
		return;

	// first write preceding message:
	va_list args;
	va_start(args, csFormat);
	xnLogWriteImplV(csLogMask, nSeverity, csFile, nLine, csFormat, args);
	va_end(args);

	// now write binary data (in lines of 16 bytes)
	XnChar csLine[256];
	XnUInt32 pos = 0;

	for (XnUInt32 i = 0; i < nDataSize; ++i)
	{
		if ((i % 16) == 0) // first byte in line
		{
			// start a new line
			pos = sprintf(csLine, "%6u: ", i);
		}

		pos += sprintf(csLine + pos, "%02x ", pBinData[i]);

		if ((i % 16) == 15 || (i == nDataSize-1)) // last byte in line
		{
			xnLogWriteImpl(csLogMask, nSeverity, csFile, nLine, "%s", csLine);
		}
	}
}

XN_C_API XnStatus xnLogSetMaskState(const XnChar* csMask, XnBool bEnabled)
{
	return xnLogBCSetMaskState(csMask, bEnabled);
}

XN_C_API XnStatus xnLogSetSeverityFilter(XnLogSeverity nMinSeverity)
{
	return xnLogBCSetSeverityFilter(nMinSeverity);
}

XN_C_API XnStatus xnLogCreateFile(const XnChar* csFileName, XN_FILE_HANDLE* phFile)
{
	XnChar strFullPath[XN_FILE_MAX_PATH];
	return xnLogCreateNewFile(csFileName, TRUE, strFullPath, XN_FILE_MAX_PATH, phFile);
}

XN_C_API XnStatus xnLogCreateFileEx(const XnChar* strFileName, XnBool bSessionBased, XN_FILE_HANDLE* phFile)
{
	XnChar strFullPath[XN_FILE_MAX_PATH];
	return xnLogCreateNewFile(strFileName, bSessionBased, strFullPath, XN_FILE_MAX_PATH, phFile);
}

#endif // #ifndef __XN_NO_BC__