FGameplayCueTranslationLink& FGameplayCueTranslatorNode::FindOrCreateLink(const UGameplayCueTranslator* RuleClassCDO, int32 LookupSize)
{
	int32 InsertIdx = 0;
	int32 NewPriority = RuleClassCDO->GetPriority();

#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
	if (CVarGameplyCueTranslatorDebugTag->GetString().IsEmpty() == false && this->CachedGameplayTagName.ToString().Contains(CVarGameplyCueTranslatorDebugTag->GetString()))
	{
		UE_LOG(LogGameplayCueTranslator, Log, TEXT("....."));
	}
#endif

	for (int32 LinkIdx=0; LinkIdx < Links.Num(); ++LinkIdx)
	{
		if (Links[LinkIdx].RulesCDO == RuleClassCDO)
		{
			// Already here, return
			return Links[LinkIdx];
		}

		if (Links[LinkIdx].RulesCDO->GetPriority() > NewPriority)
		{
			// Insert after the existing one with higher priority
			InsertIdx = LinkIdx+1;
		}
	}

	check(InsertIdx <= Links.Num());

	FGameplayCueTranslationLink& NewLink = Links[Links.Insert(FGameplayCueTranslationLink(), InsertIdx)];
	NewLink.RulesCDO = RuleClassCDO;
	NewLink.NodeLookup.SetNum( LookupSize );
	return NewLink;
}
FGameplayCueTranslatorNodeIndex FGameplayCueTranslationManager::GetTranslationIndexForName(FName Name, bool CreateIfInvalid)
{
	FGameplayCueTranslatorNodeIndex Idx;
	if (CreateIfInvalid)
	{
		FGameplayCueTranslatorNodeIndex& MapIndex = TranslationNameToIndexMap.FindOrAdd(Name);
		if (MapIndex.IsValid() == false)
		{
			MapIndex = TranslationLUT.AddDefaulted();			
		}
		
		Idx = MapIndex;

		if (TranslationLUT[Idx].CachedIndex.IsValid() == false)
		{
			TranslationLUT[Idx].CachedIndex = Idx;
			TranslationLUT[Idx].CachedGameplayTag = TagManager->RequestGameplayTag(Name, false);
			TranslationLUT[Idx].CachedGameplayTagName = Name;
		}
	}
	else
	{
		if (FGameplayCueTranslatorNodeIndex* IdxPtr = TranslationNameToIndexMap.Find(Name))
		{
			Idx = *IdxPtr;
		}
	}

#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
	if (Idx.IsValid() && TranslationLUT[Idx].CachedGameplayTagName.ToString().Contains(CVarGameplyCueTranslatorDebugTag->GetString()))
	{
		UE_LOG(LogGameplayCueTranslator, Log, TEXT("....."));
	}
#endif


	ensureAlways(!Idx.IsValid() || TranslationLUT[Idx].CachedGameplayTagName != NAME_None);

#if WITH_EDITOR
	// In the editor tags can be created after the initial creation of the translation data structures.
	// This will update the tag in subsequent requests
	if (Idx.IsValid() && TranslationLUT[Idx].CachedGameplayTag.IsValid() == false)
	{
		TranslationLUT[Idx].CachedGameplayTag = TagManager->RequestGameplayTag(Name, false);
	}
#endif

	return Idx;
}
void USignificanceManager::OnShowDebugInfo(AHUD* HUD, UCanvas* Canvas, const FDebugDisplayInfo& DisplayInfo, float& YL, float& YPos)
{
	static const FName NAME_SignificanceManager("SignificanceManager");
	if (Canvas && HUD->ShouldDisplayDebug(NAME_SignificanceManager))
	{
		if (HasAnyFlags(RF_ClassDefaultObject))
		{
			if (USignificanceManager* SignificanceManager = Get(HUD->GetWorld()))
			{
				SignificanceManager->OnShowDebugInfo(HUD, Canvas, DisplayInfo, YL, YPos);
			}
		}
		else
		{
			FDisplayDebugManager& DisplayDebugManager = Canvas->DisplayDebugManager;
			DisplayDebugManager.SetFont(GEngine->GetSmallFont());
			DisplayDebugManager.SetDrawColor(FColor::Red);
			DisplayDebugManager.DrawString(FString::Printf(TEXT("SIGNIFICANCE MANAGER - %d Managed Objects"), ManagedObjects.Num()));

			const FName SignificanceManagerTag(*CVarSignificanceManagerFilterTag->GetString());
			TArray<const FManagedObjectInfo*> AllObjects;
			const TArray<const FManagedObjectInfo*>& ObjectsToShow = (SignificanceManagerTag.IsNone() ? AllObjects : GetManagedObjects(SignificanceManagerTag));
			if (SignificanceManagerTag.IsNone())
			{
				GetManagedObjects(AllObjects, true);
			}

			DisplayDebugManager.SetDrawColor(FColor::White);
			const int32 NumObjectsToShow = FMath::Min(GSignificanceManagerObjectsToShow, ObjectsToShow.Num());
			for (int32 Index = 0; Index < NumObjectsToShow; ++Index)
			{
				const FManagedObjectInfo* ObjectInfo = ObjectsToShow[Index];
				const FString Str = FString::Printf(TEXT("%6.3f - %s (%s)"), ObjectInfo->GetSignificance(), *ObjectInfo->GetObject()->GetName(), *ObjectInfo->GetTag().ToString());
				DisplayDebugManager.DrawString(Str);
			}
		}
	}
}
void FGameplayCueTranslationManager::BuildTagTranslationTable_Forward_r(const FName& TagName, const TArray<FName>& SplitNames)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
	if (CVarGameplyCueTranslatorDebugTag->GetString().IsEmpty() == false && TagName.ToString().Contains(CVarGameplyCueTranslatorDebugTag->GetString()))
	{
		// 
		UE_LOG(LogGameplayCueTranslator, Log, TEXT("....."));
	}
#endif

	TArray<FName> SwappedNames;
	SwappedNames.Reserve(10);

	// Each nameswap rule group
	for (FNameSwapData& NameSwapData : AllNameSwaps)
	{
		// Avoid rule recursion
		{
			if (FGameplayCueTranslatorNode* ChildNode = GetTranslationNodeForName(TagName, false))
			{
				if (ChildNode->UsedTranslators.Contains(NameSwapData.ClassCDO))
				{
					continue;
				}
			}
		}

		// Each swaprule 
		for (int32 SwapRuleIdx=0; SwapRuleIdx < NameSwapData.NameSwaps.Num(); ++SwapRuleIdx)
		{
			const FGameplayCueTranslationNameSwap& SwapRule = NameSwapData.NameSwaps[SwapRuleIdx];

#if WITH_EDITOR
			if (SwapRule.EditorData.Enabled == false)
			{
				continue;
			}
#endif

			// Each subtag within this GameplayTag
			for (int32 TagIdx=0; TagIdx < SplitNames.Num(); ++TagIdx)
			{
				if (SplitNames[TagIdx] == SwapRule.FromName)
				{
					SwappedNames = SplitNames;

					// Possible match!
					// Done - full match found
					SwappedNames.RemoveAt(TagIdx, 1, false);
					for (int32 ToIdx=0; ToIdx < SwapRule.ToNames.Num(); ++ToIdx)
					{
						SwappedNames.Insert(SwapRule.ToNames[ToIdx], TagIdx + ToIdx);
					}

					FString ComposedString = SwappedNames[0].ToString();
					for (int32 ComposeIdx=1; ComposeIdx < SwappedNames.Num(); ++ ComposeIdx)
					{
						ComposedString += FString::Printf(TEXT(".%s"), *SwappedNames[ComposeIdx].ToString());
					}
						
					UE_LOG(LogGameplayCueTranslator, Log, TEXT("Found possible new expanded tag. Original: %s. Parent: %s"), *TagName.ToString(), *ComposedString);
					FName ComposedName = FName(*ComposedString);

					FGameplayCueTranslatorNodeIndex ChildIdx = GetTranslationIndexForName( ComposedName, true );
					if ( ChildIdx.IsValid() )
					{
						FGameplayCueTranslatorNodeIndex ParentIdx = GetTranslationIndexForName( TagName, true );
						if (ParentIdx.IsValid())
						{
							FGameplayCueTranslatorNode& ParentNode = TranslationLUT[ParentIdx];
							FGameplayCueTranslatorNode& ChildNode = TranslationLUT[ChildIdx];

							// Find or create the link structure out of the parent node
							FGameplayCueTranslationLink& NewLink = ParentNode.FindOrCreateLink(NameSwapData.ClassCDO, NameSwapData.NameSwaps.Num());
							
							NewLink.NodeLookup[SwapRuleIdx] = ChildNode.CachedIndex;

							ChildNode.UsedTranslators.Append(ParentNode.UsedTranslators);
							ChildNode.UsedTranslators.Add(NameSwapData.ClassCDO);
						}
					}

					BuildTagTranslationTable_Forward_r(ComposedName, SwappedNames);
				}
			}
		}
	}
}
bool FGameplayCueTranslationManager::BuildTagTranslationTable_r(const FName& TagName, const TArray<FName>& SplitNames)
{

#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
	if (CVarGameplyCueTranslatorDebugTag->GetString().IsEmpty() == false && TagName.ToString().Contains(CVarGameplyCueTranslatorDebugTag->GetString()))
	{
		// 
		UE_LOG(LogGameplayCueTranslator, Log, TEXT("....."));
	}
#endif

	bool HasValidRootTag = false;

	TArray<FName> SwappedNames;
	SwappedNames.Reserve(10);

	// Every NameSwap Rule/Class that gave us data
	for (FNameSwapData& NameSwapData : AllNameSwaps)
	{
		// Avoid rule recursion
		{
			if (FGameplayCueTranslatorNode* ChildNode = GetTranslationNodeForName(TagName, false))
			{
				if (ChildNode->UsedTranslators.Contains(NameSwapData.ClassCDO))
				{
					continue;
				}
			}
		}

		// Every Swap that this Rule/Class gave us
		for (int32 SwapRuleIdx=0; SwapRuleIdx < NameSwapData.NameSwaps.Num(); ++SwapRuleIdx)
		{
			const FGameplayCueTranslationNameSwap& SwapRule = NameSwapData.NameSwaps[SwapRuleIdx];

#if WITH_EDITOR
			if (SwapRule.EditorData.Enabled == false)
			{
				continue;
			}
#endif

			// Walk through the original tag's elements
			for (int32 TagIdx=0; TagIdx < SplitNames.Num(); ++TagIdx)
			{
				// Walk through the potential new tag's elemnts
				for (int32 ToNameIdx=0; ToNameIdx < SwapRule.ToNames.Num() && TagIdx < SplitNames.Num(); ++ToNameIdx)
				{
					// If they match
					if (SwapRule.ToNames[ToNameIdx] == SplitNames[TagIdx])
					{
						// If we are at the end
						if (ToNameIdx == SwapRule.ToNames.Num()-1)
						{
							// *Possible* tag translation found! This tag can be derived from out name swapping rules, 
							// but we don't know if there actually is a tag that matches the tag which it would be translated *from*

							// Don't operator on SplitNames, since subsequent rules and name swaps use the same array
							SwappedNames = SplitNames;

							// Remove the "To Names" from the SwappedNames array, replace with the single "From Name"
							// E.g, GC.{Steel.Master} -> GC.{Hero}
							int32 NumRemoves = SwapRule.ToNames.Num(); // We are going to remove as many tags 
							int32 RemoveAtIdx = TagIdx - (SwapRule.ToNames.Num() - 1);
							check(SwappedNames.IsValidIndex(RemoveAtIdx));

							SwappedNames.RemoveAt(RemoveAtIdx, NumRemoves, false);
							SwappedNames.Insert(SwapRule.FromName, RemoveAtIdx);

							// Compose a string from the new name
							FString ComposedString = SwappedNames[0].ToString();							
							for (int32 ComposeIdx=1; ComposeIdx < SwappedNames.Num(); ++ ComposeIdx)
							{
								ComposedString += FString::Printf(TEXT(".%s"), *SwappedNames[ComposeIdx].ToString());
							}
								
							UE_LOG(LogGameplayCueTranslator, Log, TEXT("Found possible expanded tag. Original Child Tag: %s. Possible Parent Tag: %s"), *TagName.ToString(), *ComposedString);
							FName ComposedName = FName(*ComposedString);

							// Look for this tag - is it an actual real tag? If not, continue on
							{
								FGameplayTag ComposedTag = TagManager->RequestGameplayTag(ComposedName, false);
								if (ComposedTag.IsValid() == false)
								{
									UE_LOG(LogGameplayCueTranslator, Log, TEXT("   No tag match found, recursing..."));
									
									FGameplayCueTranslatorNodeIndex ParentIdx = GetTranslationIndexForName( ComposedName, false );
									if (ParentIdx.IsValid() == false)
									{
										ParentIdx = GetTranslationIndexForName( ComposedName, true );
										check(ParentIdx.IsValid());
										TranslationLUT[ParentIdx].UsedTranslators.Add( NameSwapData.ClassCDO );
																		
										HasValidRootTag |= BuildTagTranslationTable_r(ComposedName, SwappedNames);
									}
								}
								else
								{
									HasValidRootTag = true;
								}
							}

							if (HasValidRootTag)
							{
								// Add it to our data structures
								FGameplayCueTranslatorNodeIndex ParentIdx = GetTranslationIndexForName(ComposedName, true);
								check(ParentIdx.IsValid());
								
								UE_LOG(LogGameplayCueTranslator, Log, TEXT("   Matches real tags! Adding to translation tree"));

								FGameplayCueTranslatorNodeIndex ChildIdx = GetTranslationIndexForName(TagName, true);
								ensure(ChildIdx != INDEX_NONE);

								// Note: important to do this after getting ChildIdx since allocating idx can move TranslationMap memory around
								FGameplayCueTranslatorNode& ParentNode = TranslationLUT[ParentIdx];

								FGameplayCueTranslationLink& NewLink = ParentNode.FindOrCreateLink(NameSwapData.ClassCDO, NameSwapData.NameSwaps.Num());
										
								// Verify this link hasn't already been established
								ensure(NewLink.NodeLookup[SwapRuleIdx] != ChildIdx);

								// Setup the link
								NewLink.NodeLookup[SwapRuleIdx] = ChildIdx;

								// Now make sure we don't reapply this rule to this child node or any of its child nodes
								FGameplayCueTranslatorNode& ChildNode = TranslationLUT[ChildIdx];
								ChildNode.UsedTranslators.Append( ParentNode.UsedTranslators );
								ChildNode.UsedTranslators.Add( NameSwapData.ClassCDO );
							}
							else
							{
								UE_LOG(LogGameplayCueTranslator, Log, TEXT("   No tag match found after recursing. Dead end."));
							}

							break;
						}
						else
						{
							// Keep going, advance TagIdx
							TagIdx++;
							continue;
						}
					}
					else
					{
						// Match failed
						break;
					}
				}
			}
		}
	}

	return HasValidRootTag;
}