/**
 * @file       kyassert.cpp
 * @brief      アサーションモジュール
 * @author     Nomura Kei
 * @copyright  2003 - 2017  Nomura Kei
 * License: New BSD License (3-cclause BSD license)
 */
#include <iostream>
#include <sstream>
#include <string>
#include <cstring>
#include <cerrno>

#include <kyassert.hpp>

#undef assertEquals
#undef assertTrue
#undef assertFalse
#undef assertNull
#undef assertNotNull
#undef fail

namespace ky
{

////////////////////////////////////////////////////////////////////////////////
//
// AssertError
//

/**
 * 最後に発生したエラーメッセージを持つ AssertError を構築します.
 * エラーメッセージを取得できない場合, 空文字がメッセージに設定されます.
 */
AssertError::AssertError() throw()
	: message(""), fileName("unknown"), lineNumber(0), functionName("unknown")
{
	message = std::strerror(errno);
}



/**
 * コピーコンストラクタ
 *
 * @param e コピー元
 */
AssertError::AssertError(const AssertError& e) throw()
	: message(e.message), fileName(e.fileName), lineNumber(e.lineNumber), functionName(e.functionName)
{
	// NOP
}


/**
 * 指定されたメッセージを持つ AssertError を構築します.
 *
 * @param msg メッセージ
 */
AssertError::AssertError(const std::string& msg) throw()
	: message(msg), fileName("unknown"), lineNumber(0), functionName("unknown")
{
	// NOP
}


/**
 * 指定されたメッセージを持つ AssertError を構築します.
 *
 * @param msg メッセージ
 * @param file AssertError 発生ソースファイル名
 * @param line AssertError 発生ソース行番号
 * @param func AssertError 発生関数名
 *
 */
AssertError::AssertError(const std::string& msg,
		const char* file, int line, const char* func) throw()
	: message(msg), fileName(file), lineNumber(line), functionName(func)
{
	// NOP
}


/**
 * デストラクタ.
 */
AssertError::~AssertError() throw()
{
	// NOP
}


/**
 * AssertError が throw されたファイル名を取得します.
 *
 * @return ファイル名
 */
const std::string& AssertError::getFileName() const throw()
{
	return fileName;
}


/**
 * AssertError が throw された行番号を取得します.
 *
 * @return 行番号
 */
int AssertError::getLineNumber() const throw()
{
	return lineNumber;
}


/**
 * AssertError が throw された関数名を取得します.
 *
 * @return 関数名
 */
const std::string& AssertError::getFunctionName() const throw()
{
	return functionName;
}


/**
 * エラーメッセージを返します.
 *
 * @return エラーメッセージ
 */
const char* AssertError::what() const throw()
{
	return message.c_str();
}


namespace Assertion
{


/**
 * bool 型の値比較.
 * 通常, 本メソッドを直接使用せず, assertEquals(expected, actual) マクロを使用してください.
 * 指定された excepted と actual が同じか否かを判定し, 不一致の場合 AssertError を throw します.
 *
 * @param expected 比較対象1
 * @param actual   比較対象2
 * @param file     ファイル
 * @param line     行番号
 * @param func     関数
 */
void assertEquals(bool expected, bool actual, const char* file, int line, const char* func) throw(AssertError)
{
	if (expected != actual)
	{
		if (expected)
		{
			throw AssertError("expected:<true> but was:<false>", file, line, func);
		}
		else
		{
			throw AssertError("expected:<true> but was:<false>", file, line, func);
		}
	}
}


/**
 * char 型の値比較.
 * 通常, 本メソッドを直接使用せず, assertEquals(expected, actual) マクロを使用してください.
 * 指定された excepted と actual が同じか否かを判定し, 不一致の場合 AssertError を throw します.
 *
 * @param expected 比較対象1
 * @param actual   比較対象2
 * @param file     ファイル
 * @param line     行番号
 * @param func     関数
 */
void assertEquals(char expected, char actual, const char* file, int line, const char* func) throw(AssertError)
{
	if (expected != actual)
	{
		std::ostringstream msg;
		msg << "expected <" << expected << "> but was:<" << actual << ">";
		throw AssertError(msg.str(), file, line, func);
	}
}


/**
 * double 型の値比較.
 * 通常, 本メソッドを直接使用せず, assertEquals(expected, actual) マクロを使用してください.
 * 指定された excepted と actual が同じか否かを判定し, 不一致の場合 AssertError を throw します.
 *
 * @param expected 比較対象1
 * @param actual   比較対象2
 * @param file     ファイル
 * @param line     行番号
 * @param func     関数
 */
void assertEquals(double expected, double actual, const char* file, int line, const char* func) throw(AssertError)
{
	if (expected != actual)
	{
		std::ostringstream msg;
		msg << "expected <" << expected << "> but was:<" << actual << ">";
		throw AssertError(msg.str(), file, line, func);
	}
}


/**
 * float 型の値比較.
 * 通常, 本メソッドを直接使用せず, assertEquals(expected, actual) マクロを使用してください.
 * 指定された excepted と actual が同じか否かを判定し, 不一致の場合 AssertError を throw します.
 *
 * @param expected 比較対象1
 * @param actual   比較対象2
 * @param file     ファイル
 * @param line     行番号
 * @param func     関数
 */
void assertEquals(float expected, float actual, const char* file, int line, const char* func) throw(AssertError)
{
	if (expected != actual)
	{
		std::ostringstream msg;
		msg << "expected <" << expected << "> but was:<" << actual << ">";
		throw AssertError(msg.str(), file, line, func);
	}
}


/**
 * int 型の値比較.
 * 通常, 本メソッドを直接使用せず, assertEquals(expected, actual) マクロを使用してください.
 * 指定された excepted と actual が同じか否かを判定し, 不一致の場合 AssertError を throw します.
 *
 * @param expected 比較対象1
 * @param actual   比較対象2
 * @param file     ファイル
 * @param line     行番号
 * @param func     関数
 */
void assertEquals(int expected, int actual, const char* file, int line, const char* func) throw(AssertError)
{
	if (expected != actual)
	{
		std::ostringstream msg;
		msg << "expected <" << expected << "> but was:<" << actual << ">";
		throw AssertError(msg.str(), file, line, func);
	}
}



/**
 * long 型の値比較.
 * 通常, 本メソッドを直接使用せず, assertEquals(expected, actual) マクロを使用してください.
 * 指定された excepted と actual が同じか否かを判定し, 不一致の場合 AssertError を throw します.
 *
 * @param expected 比較対象1
 * @param actual   比較対象2
 * @param file     ファイル
 * @param line     行番号
 * @param func     関数
 */
void assertEquals(long expected, long actual, const char* file, int line, const char* func) throw(AssertError)
{
	if (expected != actual)
	{
		std::ostringstream msg;
		msg << "expected <" << expected << "> but was:<" << actual << ">";
		throw AssertError(msg.str(), file, line, func);
	}
}


/**
 * 文字列 (string) 型の値比較.
 * 通常, 本メソッドを直接使用せず, assertEquals(expected, actual) マクロを使用してください.
 * 指定された excepted と actual が同じか否かを判定し, 不一致の場合 AssertError を throw します.
 *
 * @param expected 比較対象1
 * @param actual   比較対象2
 * @param file     ファイル
 * @param line     行番号
 * @param func     関数
 */
void assertEquals(const std::string& expected, const std::string& actual, const char* file, int line, const char* func) throw(AssertError)
{
	if (expected != actual)
	{
		std::ostringstream msg;
		msg << "expected <" << expected << "> but was:<" << actual << ">";
		throw AssertError(msg.str(), file, line, func);
	}
}


/**
 * 文字列型 (const char*) の値比較.
 * 通常, 本メソッドを直接使用せず, assertEquals(expected, actual) マクロを使用してください.
 * 指定された excepted と actual が同じか否かを判定し, 不一致の場合 AssertError を throw します.
 *
 * @param expected 比較対象1
 * @param actual   比較対象2
 * @param file     ファイル
 * @param line     行番号
 * @param func     関数
 */
void assertEquals(const char* expected, const char* actual, const char* file, int line, const char* func) throw(AssertError)
{
	std::string expectedStr = expected;
	std::string actualStr   = actual;
	assertEquals(expectedStr, actualStr, file, line, func);
}


/**
 * 文字列型 (const char*, string) の値比較.
 * 通常, 本メソッドを直接使用せず, assertEquals(expected, actual) マクロを使用してください.
 * 指定された excepted と actual が同じか否かを判定し, 不一致の場合 AssertError を throw します.
 *
 * @param expected 比較対象1
 * @param actual   比較対象2
 * @param file     ファイル
 * @param line     行番号
 * @param func     関数
 */
void assertEquals(const char* expected, const std::string& actual, const char* file, int line, const char* func) throw(AssertError)
{
	std::string expectedStr = expected;
	assertEquals(expectedStr, actual, file, line, func);
}


/**
 * 文字列型 (string, const char*) の値比較.
 * 通常, 本メソッドを直接使用せず, assertEquals(expected, actual) マクロを使用してください.
 * 指定された excepted と actual が同じか否かを判定し, 不一致の場合 AssertError を throw します.
 *
 * @param expected 比較対象1
 * @param actual   比較対象2
 * @param file     ファイル
 * @param line     行番号
 * @param func     関数
 */
void assertEquals(const std::string& expected, const char* actual, const char* file, int line, const char* func) throw(AssertError)
{
	std::string actualStr = actual;
	assertEquals(expected, actualStr, file, line, func);
}


/**
 * 指定された値 condition が true であるか判定し, true でない場合 AssertError を throw します.
 * 通常, 本メソッドを直接使用せず, assertTrue(condition) マクロを使用してください.
 *
 * @param condition 判定する bool
 * @param file      ファイル
 * @param line      行番号
 * @param func      関数
 */
void assertTrue(bool condition, const char* file, int line, const char* func) throw(AssertError)
{
	if (!condition)
	{
		throw AssertError("", file, line, func);
	}
}


/**
 * 指定された値 condition が false であるか判定し, false でない場合 AssertError を throw します.
 * 通常, 本メソッドを直接使用せず, assertFalse(condition) マクロを使用してください.
 *
 * @param condition 判定する bool
 * @param file      ファイル
 * @param line      行番号
 * @param func      関数
 */
void assertFalse(bool condition, const char* file, int line, const char* func) throw(AssertError)
{
	if (condition)
	{
		throw AssertError("", file, line, func);
	}
}


/**
 * 指定された値 condition が null であるか判定し, null でない場合 AssertError を throw します.
 * 通常, 本メソッドを直接使用せず, assertNull(obj) マクロを使用してください.
 *
 * @param obj  null か否か判定するオブジェクト
 * @param file ファイル
 * @param line 行番号
 * @param func 関数
 */
void assertNull(void* obj, const char* file, int line, const char* func) throw(AssertError)
{
	if (obj != 0)
	{
		throw AssertError("", file, line, func);
	}
}


/**
 * 指定された値 condition が null でないか判定し, null の場合 AssertError を throw します.
 * 通常, 本メソッドを直接使用せず, assertNotNull(obj) マクロを使用してください.
 *
 * @param obj nullでないか確認するオブジェクト
 * @param file ファイル
 * @param line 行番号
 * @param func 関数
 */
void assertNotNull(void* obj, const char* file, int line, const char* func) throw(AssertError)
{
	if (obj == 0)
	{
		throw AssertError("", file, line, func);
	}
}


/**
 * AssertError を throw します.
 * 通常, 本メソッドを直接使用せず, fail() マクロを使用してください.
 *
 * @param file ファイル
 * @param line 行番号
 * @param func 関数
 */
void fail(const char* file, int line, const char* func) throw(AssertError)
{
	throw AssertError("", file, line, func);
}


}	// namespace Assertion
}	// namespace ky



