/* vim: ts=4 sw=4 sts=4 ff=unix fenc=utf-8 :
 * =====================================================================
 *  sc_assert.c
 *  Copyright (c)  2003 - 2011  sys0tem
 *  LICENSE :
 *	LGPL (GNU Lesser General Public License - Version 3,29 June 2007)
 *	http://www.gnu.org/copyleft/lesser.html
 *	or
 *	EPL (Eclipse Public License - v1.0)
 *	http://www.eclipse.org/legal/epl-v10.html
 * =====================================================================
 */
#include <stdlib.h>
#include <string.h>

#include <sc_assert.h>


/* --------------------------------------------------------------------
 *  定数定義
 * --------------------------------------------------------------------
 */
/* assert エラー時に呼び出す最大関数登録数	*/
#define SC_ASSERT_ATEXIT_FUNC_MAX (32)


/* --------------------------------------------------------------------
 *  内部変数
 * --------------------------------------------------------------------
 */
static FILE* SC_assertOutput = NULL;
static void (*SC_assertAtExitFuncs[SC_ASSERT_ATEXIT_FUNC_MAX])(void);
static int  SC_assertAtExitFuncSize = 0;


/* --------------------------------------------------------------------
 *  内部関数 プロトタイプ宣言
 * --------------------------------------------------------------------
 */
static void SC_assertAtExitRun(void);


/**
 * エラー出力先を設定します。
 * 未初期化かつ fp が NULL の場合、stderr を設定します。
 *
 * @param fp エラー出力先
 */
void SC_setAssertOutput(FILE* fp)
{
	if (fp == NULL)
	{
		if (SC_assertOutput == NULL)
		{
			SC_assertOutput = stderr;
		}
	}
	else
	{
		SC_assertOutput = fp;
	}
}


/**
 * エラー出力先をクリアします。
 */
void SC_clearAssertOutput(void)
{
	SC_assertOutput = NULL;
}


/**
 * assert にてエラーが発生した際に実行する関数を登録します。
 *
 * @param func 登録する関数
 * @return true/false (登録成功/登録失敗)
 */
bool SC_assertAtExit(void (*func)(void))
{
	if (SC_assertAtExitFuncSize < SC_ASSERT_ATEXIT_FUNC_MAX)
	{
		SC_assertAtExitFuncs[SC_assertAtExitFuncSize] = func;
		SC_assertAtExitFuncSize++;
		return true;
	}
	return false;
}


/**
 * assert にてエラーが発生した際に実行する関数をクリアします。
 */
void SC_assertAtExitClear(void)
{
	SC_assertAtExitFuncSize = 0;
}


/**
 * 指定された val が偽の場合、プログラムの実行を停止します。
 *
 * @param val     値
 * @param valName 変数名
 * @param file    対象ソースファイル
 * @param line    行番号
 * @param func    関数名
 */
void SC_assert_(long val, const char* valName,
		const char* file, int line, const char* func)
{
	if (!val)
	{
		/* 出力先初期化			*/
		SC_setAssertOutput(NULL);

		fprintf(SC_assertOutput,
				SC_ASSERT_HEAD_FORMAT "%s is not true (val = %ld)\n",
				file, line, func, valName, val);
		SC_assertAtExitRun();
	}
}


/**
 * 指定された val2 が val1 と異なる値の場合、
 * プログラムの実行を停止します。
 *
 * @param val1    比較する値1
 * @param val2    比較する値2
 * @param valName 変数名
 * @param file    対象ソースファイル
 * @param line    行番号
 * @param func    関数名
 */
void SC_assertNumber_(long val1, long val2,
		const char* valName,
		const char* file, int line, const char* func)
{
	if (val1 != val2)
	{
		SC_setAssertOutput(NULL);
		fprintf(SC_assertOutput,
				SC_ASSERT_HEAD_FORMAT "%s is not %ld (val2 = %ld)\n",
				file, line, func, valName, val1, val2);
		SC_assertAtExitRun();
	}
}


/**
 * 指定された val2 が val1 と異なる文字列の場合、
 * プログラムの実行を停止します。
 *
 * @param val1    比較する文字列1
 * @param val2    比較する文字列2
 * @param valName 変数名
 * @param file    対象ソースファイル
 * @param line    行番号
 * @param func    関数名
 */
void SC_assertString_(const char* val1, const char* val2,
		const char* valName,
		const char* file, int line, const char* func)
{
	if (val1 == NULL)
	{
		if (val2 == NULL)
		{	/* どちらも NULL	*/
			return;
		}
		/* val2 != NULL			*/
		SC_setAssertOutput(NULL);
		fprintf(SC_assertOutput,
			SC_ASSERT_HEAD_FORMAT "%s is not NULL (val2 = %s)\n",
			file, line, func, valName, val2);
		SC_assertAtExitRun();
	}
	else if (val2 == NULL)
	{	/* val1 != NULL			*/
		SC_setAssertOutput(NULL);
		fprintf(SC_assertOutput,
			SC_ASSERT_HEAD_FORMAT "%s is NULL (val1 = %s)\n",
			file, line, func, valName, val1);
		SC_assertAtExitRun();
	}

	if (strcmp(val1, val2) != 0)
	{	/* 不一致	*/
		SC_setAssertOutput(NULL);
		fprintf(SC_assertOutput,
				SC_ASSERT_HEAD_FORMAT "%s is not %s (val2 = %s)\n",
				file, line, func, valName, val1, val2);
		SC_assertAtExitRun();
	}
}


/**
 * 指定された val2 が val1 と異なる文字列の場合、
 * プログラムの実行を停止します。
 *
 * @param val1    比較する文字列1
 * @param val2    比較する文字列2
 * @param valName 変数名
 * @param file    対象ソースファイル
 * @param line    行番号
 * @param func    関数名
 */
void SC_assertMemory_(const void* val1, const void* val2,
		size_t size,
		const char* valName,
		const char* file, int line, const char* func)
{
	if (val1 == NULL)
	{
		if (val2 == NULL)
		{	/* どちらも NULL	*/
			return;
		}
		/* val1 != NULL			*/
		SC_setAssertOutput(NULL);
		fprintf(SC_assertOutput,
			SC_ASSERT_HEAD_FORMAT "%s is not NULL\n",
			file, line, func, valName);
		SC_assertAtExitRun();
	}
	else if (val2 == NULL)
	{	/* val2 != NULL			*/
		SC_setAssertOutput(NULL);
		fprintf(SC_assertOutput,
			SC_ASSERT_HEAD_FORMAT "%s is NULL\n",
			file, line, func, valName);
		SC_assertAtExitRun();
	}

	if (memcmp(val1, val2, size) != 0)
	{	/* 不一致	*/
		SC_setAssertOutput(NULL);
		fprintf(SC_assertOutput,
				SC_ASSERT_HEAD_FORMAT "%s is not an expected value\n",
				file, line, func, valName);
		SC_assertAtExitRun();
	}
}


/* --------------------------------------------------------------------
 *  ASSERT OFF の際のダミー関数
 * --------------------------------------------------------------------
 */
long  SC_assert_dummyValue;
const void* SC_assert_dummyPtr;
void SC_assertDummy_(long val)
{
	SC_assert_dummyValue = val;
}
void SC_assertNumberDummy_(long val1, long val2)
{
	SC_assert_dummyValue = (val1 & val2);
} 
void SC_assertStringDummy_(const char* val1, const char* val2)
{
	SC_assert_dummyPtr = val1;
	SC_assert_dummyPtr = val2;
}
void SC_assertMemoryDummy_(
		const void* val1, const void* val2, size_t size)
{
	SC_assert_dummyPtr   = val1;
	SC_assert_dummyPtr   = val2;
	SC_assert_dummyValue = (long) size;
}
/* --------------------------------------------------------------------
 *  内部関数
 * --------------------------------------------------------------------
 */
/**
 * 登録されている関数を順次実行します。
 */
static
void SC_assertAtExitRun(void)
{
	int index;
	for (index = (SC_assertAtExitFuncSize - 1); index >= 0; index--)
	{
		SC_assertAtExitFuncs[index]();
	}
	exit(1);
}

