/*
  Note: Do NOT change this to load items on an as-needed basis. Since this memory is
  accessed from multiple threads, you'd need mutex's all over the place if it was
  ever to be modified/updated/added to. The overhead of the mutexes would be alot more
  in the long run than the delay in loading.

  -Quagmire
*/

#ifdef WIN32
#include <windows.h>
#else
#include "../common/unix.h"
#endif

#include <memory.h>
#include <iostream>
using namespace std;
#include "Items.h"
#include "../common/timer.h"
#include "MMF.h"

MMF ItemsMMF;
const MMFItems_Struct* MMFItemsData = 0;
MMFItems_Struct* MMFItemsData_Writable = 0;

#ifdef WIN32
extern "C" __declspec(dllexport) const Item_Struct* GetItem(uint32 id) {
	return pGetItem(id);
};

extern "C" __declspec(dllexport) bool AddItem(uint32 id, const Item_Struct* item) {
	return pAddItem(id, item);
};

extern "C" __declspec(dllexport) const Item_Struct* IterateItems(uint32* NextIndex) {
	return pIterateItems(NextIndex);
};

extern "C" __declspec(dllexport) bool DLLLoadItems(CALLBACK_DBLoadItems cbDBLoadItems, int32 iItemStructSize, sint32* iItemCount, int32* iMaxItemID) {
	return pDLLLoadItems(cbDBLoadItems, iItemStructSize, iItemCount, iMaxItemID);
};
#else
extern "C" const Item_Struct* GetItem(uint32 id) {
	return pGetItem(id);
};
extern "C" bool AddItem(uint32 id, const Item_Struct* item) {
        return pAddItem(id, item);
};
	
extern "C" const Item_Struct* IterateItems(uint32* NextIndex) {
	return pIterateItems(NextIndex);
};
		
extern "C" bool DLLLoadItems(CALLBACK_DBLoadItems cbDBLoadItems, int32 iItemStructSize, sint32* iItemCount, int32* iMaxItemID) {
	return pDLLLoadItems(cbDBLoadItems, iItemStructSize, iItemCount, iMaxItemID);
};

#endif

bool pAddItem(uint32 id, const Item_Struct* item) {
	if (!MMFItemsData_Writable)
		return false;
	if (id > MMF_EQMAX_ITEMS || MMFItemsData_Writable->NextFreeIndex >= MMFItemsData_Writable->ItemCount)
		return false;
	if (MMFItemsData_Writable->ItemIndex[id] != 0xFFFF)
		return false;
	
	MMFItemsData_Writable->ItemIndex[id] = MMFItemsData_Writable->NextFreeIndex++;
	memcpy(&MMFItemsData_Writable->Items[MMFItemsData_Writable->ItemIndex[id]], item, sizeof(Item_Struct));

	return true;
}

bool pDLLLoadItems(CALLBACK_DBLoadItems cbDBLoadItems, int32 iItemStructSize, sint32* iItemCount, int32* iMaxItemID) {
	if (iItemStructSize != sizeof(Item_Struct)) {
		cout << "Error: EMuShareMem: DLLLoadItems: iItemStructSize != sizeof(Item_Struct)" << endl;
		cout << "Item_Struct has changed, EMuShareMem.dll needs to be recompiled." << endl;
		return false;
	}
	if (*iMaxItemID > MMF_EQMAX_ITEMS) {
		cout << "Error: EMuShareMem: pDLLLoadItems: iMaxItemID > MMF_EQMAX_ITEMS" << endl;
		cout << "You need to increase the define in Items.h." << endl;
		return false;
	}

//MMF ItemsMMF;
//const MMFItems_Struct* MMFItemsData = 0;
//MMFItems_Struct* MMFItemsData_Writable = 0;
	int32 tmpMemSize = sizeof(MMFItems_Struct) + 256 + (sizeof(Item_Struct) * (*iItemCount));
	if (ItemsMMF.Open("EQEMuItems", tmpMemSize)) {
		if (ItemsMMF.CanWrite()) {
			MMFItemsData_Writable = (MMFItems_Struct*) ItemsMMF.GetWriteableHandle();
			if (!MMFItemsData_Writable) {
				cout << "Error: EMuShareMem: DLLLoadItems: !MMFItemsData_Writable" << endl;
				return false;
			}

			memset(MMFItemsData_Writable, 0, tmpMemSize);
			for(int i=0; i<MMF_EQMAX_ITEMS; i++)
				MMFItemsData_Writable->ItemIndex[i] = 0xFFFF;
			MMFItemsData_Writable->MaxItemID = *iMaxItemID;
			MMFItemsData_Writable->ItemCount = *iItemCount;
			// use a callback so the DB functions are done in the main exe
			// this way the DLL doesnt have to open a connection to mysql
			if (!cbDBLoadItems(*iItemCount, *iMaxItemID)) {
				cout << "Error: EMuShareMem: DLLLoadItems: !cbDBLoadItems" << endl;
				return false;
			}

			MMFItemsData_Writable = 0;
			ItemsMMF.SetLoaded();
			MMFItemsData = (const MMFItems_Struct*) ItemsMMF.GetHandle();
			if (!MMFItemsData) {
				cout << "Error: EMuShareMem: DLLLoadItems: !MMFItemsData (CanWrite=true)" << endl;
				return false;
			}
			return true;
		}
		else {
			if (!ItemsMMF.IsLoaded()) {
				Timer::SetCurrentTime();
				int32 starttime = Timer::GetCurrentTime();
				while ((!ItemsMMF.IsLoaded()) && ((Timer::GetCurrentTime() - starttime) < 300000)) {
					Sleep(10);
					Timer::SetCurrentTime();
				}
				if (!ItemsMMF.IsLoaded()) {
					cout << "Error: EMuShareMem: DLLLoadItems: !ItemsMMF.IsLoaded() (timeout)" << endl;
					return false;
				}
			}
			MMFItemsData = (const MMFItems_Struct*) ItemsMMF.GetHandle();
			if (!MMFItemsData) {
				cout << "Error: EMuShareMem: DLLLoadItems: !MMFItemsData (CanWrite=false)" << endl;
				return false;
			}
			*iMaxItemID = MMFItemsData->MaxItemID;
			*iItemCount = MMFItemsData->ItemCount;
			return true;
		}
	}
	else {
		cout << "Error Loading Items: Items.cpp: pDLLLoadItems: Open() == false" << endl;
		return false;
	}
	return false;
};

const Item_Struct* pGetItem(uint32 id) {
	if (MMFItemsData == 0 || (!ItemsMMF.IsLoaded()) || id > MMF_EQMAX_ITEMS || MMFItemsData->ItemIndex[id] == 0xFFFF)
		return 0;
	return &MMFItemsData->Items[MMFItemsData->ItemIndex[id]];
}

const Item_Struct* pIterateItems(uint32* NextIndex) {
	if (MMFItemsData == 0 || (!ItemsMMF.IsLoaded()) || (*NextIndex) > MMF_EQMAX_ITEMS)
		return 0;
	do {
		if (MMFItemsData->ItemIndex[*NextIndex] != 0xFFFF)
			return &MMFItemsData->Items[MMFItemsData->ItemIndex[(*NextIndex)++]];
	} while (++(*NextIndex) < MMF_EQMAX_ITEMS);
	
	return 0;
}
