#include <iostream>
#include <string>

#include <scpp_os.hpp>
#include <scpp_unittest.hpp>
#include <scpp_memory.hpp>

using namespace scpp;



class MemoryTestHandler : public MemoryListener
{
	private:
		int allocCounter;
		int freeCounter;
		int errorCounter;
	public:
		MemoryTestHandler() : allocCounter(0), freeCounter(0), errorCounter(0) { }
		~MemoryTestHandler() { }
		void notifyAlloc(const MemoryInfo& info) { allocCounter++; }
		void notifyFree(const MemoryInfo& info)  { freeCounter++; }
		void notifyError(const MemoryInfo& info, const std::string& msg) { errorCounter++; }
		int getAllocCount() { return allocCounter; }
		int getFreeCount()  { return freeCounter;  }
		int getErrorCount() { return errorCounter; }
		void reset()
		{
			allocCounter = 0;
			freeCounter  = 0;
			errorCounter = 0;
		}
};


namespace {
void entriesFunc(const MemoryInfo& info) {}
bool isCleanup(const MemoryInfo& info) { return false; }
bool isCleanupTrue(const MemoryInfo& info) { return true; }
}


/***
 * Memory 単体テスト.
 */
class MemoryTest : public TestCase
{
	private:
		MemoryTestHandler handler;
	public:
		MemoryTest()
		{
			MemoryManager::setListener(&handler);
		}
		~MemoryTest()
		{
			MemoryManager::setListener(0);
		}

		void setUp()
		{
			handler.reset();
		}

		void testNew()
		{
			Assertion::assertEquals(0, handler.getAllocCount());
			Assertion::assertEquals(0, handler.getFreeCount());
			Assertion::assertEquals(0, handler.getErrorCount());

			// new にて 2回以上メモリ確保されることがあるため 1<= に変更
			std::string* ptr = new std::string("TEST");
			Assertion::assertTrue(1 <= handler.getAllocCount());
			Assertion::assertEquals(0, handler.getFreeCount());
			Assertion::assertEquals(0, handler.getErrorCount());
	
			delete ptr;
			Assertion::assertTrue(1 <= handler.getAllocCount());
			Assertion::assertTrue(1 <= handler.getFreeCount());
			Assertion::assertEquals(0, handler.getErrorCount());

		}

		void testNewArray()
		{
			Assertion::assertEquals(0, handler.getAllocCount());
			Assertion::assertEquals(0, handler.getFreeCount());
			Assertion::assertEquals(0, handler.getErrorCount());

			int* ptr = new int[3];
			Assertion::assertEquals(1, handler.getAllocCount());
			Assertion::assertEquals(0, handler.getFreeCount());
			Assertion::assertEquals(0, handler.getErrorCount());
	
			delete[] ptr;
			Assertion::assertEquals(1, handler.getAllocCount());
			Assertion::assertEquals(1, handler.getFreeCount());
			Assertion::assertEquals(0, handler.getErrorCount());
		}

		void testNewNoThrow()
		{
			Assertion::assertEquals(0, handler.getAllocCount());
			Assertion::assertEquals(0, handler.getFreeCount());
			Assertion::assertEquals(0, handler.getErrorCount());

			try
			{
				throw new(std::nothrow) std::string("ERROR");
			}
			catch (std::string* ptr)
			{
				Assertion::assertTrue(1 <= handler.getAllocCount());
				Assertion::assertEquals(0, handler.getFreeCount());
				Assertion::assertEquals(0, handler.getErrorCount());
				Assertion::assertEquals("ERROR", *ptr);
				operator delete(ptr, std::nothrow);

				Assertion::assertTrue(1 <= handler.getAllocCount());
				Assertion::assertTrue(1 <= handler.getFreeCount());
				Assertion::assertEquals(0, handler.getErrorCount());
			}
	
		}

		void testNewArrayNoThrow()
		{
			Assertion::assertEquals(0, handler.getAllocCount());
			Assertion::assertEquals(0, handler.getFreeCount());
			Assertion::assertEquals(0, handler.getErrorCount());

			try
			{
				throw new(std::nothrow) int[1];
			}
			catch (int* ptr)
			{
				Assertion::assertEquals(1, handler.getAllocCount());
				Assertion::assertEquals(0, handler.getFreeCount());
				Assertion::assertEquals(0, handler.getErrorCount());
				operator delete[] (ptr, std::nothrow);

				Assertion::assertEquals(1, handler.getAllocCount());
				Assertion::assertEquals(1, handler.getFreeCount());
				Assertion::assertEquals(0, handler.getErrorCount());
			}
		}

		void testCalloc()
		{
			void* ptr = calloc(10, 20);
			free(ptr);
		}

		void testRealloc()
		{
			void* ptr = malloc(10);
			ptr = realloc(ptr, 100);
			free(ptr);

			realloc(0, 100);
			ptr = malloc(10);
			realloc(ptr, 0);
		}

		void testNullFree()
		{
			void*        ptr = 0;
			std::string* str = 0;
			int*         intP= 0;
			free(ptr);
			delete   str;
			delete[] intP;
		}

		void testDefaultMemoryListener()
		{
			MemoryInfo info;
			info.fileName     = __FILE__;
			info.lineNumber   = __LINE__;
			info.functionName = "UNITTEST (for test dummy)";
			info.size         = 0;
			DefaultMemoryListener* listener = new DefaultMemoryListener();
			listener->notifyAlloc(info);
			listener->notifyFree(info);
			listener->notifyError(info, "");
			delete listener;
		}

		void testEntries()
		{
			MemoryManager::entries(entriesFunc);
		}

		void testCleanup()
		{
			MemoryManager::cleanup(isCleanup);
			void* ptr = malloc(10);
			realloc(ptr, 100);
//			MemoryManager::cleanup(isCleanupTrue);
		}

		void testPrintMemoryInfo()
		{
			MemoryInfo memInfo;
			memInfo.fileName     = __FILE__;
			memInfo.lineNumber   = __LINE__;
			memInfo.functionName = "UNITTEST (for test dummy)";
			memInfo.size         = 0;
			MemoryManager::printMemoryInfo(memInfo);
		}

		void suite()
		{
			RUN_TEST(testNew);
			RUN_TEST(testNewArray);
			RUN_TEST(testNewNoThrow);
			RUN_TEST(testNewArrayNoThrow);
			RUN_TEST(testCalloc);
			RUN_TEST(testRealloc);
			RUN_TEST(testNullFree);
			RUN_TEST(testDefaultMemoryListener);
			RUN_TEST(testEntries);
			RUN_TEST(testCleanup);
			RUN_TEST(testPrintMemoryInfo);

		}



};

