#include "main.h"
#include "./deviceObjectStore.h"
#include <algorithm>

template <typename T>
struct GenericComparator
{
	typedef const char* (T::*GETTER)();
	GETTER m_getterFunc;
	const char *m_data;
	GenericComparator(GETTER getterFunc, const char *data)
	{
		m_getterFunc = getterFunc;
		m_data = data;
	}
	bool operator()(const T& obj)
	{
		return strcmp(((obj).*m_getterFunc)(), m_data) == 0;
	}
};

static ifc_deviceobject * 
DeviceObjectStore_FindLocation(const char *name, std::vector<ifc_deviceobject*> &list)
{
	if (FALSE != IS_STRING_EMPTY(name))
		return NULL;

	//size_t name_length;
	//name_length = lstrlenA(name) * sizeof(char);
	//
	//return (ifc_deviceobject*)bsearch_s(name, buffer, length, 
	//									sizeof(ifc_deviceobject*),
	//									DeviceObjectStore_FindComparer, 
	//									(void*)name_length);

	//auto it = std::find_if(list.begin(), list.begin(), GenericComparator<ifc_deviceobject>(&ifc_deviceobject::GetName, name));

	const auto it = std::find_if(list.begin(), list.end(),
		[&](ifc_deviceobject* upT) -> bool
		{
			return strcmp(upT->GetName(), name) == 0;
		}
	);
	if (it != list.end())
	{
		return *it;
	}
	return nullptr;
}

int DeviceObjectComparer(const char* arg1, const char* arg2)
{
	return stricmp(arg1, arg2);
}


// Created for std::sort
static bool DeviceObjectStore_SortComparer_V2(const void* element1, const void* element2)
{
	//return DeviceObjectStore_SortComparer(element1, element2) < 0;
	const char* name1 = (((ifc_deviceobject*)element1))->GetName();
	const char* name2 = (((ifc_deviceobject*)element2))->GetName();
	return DeviceObjectComparer(name1, name2) < 0;
}

static ifc_deviceobject *
DeviceObjectStore_FindUnsortedObject(const char *name, ifc_deviceobject **objects, size_t count)
{
	size_t index;
	size_t length;

	if (0 == count)
		return NULL;
	
	length = lstrlenA(name) * sizeof(char);
	
	for(index = 0; index < count; index++)
	{
		//if (0 == DeviceObjectStore_NameCompare(name, length, objects[index]->GetName()))
		if(0 == DeviceObjectComparer(name, objects[index]->GetName()))
			return objects[index];
	}

	return NULL;
}


DeviceObjectStore::DeviceObjectStore(DeviceObjectCallback addCallback, 
									DeviceObjectCallback _removeCallback, void *callbackData)
{
	this->addCallback = addCallback;
	this->removeCallback = _removeCallback;
	this->callbackData = callbackData;

	InitializeCriticalSection(&lock);
}

DeviceObjectStore::~DeviceObjectStore()
{
	RemoveAll();
	DeleteCriticalSection(&lock);
}

void DeviceObjectStore::Lock()
{
	EnterCriticalSection(&lock);
}

void DeviceObjectStore::Unlock()
{
	LeaveCriticalSection(&lock);
}

CRITICAL_SECTION *DeviceObjectStore::GetLock()
{
	return &lock;
}

HRESULT DeviceObjectStore::Add(ifc_deviceobject *object)
{
	const char *name;
	
	if (NULL == object)
		return E_POINTER;

	name = object->GetName();

	if (NULL == name || '\0' == *name)
		return E_INVALIDARG;

	return (1 == AddRange(&object, 1)) ? S_OK : S_FALSE;
}

size_t DeviceObjectStore::AddRange(ifc_deviceobject **objects, size_t count)
{
	const char *name;
	size_t index, registered, added;
	ifc_deviceobject *object;

	
	if (NULL == objects || 0 == count)
		return 0;
		
	Lock();

	added = 0;
	registered = list.size();
	
	for(index = 0; index < count; index++)
	{
		object = objects[index];
		if (NULL != object)
		{
			name = object->GetName();
			
			if (NULL != name && 
				'\0' != *name && 
				NULL == DeviceObjectStore_FindLocation(name, list))
				//&& NULL == DeviceObjectStore_FindUnsortedObject(name, buffer + registered, added))
			{
				list.push_back(object);
				object->AddRef();

				if (NULL != addCallback)
					this->addCallback(this, object, callbackData);

				added++;
			}
		}
	}
	
	if (0 != added)
	{
		//qsort(list.first(), list.size(), 
		//		sizeof(ifc_deviceobject**), 
		//		DeviceObjectStore_SortComparer);

		std::sort(list.begin(), list.end(), DeviceObjectStore_SortComparer_V2);
	}

	Unlock();

	return added;
}

size_t DeviceObjectStore::AddIndirect(const char **names, size_t count, DeviceObjectCreator callback, void *user)
{
	size_t index, registered, added;
	ifc_deviceobject *object;

	if (NULL == names || 0 == count || NULL == callback)
		return 0;

	Lock();

	added = 0;
	registered = list.size();
	

	for(index = 0; index < count; index++)
	{
		const char *name = names[index];

		if (NULL != name && 
			'\0' != *name && 
			NULL == DeviceObjectStore_FindLocation(name, list) )
			//&& NULL == DeviceObjectStore_FindUnsortedObject(name, buffer + registered, added))
		{
			object = callback(name, user);
			if (NULL != object)
			{
				list.push_back(object);
				
				if (NULL != addCallback)
					this->addCallback(this, object, callbackData);

				added++;
			}
		}
	}
	
	if (0 != added)
	{
		//qsort(list.first(), list.size(), 
		//		sizeof(ifc_deviceobject**), 
		//		DeviceObjectStore_SortComparer);

		std::sort(list.begin(), list.end(), DeviceObjectStore_SortComparer_V2);
	}

	Unlock();

	return added;
}

HRESULT DeviceObjectStore::Remove(const char *name)
{
	HRESULT hr = hr = S_FALSE;
	
	if (NULL == name || '\0' == *name)
		return E_INVALIDARG;

	Lock();

	//object_ptr = DeviceObjectStore_FindLocation(name, list);
	//if (NULL != object_ptr)
	//{
	//	hr = S_OK;

	//	ifc_deviceobject *object = *object_ptr;

	//	size_t index = (size_t)(object_ptr - buffer);
	//	list.erase(list.begin() + index);

	//	if (NULL != removeCallback)
	//		removeCallback(this, object, callbackData);

	//	object->Release();
	//}
	//else
	//{
	//	hr = S_FALSE;
	//}
	
	const auto it = std::find_if(list.begin(), list.end(),
		[&](ifc_deviceobject* upT) -> bool
		{
			return strcmp(upT->GetName(), name) == 0;
		}
	);

	if (it != list.end())
	{
		ifc_deviceobject* object = *it;
		list.erase(it);
		if (NULL != removeCallback)
		{
			removeCallback(this, object, callbackData);
		}
		object->Release();
		hr = S_OK;
	}
	Unlock();

	return hr;
}

void DeviceObjectStore::RemoveAll()
{
	Lock();

	size_t index = list.size();
	while(index--)
	{
		ifc_deviceobject *object = list[index];
		if (NULL != removeCallback)
			removeCallback(this, object, callbackData);

		object->Release();
	}

	list.clear();
	Unlock();
}

HRESULT DeviceObjectStore::Find(const char *name, ifc_deviceobject **object)
{
	HRESULT hr;
	ifc_deviceobject *object_ptr;

	if (NULL == name || '\0' == *name)
		return E_INVALIDARG;

	if (NULL == object)
		return E_POINTER;

	Lock();

	object_ptr = DeviceObjectStore_FindLocation(name, list);
	
	if (NULL != object_ptr)
	{
		if (NULL != object)
		{
			*object = object_ptr;
			(*object)->AddRef();
		}
		hr = S_OK;
	}
	else
	{
		if (NULL != object)
			*object = NULL;
		hr = S_FALSE;
	}
	
	Unlock();

	return hr;
}

HRESULT DeviceObjectStore::Enumerate(DeviceObjectEnum **enumerator)
{
	HRESULT hr;

	if (NULL == enumerator)
		return E_POINTER;

	Lock();

	hr = DeviceObjectEnum::CreateInstance(list.size() ? &list.at(0) : nullptr, list.size(), enumerator);
		
	Unlock();

	return hr;
}