#ifndef _DEBUG_ADAPTER_H_
#define _DEBUG_ADAPTER_H_

// Intended to be included only in "debug_allocator.h"

#define DBGALLOC_NO_STACKTRACE

#ifndef DBGALLOC_NO_STACKTRACE
#include <execinfo.h>
#endif
#include <ctime>
#include <iostream>
#include <fstream>

#ifdef __GNUC__
#include <tr1/unordered_map>
#define TR1_NS std::tr1
#else
#include <unordered_map>
#define TR1_NS std
#endif

#define DBGALLOC_LOG_FILENAME "dbgalloc.log"
#define DBGALLOC_REPORT_FILENAME "dbgalloc_report.log"

// A struct to hold the allocation information of a single pointer value.
struct AllocTag {
	AllocTag(const char* file, size_t line) : in_use(true), age(1) {
		this->file = file;
		this->line = line;
	}
	void Reuse(const char* file, size_t line) {
		in_use = true;
		this->file = file;
		this->line = line;
		age = IncreaseAge(age);
	}
	void Unuse(const char* file, size_t line) {
		in_use = false;
		this->file = file;
		this->line = line;
		age = IncreaseAge(age);
	}
	static size_t IncreaseAge(size_t value) {
		size_t result = value;
		++result;
		// Avoid zero on roll-over
		if (result == 0) {
			result = 1;
		}
		return result;
	}
	bool in_use;
	const char* file;
	size_t line;
	size_t age; // incremented on both the cases; alloc and free
};

inline std::ostream& operator<<(std::ostream& os, const AllocTag tag) {
	return os << "<" << tag.file << " line:" << tag.line <<
		" age:" << tag.age << ">";
}

// Scoped(guarded) wrapper for std::ofstream
struct ScopedOutputFile {
	ScopedOutputFile(const char* filename,
		std::ios_base::openmode mode = std::ios_base::app) {
		stream.open(filename, mode);
	}
	~ScopedOutputFile() {
		if (stream.is_open()) {
			stream.close();
		}
	}
	std::ostream& Datetime() {
		char buf[24];
		time_t t = ::time(NULL);
		::strftime(buf, 24, "%Y-%m-%d %H:%M:%S", ::localtime(&t));
		return stream << buf;
	}
	std::ofstream stream;
};

template<typename T> class DebugPtr;

// Debug allocator adapter that extends the implementation detail specified
// by the template parameter class Detail.
// Note that this is definitely NOT thread-safe.
template<class Detail>
class DebugAllocatorAdapter {
public:
	~DebugAllocatorAdapter() {}

	// Static initializer.
	static void StaticSetUp() {
		GetInstance().detail_.SetUp(); // singleton instantiation
	}
	// Static finalizer.
	static void StaticTearDown() {
		DebugAllocatorAdapter& instance = GetInstance();
		instance.DumpLeakReport();
		instance.detail_.TearDown();
	}

	// Allocates a memory block.
	static void* Alloc(size_t size) {
		return GetInstance().detail_.Alloc(size);
	}
	// Deallocates a memory block.
	static void Free(void* p) {
		GetInstance().detail_.Free(p);
	}

	// Marks the specified heap pointer as acquired in the given context.
	static size_t MarkAcquired(void* p, const char* file, size_t line, const char* context) {
		if (p == NULL) {
			return 0;
		}
		AllocMapType& alloc_map = GetInstance().alloc_map_;
		size_t age = 0;
		AllocMapType::iterator it = alloc_map.find(p);
		if (it == alloc_map.end()) {
			AllocTag tag(file, line);
			alloc_map.insert(AllocMapType::value_type(p, tag));
			age = tag.age;
		} else {
			AllocTag& tag = it->second;
			if (!tag.in_use) {
				tag.Reuse(file, line);
				age = tag.age;
			} else {
				// Is the Detail insane or...?
				ScopedOutputFile of(DBGALLOC_LOG_FILENAME);
				if (of.stream.is_open()) {
					of.Datetime() << " [" << context << "] " <<
						p << " " << tag << " already in use. " <<
						"(" << file << " line:" << line << ")" <<
						std::endl;
#ifndef DBGALLOC_NO_STACKTRACE
					PrintStack( of.stream );
#endif
				}
			}
		}
		return age;
	}

	// Marks the specified heap pointer as released in the given context.
	template<typename T>
	static T* MarkReleased(T* p, const char* file, size_t line, const char* context) {
		return (GetInstance().VerifyReference(p, file, line, context, true) ? p : NULL);
	}
    template<typename T>
    static T* MarkReleased(DebugPtr<T>& ptr, const char* file, size_t line, const char* context) {
		return (GetInstance().VerifyReference(ptr.Get(), file, line, context, true, true, ptr.GetAge()) ? ptr.Get() : NULL);
    }

	// Retrieves the age of the specified heap pointer.
	static size_t RetrieveAge(void* p) {
		if (p == NULL) {
			return 0;
		}
		AllocMapType& alloc_map = GetInstance().alloc_map_;
		AllocMapType::iterator it = alloc_map.find(p);
		if (it != alloc_map.end()) {
			AllocTag& tag = it->second;
			if (tag.in_use) {
				return tag.age;
			}
		}
		return 0;
	}

	// Verifies the specified heap pointer with its age.
	template<typename T>
	static T* Verify(T* p, size_t age, const char* file = NULL, size_t line = 0) {
		return (GetInstance().VerifyReference(p, file, line, "ref", false, true, age) ? p : NULL);
	}

	template<typename T>
	static T* VerifyDeletion(T* p, const char* file, size_t line, bool verify_age = false, size_t age = 0) {
		return (GetInstance().VerifyReference(p, file, line, "pre_delete", false, verify_age, age) ? p : NULL);
	}

	static void LogBoundaryCorruption(void* p, size_t age) {
		AllocMapType& alloc_map = GetInstance().alloc_map_;
		AllocMapType::iterator it = alloc_map.find(p);
		if (it != alloc_map.end()) {
			AllocTag& tag = it->second;
			ScopedOutputFile of(DBGALLOC_LOG_FILENAME);
			if (of.stream.is_open()) {
				of.Datetime() << " [boundary] " <<
					p << " " << tag << " age header corrupted:" <<
					" current age value " << age << std::endl;
#ifndef DBGALLOC_NO_STACKTRACE
				PrintStack( of.stream );
#endif
			}
		}
	}

private:
	// Private constructor to prohibit explicit instantiation.
	DebugAllocatorAdapter() {}

	// Returns the reference to the singleton instance.
	static DebugAllocatorAdapter& GetInstance() {
		static DebugAllocatorAdapter instance;
		return instance;
	}

	bool VerifyReference(void* p, const char* file, size_t line,
			const char* context, bool mark_released,
			bool verify_age = false, size_t age = 0) {
		if (p == NULL) {
			return mark_released;
		}
		AllocMapType::iterator it = alloc_map_.find(p);
		if (it != alloc_map_.end()) {
			AllocTag& tag = it->second;
			if (tag.in_use) {
				if (verify_age && tag.age != age) {
					ScopedOutputFile of(DBGALLOC_LOG_FILENAME);
					if (of.stream.is_open()) {
						of.Datetime() << " [" << context << "] " <<
							p << " " << tag << " has different age with " <<
							age << " (" << file << " line:" << line << ")" <<
							std::endl;
#ifndef DBGALLOC_NO_STACKTRACE
						PrintStack( of.stream );
#endif
					}
				} else {
					if (mark_released) {
						tag.Unuse(file, line);
					}
					return true;
				}
			} else {
				ScopedOutputFile of(DBGALLOC_LOG_FILENAME);
				if (of.stream.is_open()) {
					of.Datetime() << " [" << context << "] " <<
						p << " " << tag << " already freed. " <<
						"(" << file << " line:" << line << ")" <<
						std::endl;
#ifndef DBGALLOC_NO_STACKTRACE
					PrintStack( of.stream );
#endif
				}
			}
		} else {
			ScopedOutputFile of(DBGALLOC_LOG_FILENAME);
			if (of.stream.is_open()) {
				of.Datetime() << " [" << context << "] " <<
					p << " is not a valid entry. " <<
					"(" << file << " line:" << line << ")" <<
					std::endl;
#ifndef DBGALLOC_NO_STACKTRACE
				PrintStack( of.stream );
#endif
			}
		}
		return false;
	}

	// Prints out memory leak report.
	void DumpLeakReport() {
		AllocMapType::iterator it = alloc_map_.begin(), end = alloc_map_.end();
		for ( ; it != end; ++it) {
			if (it->second.in_use) {
				break;
			}
		}
		if (it == end) {
			return;
		}
		ScopedOutputFile of(DBGALLOC_REPORT_FILENAME);
		if (!of.stream.is_open()) {
			return;
		}
		of.Datetime() << std::endl;
		for ( ; it != end; ++it) {
			AllocTag& tag = it->second;
			if (tag.in_use) {
				of.stream << "[leak] " << it->first << " " << tag << std::endl;
			}
		}
	}

#ifndef DBGALLOC_NO_STACKTRACE
	void PrintStack( std::ostream& out  ) 
	{
		void* 		array[200];
		std::size_t size; 
		char** 		symbols;

		size = backtrace( array, 200 );
		symbols = backtrace_symbols( array, size );

		out << std::endl;

		for ( std::size_t i=0; i<size; ++i )
		{
			out << "Stack> " << symbols[i] << std::endl;
		}

		free( symbols );
	}
#endif

	typedef std::unordered_map<void*, AllocTag> AllocMapType;

	Detail detail_;
	AllocMapType alloc_map_;
};

#endif // _DEBUG_ADAPTER_H_
