#ifndef UTILITIES_H
#define UTILITIES_H


#include <string>
#include <cstdint>
#include <algorithm>


///
/// Various common macros
///
#define YQ_ARRAY_SIZE(x)				(sizeof(x)/sizeof((x)[0]))

#define YQ_EXPAND(x)					x

#define YQ_STRINGIZE_IMPL(val)			#val
#define YQ_STRINGIZE(val)				YQ_STRINGIZE_IMPL(val)

#define YQ_CONCATENATE_IMPL(x, y)		x##y
#define YQ_CONCATENATE(x, y)			YQ_CONCATENATE_IMPL(x, y)

// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args.
#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...)	N
#define YQ_ARG_COUNT(...)				(YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)))

#ifdef __COUNTER__
#  define YQ_ANONYMOUS_VARIABLE(str)	YQ_CONCATENATE(str, __COUNTER__)
#else
#  define YQ_ANONYMOUS_VARIABLE(str)	YQ_CONCATENATE(str, __LINE__)
#endif

#define YQ_UNUSED(x)					std::ignore = (x)

#ifdef __MINGW32__
// Avoid callbacks
#  define YQ_CALLBACK					__cdecl
#  define YQ_FUNCTION_NAME				__PRETTY_FUNCTION__ // __func__
#elif defined(_WIN32)
#  define YQ_CALLBACK					__cdecl
#  define YQ_FUNCTION_NAME				__FUNCTION__
#else
#  define YQ_CALLBACK
#  define YQ_FUNCTION_NAME  			__PRETTY_FUNCTION__ // __func__
#endif

// Helper marcos for doing a variable number of null checks
#define YQ_NULL_CHECK_IMPL_1(arg)       if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter);
#define YQ_NULL_CHECK_IMPL_2(arg,...)   YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__)
#define YQ_NULL_CHECK_IMPL_3(arg,...)   YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__)
#define YQ_NULL_CHECK_IMPL_4(arg,...)   YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__)
#define YQ_NULL_CHECK_IMPL_5(arg,...)   YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__)
#define YQ_NULL_CHECK_IMPL_N3(N, ...)	YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__)
#define YQ_NULL_CHECK_IMPL_N2(N, ...)	YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__)
#define YQ_NULL_CHECK_IMPL_N(...)       YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)

// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off
#define YQ_LOCATION     				__FILE__ "(" YQ_STRINGIZE(__LINE__) "): "

// Macros to log messages for debug builds.
// For release builds these will not output anything and no code or data will be generate (except for error logging)
#define YQ_LOG_WARNING(a_fmt, ...)     ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__))
#define YQ_LOG_DEBUG(a_fmt, ...)       ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug,   YQ_LOCATION, YQ_FUNCTION_NAME, "debug:   " a_fmt, ##__VA_ARGS__))
#define YQ_LOG_INFO(a_fmt, ...)        ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info,    YQ_LOCATION, YQ_FUNCTION_NAME, "info:    " a_fmt, ##__VA_ARGS__))
#define YQ_LOG_ERROR(a_fmt, ...)				                     yqLogMessage(LL_Error,   YQ_LOCATION, YQ_FUNCTION_NAME, "error:   " a_fmt, ##__VA_ARGS__)

// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned.
// In debug and release, on errors, the code always calls the installed or default error handling routine
enum { YQ_VERBOSE_DEBUGGING = 0 };
#define YQ_API_VERBOSE(msg)            ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0))
#define YQ_API_ERROR(result, ...)	   (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result)
#define YQ_API_ENTER()                 YQ_API_VERBOSE("Enter")
#define YQ_API_LEAVE(result, ...)      return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__))

// Macros to do common parameter and precondition checking for API function calls
// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code
// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage.
//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup)   do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0)
#define YQ_API_PRECONDITION(condition,result)	do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0))
#define YQ_API_PRECONDITION_NULL(...)			YQ_NULL_CHECK_IMPL_N(__VA_ARGS__)

// Can redirect to the user heap manager routines if provided
#define YQ_ALLOC(type)                 ((type*)(yqAllocateMemory(sizeof(type))))
#define YQ_ALLOC_ARRAY(type,siz)       ((type*)(yqAllocateMemory(sizeof(type)*(siz))))
#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz))))
#define YQ_FREE(ptr)                   (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0)

// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory)
// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this.
#define YQ_NEW(type)                   (new(YQ_ALLOC(type)) type)
#define YQ_NEW_PARAMS(type, params)    (new(YQ_ALLOC(type)) type params)
#define YQ_DELETE(obj,type)            ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0)



enum yqResult
{
	R_Okay,                  // No errors occured
	R_UnknownResult,         // The result was not set, unknown result
	R_Uninitialized,         // Initialization required before calling
	R_UnsupportedVersion,    // The requested version not supported
	R_InvalidParameter,      // The parameter supplied was invalid
	R_IndexOutOfRange,       // The parameter supplied was out of range
	R_FileNotFound,          // The requested file was not found
	R_DeviceFailure,         // The requested device failed
	R_ReadFailure,           // The read request failed
	R_WriteFailure,          // The write request failed
	R_HeapFailure,           // The requested memory was unable to be allocated
	R_CorruptData,           // The data supplied is corrupt
	R_AuthenticationFailure, // The supplied credentials are incorrect
	R_Timeout,               // The requested operation has timed out
	R_Failure                // A generic failure occured
};


enum yqLogLevel
{
	LL_Error,    // Error messages
	LL_Warning,  // Warning messages
	LL_Info,     // Informational messages
	LL_Debug,    // Highest verbosity, programmer related messages
};


enum yqBuildType
{
#ifndef NDEBUG
	BT_IsRelease = 0,   // This enum should enable us to write code
	BT_IsDebug = 1      // which reduces the use of the preprocessor.
#else
	BT_IsDebug = 0,     // Compile time checking will be able to be
	BT_IsRelease = 1    // performed on both sides of the condition.
#endif
};



// Redirects to the installed logger/error/memory handler or a platform default
extern void yqLogMessage(yqLogLevel a_logLevel, const char *a_location, const char* a_func, const char *a_format, ...);
extern void yqHandleError(yqResult a_result, const char *a_location, const char* a_func, const char *a_format, ...);
extern void* yqAllocateMemory(size_t a_size);
extern void* yqReallocateMemory(void* a_ptr, size_t a_newSize);
extern void yqFreeMemory(void* a_ptr);
extern void yqConsolePrintString(const char* a_string);
extern void yqDebugBreak();
// Something related to file opening and redirector



// A user interface can display a localized error message based on the result enum value.
class ErrorHandler
{
public:
	virtual ~ErrorHandler() {}
	virtual void handleError(yqResult a_result, const char *a_location, const char *a_message);
};


// The logger messages are not intened to be localized, they are for diagnostics.
// See ErrorHandler for localized user interface messages.
class Logger
{
public:
	virtual ~Logger() {}
	virtual void logMessage(yqLogLevel a_logLevel, const char *a_location, const char *a_message);
};


// Customizable heap management (memory allocator and deallocator)
class HeapManager
{
public:
	virtual ~HeapManager() {}
	virtual void *malloc(size_t a_size);
	virtual void *realloc(void *a_ptr, size_t a_newSize);
	virtual void free(void *a_ptr);
};


class ResourceDevice
{
public:
	virtual ~ResourceDevice() {}
	virtual yqResult getResourceName(const char ** name);
	virtual yqResult getSize(uint64_t * a_size);
	virtual yqResult read(void * buffer, uint64_t resourceOffset, uint64_t byteReadCount);
	virtual yqResult readAsyncBegin(void * buffer, uint64_t resourceOffset, uint64_t byteReadCount, uintptr_t * readID);
	virtual yqResult readAsyncWait(uintptr_t readID);
	virtual yqResult write(const void * buffer, uint64_t resourceOffset, uint64_t byteWriteCount);
};


class ResourceDeviceFactory
{
public:
	enum WriterFlags
	{
		WF_PreserveExisting = 1 // Preserve existing contents if opening an existing file, else, create a new file
	};
	virtual ~ResourceDeviceFactory() {}
	virtual yqResult CreateResourceDevice(const char * resourceID, uint32_t writerFlags, ResourceDevice **newDevice);
};


class Module
{
public:
	bool                   m_initialized;
	ErrorHandler*          m_errorHandler;
	Logger*                m_messageLogger;
	HeapManager*           m_heapManager;
	ResourceDeviceFactory* m_deviceFactory;
};


extern Module g_currentModule;


// std::vector replacement that is cut down but familiar, uses the custom allocators, and restricted to POD elements only
template<typename T>
struct PODArrayPOD   // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these).
{
	static_assert(std::is_pod<T>::value, "T must be a POD type.");

	void init()                              { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; }
	void deinit()                            { if (m_data != nullptr) YQ_FREE(m_data); }

	const T& at(size_t a_pos) const          { checkInited(); checkIndex(a_pos); return m_data[a_pos]; }
	T& at(size_t a_pos)                      { checkInited(); checkIndex(a_pos); return m_data[a_pos]; }
	const T& operator[](size_t a_pos) const  { checkInited(); checkIndex(a_pos); return m_data[a_pos]; }
	void pop_back()                          { checkInited(); checkIndex(1); m_size--; }
	const T& back() const					 { checkInited(); return m_data[m_size - 1]; }
	T& back()								 { checkInited(); return m_data[m_size - 1]; }
	void push_back(const T& a_val)			 { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; }
	void push(const T& a_val)				 { checkInited(); push_back(a_val); }
	T pop()									 { checkInited(); pop_back(); return m_data[m_size]; }
	void reserve(size_t a_count)             { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); }
	size_t capacity() const                  { checkInited(); return m_capacity; }
	size_t size() const                      { checkInited(); return m_size; }
	T* data() const                          { checkInited(); return m_data; }
	void clear()                             { checkInited(); m_size = 0; }
//private: // Actually making private would make this no longer a POD type
	yqResult checkInited() const             { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; }
	yqResult checkIndex(size_t a_pos) const  { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; }
	void ensureSpaceFor(size_t a_count)      { reserve(std::max<size_t>(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50%	
	T* m_data;  // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type
	size_t m_capacity;
	size_t m_size;
	uint32_t m_magic;
};


static_assert(std::is_pod<PODArrayPOD<char> >::value, "PODArrayPOD must be a POD type.");


template<typename T>
struct ArrayPOD     // A dynamic array of POD type elements. It isn't POD itself, but is instead reference counted so safer to deal with.
{
	static_assert(std::is_pod<T>::value, "T must be a POD type.");

	ArrayPOD& operator=(const ArrayPOD&) = delete;
	ArrayPOD(const ArrayPOD&) = delete;
	ArrayPOD()                               { m_capacity = m_size = 0; m_data = nullptr; }
	~ArrayPOD()                              { if (m_data != nullptr) YQ_FREE(m_data); }

	const T& at(size_t a_pos) const          { checkIndex(a_pos); return m_data[a_pos]; }
	T& at(size_t a_pos)                      { checkIndex(a_pos); return m_data[a_pos]; }
	const T& operator[](size_t a_pos) const  { checkIndex(a_pos); return m_data[a_pos]; }
	void pop_back()                          { checkIndex(1); m_size--; }
	const T& back() const					 { return m_data[m_size - 1]; }
	T& back()								 { return m_data[m_size - 1]; }
	void push_back(const T& a_val)			 { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; }
	void push(const T& a_val)				 { push_back(a_val); }
	T pop()									 { pop_back(); return m_data[m_size]; }
	void reserve(size_t a_count)             { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); }
	size_t capacity() const                  { return m_capacity; }
	size_t size() const                      { return m_size; }
	T* data() const                          { return m_data; }
	void clear()                             { m_size = 0; }
private:
	yqResult checkIndex(size_t a_pos) const  { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; }
	void ensureSpaceFor(size_t a_count)      { reserve(std::max<size_t>(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50%	
	T* m_data;  // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type
	size_t m_capacity;
	size_t m_size;
};


struct PODString
{
public:
	void init()                              { m_data.init(); }
	void deinit()                            { m_data.deinit(); }
	void operator=(const char* a_str)        { m_data.clear(); operator+=(a_str); }
	void operator+=(const char* a_str)       { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); }
	char* c_str()                            { return m_data.data(); }
//private: // Actually making private would make this no longer a POD type
	PODArrayPOD<char> m_data;
};


static_assert(std::is_pod<PODString>::value, "PODString must be a POD type.");


/*

Example usage:

Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4)
{
	YQ_API_ENTER();

	// Check pre-conditions
	YQ_API_PRECONDITION_NULL(Param1, Param2, Param3);
	YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter);

	if (errorHappened)
		YQ_API_LEAVE(R_Failure);

	YQ_API_LEAVE(R_Okay);
}

*/


#endif // UTILITIES_H
