/* vim: ts=4 sw=4 sts=4 ff=unix fenc=utf-8 :
 * =====================================================================
 *  sc_mmgr.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 <string.h>

#include <sc_error.h>

/* 本来の malloc, free を使用するため SC_DEBUG を無効にする。	*/
#ifdef SC_DEBUG
	#undef SC_DEBUG
#endif
#include <sc_mmgr.h>

/* =====================================================================
 *  変数定義
 * =====================================================================
 */
static bool    SC_MMgr_initialized = false;
static SC_MMgr SC_MMgr_head;					/*< 管理メモリ先頭	*/
static SC_MMgr SC_MMgr_tail;					/*< 管理メモリ末尾	*/
static SC_MMgr SC_MMgr_error;					/*< エラー発生時用	*/
static void (*SC_MMgr_mHandler)(SC_MMgr*) = NULL;
static void (*SC_MMgr_fHandler)(SC_MMgr*) = NULL;
static void (*SC_MMgr_eHandler)(SC_MMgr*) = NULL;

/* =====================================================================
 *  プロトタイプ宣言
 * =====================================================================
 */
static bool  SC_MMgr_isMatchEntry(SC_MMgr* mgr, const char* file, int line);
static void  SC_MMgr_init(void);
static void  SC_MMgr_add(SC_MMgr* entry);
static void  SC_MMgr_remove(SC_MMgr* entry);
static void* SC_MMgr_allocate(size_t n, const char* file, int line);
static void* SC_MMgr_reallocate(void* ptr, size_t n, const char* file, int line, bool isMng);


/**
 * メモリ確保、開放、エラー発生時のハンドラを設定します。
 * ハンドラを解除する場合、値にNULLを指定してください。
 *
 * エラー時には、以下のエラー番号が設定されている可能性があります。
 * <pre>
 * SC_ENOMEM : メモリ不足
 * SC_EINVAL : 引数不正
 * </pre>
 *
 * @param mHandler メモリ確保時のハンドラ
 * @param fHandler メモリ開放時のハンドラ
 * @param eHandler エラー発生時のハンドラ
 */
void SC_MMgr_setHandler(
		void (*mHandler)(SC_MMgr*),
		void (*fHandler)(SC_MMgr*),
		void (*eHandler)(SC_MMgr*))
{
	SC_MMgr_mHandler = mHandler;
	SC_MMgr_fHandler = fHandler;
	SC_MMgr_eHandler = eHandler;
}


/**
 * 管理している全メモリエントリを引数に、
 * 指定されたハンドラを実行します。
 *
 * @param handler ハンドラ
 */
void SC_MMgr_entries(void (*handler)(SC_MMgr*))
{
	SC_MMgr* entry;
	/* 一度も初期化されていない場合、何もしない	*/
	if (SC_MMgr_initialized)
	{
		entry = SC_MMgr_head._next;
		while (entry != &SC_MMgr_tail)
		{
			handler(entry);
			entry = entry->_next;
		}
	}
}



/**
 * 指定されたソースファイル名、行番号で確保されたメモリを強制開放します。
 * 既に開放済みの場合はなにもしません。
 * fileがNULLの場合、すべての管理メモリを開放します。
 * lineが負の値の場合、行番号は無視します。
 *
 * @param file ファイル名
 * @param line 行番号
 */
void SC_MMgr_cleanup(const char* file, int line)
{
	bool     ret;
	SC_MMgr* entry;
	SC_MMgr* tmpEntry;
	/* 一度も初期化されていない場合、何もしない	*/
	if (SC_MMgr_initialized)
	{
		entry = SC_MMgr_head._next;
		while (entry != &SC_MMgr_tail)
		{
			ret = SC_MMgr_isMatchEntry(entry, file, line);
			if (ret)
			{	/* 条件に一致⇒メモリを開放する	*/
				tmpEntry = entry;
				entry    = entry->_next;
				SC_MMgr_remove(tmpEntry);
				tmpEntry->_mark = SC_MMgr_DELETED;
				tmpEntry->size  = 0;
				free(tmpEntry);
			}
			else
			{
				entry = entry->_next;
			}
		}
	}
}

/**
 * 本来の malloc。
 *
 * @param size サイズ
 * @return ポインタ
 */
void* SC_realMalloc(size_t size)
{
	return malloc(size);
}


/**
 * 指定されたメモリサイズのメモリを確保します。
 *
 * @param nmemb 確保するメモリの要素数
 * @param size  １つのメモリ要求サイズ
 * @param file  呼び出し元ソースファイル
 * @param line  呼び出し元ソース行番号
 */
void* SC_calloc(size_t nmemb, size_t size, const char* file, int line)
{
	size_t n   = nmemb * size;
	void*  ptr = SC_MMgr_allocate(n, file, line);
	if (ptr != NULL)
	{
		memset(ptr, 0x00, n);
	}
	return ptr;
}


/**
 * 指定されたサイズのメモリを確保します。
 *
 * @param size 確保するメモリサイズ
 * @param file 呼び出し元ソースファイル
 * @param line 呼び出し元ソース行番号
 * @return 確保したメモリへのポインタ
 */
void* SC_malloc(size_t size, const char* file, int line)
{
	void* ptr = SC_MMgr_allocate(size, file, line);
	return ptr;
}


/**
 * ポインタが示すメモリブロックのサイズを変更します。
 *
 * @param ptr  ポインタ
 * @param size 確保するメモリのサイズ
 * @param file 呼び出し元ソースファイル
 * @param line 呼び出し元ソース行番号
 * @return 確保したメモリへのポインタ
 */
void* SC_realloc(void* ptr, size_t size, const char* file, int line)
{
	SC_MMgr* entry;
	if (ptr == NULL)
	{	/* ptr が NULL の場合、 malloc と等価	*/
		ptr = SC_malloc(size, file, line);
		return ptr;
	}

	if (size == 0)
	{	/* size が 0 の場合 free と等価			*/
		SC_free(ptr);
		return NULL;
	}

	/* メモリ管理領域を取得						*/
	entry = (SC_MMgr*) ptr;
	entry--;
	switch (entry->_mark)
	{
		case SC_MMgr_ALLOCATED:	/* 管理メモリ		*/
			ptr = SC_MMgr_reallocate(entry, size, file, line, true);
			break;
		case SC_MMgr_DELETED:	/* 削除済みメモリ	*/
			ptr = SC_malloc(size, file, line);
			break;
		default:				/* 管理外メモリ		*/
			ptr = SC_MMgr_reallocate(ptr, size, file, line, false);
			break;
	}
	return ptr;
}


/**
 * 指定されたメモリを開放します。
 *
 * @param ptr 開放するメモリへのポインタ
 */
void SC_free(void* ptr)
{
	SC_MMgr* entry;
	if (ptr == NULL)
	{	/* NULL ポインタに対してはなにもしない	*/
		return;
	}

	/* メモリ管理領域を取得	*/
	entry = (SC_MMgr*) ptr;
	entry--;
	switch (entry->_mark)
	{
		case SC_MMgr_ALLOCATED:		/* メモリ開放		*/
			SC_MMgr_remove(entry);
			entry->_mark = SC_MMgr_DELETED;
			entry->size  = 0;
			free(entry);
			break;
		case SC_MMgr_DELETED:		/* メモリ二重開放	*/
			SC_setError(SC_EINVAL);
			if (SC_MMgr_eHandler != NULL)
			{
				SC_MMgr_eHandler(entry);
			}
			break;
		default:					/* 管理外メモリ開放	*/
			entry++;
			free(entry);
			break;
	}
}


/* =====================================================================
 *  以下、内部でのみ使用する関数
 * =====================================================================
 */
/**
 * 初期化します。
 * 初期化は一度だけ実施されます。
 */
static
void SC_MMgr_init(void)
{
	if (!SC_MMgr_initialized)
	{
		SC_MMgr_initialized = true;
		SC_MMgr_head.file   = SC_MMgr_tail.file  = NULL;
		SC_MMgr_head.line   = SC_MMgr_tail.line  = 0;
		SC_MMgr_head._prev  = SC_MMgr_head._next = &SC_MMgr_tail;
		SC_MMgr_tail._prev  = SC_MMgr_tail._next = &SC_MMgr_head;
		SC_MMgr_head._mark  = SC_MMgr_tail._mark = SC_MMgr_DELETED;

		/* エラー処理用			*/
		SC_MMgr_error.file  = NULL;
		SC_MMgr_error.line  = 0;
		SC_MMgr_error._mark = SC_MMgr_DELETED;
		SC_MMgr_error._prev = NULL;
		SC_MMgr_error._next = NULL;
	}
}


/**
 * エントリを追加します。
 *
 * @param entry 追加するエントリ
 */
static
void SC_MMgr_add(SC_MMgr* entry)
{
	SC_MMgr_init();
	entry->_next              = &SC_MMgr_tail;
	entry->_prev              = SC_MMgr_tail._prev;
	SC_MMgr_tail._prev->_next = entry;
	SC_MMgr_tail._prev        = entry;

	if (SC_MMgr_mHandler != NULL)
	{	/* ハンドラが設定されている場合、実行する	*/
		SC_MMgr_mHandler(entry);
	}
}


/**
 * エントリを削除する。
 *
 * @param entry 削除するエントリ
 */
static
void SC_MMgr_remove(SC_MMgr* entry)
{
	entry->_prev->_next = entry->_next;
	entry->_next->_prev = entry->_prev;

	if (SC_MMgr_fHandler != NULL)
	{	/* ハンドラが設定されている場合、実行する	*/
		SC_MMgr_fHandler(entry);
	}
}


/**
 * 指定された size のメモリを確保します。
 * 確保できない場合、NULL を返します。
 *
 * @param size サイズ
 * @param file ファイル
 * @param line 行番号
 * @return 確保したメモリへのポインタ
 */
static
void* SC_MMgr_allocate(size_t n, const char* file, int line)
{
	SC_MMgr* pm;
	pm = (SC_MMgr*) malloc(n + sizeof(SC_MMgr));
	if (pm == NULL)
	{	/* メモリ確保失敗 -> NULL を返す	*/
		SC_setError(SC_ENOMEM);
		if (SC_MMgr_eHandler != NULL)
		{	/* エラーハンドラ実行			*/
			SC_MMgr_error.size = n;
			SC_MMgr_error.file = file;
			SC_MMgr_error.line = line;
			SC_MMgr_eHandler(&SC_MMgr_error);
		}
		return NULL;
	}
	pm->_mark = SC_MMgr_ALLOCATED;
	pm->size  = n;
	pm->file  = file;
	pm->line  = line;
	SC_MMgr_add(pm);
	return (pm + 1);
}


/**
 * 指定されたポインタが示すメモリブロックのサイズを n に変更します。
 *
 * @param ptr   ポインタ
 * @param n     変更後のサイズ
 * @param file  呼び出し元ソースファイル
 * @param line  呼び出し元ソース行番号
 * @param isMng true/false (管理メモリ/管理外メモリ)
 * @return メモリへのポインタ
 */
static
void* SC_MMgr_reallocate(void* ptr, size_t n,
		const char* file, int line, bool isMng)
{
	SC_MMgr* pm;
	if (isMng)
	{
		SC_MMgr_remove(ptr);
	}
	pm = (SC_MMgr*) realloc(ptr, n + sizeof(SC_MMgr));
	if (pm == NULL)
	{	/* メモリ確保失敗 -> NULL を返す。
		 * 元の領域は開放されない(再管理する)
		 */
		SC_MMgr_add(ptr);
		SC_setError(SC_ENOMEM);
		if (SC_MMgr_eHandler != NULL)
		{	/* エラーハンドラ実行				*/
			SC_MMgr_error.size = n;
			SC_MMgr_error.file = file;
			SC_MMgr_error.line = line;
			SC_MMgr_eHandler(&SC_MMgr_error);
		}
		return NULL;
	}

	if (!isMng)
	{	/* 管理対象外メモリの場合

		   |<---- 新たな確保領域 ---->|
		   +----------+---------------+
		   |元々の領域|追加分+管理領域|
		   +----------+---------------+
		     ↓
		   +--------+----------+------+
		   |管理領域|元々の領域|追加分|
		   +--------+----------+------+
		 */
		memmove((pm + 1), pm, n);
	}

	/* 管理領域に情報を書き込む	*/
	pm->_mark = SC_MMgr_ALLOCATED;
	pm->size  = n;
	pm->file  = file;
	pm->line  = line;
	SC_MMgr_add(pm);
	return (pm + 1);
}


/**
 * 指定されたエントリが、指定されたソースファイル、行番号で
 * 確保されたメモリかどうか確認します。
 * file が NULL の場合、常に ture を返します。
 * line が 0 未満の場合、行番号は無視します。
 *
 * @param mgr エントリ
 * @param file ファイル名
 * @param line 行番号
 * @return true/false (一致/不一致)
 */
static
bool SC_MMgr_isMatchEntry(SC_MMgr* mgr, const char* file, int line)
{
	int ret;

	if (file == NULL)
	{	/* ファイル名指定なし	*/
		return true;
	}

	ret = strcmp(file, mgr->file);
	if (ret == 0)
	{	/* ファイル名一致		*/
		if ((line < 0) || (line == mgr->line))
		{	/* 行番号指定なし or 行番号一致	*/
			return true;
		}
	}

	return false;
}
