/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2020  Warzone 2100 Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*! \file debug.h
 *  \brief Debugging functions
 */

#ifndef __INCLUDED_LIB_FRAMEWORK_DEBUG_H__
#define __INCLUDED_LIB_FRAMEWORK_DEBUG_H__

/* Check the header files have been included from frame.h if they
 * are used outside of the framework library.
 */
#if !defined(_frame_h) && !defined(FRAME_LIB_INCLUDE)
# error Framework header files MUST be included from Frame.h ONLY.
#endif

#if defined(_WIN32)
#include <assert.h>
#else
#include <signal.h>
#endif
#include "macros.h"
#include "types.h"

#if defined(__MINGW32__) || defined(__MINGW64__)
#include <cstdio>		// For __MINGW_PRINTF_FORMAT define
#endif

/****************************************************************************************
 *
 * Basic debugging macro's
 *
 */

#define MAX_EVENT_NAME_LEN	100

/** Stores name of the last function or event called by scripts. */
extern char last_called_script_event[MAX_EVENT_NAME_LEN];

/** Whether asserts are currently enabled. */
extern bool assertEnabled;


/* Do the correct assert call for each compiler */
#if defined(_WIN32)
#define wz_assert(expr) assert(expr)
#else
#define wz_assert(expr) raise(SIGTRAP)
#endif


/*! \def WZ_DEBUG_DECL_FORMAT
 * GCC: "The format attribute specifies that a function takes printf, scanf, strftime or strfmon
 *       style arguments which should be type-checked against a format string."
 */
#if defined(__GNUC__) && !defined(__INTEL_COMPILER)
#  define WZ_DEBUG_DECL_FORMAT(archetype, string_index, first_to_check) \
	__attribute__((__format__(archetype, string_index, first_to_check)))
#else
#  define WZ_DEBUG_DECL_FORMAT(archetype, string_index, first_to_check)
#endif

#ifdef HAVE___BUILTIN_EXPECT
# define debug_likely(expr)	__builtin_expect(!!(expr),1)
#else
# define debug_likely(expr)	(expr)
#endif


/** Deals with failure in an assert. Expression is (re-)evaluated for output in the assert() call. */
#define ASSERT_FAILURE(expr, expr_string, location_description, function, ...) \
	( \
	  (void)_debug(__LINE__, LOG_INFO, function, __VA_ARGS__), \
	  (void)_debug(__LINE__, LOG_INFO, function, "Assert in Warzone: %s (%s), last script event: '%s'", \
	               location_description, expr_string, last_called_script_event), \
	  ( assertEnabled ? (void)wz_assert(expr) : (void)0 )\
	)

/**
 * Internal assert helper macro to allow some debug functions to use an alternate calling location.
 * Expression is only evaluated once if true, if false it is evaluated another time to provide decent
 * feedback on OSes that have good GUI facilities for asserts and lousy backtrace facilities.
 *
 * \param expr                 Expression to assert on.
 * \param location_description A string describing the calling location, e.g.:
 *                             "filename:linenum".
 * \param function             The name of the function that called
 *                             ASSERT_HELPER or the debug function that uses ASSERT_HELPER.
 *
 * \param ...                  printf-style format string followed by its parameters
 *
 * \return Will return whatever assert(expr) returns. That's undefined though,
 *         so unless you have a good reason to, don't depend on it.
 */
#define ASSERT_HELPER(expr, location_description, function, ...) \
	( \
	  debug_likely(expr) ? /* if (expr) */ \
	  (void)0 \
	  : /* else */\
	  ASSERT_FAILURE(expr, #expr, location_description, function, __VA_ARGS__) \
	)

/**
 *
 * Rewritten version of assert that allows a printf format text string to be passed
 * to ASSERT along with the condition.
 *
 * Arguments:	ASSERT( condition, "Format string with variables: %d, %d", var1, var2 );
 */
#define ASSERT(expr, ...) \
	ASSERT_HELPER(expr, AT_MACRO, __FUNCTION__, __VA_ARGS__)

/**
 *
 * Assert-or-return macro that returns given return value (can also be a mere comma if function has no return value) on failure,
 * and also provides asserts and debug output for debugging.
 */
#define ASSERT_OR_RETURN(retval, expr, ...) \
	do { bool _wzeval = debug_likely(expr); if (!_wzeval) { ASSERT_FAILURE(expr, #expr, AT_MACRO, __FUNCTION__, __VA_ARGS__); return retval; } } while (0)

#define ASSERT_NOT_NULLPTR_OR_RETURN(retval, ptrexpr) \
	ASSERT_OR_RETURN(retval, (ptrexpr) != nullptr, "%s should not be nullptr", #ptrexpr)


/**
 * Compile time assert
 *
 * Forces a compilation error if condition is true, but also produce a result
 * (of value 0 and type size_t), so the expression can be used anywhere that
 * a comma expression isn't permitted.
 *
 * \param expr Expression to evaluate
 *
 * \note BUILD_BUG_ON_ZERO from <linux/kernel.h>
 */
template<bool> class StaticAssert;
template<> class StaticAssert<true> {};
#define STATIC_ASSERT_EXPR(expr) \
	(0*sizeof(StaticAssert<(expr)>))
/**
 * Compile time assert
 * Not to be used in global context!
 * \param expr Expression to evaluate
 */
#define STATIC_ASSERT( expr ) \
	static_assert(expr, "");


/***
 ***
 ***  New debug logging output interface below. Heavily inspired
 ***  by similar code in Freeciv. Parts ripped directly.
 ***
 ***/

/** Debug enums. Must match code_part_names in debug.c */
enum code_part
{
	LOG_ALL, /* special: sets all to on */
	LOG_MAIN,
	LOG_SOUND,
	LOG_VIDEO,
	LOG_WZ,
	LOG_3D,
	LOG_TEXTURE,
	LOG_NET,
	LOG_MEMORY,
	LOG_WARNING, /**< special; on in debug mode */
	LOG_ERROR, /**< special; on by default */
	LOG_NEVER, /**< if too verbose for anything but dedicated debugging... */
	LOG_SCRIPT,
	LOG_MOVEMENT,
	LOG_ATTACK,
	LOG_FOG,
	LOG_SENSOR,
	LOG_GUI,
	LOG_MAP,
	LOG_SAVE,
	LOG_SYNC,
	LOG_DEATH,
	LOG_LIFE,
	LOG_GATEWAY,
	LOG_MSG,
	LOG_INFO, /**< special; on by default, for both debug & release builds */
	LOG_TERRAIN,
	LOG_FEATURE,
	LOG_FATAL,	/**< special; on by default, for both debug & release builds  */
	LOG_INPUT,	// mouse / keyboard events
	LOG_POPUP,	// special, on by default, for both debug & release builds (used for OS dependent popup code)
	LOG_CONSOLE,	// send console messages to file
	LOG_ACTIVITY,
	LOG_RESEARCH,
	LOG_SAVEGAME,
	LOG_REPAIRS,
	LOG_LOBBY,
	LOG_LAST /**< _must_ be last! */
};

extern bool enabled_debug[LOG_LAST];

typedef void (*debug_callback_fn)(void **, const char *, code_part);
typedef bool (*debug_callback_init)(void **);
typedef void (*debug_callback_exit)(void **);

struct debug_callback
{
	debug_callback *next;
	debug_callback_fn callback; /// Function which does the output
	debug_callback_init init; /// Setup function
	debug_callback_exit exit; /// Cleaning function
	void *data;  /// Used to pass data to the above functions. Eg a filename or handle.
};

/**
 * Call once to initialize the debug logging system.
 *
 * Doesn't register any callbacks!
 */
void debug_init();

/**
 * Shutdown the debug system and remove all output callbacks
 */
void debug_exit();

/**
 * Have the stderr output callback flush its output before returning.
 *
 * NOTE: This may cause significant slowdowns on some systems.
 */
void debugFlushStderr();

/// Return the last set error message, or NULL is none set since last time we were called.
const char *debugLastError();

/**
 * Register a callback to be called on every call to debug()
 *
 * \param	callback	Function which does the output
 * \param	init		Initializer function which does all setup for the callback (optional, may be NULL)
 * \param	exit		Cleanup function called when unregistering the callback (optional, may be NULL)
 * \param	data		Data to be passed to all three functions (optional, may be NULL)
 */
void debug_register_callback(debug_callback_fn callback, debug_callback_init init, debug_callback_exit exit, void *data);

void debug_callback_file(void **data, const char *outputBuffer, code_part part);
bool debug_callback_file_init(void **data);
void debug_callback_file_exit(void **data);

void debug_callback_stderr(void **data, const char *outputBuffer, code_part part);

#if defined(__EMSCRIPTEN__)
void debug_callback_emscripten_log(void **data, const char *outputBuffer, code_part part);
#endif

#if defined(_WIN32) && defined(DEBUG)
void debug_callback_win32debug(void **data, const char *outputBuffer, code_part part);
#endif

/**
 * Toggle debug output for part associated with str
 *
 * \param	str	Codepart in textformat
 */
bool debug_enable_switch(const char *str);
// macro for always outputting informational responses on both debug & release builds
#define wz_info(...) do { _debug(__LINE__, LOG_INFO, __FUNCTION__, __VA_ARGS__); } while(0)
/**
 * Output printf style format str with additional arguments.
 *
 * Only outputs if debugging of part was formerly enabled with debug_enable_switch.
 */
#define debug(part, ...) do { if (enabled_debug[part]) _debug(__LINE__, part, __FUNCTION__, __VA_ARGS__); } while(0)
#define debugLogFromGfxCallback(part, ...) do { if (enabled_debug[part]) _debugFromGfxCallback(__LINE__, part, __FUNCTION__, __VA_ARGS__); } while(0)
#if defined(__MINGW32__) || defined(__MINGW64__)
void _debug(int line, code_part part, const char *function, const char *str, ...) WZ_DEBUG_DECL_FORMAT(__MINGW_PRINTF_FORMAT, 4, 5);
void _debugFromGfxCallback(int line, code_part part, const char *function, const char *str, ...) WZ_DEBUG_DECL_FORMAT(__MINGW_PRINTF_FORMAT, 4, 5);
#else
void _debug(int line, code_part part, const char *function, const char *str, ...) WZ_DEBUG_DECL_FORMAT(printf, 4, 5);
void _debugFromGfxCallback(int line, code_part part, const char *function, const char *str, ...) WZ_DEBUG_DECL_FORMAT(printf, 4, 5);
#endif

#include <string>

#define debug_multiline(part, string) do { if (enabled_debug[part]) _debug_multiline(__LINE__, part, __FUNCTION__, string); } while(0)
void _debug_multiline(int line, code_part part, const char *function, const std::string &multilineString);

#define debugBacktrace(part, ...) do { if (enabled_debug[part]) { _debug(__LINE__, part, __FUNCTION__, __VA_ARGS__); _debugBacktrace(part); }} while(0)
void _debugBacktrace(code_part part);

/** Global to keep track of which game object to trace. */
extern UDWORD traceID;

/**
 * Output printf style format str for debugging a specific game object whose debug part
 * has been enabled.
 * @see debug
 */
#define objTrace(id, ...) do { if (id == traceID) _realObjTrace(id, __FUNCTION__, __VA_ARGS__); } while(0)
void _realObjTrace(int id, const char *function, const char *str, ...) WZ_DEBUG_DECL_FORMAT(printf, 3, 4);
static inline void objTraceEnable(UDWORD id)
{
	traceID = id;
}
static inline void objTraceDisable()
{
	traceID = (UDWORD) - 1;
}

// MSVC specific rotuines to set/clear allocation tracking
#if defined(_MSC_VER) && defined(DEBUG)
void debug_MEMCHKOFF();
void debug_MEMCHKON();
void debug_MEMSTATS();
#endif

/** Checks if a particular debub flag was enabled */
bool debugPartEnabled(code_part codePart);

void debugDisableAssert();

#endif // __INCLUDED_LIB_FRAMEWORK_DEBUG_H__
