/**
 * @file      nstdc_assert.c
 * @brief     アサーションを扱うモジュール
 * @author    Nomura Kei
 * @copyright 2003 - 2017  Nomura Kei
 * License: New BSD License (3-cclause BSD license)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <nstdc_assert.h>

/** デフォルトハンドラの出力先	*/
#define DEFAULT_OUTPUT	stderr


/* -----------------------------------------------------------------------------
 *  内部関数 プロトタイプ宣言
 * -----------------------------------------------------------------------------
 */
static void default_handler    (long val,
	const char* vname, const char* file, int line, const char* func);

static void default_handler_num(long v1, long v2,
	const char* vname, const char* file, int line, const char* func);

static void default_handler_str(const char* v1, const char* v2 ,
	const char* vname, const char* file, int line, const char* func);

static void default_handler_mem(const void* v1, const void* v2, size_t size,
	const char* vname, const char* file, int line, const char* func);

static void default_handler_bef(bool is_error);
static void default_handler_aft(bool is_error);
static void assert_init(void);



/* -----------------------------------------------------------------------------
 *  内部変数
 * -----------------------------------------------------------------------------
 */
/** アサーションエラー発生時に呼び出されるハンドラ関数.	*/
static NstdcAssertHandlers assert_handlers;


/**
 * アサーション発生時に呼び出されるハンドラ関数を設定します.
 * 指定がないハンドラは, デフォルトのハンドラが使用されます.
 *
 * @param handlers アサーション発生時に呼び出されるハンドラ
 */
void nstdc_assert_set_handlers(NstdcAssertHandlers* handlers)
{
	if (handlers->handler      != NULL) { assert_handlers.handler     = handlers->handler;		}
	if (handlers->handler_num  != NULL) { assert_handlers.handler_num = handlers->handler_num;	}
	if (handlers->handler_str  != NULL) { assert_handlers.handler_str = handlers->handler_str;	}
	if (handlers->handler_mem  != NULL) { assert_handlers.handler_mem = handlers->handler_mem;	}
	if (handlers->handler_bef  != NULL) { assert_handlers.handler_bef = handlers->handler_bef;	}
	if (handlers->handler_aft  != NULL) { assert_handlers.handler_aft = handlers->handler_aft;	}
}


/**
 * 指定された val が偽の場合, 予め登録されているハンドラ関数 (handler) を呼び出します.
 * ※handler_bef, handler_aft は常に呼び出されます.
 *
 * @param val   値
 * @param vname 変数名
 * @param file  ソースファイル名
 * @param line  行番号
 * @param func  関数名
 */
void nstdc_assert_(long val, const char* vname,
		const char* file, int line, const char* func)
{
	bool is_error = !val;
	assert_init();
	assert_handlers.handler_bef(is_error);
	if (is_error)
	{
		assert_handlers.handler(val, vname, file, line, func);
	}
	assert_handlers.handler_aft(is_error);
}


/**
 * 指定された v1, v2 が異なる場合, 予め登録されているハンドラ関数 (handler_num) を呼び出します.
 * ※handler_bef, handler_aft は常に呼び出されます.
 *
 * @param v1    比較する値1
 * @param v2    比較する値2
 * @param vname 変数名
 * @param file  ソースファイル名
 * @param line  行番号
 * @param func  関数名
 */
void nstdc_assert_num_(long v1 , long v2,
		const char* vname, const char* file, int line, const char* func)
{
	bool is_error = (v1 != v2);
	assert_init();
	assert_handlers.handler_bef(is_error);
	if (is_error)
	{
		assert_handlers.handler_num(v1, v2, vname, file, line, func);
	}
	assert_handlers.handler_aft(is_error);
}


/**
 * 指定された v1, v2 が異なる場合, 予め登録されているハンドラ関数 (handler_str) を呼び出します.
 * ※handler_bef, handler_aft は常に呼び出されます.
 *
 * @param v1    比較する文字列1
 * @param v2    比較する文字列2
 * @param vname 変数名
 * @param file  ソースファイル名
 * @param line  行番号
 * @param func  関数名
 */
void nstdc_assert_str_(const char* v1 , const char* v2,
		const char* vname, const char* file, int line, const char* func)
{
	int  ret;
	bool is_error;
	assert_init();
	if ((v1 != NULL) && (v2 != NULL))
	{	/* v1, v2 どちらも NULL でなければ文字列比較	*/
		ret      = strcmp(v1, v2);
		is_error = (ret != 0);
	}
	else if ((v1 == NULL) && (v2 == NULL))
	{	/* v1, v2 どちらも NULL であれば一致とする		*/
		is_error = false;
	}
	else
	{	/* v1, v2 どちらかが NULL のため不一致			*/
		is_error = true;
	}

	/* ハンドラ関数を呼び出す	*/
	assert_handlers.handler_bef(is_error);
	if (is_error)
	{
		assert_handlers.handler_str(v1, v2, vname, file, line, func);
	}
	assert_handlers.handler_aft(is_error);
}


/**
 * 指定された v1, v2 が異なる場合, 予め登録されているハンドラ関数 (handler_mem) を呼び出します.
 * ※handler_bef, handler_aft は常に呼び出されます.
 *
 * @param v1    比較するデータ1
 * @param v2    比較するデータ2
 * @param size  比較するデータサイズ
 * @param vname 変数名
 * @param file  ソースファイル名
 * @param line  行番号
 * @param func  関数名
 */

void nstdc_assert_mem_(const char* v1, const char* v2, size_t size,
		const char* vname, const char* file, int line, const char* func)
{
	int  ret;
	bool is_error;
	assert_init();
	if ((v1 != NULL) && (v2 != NULL))
	{	/* v1, v2 どちらも NULL でなければデータ比較	*/
		ret      = memcmp(v1, v2, size);
		is_error = (ret != 0);
	}
	else if ((v1 == NULL) && (v2 == NULL))
	{	/* v1, v2 どちらも NULL であれば一致とする		*/
		is_error = false;
	}
	else
	{	/* v1, v2 どちらかが NULL のため不一致			*/
		is_error = true;
	}

	/* ハンドラ関数を呼び出す	*/
	assert_handlers.handler_bef(is_error);
	if (is_error)
	{
		assert_handlers.handler_mem(v1, v2, size, vname, file, line, func);
	}
	assert_handlers.handler_aft(is_error);
}


/* -----------------------------------------------------------------------------
 *  以下, 内部関数実装
 * -----------------------------------------------------------------------------
 */

/**
 * デフォルトのハンドラ関数(真偽).
 *
 * @param val   値
 * @param vname 変数名
 * @param file  ソースファイル名
 * @param line  行番号
 * @param func  関数名
 */
static
void default_handler(long val,
	const char* vname, const char* file, int line, const char* func)
{
	fprintf(DEFAULT_OUTPUT, "%s:%d:%s %s is not true (val = %ld)\n", file, line, func, vname, val);
}


/**
 * デフォルトのハンドラ関数(数値比較).
 *
 * @param v1    比較する値1
 * @param v2    比較する値2
 * @param vname 変数名
 * @param file  ソースファイル名
 * @param line  行番号
 * @param func  関数名
 */
static
void default_handler_num(long v1, long v2,
	const char* vname, const char* file, int line, const char* func)
{
	fprintf(DEFAULT_OUTPUT, "%s:%d:%s %s is not %ld (v2 = %ld)\n", file, line, func, vname, v1, v2);
}


/**
 * デフォルトのハンドラ関数(文字列比較).
 *
 * @param v1    比較する文字列1
 * @param v2    比較する文字列2
 * @param vname 変数名
 * @param file  ソースファイル名
 * @param line  行番号
 * @param func  関数名
 */
static
void default_handler_str(const char* v1, const char* v2,
	const char* vname, const char* file, int line, const char* func)
{
	const char* v1str = (v1 == NULL) ? "(NULL)" : v1;
	const char* v2str = (v2 == NULL) ? "(NULL)" : v2;
	fprintf(DEFAULT_OUTPUT, "%s:%d:%s %s is not %s (v2 = %s)\n", file, line, func, vname, v1str, v2str);
}


/**
 * デフォルトのハンドラ関数(メモリ比較).
 *
 * @param v1    比較するデータ1
 * @param v2    比較するデータ2
 * @param vname 変数名
 * @param file  ソースファイル名
 * @param line  行番号
 * @param func  関数名
 */
static
void default_handler_mem(const void* v1, const void* v2, size_t size,
	const char* vname, const char* file, int line, const char* func)
{
	UNUSED_VARIABLE(size);
	if (v1 == NULL)
	{	/* v1 が NULL で v2 が NULL でない				*/
		fprintf(DEFAULT_OUTPUT, "%s:%d:%s %s is not NULL\n", file, line, func, vname);
	}
	else if (v2 == NULL)
	{	/* v2 が NULL で v1 が NULL でない				*/
		fprintf(DEFAULT_OUTPUT, "%s:%d:%s %s is NULL\n", file, line, func, vname);
	}
	else
	{	/* v1, v2 共に NULL でなく不一致				*/
		fprintf(DEFAULT_OUTPUT, "%s:%d:%s %s is not an expected value\n", file, line, func, vname);
	}
}


/**
 * アサーションの前処理として呼び出されるデフォルトハンドラ関数.
 * 本関数ではなにもしません.
 *
 * @param is_error true/false (アサーションエラー発生/エラーなし)
 */
static
void default_handler_bef(bool is_error)
{
	/* Nothing to do.	*/
	UNUSED_VARIABLE(is_error);
}


/**
 * アサーションの後処理として呼び出されるハンドラ関数.
 * アサーションエラー発生時に, abort する.
 *
 * @param is_error true/false (アサーションエラー発生/エラーなし)
 */
static
void default_handler_aft(bool is_error)
{
	if (is_error)
	{	/* アサーションエラー発生時は abort	*/
		abort();
	}
}




/**
 * アサーションの初期化を実施します.
 * 初期化では, アサーションエラー発生時のデフォルトハンドラを設定します.
 * 初期化は一度だけ実施されます.
 */
static
void assert_init(void)
{
	static bool isinit = false;
	if (!isinit)
	{
		assert_handlers.handler     = default_handler;
		assert_handlers.handler_num = default_handler_num;
		assert_handlers.handler_str = default_handler_str;
		assert_handlers.handler_mem = default_handler_mem;
		assert_handlers.handler_bef = default_handler_bef;
		assert_handlers.handler_aft = default_handler_aft;
		isinit = true;
	}
}

