/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
****************************************************************************/

#pragma once

#include "glmCoreAPI.h"
#include "glmCoreDefinitions.h"
#include "glmTypes.h"
#include <stdlib.h>

#if GLM_IS_COMPILER_GCC
	#include <signal.h>
#endif

#if !defined(GLM_ASSERTS_ENABLED)
	#define GLM_ASSERTS_ENABLED		1
#endif

namespace glm
{
	void GLM_CORE_API assertFailedOutput(const char* condition, const char* file, const char* function, uint32_t line, const char* formattedMessage, ...);
	void GLM_CORE_API exitOutput(const char* file, const char* function, uint32_t line, const char* formattedMessage, ...);
	void GLM_CORE_API showCallStack();
}


//---------------------------------------------------------------------------
#if GLM_IS_COMPILER_MSVC
	//#define DEBUG_BREAK()		DebugBreak()		//NOTE: was breaking at a lower level in callstack
	#define GLM_DEBUG_BREAK()	__debugbreak()
#elif GLM_IS_COMPILER_GCC
	//#define GLM_DEBUG_BREAK()	asm("int3\n")		//NOTE: x86 only, not for x64
	//#define GLM_DEBUG_BREAK()	__builtin_trap()	//NOTE: requires at least GCC 4.3.0, raise a SIGILL signal
	#define GLM_DEBUG_BREAK()	::raise( SIGTRAP )
#else
	#define GLM_DEBUG_BREAK()
#endif

#define GLM_HALT() glm::showCallStack(); GLM_DEBUG_BREAK(); exit(-1)

//---------------------------------------------------------------------------
#ifdef __clang__
#define GLM_COMPILE_TIME_ASSERT(cond)   void(cond)
#else
#define GLM_COMPILE_TIME_ASSERT(cond)   \
	GLM_MULTI_LINE_MACRO_BEGIN \
		typedef char compileTimeAssert[(cond) ? 1 : -1]; \
		GLM_UNUSED(compileTimeAssert); \
	GLM_MULTI_LINE_MACRO_END
#endif
//---------------------------------------------------------------------------

// Assert macros from http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/

#if GLM_ASSERTS_ENABLED
	
	/**
	* \brief halts if the tested condition failed.
	*
	* Before halting, GLM_ASSERT traces the condition.
	*
	* \ingroup debug
	* \author Golaem <dev@golaem.com>
	*/
	#define GLM_ASSERT( condition ) \
		GLM_MULTI_LINE_MACRO_BEGIN \
			if ( !( condition ) ) \
			{ \
				glm::assertFailedOutput(#condition, __FILE__, __FUNCTION__, __LINE__, NULL); \
				GLM_HALT() ; \
			} \
		GLM_MULTI_LINE_MACRO_END

	/**
	* \brief halts if the tested condition failed.
	*
	* Before halting, GLM_ASSERT traces the message.
	*
	* \ingroup debug
	* \author Golaem <dev@golaem.com>
	*/
	#define GLM_ASSERT_MSG( condition, formattedMessage, ... ) \
		GLM_MULTI_LINE_MACRO_BEGIN \
			if ( !( condition ) ) \
			{ \
				glm::assertFailedOutput(#condition, __FILE__, __FUNCTION__, __LINE__, formattedMessage, ##__VA_ARGS__); \
				GLM_HALT() ; \
			} \
		GLM_MULTI_LINE_MACRO_END

	#ifndef NDEBUG
		/**
		* \brief halts if the tested condition failed in debug mode.
		*
		* Before halting, GLM_ASSERT traces the condition.
		*
		* \ingroup debug
		* \author Golaem <dev@golaem.com>
		*/
		#define GLM_DEBUG_ASSERT_MSG( condition, formattedMessage, ... ) GLM_ASSERT_MSG( condition, formattedMessage, ##__VA_ARGS__ )

		/**
		* \brief halts if the tested condition failed in debug mode.
		*
		* Before halting, GLM_DEBUG_ASSERT traces a critical message labelled with the string representation
		* of the expression which caused the condition to fail.
		*
		* \ingroup debug
		* \author Golaem <dev@golaem.com>
		*/
		#define GLM_DEBUG_ASSERT( condition ) GLM_ASSERT( condition )
	#else
		#define GLM_DEBUG_ASSERT_MSG( condition, formattedMessage, ... ) GLM_MULTI_LINE_MACRO_BEGIN GLM_UNUSED( condition ) ; GLM_UNUSED( #formattedMessage ) ; GLM_MULTI_LINE_MACRO_END
		#define GLM_DEBUG_ASSERT( condition ) GLM_MULTI_LINE_MACRO_BEGIN GLM_UNUSED( condition ) ; GLM_MULTI_LINE_MACRO_END
	#endif

#else // #if GLM_ASSERTS_ENABLED

	#define GLM_ASSERT( condition ) GLM_MULTI_LINE_MACRO_BEGIN GLM_UNUSED( condition ) ; GLM_MULTI_LINE_MACRO_END
	#define GLM_ASSERT_MSG( condition, formattedMessage, ... ) GLM_MULTI_LINE_MACRO_BEGIN GLM_UNUSED( condition ) ; GLM_UNUSED( #formattedMessage ) ; GLM_MULTI_LINE_MACRO_END
	#define GLM_DEBUG_ASSERT_MSG( condition, formattedMessage, ... ) GLM_MULTI_LINE_MACRO_BEGIN GLM_UNUSED( condition ) ; GLM_UNUSED( #formattedMessage ) ; GLM_MULTI_LINE_MACRO_END
	#define GLM_DEBUG_ASSERT( condition ) GLM_MULTI_LINE_MACRO_BEGIN GLM_UNUSED( condition ) ; GLM_MULTI_LINE_MACRO_END
#endif

#define GLM_EXIT( formattedMessage, ... ) \
	GLM_MULTI_LINE_MACRO_BEGIN \
	glm::exitOutput(__FILE__, __FUNCTION__, __LINE__, formattedMessage, ##__VA_ARGS__); \
	GLM_HALT() ; \
	GLM_MULTI_LINE_MACRO_END
