#include "Item.h"
#include "safemap.h"
#include "DEFINES.h"
#include "Crawler.h"
#include "Menu.h"

INCLUDE_game
INCLUDE_DATA
INCLUDE_GFX

safemap<std::string,ItemInfo>ITEM_DATA;
safemap<std::string,ItemScript>ITEM_SCRIPTS;
safemap<std::string,std::set<std::string>>ITEM_CATEGORIES;
Item Item::BLANK;
std::map<IT,Item>Inventory::_inventory;
std::map<ITCategory,std::vector<IT>>Inventory::sortedInv;

ItemInfo::ItemInfo()
	:customProps({nullptr,nullptr}),img(nullptr){}

void ItemInfo::InitializeItems(){

	InitializeScripts();

	for(auto&key:DATA["ItemCategory"].GetKeys()){
		ITEM_CATEGORIES[key.first];
		Inventory::sortedInv[key.first];
		Menu::inventoryListeners[key.first];
	}

	for(auto&key:DATA["ItemDatabase"].GetKeys()){
		std::string imgPath="assets/"+"item_img_directory"_S+key.first+".png";
		Renderable&img=GFX["item_img_directory"_S+key.first+".png"];
		img.Load(imgPath);

		std::string scriptName="",description="",category="";
		for(auto&itemKey:DATA["ItemDatabase"][key.first].GetKeys()){
			std::string keyName=itemKey.first;
			if(keyName=="Description"){
				description=DATA["ItemDatabase"][key.first][keyName].GetString();
			}else
			if(keyName=="ItemCategory"){
				category=DATA["ItemDatabase"][key.first][keyName].GetString();
			}else
			if(keyName=="ItemScript"){
				scriptName=DATA["ItemDatabase"][key.first][keyName].GetString();
			}else{ //THis is a custom override modifier for a script. NO-OP
			}
		}

		if(scriptName!=""){
			if(!ITEM_SCRIPTS.count(scriptName)){
				std::cout<<"Could not load script "<<scriptName<<" for Item "<<key.first<<"!"<<std::endl;
				throw;
			}
		}

		ItemInfo&it=ITEM_DATA[key.first];
		it.name=key.first;
		it.description=description;
		it.category=category;
		if(!ITEM_CATEGORIES.count(it.category)){
			std::cout<<"WARNING! Tried to add item "<<it.name<<" to category "<<it.category<<" which does not exist!"<<std::endl;
			throw;
		}
		ITEM_CATEGORIES.at(it.category).insert(it.name);
		it.img=img.Decal();
		ItemProps&props=it.customProps;
		if(scriptName!=""){
			props.scriptProps=&DATA["ItemScript"][scriptName];
			props.customProps=&DATA["ItemDatabase"][key.first];
		}
		it.useFunc=scriptName;
	}
	
	ITEM_DATA.SetInitialized();
	ITEM_CATEGORIES.SetInitialized();
	Menu::inventoryListeners.SetInitialized();

	std::cout<<ITEM_DATA.size()<<" items have been loaded."<<std::endl;
	std::cout<<ITEM_CATEGORIES.size()<<" item categories have been loaded."<<std::endl;
}

ItemProps::ItemProps(utils::datafile*scriptProps,utils::datafile*customProps)
	:scriptProps(scriptProps),customProps(customProps){}

int ItemProps::GetIntProp(std::string prop){
	if(customProps->HasProperty(prop)) return (*customProps)[prop].GetInt();
	else return (*scriptProps)[prop].GetInt();
};
float ItemProps::GetFloatProp(std::string prop){
	if(customProps->HasProperty(prop)) return (*customProps)[prop].GetReal();
	else return (*scriptProps)[prop].GetReal();
};
std::string ItemProps::GetStringProp(std::string prop){
	if(customProps->HasProperty(prop)) return (*customProps)[prop].GetString();
	else return (*scriptProps)[prop].GetString();
};

void ItemInfo::InitializeScripts(){
	ITEM_SCRIPTS["Restore"]=[](Crawler*game,ItemProps props){
		game->GetPlayer()->Heal(props.GetIntProp("HP Restore"));
		game->GetPlayer()->Heal(game->GetPlayer()->GetMaxHealth()*props.GetIntProp("HP % Restore")/100.f);
		game->GetPlayer()->RestoreMana(props.GetIntProp("MP Restore"));
		game->GetPlayer()->RestoreMana(game->GetPlayer()->GetMaxMana()*props.GetIntProp("MP % Restore")/100.f);
		return true;
	};

	ITEM_SCRIPTS.SetInitialized();
	std::cout<<ITEM_SCRIPTS.size()<<" item scripts have been loaded."<<std::endl;
}

Item::Item()
	:amt(0),it(nullptr){}

Item::Item(uint32_t amt,IT item)
	:amt(amt),it(&ITEM_DATA.at(item)){}

void Inventory::AddItem(IT it,uint32_t amt){
	//There are two places to manipulate items in (Both the sorted inventory and the actual inventory)
	if(!_inventory.count(it)){
		_inventory[it]=Item{amt,it};
		InsertIntoSortedInv(it);
	}else{
		_inventory.at(it).amt+=amt;
	}
}

Item Inventory::GetItem(IT it){
	if(!_inventory.count(it))return Item::BLANK;
	return _inventory.at(it);
}

uint32_t Inventory::GetItemCount(IT it){
	if(!_inventory.count(it)){
		return 0;
	}else{
		return _inventory.at(it).Amt();
	}
}

void Inventory::UseItem(IT it,uint32_t amt){
	if(!_inventory.count(it))return;
	//There are two places to manipulate items in (Both the sorted inventory and the actual inventory)
	for(int i=0;i<amt;i++){
		if(ExecuteAction(it)){
			RemoveItem(it);
		}
	}
}

void Inventory::RemoveItem(IT it,uint32_t amt){
	//There are two places to manipulate items in (Both the sorted inventory and the actual inventory)
	if(!_inventory.count(it))return;
	if(amt>=_inventory.at(it).Amt()){
		int count=0;
		std::vector<IT>&sortedList=sortedInv.at(_inventory.at(it).Category());
		for(std::string&itemName:sortedList){
			if(itemName==it)break;
			count++;
		}
		sortedList.erase(sortedList.begin()+count);
		_inventory.erase(it);
		ITCategory cat=ITEM_DATA[it].category;
		//Callback for GUI inventories.
		Menu::InventorySlotsUpdated(cat);
	}else{
		_inventory.at(it).amt-=amt;
	}
}

std::vector<IT>&Inventory::get(ITCategory itemCategory){
	return sortedInv.at(itemCategory);
}

void Inventory::InsertIntoSortedInv(IT item){
	sortedInv.at(ITEM_DATA[item].category).push_back(item);
	//This should be a callback to menus that we need to update the interface with another item slot since a new one has appeared.
	Menu::InventorySlotsUpdated(ITEM_DATA[item].category);
}

bool Inventory::ExecuteAction(IT item){
	if(ITEM_SCRIPTS.count(ITEM_DATA.at(item).useFunc)){
		return ITEM_SCRIPTS.at(ITEM_DATA.at(item).useFunc)(game,ITEM_DATA[item].customProps);
	}else{
		return false;
	}
}

bool Inventory::SwapItems(ITCategory itemCategory,uint32_t slot1,uint32_t slot2){
	std::vector<IT>&inv=sortedInv.at(itemCategory);
	int largestSlot=std::max(slot1,slot2);
	if(inv.size()<=largestSlot){
		//The inventory is too small, so expand out blank slots to accomodate.
		inv.resize(largestSlot+size_t(1));
	}
	IT&item1=inv.at(slot1);
	IT&item2=inv.at(slot2);
	std::swap(item1,item2);
	return true;
}

uint32_t Item::Amt(){
	return amt;
};
std::string Item::Name(){
	return it->Name();
};
std::string Item::Description(){
	return it->Description();
};
ITCategory Item::Category(){
	return it->Category();
};
Decal*Item::Decal(){
	return it->Decal();
};
ItemScript&Item::OnUseAction(){
	return it->OnUseAction();
};

std::string ItemInfo::Name(){
	return name;
};
std::string ItemInfo::Description(){
	return description;
};
ITCategory ItemInfo::Category(){
	return category;	
};
Decal*ItemInfo::Decal(){
	return img;
};
ItemScript&ItemInfo::OnUseAction(){
	return ITEM_SCRIPTS.at(useFunc);
};

bool Item::IsBlank(){
	return amt==0||it==nullptr;
}