/* =============================================================================
 *  scpp_memory.cpp
 *  Copyright (c)  2003 - 2011  Nomura Kei
 *  LICENSE :
 *  LGPL (GNU Lesser General General Public License - Version 3,29 June 2007)
 *  http://www.gnu.org/copyleft/lesser.html
 * =============================================================================
 *
 * メモリ管理
 *
 */
#include <iostream>
#include <cstdlib>
#include <cstring>

#include <scpp_errno.hpp>

// 本来の malloc, free を使用するため SCPPP_DEBUG を無効にする
#ifdef SCPP_DEBUG
#undef SCPP_DEBUG
#endif

#include <scpp_memory.hpp>

using namespace scpp;
namespace
{
	int const PADDING                  = sizeof(int) * 2;
	int const MARK_ALLOCATED           = 0x12340000;
	int const MARK_ALLOCATED_NEW       = 0x12341111;
	int const MARK_ALLOCATED_NEW_ARRAY = 0x12342222;
	int const MARK_DELETED             = 0xFEDCBA00;

	bool            infoIsInitialized = false;
	MemoryInfo      infoHead;					//< 管理メモリ先頭
	MemoryInfo      infoTail;					//< 管理メモリ末尾
	MemoryInfo      infoError;					//< エラー発生時用
	MemoryListener* listener = 0;				//< リスナ

	// プロトタイプ宣言
	void init();
	void add(MemoryInfo* entry);
	void remove(MemoryInfo* entry);
	void* allocate(std::size_t n, int mark, const char* file, int line, const char* func);
	void* reallocate(void* ptr, std::size_t n, int mark, const char* file, int line, const char* func, bool isMng);

	/**
	 * new ハンドラを取得します.
	 *
	 * @return new_handler
	 */
	std::new_handler getNewHandler()
	{
		std::new_handler p = std::set_new_handler(0);
		std::set_new_handler(p);
		return p;
	}


	/**
	 * メモリ管理初期化.
	 */
	void init()
	{
		if (!infoIsInitialized)
		{
			infoIsInitialized = true;
			infoHead.fileName     = infoTail.fileName     = 0;
			infoHead.lineNumber   = infoTail.lineNumber   = 0;
			infoHead.functionName = infoTail.functionName = 0;
			infoHead.size         = infoTail.size         = 0;
			infoHead._prev        = infoHead._next        = &infoTail;
			infoTail._prev        = infoTail._next        = &infoHead;
			infoHead._mark        = infoTail._mark        = MARK_DELETED;

			// エラー処理用
			infoError.fileName     = 0;
			infoError.lineNumber   = 0;
			infoError.functionName = 0;
			infoError.size         = 0;
			infoError._prev        = 0;
			infoError._next        = 0;
			infoError._mark        = MARK_DELETED;
		}
	}


	/**
	 * メモリエントリを追加します.
	 *
	 * @param entry 追加するエントリ
	 */
	void add(MemoryInfo* entry)
	{
		init();
		entry->_next          = &infoTail;
		entry->_prev          = infoTail._prev;
		infoTail._prev->_next = entry;
		infoTail._prev        = entry;

		if (listener)
		{	// ハンドラが設定されていれば実行する
			listener->notifyAlloc(*entry);
		}
	}


	/**
	 * メモリエントリを削除します.
	 *
	 * @param entry 削除するエントリ
	 */
	void remove(MemoryInfo* entry)
	{
		entry->_prev->_next = entry->_next;
		entry->_next->_prev = entry->_prev;

		if (listener)
		{	// ハンドラが設定されていれば実行する
			listener->notifyFree(*entry);
		}
	}


	/**
	 * 指定されたサイズのメモリを確保します.
	 *
	 * @param n    確保するメモリサイズ
	 * @param mark マーク
	 * @param file ファイル
	 * @param line 行番号
	 * @param func 関数名
	 */
	void* allocate(std::size_t n, int mark, const char* file, int line, const char* func)
	{
		MemoryInfo* entry;
	
		// メモリ確保 [@see C++ Programming Language 3rd $14.4.5]
		for (;;)
		{
			entry = static_cast<MemoryInfo*> (::malloc(n + sizeof(MemoryInfo) + PADDING));
			if (entry != 0) { break; }
	
			if (std::new_handler nhandler = getNewHandler())
			{
				nhandler();
			}
			else
			{
				return 0;
			}
		}
	
		void* ptr = (entry + 1);
	
		entry->fileName     = file;
		entry->lineNumber   = line;
		entry->functionName = func;
		entry->size         = n;
		entry->_mark        = mark;
		entry->_data        = ptr;

		// エントリに追加
		add(entry);
		return ptr;
	}


	/**
	 * 指定されたサイズのメモリを確保します.
	 *
	 * @param n    確保するメモリサイズ
	 * @param mark マーク
	 * @param file ファイル
	 * @param line 行番号
	 * @param func 関数名
	 */
	void* reallocate(void* ptr, std::size_t n, int mark, const char* file, int line, const char* func, bool isMng)
	{
		if (isMng)
		{	// 管理メモリの場合, いったん削除
			remove(static_cast<MemoryInfo*>(ptr));
		}

		MemoryInfo* entry = static_cast<MemoryInfo*> (::realloc(ptr, n + sizeof(MemoryInfo) + PADDING));
		if (entry == 0)
		{
			// メモリ確保失敗 -> 元の領域は開放されないので再管理する
			add(static_cast<MemoryInfo*>(ptr));
			Errno::setError(SCPP_ENOMEM);
			if (listener)
			{	// エラーハンドラが登録されている
				infoError.size         = n;
				infoError.fileName     = file;
				infoError.lineNumber   = line;
				infoError.functionName = func;
				listener->notifyError(infoError, "can't realloc");
			}
			return 0;
		}
	
		if (!isMng)
		{	// 管理対象外メモリの場合
			// |<---- 新たな確保領域 ---->|
			// +----------+---------------+
			// |元々の領域|追加分+管理領域|
			// +----------+---------------+
			//   ↓
			// +--------+----------+------+
			// |管理領域|元々の領域|追加分|
			// +--------+----------+------+
			memmove((entry + 1), entry, n);
		}

		// 管理領域に情報を書き込む
		void* nptr          = (entry + 1);
		entry->fileName     = file;
		entry->lineNumber   = line;
		entry->functionName = func;
		entry->size         = n;
		entry->_mark        = mark;
		entry->_data        = nptr;

		// エントリに追加
		add(entry);
		return nptr;
	}

}	// namespace 無名



////////////////////////////////////////////////////////////////////////////////
//
// 以下, new / delete のオーバライド
//


void* operator new(std::size_t size) throw(std::bad_alloc)
{
	void* p = allocate(size, MARK_ALLOCATED_NEW,
			MemoryManager::fileName,
			MemoryManager::lineNumber,
			MemoryManager::functionName);
	if (p == 0)
	{
		throw std::bad_alloc();
	}
	return p;
}


void* operator new(std::size_t size, const std::nothrow_t& t) throw()
{
	void* p = allocate(size, MARK_ALLOCATED_NEW,
			MemoryManager::fileName,
			MemoryManager::lineNumber,
			MemoryManager::functionName);
	return p;
}

void* operator new[](std::size_t size) throw(std::bad_alloc)
{
	void* p = allocate(size, MARK_ALLOCATED_NEW_ARRAY,
			MemoryManager::fileName,
			MemoryManager::lineNumber,
			MemoryManager::functionName);
	if (p == 0)
	{
		throw std::bad_alloc();
	}
	return p;
}

void* operator new[](std::size_t size, const std::nothrow_t& t) throw()
{
	void* p = allocate(size, MARK_ALLOCATED_NEW_ARRAY,
			MemoryManager::fileName,
			MemoryManager::lineNumber,
			MemoryManager::functionName);
	return p;
}

void  operator delete(void* p) throw()
{
	if (p == 0)
	{	// null ポインタの場合なにもしない
		return;
	}

	MemoryInfo* entry = (MemoryInfo*) p;
	entry--;
	switch (entry->_mark)
	{
		case MARK_ALLOCATED:			// malloc で確保されたメモリ
			Errno::setError(SCPP_EINVAL);
			if (listener) { listener->notifyError(*entry, "please use free");		}
			break;
		case MARK_DELETED:				// 削除済みのメモリ
			Errno::setError(SCPP_EINVAL);
			if (listener) { listener->notifyError(*entry, "deleted pointer");		}
			break;
		case MARK_ALLOCATED_NEW:		// new で確保したメモリ
			remove(entry);
			entry->_mark = MARK_DELETED;
			entry->size  = 0;
			::free(entry);
			break;
		case MARK_ALLOCATED_NEW_ARRAY:	// new[] で確保したメモリ
			Errno::setError(SCPP_EINVAL);
			if (listener) { listener->notifyError(*entry, "please use delete[]");	}
			break;
		default:						// 管理外メモリ開放
			entry++;
			::free(entry);
			break;
	}
}


void  operator delete(void* p, const std::nothrow_t& t) throw()
{
	operator delete(p);
}

void  operator delete[](void* p) throw()
{
	if (p == 0)
	{	// null ポインタの場合なにもしない
		return;
	}

	MemoryInfo* entry = (MemoryInfo*) p;
	entry--;
	switch (entry->_mark)
	{
		case MARK_ALLOCATED:			// malloc で確保されたメモリ
			Errno::setError(SCPP_EINVAL);
			if (listener) { listener->notifyError(*entry, "please use free");		}
			break;
		case MARK_DELETED:				// 削除済みのメモリ
			Errno::setError(SCPP_EINVAL);
			if (listener) { listener->notifyError(*entry, "deleted pointer");		}
			break;
		case MARK_ALLOCATED_NEW:		// new で確保したメモリ
			Errno::setError(SCPP_EINVAL);
			if (listener) { listener->notifyError(*entry, "please use delete");		}
			break;
		case MARK_ALLOCATED_NEW_ARRAY:	// new[] で確保したメモリ
			remove(entry);
			entry->_mark = MARK_DELETED;
			entry->size  = 0;
			::free(entry);
			break;
		default:						// 管理外メモリ開放
			entry++;
			::free(entry);
			break;
	}

}
void  operator delete[](void* p, const std::nothrow_t& t) throw()
{
	operator delete[](p);
}





namespace scpp
{

////////////////////////////////////////////////////////////////////////////////
//
// デフォルトのメモリ確保/開放時に実行されるリスナ
// 

DefaultMemoryListener::DefaultMemoryListener()
{
	// NOP
}
DefaultMemoryListener::~DefaultMemoryListener()
{
	// NOP
}

/**
 * メモリが確保された際に実行されるリスナ.
 * デフォルトのリスナでは何もしません.
 *
 * @param info メモリ情報
 */
void DefaultMemoryListener::notifyAlloc(const MemoryInfo& info)
{
	// NOP
}


/**
 * メモリが開放された際に実行されるリスナ.
 * デフォルトのリスナでは何もしません.
 *
 * @param info メモリ情報
 */
void DefaultMemoryListener::notifyFree(const MemoryInfo& info)
{
	// NOP
}


/**
 * メモリの確保/開放でエラーが発生した際に実行されるリスナ.
 * エラー内容をエラー出力します.
 *
 * @param info メモリ情報
 * @param msg  メッセージ
 */
void DefaultMemoryListener::notifyError(const MemoryInfo& info, const std::string& msg)
{
	std::cerr << "MemoryError: " << msg << std::endl;
	std::cerr << "\tat " << info.fileName << ":" << info.lineNumber << ":" << info.functionName
		<< " (size = " << info.size << ")" << std::endl;
}



////////////////////////////////////////////////////////////////////////////////
//
// メモリ管理
// 

/**
 * メモリ管理.
 * メモリの確保, 開放を行います.
 * リスナを登録することで, メモリ確保, 開放, エラー発生時の通知を
 * 受信することができます.
 *
 * 以下のようにメモリを管理しています.
 * @code
 *
 * |<----管理メモリ------------->|
 * +--------+--------------------+
 * |管理領域|ユーザ使用メモリ領域|
 * +--------+--------------------+
 *           ↑ユーザにはこの位置を返す
 *
 * c++ なので std::list 等を使っても良いが,
 * list のメモリ確保でエラーがでると本末転倒なので,
 * メモリ確保時に管理領域をまとめて確保＆管理する.
 * @endcode
 *
 */
namespace MemoryManager
{

	/** 名前一時保存用.		*/
	const char* fileName     = "";

	/** 行番号一時保存用.	*/
	int         lineNumber   = 0;

	/** 関数名一時保存用.	*/
	const char* functionName = "";


	/**
	 * メモリの確保,開放,エラー発生の通知を受信するリスナを登録します.
	 *
	 * @param l リスナ
	 */
	void setListener(MemoryListener* l)
	{
		listener = l;
	}


	/**
	 * 管理している全メモリエントリを引数に指定されたハンドラを実行します.
	 * デフォルトで用意しているハンドラ printMemoryInfo を使用できます.
	 *
	 * @param handler ハンドラ
	 */
	void entries(void (*handler)(const MemoryInfo& info))
	{
		if (!infoIsInitialized) { return; }
		MemoryInfo* info = infoHead._next;
		while (info != &infoTail)
		{
			handler(*info);
			info = info->_next;
		}
	}


	/**
	 * 指定されたメソッドが true を返す管理メモリをクリア(削除)します.
	 * 本メソッド実行時, 管理しているメモリの情報を引数に
	 * 指定された関数が実行されます.
	 *
	 * [注意事項]
	 * 本メソッドでメモリはクリアしますが, デストラクタは呼び出されないので注意
	 * 
	 * 使用例)
	 * @code
	 * bool isCleanup(const scpp::MemoryInfo& info)
	 * {   // 管理しているメモリ全てクリアする
	 *     return true;
	 * }
	 * scpp::MemoryManager::cleanup(&isCleanup);
	 * @endcode
	 *
	 * @param isCleanup クリアするか否かを返す関数ポインタ
	 */
	void cleanup(bool (*isCleanup)(const MemoryInfo& info))
	{
		if (!infoIsInitialized) { return; }
		MemoryInfo* info = infoHead._next;
		while (info != &infoTail)
		{
			bool ret = isCleanup(*info);
			if (ret)
			{	// クリア対象
				MemoryInfo* tmpInfo;
				tmpInfo        = info;
				info           = info->_next;
				remove(tmpInfo);
				tmpInfo->size  = 0;
				tmpInfo->_mark = MARK_DELETED;
				free(tmpInfo);
			}
			else
			{
				info = info->_next;
			}
		}
	}


	/**
	 * 指定されたメモリの情報を出力します.
	 *
	 * @param info メモリの情報
	 */
	void printMemoryInfo(const MemoryInfo& info)
	{
		std::cout << "[MemoryInfo] "
			<< info.fileName     << ":" 
			<< info.lineNumber   << ":"
			<< info.functionName << " (size = "
			<< info.size         << ")"
			<< std::endl;
	}


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


	/**
	 * 指定されたサイズのメモリを確保します.
	 *
	 * @param nmemb 確保するメモリの要素数
	 * @param size  確保するメモリサイズ
	 * @param file  呼び出し元ソースファイル
	 * @param line  呼び出し元ソース行番号
	 * @param func  呼び出し元関数名
	 * @return 確保したメモリへのポインタ
	 */
	void* calloc(std::size_t nmemb, std::size_t size, const char* file, int line, const char* func)
	{
		std::size_t n = nmemb * size;
		void* ptr = allocate(n, MARK_ALLOCATED, file, line, func);
		if (ptr)
		{	// callc なのでゼロクリアする
			memset(ptr, 0x00, n);
		}
		return ptr;
	}


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

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

		// メモリ管理領域を取得
		MemoryInfo* entry = static_cast<MemoryInfo*> (ptr);
		entry--;
		void* nptr = 0;
		switch (entry->_mark)
		{
			case MARK_ALLOCATED:			// 管理メモリ
				nptr = reallocate(entry, size, MARK_ALLOCATED, file, line, func, true);
				break;
			case MARK_DELETED:				// 削除済みメモリ
				nptr = malloc(size, file, line, func);
				break;
			case MARK_ALLOCATED_NEW:		// new で確保されたメモリ
				Errno::setError(SCPP_EINVAL);
				if (listener) { listener->notifyError(*entry, "alloced new : can't realloc");	}
				break;
			case MARK_ALLOCATED_NEW_ARRAY:
				Errno::setError(SCPP_EINVAL);
				if (listener) { listener->notifyError(*entry, "alloced new[] : can't realloc");	}
				break;
			default:						// 管理対象外メモリ
				nptr = reallocate(ptr, size, MARK_ALLOCATED, file, line, func, false);
		}
		
		return nptr;
	}


	/**
	 * 指定されたメモリを開放します.
	 *
	 * @param ptr 開放するメモリへのポインタ
	 */
	void free(void* ptr)
	{
		MemoryInfo* entry;
		if (ptr == 0)
		{	// null ポインタの場合なにもしない
			return;
		}

		// メモリ管理領域を取得
		entry = (MemoryInfo*) ptr;
		entry--;
		switch (entry->_mark)
		{
			case MARK_ALLOCATED:			// 確保されたメモリ
				remove(entry);
				entry->_mark = MARK_DELETED;
				entry->size  = 0;
				::free(entry);
				break;
			case MARK_DELETED:				// 削除済みのメモリ
				Errno::setError(SCPP_EINVAL);
				if (listener) { listener->notifyError(*entry, "deleted pointer");		}
				break;
			case MARK_ALLOCATED_NEW:		// new で確保したメモリ
				Errno::setError(SCPP_EINVAL);
				if (listener) { listener->notifyError(*entry, "please use delete");		}
				break;
			case MARK_ALLOCATED_NEW_ARRAY:	// new[] で確保したメモリ
				Errno::setError(SCPP_EINVAL);
				if (listener) { listener->notifyError(*entry, "please use delete[]");	}
				break;
			default:						// 管理外メモリ開放
				entry++;
				::free(entry);
				break;
		}

	}


}	// namespace MemoryManager
}	// namespace scpp



