/*
 * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
 */

#include "QuestValues.h"
#include "MapMgr.h"
#include "Playerbots.h"
#include "SharedValueContext.h"

//What kind of a relation does this entry have with this quest.
entryQuestRelationMap EntryQuestRelationMapValue::Calculate()
{
	entryQuestRelationMap rMap;

	for (auto relation : *sObjectMgr->GetCreatureQuestRelationMap())
		rMap[relation.first][relation.second] |= (int)QuestRelationFlag::questGiver;

	for (auto relation : *sObjectMgr->GetCreatureQuestInvolvedRelationMap())
		rMap[relation.first][relation.second] |= (int)QuestRelationFlag::questTaker;

	for (auto relation : *sObjectMgr->GetGOQuestRelationMap())
		rMap[-(int32)relation.first][relation.second] |= (int)QuestRelationFlag::questGiver;

	for (auto relation : *sObjectMgr->GetGOQuestInvolvedRelationMap())
		rMap[-(int32)relation.first][relation.second] |= (int)QuestRelationFlag::questGiver;

	//Quest objectives
	ObjectMgr::QuestMap const& questMap = sObjectMgr->GetQuestTemplates();

	for (auto& questItr : questMap)
	{
		uint32 questId = questItr.first;
		Quest* quest = questItr.second;

		for (uint32 objective = 0; objective < QUEST_OBJECTIVES_COUNT; objective++)
		{
			uint32 relationFlag = 1 << objective;

			//Kill objective
			if (quest->RequiredNpcOrGo[objective])
				rMap[quest->RequiredNpcOrGo[objective]][questId] |= relationFlag;

			//Loot objective
			if (quest->RequiredItemId[objective])
			{
				for (auto& entry : GAI_VALUE2(std::vector<int32>, "item drop list", quest->RequiredItemId[objective]))
					rMap[entry][questId] |= relationFlag;
			}
		}
	}

	return rMap;
}

//Get all the objective entries for a specific quest.
void FindQuestObjectData::GetObjectiveEntries()
{
    relationMap = GAI_VALUE(entryQuestRelationMap, "entry quest relation");
}

//Data worker. Checks for a specific creature what quest they are needed for and puts them in the proper place in the quest map.
void FindQuestObjectData::operator()(CreatureData const& creData)
{
    uint32 entry = creData.id1;

	for (auto& relation : relationMap[entry])
	{
		uint32 questId = relation.first;
		uint32 flag = relation.second;
        data[questId][flag][entry].push_back(GuidPosition(creData));
	}
}

//GameObject data worker. Checks for a specific gameObject what quest they are needed for and puts them in the proper place in the quest map.
void FindQuestObjectData::operator()(GameObjectData const& goData)
{
    int32 entry = goData.id * -1;

	for (auto& relation : relationMap[entry])
	{
		uint32 questId = relation.first;
		uint32 flag = relation.second;
        data[questId][flag][entry].push_back(GuidPosition(goData));
	}
}

//Goes past all creatures and gameobjects and creatures the full quest guid map.
questGuidpMap QuestGuidpMapValue::Calculate()
{
	FindQuestObjectData worker;
    for (auto const& itr : sObjectMgr->GetAllCreatureData())
        worker(itr.second);
    for (auto const& itr : sObjectMgr->GetAllGOData())
        worker(itr.second);

	return worker.GetResult();
}

//Selects all questgivers for a specific level (range).
questGiverMap QuestGiversValue::Calculate()
{
	uint32 level = 0;
	std::string const q = getQualifier();
	bool hasQualifier = !q.empty();

	if (hasQualifier)
		level = stoi(q);

	questGuidpMap questMap = GAI_VALUE(questGuidpMap, "quest guidp map");

	questGiverMap guidps;

	for (auto& qPair : questMap)
	{
		for (auto& entry : qPair.second[(int)QuestRelationFlag::questGiver])
		{
			for (auto& guidp : entry.second)
			{
				uint32 questId = qPair.first;

				if (hasQualifier)
				{
					Quest const* quest = sObjectMgr->GetQuestTemplate(questId);

    			    if (quest && (level < quest->GetMinLevel() || (int)level > quest->GetQuestLevel() + 10))
						continue;
				}

				guidps[questId].push_back(guidp);
			}
		}
	}

	return guidps;
}

std::vector<GuidPosition> ActiveQuestGiversValue::Calculate()
{
    questGiverMap qGivers = GAI_VALUE2(questGiverMap, "quest givers", bot->getLevel());

	std::vector<GuidPosition> retQuestGivers;

	for (auto& qGiver : qGivers)
	{
		uint32 questId = qGiver.first;
		Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
		if (!quest)
		{
			continue;
		}

		if (!bot->CanTakeQuest(quest, false))
			continue;

		QuestStatus status = bot->GetQuestStatus(questId);

		if (status != QUEST_STATUS_NONE)
			continue;

		for (auto& guidp : qGiver.second)
		{
			CreatureTemplate const* creatureTemplate = guidp.GetCreatureTemplate();

			if (creatureTemplate)
			{
				if (bot->GetFactionReactionTo(bot->GetFactionTemplateEntry(), sFactionTemplateStore.LookupEntry(creatureTemplate->faction)) < REP_FRIENDLY)
					continue;
			}

			if (guidp.isDead())
				continue;

			retQuestGivers.push_back(guidp);
		}
	}

	return retQuestGivers;
}

std::vector<GuidPosition> ActiveQuestTakersValue::Calculate()
{
	questGuidpMap questMap = GAI_VALUE(questGuidpMap, "quest guidp map");

	std::vector<GuidPosition> retQuestTakers;

	QuestStatusMap& questStatusMap = bot->getQuestStatusMap();

	for (auto& questStatus : questStatusMap)
	{
		uint32 questId = questStatus.first;

		Quest const* quest = sObjectMgr->GetQuestTemplate(questId);

		if (!quest)
		{
			continue;
		}

		QuestStatus status = questStatus.second.Status;
		if ((status != QUEST_STATUS_COMPLETE || bot->GetQuestRewardStatus(questId)) && (!quest->IsAutoComplete() || !bot->CanTakeQuest(quest, false)))
			continue;

		auto q = questMap.find(questId);

		if (q == questMap.end())
			continue;

		auto qt = q->second.find((int) QuestRelationFlag::questTaker);

		if (qt == q->second.end())
			continue;

		for (auto& entry : qt->second)
		{
			if (entry.first > 0)
			{
				if (CreatureTemplate const* info = sObjectMgr->GetCreatureTemplate(entry.first))
				{
		            if (bot->GetFactionReactionTo(bot->GetFactionTemplateEntry(), sFactionTemplateStore.LookupEntry(info->faction)) < REP_FRIENDLY)
                        continue;
				}
			}

			for (auto& guidp : entry.second)
			{
				if (guidp.isDead())
					continue;

			    retQuestTakers.push_back(guidp);
			}
		}
	}

	return retQuestTakers;
}

std::vector<GuidPosition> ActiveQuestObjectivesValue::Calculate()
{
	questGuidpMap questMap = GAI_VALUE(questGuidpMap, "quest guidp map");

	std::vector<GuidPosition> retQuestObjectives;

	QuestStatusMap& questStatusMap = bot->getQuestStatusMap();

	for (auto& questStatus : questStatusMap)
	{
		uint32 questId = questStatus.first;

		Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
		if (!quest)
		{
			continue;
		}

		QuestStatusData statusData = questStatus.second;
		if (statusData.Status != QUEST_STATUS_INCOMPLETE)
			continue;

		for (uint32 objective = 0; objective < QUEST_OBJECTIVES_COUNT; objective++)
		{
			if (quest->RequiredItemCount[objective])
			{
				uint32 reqCount = quest->RequiredItemCount[objective];
				uint32 hasCount = statusData.ItemCount[objective];

				if (!reqCount || hasCount >= reqCount)
					continue;
			}

            if (quest->RequiredNpcOrGoCount[objective])
			{
				uint32 reqCount = quest->RequiredItemCount[objective];
				uint32 hasCount = statusData.CreatureOrGOCount[objective];

				if (!reqCount || hasCount >= reqCount)
					continue;
			}

			auto q = questMap.find(questId);

			if (q == questMap.end())
				continue;

			auto qt = q->second.find((int) QuestRelationFlag(1 << objective));

			if (qt == q->second.end())
				continue;

			for (auto& entry : qt->second)
			{
				for (auto& guidp : entry.second)
				{
					if (guidp.isDead())
						continue;

				    retQuestObjectives.push_back(guidp);
                }
			}
		}
	}

	return retQuestObjectives;
}

uint8 FreeQuestLogSlotValue::Calculate()
{
	uint8 numQuest = 0;
	for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
	{
		uint32 questId = bot->GetQuestSlotQuestId(slot);

		if (!questId)
			continue;

		Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
		if (!quest)
			continue;

		numQuest++;
	}

	return MAX_QUEST_LOG_SIZE - numQuest;
}

uint32 DialogStatusValue::getDialogStatus(Player* bot, int32 questgiver, uint32 questId)
{
	uint32 dialogStatus = DIALOG_STATUS_NONE;

	QuestRelationBounds rbounds;                        // QuestRelations (quest-giver)
	QuestRelationBounds irbounds;                       // InvolvedRelations (quest-finisher)

	if (questgiver > 0)
	{
		rbounds = sObjectMgr->GetCreatureQuestRelationBounds(questgiver);
		irbounds = sObjectMgr->GetCreatureQuestInvolvedRelationBounds(questgiver);
	}
	else
	{
		rbounds = sObjectMgr->GetGOQuestRelationBounds(questgiver * -1);
		irbounds = sObjectMgr->GetGOQuestInvolvedRelationBounds(questgiver * -1);
	}

	// Check markings for quest-finisher
	for (QuestRelations::const_iterator itr = irbounds.first; itr != irbounds.second; ++itr)
	{
		if (questId && itr->second != questId)
			continue;

		Quest const* pQuest = sObjectMgr->GetQuestTemplate(itr->second);
		if (!pQuest)
		{
			continue;
		}

        uint32 dialogStatusNew = DIALOG_STATUS_NONE;

		QuestStatus status = bot->GetQuestStatus(itr->second);

		if ((status == QUEST_STATUS_COMPLETE && !bot->GetQuestRewardStatus(itr->second)) ||
			(pQuest->IsAutoComplete() && bot->CanTakeQuest(pQuest, false)))
		{
			if (pQuest->IsAutoComplete() && pQuest->IsRepeatable())
			{
				dialogStatusNew = DIALOG_STATUS_REWARD_REP;
			}
			else
			{
				dialogStatusNew = DIALOG_STATUS_REWARD2;
			}
		}
		else if (status == QUEST_STATUS_INCOMPLETE)
		{
			dialogStatusNew = DIALOG_STATUS_INCOMPLETE;
		}

		if (dialogStatusNew > dialogStatus)
		{
			dialogStatus = dialogStatusNew;
		}
	}

	// check markings for quest-giver
	for (QuestRelations::const_iterator itr = rbounds.first; itr != rbounds.second; ++itr)
	{
		if (questId && itr->second != questId)
			continue;

		Quest const* pQuest = sObjectMgr->GetQuestTemplate(itr->second);
		if (!pQuest)
		{
			continue;
		}

        uint32 dialogStatusNew = DIALOG_STATUS_NONE;

		QuestStatus status = bot->GetQuestStatus(itr->second);

		if (status == QUEST_STATUS_NONE)                    // For all other cases the mark is handled either at some place else, or with involved-relations already
		{
			if (bot->CanSeeStartQuest(pQuest))
			{
				if (bot->SatisfyQuestLevel(pQuest, false))
				{
					int32 lowLevelDiff = sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF);
					if (pQuest->IsAutoComplete() || (pQuest->IsRepeatable() && bot->getQuestStatusMap()[itr->second].Status == QUEST_STATUS_REWARDED))
					{
						dialogStatusNew = DIALOG_STATUS_REWARD_REP;
					}
					else if (lowLevelDiff < 0 || bot->getLevel() <= bot->GetQuestLevel(pQuest) + uint32(lowLevelDiff))
					{
						dialogStatusNew = DIALOG_STATUS_AVAILABLE;
					}
					else
					{
						dialogStatusNew = DIALOG_STATUS_LOW_LEVEL_AVAILABLE;
					}
				}
				else
				{
					dialogStatusNew = DIALOG_STATUS_UNAVAILABLE;
				}
			}
		}

		if (dialogStatusNew > dialogStatus)
		{
			dialogStatus = dialogStatusNew;
		}
	}

	return dialogStatus;
}

uint32 DialogStatusValue::Calculate()
{
    return getDialogStatus(bot, stoi(getQualifier()));
}

uint32 DialogStatusQuestValue::Calculate()
{
    return getDialogStatus(bot, getMultiQualifier(getQualifier(), 0), getMultiQualifier(getQualifier(), 1));
}

bool CanAcceptQuestValue::Calculate()
{
    return AI_VALUE2(uint32, "dialog status", getQualifier()) == DIALOG_STATUS_AVAILABLE;
};

bool CanAcceptQuestLowLevelValue::Calculate()
{
    uint32 dialogStatus = AI_VALUE2(uint32, "dialog status", getQualifier());
    return dialogStatus == DIALOG_STATUS_LOW_LEVEL_AVAILABLE;
};

bool CanTurnInQuestValue::Calculate()
{
    uint32 dialogStatus = AI_VALUE2(uint32, "dialog status", getQualifier());
    return dialogStatus == DIALOG_STATUS_REWARD2 || dialogStatus == DIALOG_STATUS_REWARD || dialogStatus == DIALOG_STATUS_REWARD_REP;
};
