#include <uf/utils/userdata/userdata.h> 	// userdata
#include <uf/utils/string/base64.h> 		// base64
#include <cstdlib> 							// malloc, free
#include <cstring> 							// memcpy
#include <iostream> 						

uf::MemoryPool uf::userdata::memoryPool;
bool uf::userdata::autoDestruct = true;
bool uf::userdata::autoValidate = true;

uf::stl::unordered_map<UF_USERDATA_CTTI_TYPE, pod::UserdataTraits> uf::userdata::traits;

// Constructs a pod::Userdata struct
pod::Userdata* uf::userdata::create( std::size_t len, const void* data, UF_USERDATA_CTTI_TYPE type ) {
	return uf::userdata::create( uf::userdata::memoryPool, len, data, type );
}
void uf::userdata::destroy( pod::Userdata* userdata ) {
	return uf::userdata::destroy( uf::userdata::memoryPool, userdata );
}
pod::Userdata* uf::userdata::copy( const pod::Userdata* userdata ) {
	return uf::userdata::copy( uf::userdata::memoryPool, userdata );
}

size_t uf::userdata::size( size_t len, size_t padding ) {
	return sizeof(pod::Userdata) - sizeof(uint8_t) + len;
}

//
pod::Userdata* uf::userdata::create( uf::MemoryPool& requestedMemoryPool, size_t len, const void* data, UF_USERDATA_CTTI_TYPE type ) {
	if ( len <= 0 ) return NULL;
	size_t requestedLen = size( len );
#if UF_MEMORYPOOL_INVALID_MALLOC
	uf::MemoryPool& memoryPool = requestedMemoryPool.size() > 0 ? requestedMemoryPool : uf::memoryPool::global;
	pod::Userdata* userdata = (pod::Userdata*) memoryPool.alloc( requestedLen );
#else
	uf::MemoryPool* memoryPool = NULL;
	if ( requestedMemoryPool.size() > 0 ) memoryPool = &requestedMemoryPool;
	else if ( uf::memoryPool::global.size() > 0 ) memoryPool = &uf::memoryPool::global;
	pod::Userdata* userdata = NULL;
	if ( memoryPool )
		userdata = (pod::Userdata*) memoryPool->alloc( requestedLen );
	else {
		userdata = (pod::Userdata*) uf::allocator::malloc_m( requestedLen ); 	// allocate data for the userdata struct, and then some
	}
#endif
	userdata->len = requestedLen;
	userdata->type = type;
	auto& trait = uf::userdata::getTrait( userdata->type );
	if ( data && userdata->type != UF_USERDATA_CTTI(void) ) trait.constructor( userdata->data, data );
	else if ( data ) memcpy( userdata->data, data, requestedLen );
	else memset( userdata->data, 0, requestedLen );
	//UF_MSG_DEBUG("Calling create: {}: {}", trait.name, (void*) userdata->data);
	return userdata;
}
void uf::userdata::destroy( uf::MemoryPool& requestedMemoryPool, pod::Userdata* userdata ) {
	auto& trait = uf::userdata::getTrait( userdata->type );
	if ( userdata->type != UF_USERDATA_CTTI(void) ) trait.destructor( userdata->data );
	//UF_MSG_DEBUG("Calling destroy: {}: {}", trait.name, (void*) userdata->data);
#if UF_MEMORYPOOL_INVALID_FREE
	uf::MemoryPool& memoryPool = requestedMemoryPool.size() > 0 ? requestedMemoryPool : uf::memoryPool::global;
	memoryPool.free( userdata, size(userdata->len) );
#else
	uf::MemoryPool* memoryPool = NULL;
	if ( requestedMemoryPool.size() > 0 ) memoryPool = &requestedMemoryPool;
	else if ( uf::memoryPool::global.size() > 0 ) memoryPool = &uf::memoryPool::global;
	
	if ( memoryPool ) memoryPool->free( userdata, size(userdata->len) );
	else uf::allocator::free_m(userdata);
#endif
}
pod::Userdata* uf::userdata::copy( uf::MemoryPool& requestedMemoryPool, const pod::Userdata* userdata ) {
	if ( !userdata || userdata->len <= 0 ) return NULL;
	auto* copied = uf::userdata::create( userdata->len, const_cast<void*>((const void*) &userdata->data[0]), userdata->type );
	return copied;
}

const pod::UserdataTraits& uf::userdata::getTrait( UF_USERDATA_CTTI_TYPE type ) {
	return uf::userdata::traits[type];
}

uf::stl::string uf::userdata::toBase64( pod::Userdata* userdata ) {
	return uf::base64::encode( userdata->data, userdata->len );
}
pod::Userdata* uf::userdata::fromBase64( const uf::stl::string& base64 ) {
	uf::stl::vector<uint8_t> decoded = uf::base64::decode( base64 );
	return uf::userdata::create( decoded.size(), decoded.data() );
}
// 	C-tor

// 	Initializes POD
uf::Userdata::Userdata(std::size_t len, const void* data, UF_USERDATA_CTTI_TYPE type) : m_pod(NULL), autoDestruct(uf::userdata::autoDestruct) {
	if ( len && data ) this->create(len, data, type);
}

// Initializes from POD
uf::Userdata::Userdata( pod::Userdata* pointer ) : m_pod(pointer), autoDestruct(uf::userdata::autoDestruct) {

}

// Move c-tor
uf::Userdata::Userdata( Userdata&& move ) noexcept :
	m_pod(move.m_pod),
	autoDestruct(move.autoDestruct) {
	move.m_pod = {};
}
// Copy c-tor
uf::Userdata::Userdata( const Userdata& userdata ) {
	autoDestruct = userdata.autoDestruct;
	this->copy( userdata );
}

// 	Creates the POD
pod::Userdata* uf::Userdata::create( std::size_t len, const void* data, UF_USERDATA_CTTI_TYPE type ) {
	if ( len <= 0 ) return NULL;
	this->destroy();
	return this->m_pod = uf::userdata::create( len, data, type );
}
void uf::Userdata::move( Userdata& moved ) {
//	if ( this->m_pod && move.m_pod ) uf::userdata::move( *this->m_pod, *move.m_pod );
	this->destroy();
	this->m_pod = moved.m_pod;
	moved.m_pod = NULL;
}
void uf::Userdata::move( Userdata&& moved ) {
//	if ( this->m_pod && move.m_pod ) uf::userdata::move( *this->m_pod, *move.m_pod );
	this->destroy();
	this->m_pod = moved.m_pod;
	moved.m_pod = NULL;
}
void uf::Userdata::copy( const Userdata& userdata ) {
	if ( !userdata.m_pod || !userdata.m_pod->len ) return;
	this->destroy();
	this->create( userdata.m_pod->len, userdata.m_pod->data, userdata.m_pod->type );
}
// 	D-tor
uf::Userdata::~Userdata() noexcept {
	if ( autoDestruct ) this->destroy();
}
// 	Destroys the POD
void uf::Userdata::destroy() {
	if ( this->m_pod ) uf::userdata::destroy( this->m_pod );
}
// 	POD access
// 	Returns a reference of POD
uf::Userdata::pod_t& uf::Userdata::data() {
	static Userdata::pod_t null = { 0 };
	return this->m_pod ? *this->m_pod : null;
}
// 	Returns a const-reference of POD
const uf::Userdata::pod_t& uf::Userdata::data() const {
	static Userdata::pod_t null = { 0 };
	return this->m_pod ? *this->m_pod : null;
}
size_t uf::Userdata::size() const { return this->m_pod->len; }
UF_USERDATA_CTTI_TYPE uf::Userdata::type() const { return this->m_pod->type; }
// 	Validity checks
bool uf::Userdata::initialized() const {
	return this->m_pod != NULL;
}
// Overloaded ops
uf::Userdata::operator void*() {
	// return this->m_pod ? (void*) this->m_pod->data : NULL;
	if ( !this->m_pod ) return NULL;
	return this->m_pod->data;
}
uf::Userdata::operator void*() const {
	// return this->m_pod ? (void*) this->m_pod->data : NULL;
	if ( !this->m_pod ) return NULL;
	return this->m_pod->data;
}
		
uf::Userdata::operator bool() const {
	return this->initialized();
}

uf::Userdata& uf::Userdata::operator=( pod::Userdata* pointer ) {
	this->m_pod = pointer;
	return *this;
}
uf::Userdata& uf::Userdata::operator=( Userdata&& move ) {
	if ( this->m_pod && move.m_pod ) {
		this->m_pod = move.m_pod;
		move.m_pod = NULL;
	}
	return *this;
}
uf::Userdata& uf::Userdata::operator=( const Userdata& copy ) {
	if ( this->m_pod && copy.m_pod ) this->copy( copy );
	return *this;
}