/* ----------------------------------------------------------------------------------------------------
	ASWDebug.h - AAW 2/13/2006
	
	This code is Copyright (C)2005-6 by Ambrosia Software, Inc.  All rights reserved.
	
	ASWDebug implements debugging macros that include message logging functions, conditional logging, 
	filtered logging via message priority levels, variable argument lists, throw/catch flow control
	and error handling, assertion crash log generation, etc. with "Release" analogs that result in
	zero-overhead builds from the same codebase.
	
	"ASWDebug.h" should be the prefix file for debug builds
	"ASWRelease.h" should be the prefix file for release build
	...then add whatever logging implementation bottleneck you'd like to use.  eg: you'd add
	"ASWDebugCocoaNSLog.m" for logging messages in Cocoa applications via NSLog()
	
	The "ASWRelease.h" file causes the debug macros to evaluate out to zero-overhead operations, so
	you can have just one code base for both debug and release builds.
	

	dLog Functions:
	---------------
	
		In typical usage, the few functions that ASWDebug offers are rarely used.  Rather, the macros
		defined below are used throughout your code to implement various debug logging, flow control,
		and error handling.
	
		dLogSetDebugLevel(short level) -- sets the minimum required priority level that messages
				need in order to be logged.  This is useful for refining the logging granularity
				so you can exempt messages that don't interest you while still leaving them in
				your code.  Pass in any ASWMessagePriorityLevel (see below), and any messages sent
				to the log must be >= this level in order to actually be logged.  The various
				error, conditional, throw, and assert macros have implicit priority levels.
		
		dLogSetDebugFile(char *file) -- provides ASWDebug with the path name for the file to log
				messages to.  Some logging implementions may ignore this routine entirely, so see
				the logging implementation bottleneck file for details.  dLogSetDebugFile must be
				called before any actual logging is done for it to take effect.
		
		dLogDebugLog(short level, char *msg, ...) -- probably should never be called directly.
				Instead, use the various macros provided below to do your logging.  This is the
				bottleneck routine that actually routes messages to a specific logging mechanism
				or file.
		
		dLogCleanup(void) -- should be called just before your application terminates and you're done
				with any message logging/debugging macros.  It isn't strictly necessary to call
				dLogCleanup, but it allows ASWDebug to clean up any open files, allocated data
				structures/objects, etc. in a friendly manner.
				
				
	Macro parameter naming conventions:
	------------------------------------
	
	l = short -- the priority level of a given message
	m = const char * -- the message you're passing in to be logged
	e = signed long -- signed long error code
	x = an arbitrary C expression
	
	
	Implicit variables/labels:
	--------------------------
	
		While the logging/debugging macros are fairly straightforward, there are a few special cases
		you should be aware of:
		
		error (SInt32) -- all of the macros that deal with errors (dLogIfError, dThrowErr, etc.) expect
				that a variable named 'error' (which is a SInt32) has been declared in the code block in
				which the macros are used. These macros set 'error' to the error code passed in to the
				macro, allowing you to do things like: dLogIfError(SetFPos(myFile, myPos), "SetFPos error");
				to log soft errors, and also have 'error' set to the error code encountered, for later
				error handling/recovery.
		
		CLEANUP: -- all of the dThrow macros (dThrow, dThrowErr, etc.) jump to the CLEANUP: label in your
				code when a Throw takes place.  The dCatch() macro automatically inserts this label for you
				where it is declared.  The idea is that you have one section of code in your function that
				handles gracefully cleaning up before the function exits, no matter what errors (if any) were
				encountered. For example, you might allocate a number of data structures/objects in your
				function, all of which are wrapped by dThrowIfNull() macros that will jump to CLEANUP: if any
				are indeed NULL.  Your CLEANUP: code (marked by the dCatch() macro) would then deallocate
				any data structures/objects that are not NULL, just as it would if the function exited
				normally.
				
				
	Error/status logging macros:
	----------------------------
	
		Use these macros to log status information and errors.  If the ASWRelease.h file is used,
		nothing is logged to the console, but 'error' is properly set if dLogErr or dLogIfError are used.
	
		dLogFnEnter(void) & dLogFnExit(void) -- should be placed at the beginning and end of each
				function/method, respectively; they simply mark that a function/method has been entered
				or exited by logging the name of the function/method.  These messages are implicitly set
				to dLogTrivialMsg priority level (the lowest level).

		dLog(l, m, ...) -- works just like NSLog() or fprintf, including variable argument lists, except you
				pass in just a literal C string as a message, not an NSString.  You can pass in any 
				ASWMessagePriorityLevel you wish for this message.
				USAGE EXAMPLE:	dLog(dLogStatusMsg, "Our object = %@ at index = %d", myObject, myIndex);

		dLogErr(l, e, m, ...) -- is identical to dLog() except that it also explicitly logs an error
				code in addition to a message, and other optional arguments.  It implicitely expects
				that a local variable named 'error' is declared in the function, which is set to the
				error code passed in.    You can pass in any ASWMessagePriorityLevel you wish for
				this message.
				USAGE EXAMPLE:	dLogErr(dLogErrorMsg, myErr, "Error with object %@", myObject);

		dLogIfError(e, m, ...) -- should be wrapped around any function that returns an error result
				code.  It implicitly expects that a local variable named 'error' is declared in the
				function, which is set to the result returned by the function.  The message is only
				logged if the function result is not noErr (0).  These messages are implicitly set to
				dLogErrorMsg priority level.
				EXAMPLE USAGE:	dLogIfError(SetFPos(myFile, myPos), "This shouldn't happen with object %@", myObject);
								if (error == fnfErr) ...

		dLogIfNull(x, m, ...) -- is identical to dLog() except that it only logs the passed message
				if the expression evaluates to NULL.  These messages are implicitly set to
				dLogConditionalMsg priority level and it implicitely expects that a local variable 
				named 'error' is declared in the function, which is set to the error code passed in.
				EXAMPLE USAGE: dLogIfNull(myObject, "How could this happen?");

		dLogIfFalse(x, m, ...) -- is identical to dLog() except that it only logs the passed message
				if the expression evaluates to FALSE.  These messages are implicitly set to
				dLogConditionalMsg priority level and it implicitely expects that a local variable 
				named 'error' is declared in the function, which is set to the error code passed in.
				EXAMPLE USAGE: dLogIfFalse(didFinish, "I guess we didn't finish");

		dLogIfTrue(x, m, ...) -- is identical to dLog() except that it only logs the passed message
				if the expression evaluates to TRUE.  These messages are implicitly set to
				dLogConditionalMsg priority level and it implicitely expects that a local variable 
				named 'error' is declared in the function, which is set to the error code passed in.
				EXAMPLE USAGE: dLogIfTrue(didFinish, "We finished at index %d", myIndex);

		dLogIfException(f, e, m, ...) -- is identical to dLog() except that it only logs the passed 
				messageif the evaluationed function throws an objective-C exception.  These messages are implicitly set to
				dLogConditionalMsg priority level and it implicitely expects that a local variable 
				named 'error' is declared in the function, which is set to the error code passed in.
				EXAMPLE USAGE: dLogIfTrue(didFinish, "We finished at index %d", myIndex);


	Error handling Throw/Catch macros:
	----------------------------------
	
		Use these macros to handle soft errors via a throw/catch mechanism.  When a serious error
		condition is encountered, the error message is logged, and the error is "thrown" to the code
		block indicated by the corresponding "catch" macro, which should handle cleaning up and
		recovering from the error.
		
		If the ASWRelease.h file is used, nothing is logged to the console, but 'error' is properly set
		if dThrowErr or dThrowIfError are used, and the throw/catch control flow mechanism still works.

		These messages are implicitly set to dLogThrowMsg priority level.

		dThrow(m, ...) -- analogous to dLog() above, except that code execution jumps to the block
				defined by the corresponding dCatch() macro

		dThrowErr(e, m, ...) -- analogous to dLogErr() above, except that code execution jumps to
				the block defined by the corresponding dCatch() macro

		dThrowIfError(e, m, ...) -- analogous to dLogIfError() above, except that code execution
				jumps to the block defined by the corresponding dCatch() macro

		dThrowIfNull(x, m, ...) -- analogous to dLogIfNull() above, except that code execution jumps
				to the block defined by the corresponding dCatch() macro and it implicitely expects
				that a local variable named 'error' is declared in the function, which is set to the
				error code passed in.
		
		dThrowIfFalse(x, m, ...) -- analogous to dLogIfFalse() above, except that code execution jumps
				to the block defined by the corresponding dCatch() macro and it implicitely expects
				that a local variable named 'error' is declared in the function, which is set to the
				error code passed in.
		
		dThrowIfTrue(x, m, ...) -- analogous to dLogIfTrue() above, except that code execution jumps
				to the block defined by the corresponding dCatch() macro and it implicitely expects
				that a local variable named 'error' is declared in the function, which is set to the
				error code passed in.

		dCatch(void) -- this macro doesn't actually do anything, it just marks the block of code where
				execution should jump to when a dThrow macro is used in a function.  The code block
				marked by dCatch() should properly handle error conditions, and do any necessary cleanup.


	Fatal assertion error handling macros:
	--------------------------------------
	
		Assertions are raised when an unrecoverable error condition occurs.  Assertions should generally
		only be used for conditions that should never typically occur, and offer no way to gracefully
		recover.  All of these routines will cause the program execution to halt, and a crash log to be
		written out to disk by CrashReporter.

		If the ASWRelease.h file is used, nothing is logged to the console, the assertion does not cause
		the program to dump core to a crash log or terminate, but 'error' is properly set if dAssertErr or
		dAssertIfError are used.

		These messages are implicitly set to dLogAssertMsg priority level.

		dAssert(m, ...) -- analogous to dLog() above, except that after logging the message, it causes
				the application to dump core to a crash log and terminate

		dAssertErr(e, m, ...) -- analogous to dLogErr() above, except that after logging the message,
				it causes the application to dump core to a crash log and terminate

		dAssertIfError(e, m, ...) -- analogous to dLogIfError() above, except that after logging the
				message, it causes the application to dump core to a crash log and terminate

		dAssertIfNull(x, m, ...) -- analogous to dLogIfNull() above, except that after logging the
				message, it causes the application to dump core to a crash log and terminate
		
		dAssertIfFalse(x, m, ...) -- analogous to dLogIfFalse() above, except that after logging the
				message, it causes the application to dump core to a crash log and terminate
		
		dAssertIfTrue(x, m, ...) -- analogous to dLogIfTrue() above, except that after logging the
				message, it causes the application to dump core to a crash log and terminate
 
	Compile-time assertions:
	------------------------
 
		These macros allow you to declare a constraint that must be true at compile time. Since the code
		does nothing, it should optimize away to nothing when the constrain is satisfied.
 
		If the compiler supports static_assert, that will be used and you'll get the message parameter as the
		error message. As of 4.3, GCC in Xcode does not, so you will get a duplicate
		case label error on the line where the assertion is and you'll have to look at the code to see the 
		error.
 
		dCompileTimeAssert(pred, message) -- Assert that a predicate is true. Remember that pred is a compile time
				expression.
 
		dComileTimeAssertSizeOfType(type, requiredSize, message) -- Assert that the size of type is equal 
				to requiredSize.
 

---------------------------------------------------------------------------------------------------- */

#ifndef __ASW_DEBUG_HEADER__
#define __ASW_DEBUG_HEADER__

#include <sys/types.h> // For size_t

#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

/* -- Kludges to make the preprocessor convert __LINE__ into a string */

#define _ASW_mkstr(x)				_ASW_mkval(x)
#define _ASW_mkval(x)				#x

#define _ASW_FILE					__FILE__
#define _ASW_LINE					_ASW_mkstr(__LINE__)
#define	_ASW_FUNC					__FUNCTION__

/* -- DEBUGGER macro to generate CrashReporter or Macsbug stack crawl and abort the application */

#ifndef DEBUGGER

#if defined(__MWERKS__) && (__MWERKS__ >= 0x2000)
	#if TARGET_API_MAC_CARBON && (__MWERKS__ >= 0x2405)
		#define DEBUGGER()	asm { trap } /* CWPro 7.x supports inline ppc asm */
	#elif TARGET_API_MAC_CARBON
		static asm void __DEBUGGER__() { trap } /* Fallback to function level */
		#define DEBUGGER() __DEBUGGER__()
	#else
		/* If Macsbug is enabled, dump a stdlog and exit -- or just exit */
		#define DEBUGGER()	do { if (0x20 & ((*(UInt8 *) 0x0BFF == 0x00FF) ?  \
							*(UInt8 *) 0x0123 : *(UInt8 *) 0x0BFF)) DebugStr( \
							"\pstdlog;es"); else ExitToShell(); } while(0)
	#endif /* TARGET_API_MAC_CARBON */
#elif defined(__GNUC__)
	#if defined(__ppc__) || defined(__ppc64__)
		#define DEBUGGER()	__asm__ volatile ("trap")
	#elif defined(__i386__) || defined(__x86_64__)
		#define DEBUGGER()	__asm__ volatile ("int3")
	#elif defined(__arm__)
	#else
		#error architecture not supported
	#endif /* __ppc__ || __ppc64__ */
#endif /* __MWERKS__ */

#endif /* DEBUGGER */

/* ----------------------------------------------------------------------------------------------------
	Message priority levels
---------------------------------------------------------------------------------------------------- */

typedef enum ASWMessagePriorityLevel
	{
	dLogTrivialMsg = 0,
	dLogStatusMsg,
	dLogConditionalMsg,
	dLogErrorMsg,
	dLogThrowMsg,
	dLogAssertMsg,
	dLogAlwaysMsg
	} ASWMessagePriorityLevel;

typedef enum ASWVerboseOutputLevel
	{
	dLogOutputQuiet = 0,		/* >= dLogAssertMsg // Only for most critical messages */
	dLogOutputTerse,			/* >= dLogConditionalMsg // Only direct error messages */
	dLogOutputNormal,			/* >= dLogStatusMsg // Also "observed" error messages */
	dLogOutputVerbose			/* >= dLogTrivialMsg // Any and all logged output */
	} ASWVerboseOutputLevel;

/* ----------------------------------------------------------------------------------------------------
	dLog error/status logging macros
---------------------------------------------------------------------------------------------------- */
	
#define	dLogFnEnter()				dLogDebugLog(dLogTrivialMsg, "--> %s --> ENTERED", _ASW_FUNC)
#define	dLogFnExit()				dLogDebugLog(dLogTrivialMsg, "<-- %s <-- EXITED", _ASW_FUNC)

#define	dLog(l, m, ...)				dLogDebugLog(l, "%s:%s \n ... " m, _ASW_FILE, _ASW_LINE, ## __VA_ARGS__)	
#define	dLogErr(l, e, m, ...)		dLogDebugLog(l, "%s:%s \n ... ERROR: %d -- " m, _ASW_FILE, _ASW_LINE, error = (SInt32) (e), ## __VA_ARGS__)
	
#define	dLogIfError(e, m, ...)		do { register long _e=(e); if (_e) dLogErr(dLogErrorMsg, _e, m, ## __VA_ARGS__); } while(0)
#define	dLogIfNull(x, e, m, ...)	do { if (!(x)) dLogErr(dLogConditionalMsg, e, "NULL -- " m, ## __VA_ARGS__); } while(0)
#define	dLogIfFalse(x, e, m, ...)	do { if (!(x)) dLogErr(dLogConditionalMsg, e, "FALSE -- " m, ## __VA_ARGS__); } while(0)
#define	dLogIfTrue(x, e, m, ...)	do { if (x) dLogErr(dLogConditionalMsg, e, "TRUE --- " m, ## __VA_ARGS__); } while(0)

#ifdef __OBJC__
#define	dLogIfException(f, e, m, ...)	do { @try { (f); } @catch(NSException *_x) { dLogErr(dLogErrorMsg, e, "Exception '%@' -- " m, [_x reason], ## __VA_ARGS__); } } while(0)
#endif


/* ----------------------------------------------------------------------------------------------------
	dThrow/dCatch error handling & cleanup macros
---------------------------------------------------------------------------------------------------- */

#define	dThrow(m, ...)				do { dLog(dLogThrowMsg, "*** THROW *** " m, ## __VA_ARGS__); goto CLEANUP; } while(0)
#define	dThrowErr(e, m, ...)		do { dLogErr(dLogThrowMsg, e, "*** THROW *** " m, ## __VA_ARGS__); goto CLEANUP; } while(0)

#define	dThrowIfError(e, m, ...)	do { register long _e=(e); if (_e) dThrowErr(_e, m, ## __VA_ARGS__); } while(0)
#define	dThrowIfNull(x, e, m, ...)	do { if (!(x)) dThrowErr(e, "NULL -- " m, ## __VA_ARGS__); } while(0)
#define	dThrowIfFalse(x, e, m, ...)	do { if (!(x)) dThrowErr(e, "FALSE -- " m, ## __VA_ARGS__); } while(0)
#define	dThrowIfTrue(x, e, m, ...)	do { if (x) dThrowErr(e, "TRUE -- " m, ## __VA_ARGS__); } while(0)

#ifdef __OBJC__
#define	dThrowIfException(f, e, m, ...)	do { @try { (f); } @catch(NSException *_x) { dThrowErr(e, "Exception '%@' -- " m, [_x reason], ## __VA_ARGS__); } } while(0)

#define dThrowIfNSError(nserr, m, ...) do { if (nserr) dThrowErr([nserr code], "NSError '%@' -- " m, [nserr description], ## __VA_ARGS__); } while(0)

#endif

#define dCatch()					do { CLEANUP: ; } while(0)


/* ----------------------------------------------------------------------------------------------------
	dRemap abstraction layer errors - log one error code, pass up another
---------------------------------------------------------------------------------------------------- */

#define	dRemapErr(e,r,m, ...)		do { dLogErr(dLogThrowMsg, e, "*** THROW *** " m, ## __VA_ARGS__); error = (SInt32) (r); goto CLEANUP; } while(0)

#define	dRemapIfError(e,r,m,...)	do { register long _e=(e); if (_e) dRemapErr(_e, r, m, ## __VA_ARGS__); } while(0)
#define	dRemapIfNull(x,e,r,m,...)	do { if (!(x)) dRemapErr(e, r, "NULL -- " m, ## __VA_ARGS__); } while(0)
#define	dRemapIfFalse(x,e,r,m,...)	do { if (!(x)) dRemapErr(e, r, "FALSE -- " m, ## __VA_ARGS__); } while(0)
#define	dRemapIfTrue(x,e,r,m,...)	do { if (x) dRemapErr(e, r, "TRUE -- " m, ## __VA_ARGS__); } while(0)

#ifdef __OBJC__
#define	dRemapIfException(f,e,r,m, ...)	do { @try { (f); } @catch(NSException *_x) { dRemapErr(e, r, "Exception '%@' -- " m, [_x reason], ## __VA_ARGS__); } } while(0)
#endif


/* ----------------------------------------------------------------------------------------------------
	dAssert fatal error handling macros
---------------------------------------------------------------------------------------------------- */

#define	dAssert(m, ...)				do { dLog(dLogAssertMsg, "### FATAL ERROR ### " m, ## __VA_ARGS__); DEBUGGER(); exit(-1); } while(0)
#define	dAssertErr(e, m, ...)		do { dLogErr(dLogAssertMsg, e, "### FATAL ERROR ### " m, ## __VA_ARGS__); DEBUGGER(); exit(-1); } while(0)

#define	dAssertIfError(e, m, ...)	do { register long _e=(e); if (_e) dAssertErr(_e, m, ## __VA_ARGS__); } while(0)
#define	dAssertIfNull(x, m, ...)	do { if (!(x)) dAssert("NULL -- " m, ## __VA_ARGS__); } while(0)
#define	dAssertIfFalse(x, m, ...)	do { if (!(x)) dAssert("FALSE -- " m, ## __VA_ARGS__); } while(0)
#define	dAssertIfTrue(x, m, ...)	do { if (x) dAssert("TRUE -- " m, ## __VA_ARGS__); } while(0)

#ifdef __OBJC__
#define	dAssertIfException(f, e, m, ...)	do { @try { (f); } @catch(NSException *_x) { dAssertErr(e, "Exception '%@' -- " m, [_x reason], ## __VA_ARGS__); } } while(0)
#endif

/* ----------------------------------------------------------------------------------------------------
	dCompileTimeAssert Use within the scope of a function to assert something that must be true for the
		compiler to generate correct code.
---------------------------------------------------------------------------------------------------- */

#ifdef _HAS_CPP0X
	#define dCompileTimeAssert static_assert
#else
	#define dCompileTimeAssert(pred, message) switch(0){case 0:case pred:;}
#endif

#define dCompileTimeAssertSizeOfType(type, requiredSize, message) dCompileTimeAssert(sizeof(type) == requiredSize, message)


/* ----------------------------------------------------------------------------------------------------
	Function prototypes
---------------------------------------------------------------------------------------------------- */

#ifdef __cplusplus
extern "C" {
#endif

/* -- dLogSetDebugVerbose -- sets the minimum priority for logging error messages, using new VERBOSE scale */

void dLogSetDebugVerbose(ASWVerboseOutputLevel verbosity);

/* -- dLogSetDebugVerbose -- returns the current logging verbosity level */

ASWVerboseOutputLevel dLogVerboseLevel(void);

/* -- dLogSetDebugLevel -- sets the minimum priority for logging error messages, using specific PRIORITY value */

void dLogSetDebugLevel(ASWMessagePriorityLevel priority);

/* -- dLogDebugLevel -- returns the current logging priority level */

ASWMessagePriorityLevel dLogDebugLevel(void);

/* -- dLogSetDebugFile -- sets the file name that dLog should log to; this is unused for some logging implementations */

void dLogSetDebugFile(const char *file);

/* -- dLogDebugLog -- should rarely be called directly; instead, use the convenience macros above */

void dLogDebugLog(/* ASWMessagePriorityLevel */ unsigned short priority, const char *msg, ...);

/* -- dLogPrintData -- Output data from an arbitrary memory location */

void dLogPrintData(/* ASWMessagePriorityLevel */ unsigned short level, const char *label, const void *data, size_t length);

/* -- dLogCleanup -- should be used just before your application terminates to allow dLog to clean up */

void dLogCleanup(void);

/* -- Timer functions */

void dTimerStart(void);
double dTimerEnd(void);

#ifdef __cplusplus
    }
#endif


#endif /* -- __ASW_DEBUG_HEADER__ */
