#include "SUDSMessageLogger.h"
#include "SUDSScript.h"
#include "Misc/AutomationTest.h"
#include "SUDSScriptImporter.h"
#include "SUDSScriptNode.h"
#include "TestUtils.h"

UE_DISABLE_OPTIMIZATION

const FString SimpleParsingInput = R"RAWSUD(
===
# Nothing in header but a comment
===

# A comment in body

Player: Excuse me?
NPC: Well, hello there. This is a test.
  * A test?
    NPC: Yes, a test. This is some indented continuation text.
    Player: Oh I see, thank you.
	NPC: You're welcome.
  * Another option
    NPC: This is another option with an embedded choice.
    * How far can this go?
      NPC: Theoretically forever but who knows?
	* This is an extra question
	  NPC: That should have been added to the previous choice
	* Another question?
	  NPC: Yep, this one too
		* A third level of questions?
		  NPC: Yes, really!
		* Wow
          NPC: IKR
        * Continuation with no response, just fallthrough
	Player: This is a level 2 fallthrough 
Player: Well, that's all for now. This should appear for all paths as a fall-through.
	This, in fact, is a multi-line piece of text
	Which is joined to the previous text node with the line breaks
NPC: Bye!
)RAWSUD";

const FString GotoParsingInput = R"RAWSUD(
:start
:alsostart
Player: This is the start
	:choice
	* Go to end
		NPC: How rude, bye then
		[goto end]
	* Nested option
		NPC: Some nested text with <Bounce>formatting</>
		* Go to goodbye
			Player: Gotta go!
			[go to goodbye] 
		* Skip
			[goto secondchoice]
		* This is a <Bounce>mistake</>
			NPC: Oh no
			[goto this_is_an_error]
:secondchoice
NPC: Yep, this one too
	* Go back to choice
		NPC: Okay!
		[goto choice]
	* Return to the start
		NPC: Gotcha
		[goto start]
	* Alternative start, also with no text before
		[goto alsostart]
:goodbye
NPC: Bye!
)RAWSUD";


const FString SetVariableParsingInput = R"RAWSUD(
===
# Set some vars in header
# Text var with an existing localised ID
[set SpeakerName.Player "Protagonist"] @12345@
# Text var no localised ID
[set ValetName "Bob"]
[set SomeFloat 12.5]
[set SomeName `AName`]
[set EmbeddedQuoteString "Hello this has some \"Embedded Quotes\""]
===

Player: Hello
[set SomeInt 99]
NPC: Wotcha
# Test that inserting a set node in between text and choice doesn't break link 
[set SomeGender masculine]
	* Choice 1
		[set SomeBoolean True]
		NPC: Truth
	* Choice 2
		NPC: Surprise
		[set ValetName "Kate"]
		[set SomeGender feminine]
Player: Well
	
)RAWSUD";



const FString ConditionalParsingInput = R"RAWSUD(
===
# Nothing in header but a comment
===

# A comment in body

Player: Excuse me?
NPC: Well, hello there. This is a test.
  * A test?
    NPC: Yes, a test. This is some indented continuation text.
    Player: Oh I see, thank you.
	[if $polite == 1]
		NPC: You're welcome.
    [else]
		NPC: Too right.
    [endif]
  * Another option
    NPC: This is another option with an embedded choice.
    * How far can this go?
      NPC: Theoretically forever but who knows?
	[if $extraq]
		* This is an extra question
		  NPC: That should have been added to the previous choice
		* Another question?
		  NPC: Yep, this one too
	[endif]
Player: Well, that's all for now. This should appear for all paths as a fall-through.
	This, in fact, is a multi-line piece of text
	Which is joined to the previous text node with the line breaks
NPC: Bye!
)RAWSUD";


IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestSimpleParsing,
                                 "SUDSTest.TestSimpleParsing",
                                 EAutomationTestFlags::EditorContext |
                                 EAutomationTestFlags::ClientContext |
                                 EAutomationTestFlags::ProductFilter)


bool FTestSimpleParsing::RunTest(const FString& Parameters)
{
	FSUDSMessageLogger Logger(false);
	FSUDSScriptImporter Importer;
	TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(SimpleParsingInput), SimpleParsingInput.Len(), "SimpleParsingInput", &Logger, true));

	// Test the content of the parsing
	auto RootNode = Importer.GetNode(0);
	if (!TestNotNull("Root node should exist", RootNode))
		return false;

	TestEqual("Root node type", RootNode->NodeType, ESUDSParsedNodeType::Text);
	TestEqual("Root node speaker", RootNode->Identifier, "Player");
	TestEqual("Root node text", RootNode->Text, "Excuse me?");
	TestEqual("Root node path", RootNode->ChoicePath, "/");
	TestEqual("Root node edges", RootNode->Edges.Num(), 1);

	auto NextNode = Importer.GetNode(RootNode->Edges[0].TargetNodeIdx);
	if (!TestNotNull("Next node should exist", NextNode))
		return false;

	TestEqual("Second node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
	TestEqual("Second node speaker", NextNode->Identifier, "NPC");
	TestEqual("Second node text", NextNode->Text, "Well, hello there. This is a test.");
	TestEqual("Second node path", RootNode->ChoicePath, "/");
	TestEqual("Second node edges", NextNode->Edges.Num(), 1);

	NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
	if (!TestNotNull("Third node should exist", NextNode))
		return false;
	TestEqual("Third node type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
	// Choice itself is still at root, only the edges (individual choices) introduce new path levels
	TestEqual("Third node path", NextNode->ChoicePath, "/");
	TestEqual("Third node edges", NextNode->Edges.Num(), 2);

	auto Choice1Node = NextNode;
	const FSUDSParsedNode* FallthroughNode = nullptr;
	if (NextNode->Edges.Num() >= 2)
	{
		TestEqual("Choice 1 node edge 0 text", Choice1Node->Edges[0].Text, "A test?");
		TestEqual("Choice 1 node edge 1 text", Choice1Node->Edges[1].Text, "Another option");
		// Follow choice 1
		NextNode = Importer.GetNode(Choice1Node->Edges[0].TargetNodeIdx);
		if (TestNotNull("Next node should exist", NextNode))
		{
			TestEqual("Choice 1 1st text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
			TestEqual("Choice 1 1st text node speaker", NextNode->Identifier, "NPC");
			TestEqual("Choice 1 1st text node text", NextNode->Text, "Yes, a test. This is some indented continuation text.");
			TestEqual("Choice 1 1st text node path", NextNode->ChoicePath, "/C001/");
			TestEqual("Choice 1 1st text node edges", NextNode->Edges.Num(), 1);
			NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
			if (TestNotNull("Next node should exist", NextNode))
			{
				TestEqual("Choice 1 2nd text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
				TestEqual("Choice 1 2nd text node speaker", NextNode->Identifier, "Player");
				TestEqual("Choice 1 2nd text node text", NextNode->Text, "Oh I see, thank you.");
				TestEqual("Choice 1 2nd text node path", NextNode->ChoicePath, "/C001/");
				TestEqual("Choice 1 2nd text node edges", NextNode->Edges.Num(), 1);
				NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
				if (TestNotNull("Next node should exist", NextNode))
				{
					TestEqual("Choice 1 3rd text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
					TestEqual("Choice 1 3rd text node speaker", NextNode->Identifier, "NPC");
					TestEqual("Choice 1 3rd text node text", NextNode->Text, "You're welcome.");
					TestEqual("Choice 1 3rd text node path", NextNode->ChoicePath, "/C001/");

					// Should fall through, all the way to the end and not to "level 2 fallthrough" since that's deeper level
					TestEqual("Choice 1 3rd text node edges", NextNode->Edges.Num(), 1);
					if (NextNode->Edges.Num() >= 1)
					{
						auto LinkedNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
						if (TestNotNull("Choice 1 3rd text linked node", LinkedNode))
						{
							TestTrue("Choice 1 3rd text target node", LinkedNode->Text.StartsWith("Well, that's all for now"));
							FallthroughNode = LinkedNode;
						}
					}
				}
			}
		}


		// Follow choice 2
		NextNode = Importer.GetNode(Choice1Node->Edges[1].TargetNodeIdx);
		if (!TestNotNull("Next node should exist", NextNode))
			return false;
		
		TestEqual("Choice 2 1st text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
		TestEqual("Choice 2 1st text node speaker", NextNode->Identifier, "NPC");
		TestEqual("Choice 2 1st text node text", NextNode->Text, "This is another option with an embedded choice.");
		TestEqual("Choice 2 2nd text node path", NextNode->ChoicePath, "/C002/");
		TestEqual("Choice 2 1st text node edges", NextNode->Edges.Num(), 1);
		NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
		if (!TestNotNull("Next node should exist", NextNode))
			return false;
		// This is nested choice node
		TestEqual("Choice 2 1st text node type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
		TestEqual("Choice 2 nested choice edges", NextNode->Edges.Num(), 3);

		auto NestedChoiceNode = NextNode;
		if (NestedChoiceNode->Edges.Num() >= 3)
		{
			TestEqual("Nested choice edge text 0", NestedChoiceNode->Edges[0].Text, "How far can this go?");
			
			NextNode = Importer.GetNode(NestedChoiceNode->Edges[0].TargetNodeIdx);
			if (TestNotNull("Next node should exist", NextNode))
			{
				TestEqual("Nested Choice 1st text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
				TestEqual("Nested Choice 1st text node speaker", NextNode->Identifier, "NPC");
				TestEqual("Nested Choice 1st text node text", NextNode->Text, "Theoretically forever but who knows?");
				// Choice edges are assigned unique numbers in ascending order, but nested
				// This helps with fallthrough
				TestEqual("Nested Choice 1st text node path", NextNode->ChoicePath, "/C002/C003/");

				if (TestEqual("Nested Choice 1st text node edges", NextNode->Edges.Num(), 1))
				{
					// Should fall through
					auto LinkedNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
					if (TestNotNull("Nested Choice 1st linked node", LinkedNode))
					{
						TestEqual("Nested Choice 1st text target node", LinkedNode->Text, "This is a level 2 fallthrough");
					}
				}
			}

			TestEqual("Nested choice edge text 1", NestedChoiceNode->Edges[1].Text, "This is an extra question");
			NextNode = Importer.GetNode(NestedChoiceNode->Edges[1].TargetNodeIdx);
			if (TestNotNull("Next node should exist", NextNode))
			{
				TestEqual("Nested Choice 2nd text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
				TestEqual("Nested Choice 2nd text node speaker", NextNode->Identifier, "NPC");
				TestEqual("Nested Choice 2nd text node text", NextNode->Text, "That should have been added to the previous choice");
				TestEqual("Nested Choice 2nd text node path", NextNode->ChoicePath, "/C002/C004/");
				TestEqual("Nested Choice 2nd text node edges", NextNode->Edges.Num(), 1);
				if (TestEqual("Nested Choice 2nd text node edges", NextNode->Edges.Num(), 1))
				{
					// Should fall through
					auto LinkedNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
					if (TestNotNull("Nested Choice 2nd linked node", LinkedNode))
					{
						TestEqual("Nested Choice 2nd text target node", LinkedNode->Text, "This is a level 2 fallthrough");
					}
				}
			}
			
			TestEqual("Nested choice edge text 2", NestedChoiceNode->Edges[2].Text, "Another question?");
			NextNode = Importer.GetNode(NestedChoiceNode->Edges[2].TargetNodeIdx);
			if (TestNotNull("Next node should exist", NextNode))
			{
				TestEqual("Nested Choice 3rd text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
				TestEqual("Nested Choice 3rd text node speaker", NextNode->Identifier, "NPC");
				TestEqual("Nested Choice 3rd text node text", NextNode->Text, "Yep, this one too");
				TestEqual("Nested Choice 3rd text node path", NextNode->ChoicePath, "/C002/C005/");
				if (TestEqual("Nested Choice 3rd text node edges", NextNode->Edges.Num(), 1))
				{
					// Double nested
					auto DoubleChoiceNode  = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
					if (TestNotNull("Double Nested Choice node", DoubleChoiceNode))
					{
						TestEqual("Double Nested Choice node type", DoubleChoiceNode->NodeType, ESUDSParsedNodeType::Choice);
						if (TestEqual("Double Nested Choice node edges", DoubleChoiceNode->Edges.Num(), 3))
						{
							
							TestEqual("Double Nested choice edge text 0", DoubleChoiceNode->Edges[0].Text, "A third level of questions?");
							NextNode = Importer.GetNode(DoubleChoiceNode->Edges[0].TargetNodeIdx);
							if (TestNotNull("Double Nested Choice node option 0", NextNode))
							{
								TestEqual("Double Nested Choice option 0 text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
								TestEqual("Double Nested Choice option 0 text node speaker", NextNode->Identifier, "NPC");
								TestEqual("Double Nested Choice option 0 text node text", NextNode->Text, "Yes, really!");
								// Should fall through
								if (TestEqual("Double Nested Choice option 0 text edge", NextNode->Edges.Num(), 1))
								NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
								if (TestNotNull("Double Nested Choice option 0 text linked node", NextNode))
								{
									TestEqual("Double Nested Choice option 0 text linked node", NextNode->Text, "This is a level 2 fallthrough");
								}
							}
							
							TestEqual("Double Nested choice edge text 1", DoubleChoiceNode->Edges[1].Text, "Wow");
							NextNode = Importer.GetNode(DoubleChoiceNode->Edges[1].TargetNodeIdx);
							if (TestNotNull("Double Nested Choice node option 1", NextNode))
							{
								TestEqual("Double Nested Choice option 1 text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
								TestEqual("Double Nested Choice option 1 text node speaker", NextNode->Identifier, "NPC");
								TestEqual("Double Nested Choice option 1 text node text", NextNode->Text, "IKR");
								// Should fall through
								if (TestEqual("Double Nested Choice option 1 text edge", NextNode->Edges.Num(), 1))
									NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
								if (TestNotNull("Double Nested Choice option 0 text linked node", NextNode))
								{
									TestEqual("Double Nested Choice option 0 text linked node", NextNode->Text, "This is a level 2 fallthrough");
								}
							}
							
							// Last Should fall through
							TestEqual("Double Nested choice edge text 2", DoubleChoiceNode->Edges[2].Text, "Continuation with no response, just fallthrough");
							auto LinkedNode = Importer.GetNode(DoubleChoiceNode->Edges[2].TargetNodeIdx);
							if (TestNotNull("Nested Choice 3rd linked node", LinkedNode))
							{
								TestEqual("Nested Choice 3rd text target node", LinkedNode->Text, "This is a level 2 fallthrough");
							}
						}
						
					}
					
				}
			}
			
		}

		if (TestNotNull("Should have found fallthrough node", FallthroughNode))
		{
			// Test the final fallthrough
			TestEqual("Fallthrough node type", FallthroughNode->NodeType, ESUDSParsedNodeType::Text);
			TestEqual("Fallthrough node speaker", FallthroughNode->Identifier, "Player");
			TestEqual("Fallthrough node text", FallthroughNode->Text, "Well, that's all for now. This should appear for all paths as a fall-through.\nThis, in fact, is a multi-line piece of text\nWhich is joined to the previous text node with the line breaks");
			if (TestEqual("Fallthrough node edge count", FallthroughNode->Edges.Num(), 1))
			{
				NextNode = Importer.GetNode(FallthroughNode->Edges[0].TargetNodeIdx);
				if (TestNotNull("Fallthrough node next node not null", NextNode))
				{
					TestEqual("Fallthrough node 2 type", NextNode->NodeType, ESUDSParsedNodeType::Text);
					TestEqual("Fallthrough node 2 speaker", NextNode->Identifier, "NPC");
					TestEqual("Fallthrough node 2 text", NextNode->Text, "Bye!");
					// Should have no further edges since is at end
					TestEqual("Fallthrough node 2 edge count", NextNode->Edges.Num(), 0);
				}
			}
			
			
		}
		
	}		

	return true;
}


IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestGotoParsing,
								 "SUDSTest.TestGotoParsing",
								 EAutomationTestFlags::EditorContext |
								 EAutomationTestFlags::ClientContext |
								 EAutomationTestFlags::ProductFilter)


bool FTestGotoParsing::RunTest(const FString& Parameters)
{
	FSUDSMessageLogger Logger(false);
	FSUDSScriptImporter Importer;
	TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(GotoParsingInput), GotoParsingInput.Len(), "GotoParsingInput", &Logger, true));

	// Test the content of the parsing
	auto RootNode = Importer.GetNode(0);
	if (!TestNotNull("Root node should exist", RootNode))
		return false;

	TestEqual("Root node type", RootNode->NodeType, ESUDSParsedNodeType::Text);
	TestEqual("Root node speaker", RootNode->Identifier, "Player");
	TestEqual("Root node text", RootNode->Text, "This is the start");
	TestEqual("Root node edges", RootNode->Edges.Num(), 1);

	auto NextNode = Importer.GetNode(RootNode->Edges[0].TargetNodeIdx);
	if (!TestNotNull("Next node should exist", NextNode))
		return false;

	TestEqual("Choice node type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
	if (TestEqual("Choice node edges", NextNode->Edges.Num(), 2))
	{
		auto ChoiceNode = NextNode;
		TestEqual("Choice 1 text", ChoiceNode->Edges[0].Text, "Go to end");
		NextNode = Importer.GetNode(ChoiceNode->Edges[0].TargetNodeIdx);
		if (TestNotNull("Next node should not be null", NextNode))
		{
			TestEqual("Goto End node text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
			TestEqual("Goto End node text speaker", NextNode->Identifier, "NPC");
			TestEqual("Goto End node text text", NextNode->Text, "How rude, bye then");
			if (TestEqual("Goto End node text edges", NextNode->Edges.Num(), 1))
			{
				NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
				if (TestNotNull("Goto node should not be null", NextNode))
				{
					TestEqual("Goto End node type", NextNode->NodeType, ESUDSParsedNodeType::Goto);
					TestEqual("Goto End node label", NextNode->Identifier, FSUDSScriptImporter::EndGotoLabel);
				}
				
			}
			
		}
		TestEqual("Choice 2 text", ChoiceNode->Edges[1].Text, "Nested option");
		NextNode = Importer.GetNode(ChoiceNode->Edges[1].TargetNodeIdx);
		if (TestNotNull("Next node should not be null", NextNode))
		{
			TestEqual("Choice 2 node text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
			TestEqual("Choice 2 node text speaker", NextNode->Identifier, "NPC");
			TestEqual("Choice 2 node text text", NextNode->Text, "Some nested text with <Bounce>formatting</>");
			if (TestEqual("Choice 2 node text edges", NextNode->Edges.Num(), 1))
			{
				NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
				if (TestNotNull("Nested choice node should not be null", NextNode))
				{
					TestEqual("Goto End node type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
					auto NestedChoice = NextNode;
					if (TestEqual("Nested Choice node edges", NextNode->Edges.Num(), 3))
					{
						TestEqual("Nested choice 0 text", NestedChoice->Edges[0].Text, "Go to goodbye");
						NextNode = Importer.GetNode(NestedChoice->Edges[0].TargetNodeIdx);
						if (TestNotNull("Should not be null", NextNode))
						{
							TestEqual("Nested choice 0 text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
							TestEqual("Nested choice 0 text speaker", NextNode->Identifier, "Player");
							TestEqual("Nested choice 0 text text", NextNode->Text, "Gotta go!");

							if (TestEqual("Nested Choice textnode edges", NextNode->Edges.Num(), 1))
							{
								NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
								if (TestNotNull("Should not be null", NextNode))
								{
									// This is the goto
									TestEqual("Nested choice 0 goto type", NextNode->NodeType, ESUDSParsedNodeType::Goto);
									TestEqual("Nested choice 0 goto label", NextNode->Identifier, "goodbye");
									const int DestIdx = Importer.GetGotoTargetNodeIndex(NextNode->Identifier);
									TestNotEqual("Label should be valid", DestIdx, -1);
									NextNode = Importer.GetNode(DestIdx);
									if (TestNotNull("Goto dest node", NextNode))
									{
										TestEqual("Goto dest text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
										TestEqual("Goto dest text speaker", NextNode->Identifier, "NPC");
										TestEqual("Goto dest text text", NextNode->Text, "Bye!");
									}
								}
							}
						}
						TestEqual("Nested choice 1 text", NestedChoice->Edges[1].Text, "Skip");
						NextNode = Importer.GetNode(NestedChoice->Edges[1].TargetNodeIdx);
						if (TestNotNull("Should not be null", NextNode))
						{
							// This one goes straight to goto
							TestEqual("Nested choice 1 goto type", NextNode->NodeType, ESUDSParsedNodeType::Goto);
							TestEqual("Nested choice 1 goto label", NextNode->Identifier, "secondchoice");

							const int DestIdx = Importer.GetGotoTargetNodeIndex(NextNode->Identifier);
							TestNotEqual("Label should be valid", DestIdx, -1);
							NextNode = Importer.GetNode(DestIdx);
							if (TestNotNull("Goto dest node", NextNode))
							{
								TestEqual("Goto dest text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
								TestEqual("Goto dest text speaker", NextNode->Identifier, "NPC");
								TestEqual("Goto dest text text", NextNode->Text, "Yep, this one too");
										
							}
						}
						TestEqual("Nested choice 2 text", NestedChoice->Edges[2].Text, "This is a <Bounce>mistake</>");
						NextNode = Importer.GetNode(NestedChoice->Edges[2].TargetNodeIdx);
						if (TestNotNull("Should not be null", NextNode))
						{
							TestEqual("Nested choice 2 text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
							TestEqual("Nested choice 2 text speaker", NextNode->Identifier, "NPC");
							TestEqual("Nested choice 2 text text", NextNode->Text, "Oh no");

							if (TestEqual("Nested Choice text node edges", NextNode->Edges.Num(), 1))
							{
								NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
								if (TestNotNull("Should not be null", NextNode))
								{
									// This is the goto
									TestEqual("Nested choice 0 goto type", NextNode->NodeType, ESUDSParsedNodeType::Goto);
									TestEqual("Nested choice 0 goto label", NextNode->Identifier, "this_is_an_error");
									TestEqual("Label should go nowhere", Importer.GetGotoTargetNodeIndex(NextNode->Identifier), -1);
								}
							}
							
						}
						
					}
				}
				
			}
			
		}

		// Pick up the latter part (only reachable by goto)
		NextNode = Importer.GetNode(Importer.GetGotoTargetNodeIndex("secondchoice"));
		if (TestNotNull("End choice node should not be null", NextNode))
		{
			TestEqual("End choice node text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
			TestEqual("End choice node text speaker", NextNode->Identifier, "NPC");
			TestEqual("End choice node text text", NextNode->Text, "Yep, this one too");
			if (TestEqual("End choice node text edges", NextNode->Edges.Num(), 1))
			{
				NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
				if (TestNotNull("", NextNode))
				{
					TestEqual("End choice node text type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
								
					if (TestEqual("End choice node edges", NextNode->Edges.Num(), 3))
					{
						auto EndChoiceNode = NextNode;
						TestEqual("End choice 0 text", EndChoiceNode->Edges[0].Text, "Go back to choice");
						NextNode = Importer.GetNode(EndChoiceNode->Edges[0].TargetNodeIdx);
						if (TestNotNull("", NextNode))
						{
							TestEqual("Node text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
							TestEqual("Node text speaker", NextNode->Identifier, "NPC");
							TestEqual("Node text text", NextNode->Text, "Okay!");
							if (TestEqual("Next node edges", NextNode->Edges.Num(), 1))
							{
								NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
								if (TestNotNull("", NextNode))
								{
									TestEqual("Should be goto node", NextNode->NodeType, ESUDSParsedNodeType::Goto);
									TestEqual("Goto label", NextNode->Identifier, "choice");
									
									// This should lead back to a choice node, ie letting the previous text node use the same choices
									// without repeating the text (loop with context)
									const int DestIdx = Importer.GetGotoTargetNodeIndex(NextNode->Identifier);
									TestNotEqual("Label should be valid", DestIdx, -1);
									NextNode = Importer.GetNode(DestIdx);
									if (TestNotNull("Goto dest node", NextNode))
									{
										TestEqual("Goto dest choice type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
										// Just make sure it's the right choice node
										if (TestEqual("Goto dest choice edges", NextNode->Edges.Num(), 2))
										{
											TestEqual("Goto dest choice check edge", NextNode->Edges[0].Text, "Go to end");
										}
									}
									
								}
							}

							NextNode = Importer.GetNode(EndChoiceNode->Edges[1].TargetNodeIdx);
							if (TestNotNull("", NextNode))
							{
								TestEqual("Node text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
								TestEqual("Node text speaker", NextNode->Identifier, "NPC");
								TestEqual("Node text text", NextNode->Text, "Gotcha");
								if (TestEqual("Next node edges", NextNode->Edges.Num(), 1))
								{
									NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
									if (TestNotNull("", NextNode))
									{
										TestEqual("Should be goto node", NextNode->NodeType, ESUDSParsedNodeType::Goto);
										TestEqual("Goto label", NextNode->Identifier, "start");
									
										// This should lead back to a choice node, ie letting the previous text node use the same choices
										// without repeating the text (loop with context)
										const int DestIdx = Importer.GetGotoTargetNodeIndex(NextNode->Identifier);
										TestNotEqual("Label should be valid", DestIdx, -1);
										NextNode = Importer.GetNode(DestIdx);
										if (TestNotNull("Goto dest node", NextNode))
										{
											// Just make sure it's the right node
											TestEqual("Goto dest text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
											TestEqual("Node text speaker", NextNode->Identifier, "Player");
											TestEqual("Node text text", NextNode->Text, "This is the start");
										}
									
									}
								}
							}
							NextNode = Importer.GetNode(EndChoiceNode->Edges[2].TargetNodeIdx);
							if (TestNotNull("", NextNode))
							{
								// Straight to goto
								TestEqual("Should be goto node", NextNode->NodeType, ESUDSParsedNodeType::Goto);
								TestEqual("Goto label", NextNode->Identifier, "alsostart");
							
								// This should lead back to a choice node, ie letting the previous text node use the same choices
								// without repeating the text (loop with context)
								const int DestIdx = Importer.GetGotoTargetNodeIndex(NextNode->Identifier);
								TestNotEqual("Label should be valid", DestIdx, -1);
								NextNode = Importer.GetNode(DestIdx);
								if (TestNotNull("Goto dest node", NextNode))
								{
									// Just make sure it's the right node
									TestEqual("Goto dest text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
									TestEqual("Node text speaker", NextNode->Identifier, "Player");
									TestEqual("Node text text", NextNode->Text, "This is the start");
								}
							}
						}
					}
				}
			}

			
			
		}

		// Test that start & autostart point to the same place
		const int StartNodeIdx = Importer.GetGotoTargetNodeIndex("start");
		const auto StartNode = Importer.GetNode(StartNodeIdx);
		if (TestNotNull("Second choice node should not be null", StartNode))
		{
			TestEqual("Start node text type", StartNode->NodeType, ESUDSParsedNodeType::Text);
			TestEqual("Start node text speaker", StartNode->Identifier, "Player");
			TestEqual("Start node text text", StartNode->Text, "This is the start");
		}
		const int AlsoStartNodeIdx = Importer.GetGotoTargetNodeIndex("alsostart");
		TestEqual("start and alsostart should reference the same node", AlsoStartNodeIdx, StartNodeIdx);
		
	}
	
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestSetVariableParsing,
								 "SUDSTest.TestSetVariableParsing",
								 EAutomationTestFlags::EditorContext |
								 EAutomationTestFlags::ClientContext |
								 EAutomationTestFlags::ProductFilter)


bool FTestSetVariableParsing::RunTest(const FString& Parameters)
{
	FSUDSMessageLogger Logger(false);
	FSUDSScriptImporter Importer;
	TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(SetVariableParsingInput), SetVariableParsingInput.Len(), "SetVariableParsingInput", &Logger, true));
	// Test the content of the parsing

	// Header nodes first
	auto NextNode = Importer.GetHeaderNode(0);
	TestParsedSetLiteral(this, "Header node 1", NextNode, "SpeakerName.Player", "Protagonist");
	TestGetParsedNextNode(this, "Header node 1 next", NextNode, Importer, true, &NextNode);
	TestParsedSetLiteral(this, "Header node 2", NextNode, "ValetName", "Bob");
	TestGetParsedNextNode(this, "Header node 2 next", NextNode, Importer, true, &NextNode);
	TestParsedSetLiteral(this, "Header node 3", NextNode, "SomeFloat", 12.5f);
	TestGetParsedNextNode(this, "Header node 3 next", NextNode, Importer, true, &NextNode);
	TestParsedSetLiteral(this, "Header node 4", NextNode, "SomeName", FName("AName"));
	TestGetParsedNextNode(this, "Header node 4 next", NextNode, Importer, true, &NextNode);
	TestParsedSetLiteral(this, "Header node 5", NextNode, "EmbeddedQuoteString", "Hello this has some \"Embedded Quotes\"");

	// Now body nodes
	NextNode = Importer.GetNode(0);
	TestParsedText(this, "Root node", NextNode, "Player", "Hello");
	TestGetParsedNextNode(this, "Node 1 next", NextNode, Importer, false, &NextNode);
	TestParsedSetLiteral(this, "Node 2", NextNode, "SomeInt", 99);
	TestGetParsedNextNode(this, "Node 2 next", NextNode, Importer, false, &NextNode);
	TestParsedText(this, "Node 3 text", NextNode, "NPC", "Wotcha");
	TestGetParsedNextNode(this, "Node 3 next", NextNode, Importer, false, &NextNode);
	TestParsedSetLiteral(this, "Node 4", NextNode, "SomeGender", ETextGender::Masculine);
	TestGetParsedNextNode(this, "Node 4 next", NextNode, Importer, false, &NextNode);
	TestParsedChoice(this, "Node 5 choice", NextNode, 2);
	auto ChoiceNode = NextNode;
	TestParsedChoiceEdge(this, "Choice 1 edge", ChoiceNode, 0, "Choice 1", Importer, &NextNode);
	TestParsedSetLiteral(this, "Choice 1 set", NextNode, "SomeBoolean", true);
	TestGetParsedNextNode(this, "Choice 1 next", NextNode, Importer, false, &NextNode);
	TestParsedText(this, "Choice 1 text", NextNode, "NPC", "Truth");

	TestParsedChoiceEdge(this, "Choice 2 edge", ChoiceNode, 1, "Choice 2", Importer, &NextNode);
	TestParsedText(this, "Choice 2 text", NextNode, "NPC", "Surprise");
	TestGetParsedNextNode(this, "Choice 2 next", NextNode, Importer, false, &NextNode);
	TestParsedSetLiteral(this, "Choice 2 set", NextNode, "ValetName", "Kate");
	TestGetParsedNextNode(this, "Choice 2 next 2", NextNode, Importer, false, &NextNode);
	TestParsedSetLiteral(this, "Choice 2 set 2", NextNode, "SomeGender", ETextGender::Feminine);
	
	return true;
}




IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestConversionToRuntime,
								 "SUDSTest.TestConversionToRuntime",
								 EAutomationTestFlags::EditorContext |
								 EAutomationTestFlags::ClientContext |
								 EAutomationTestFlags::ProductFilter)


bool FTestConversionToRuntime::RunTest(const FString& Parameters)
{
	FSUDSMessageLogger Logger(false);
	FSUDSScriptImporter Importer;
	TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(GotoParsingInput), GotoParsingInput.Len(), "GotoParsingInput", &Logger, true));

	auto Asset = NewObject<USUDSScript>(GetTransientPackage(), "Test");
	const ScopedStringTableHolder StringTableHolder;
	Importer.PopulateAsset(Asset, StringTableHolder.StringTable);

	auto StartNode = Asset->GetFirstNode();
	if (!TestNotNull("Start node should be true", StartNode))
	{
		return false;
	}

	TestTextNode(this, "Start node", StartNode, "Player", "This is the start");

	auto NextNode = StartNode;
	if (!TestEqual("Start node edges", NextNode->GetEdgeCount(), 1))
	{
		return false;
	}
	
	auto pEdge = NextNode->GetEdge(0);
	if (!TestNotNull("Start node edge", pEdge))
	{
		return false;
	}
	
	NextNode = pEdge->GetTargetNode().Get();
	if (TestChoiceNode(this, "Start node next", NextNode, 2))
	{
		auto ChoiceNode = NextNode;
		if (TestChoiceEdge(this, "Choice 1 text", ChoiceNode, 0, "Go to end", &NextNode))
		{
			TestTextNode(this, "Goto End node text type", NextNode, "NPC", "How rude, bye then");
			// This is a goto end so should just have one edge
			auto GotoEdge = NextNode->GetEdge(0);
			if (TestNotNull("Goto end node", GotoEdge))
			{
				TestFalse("Goto end should be null", GotoEdge->GetTargetNode().IsValid());
			}
		}
		
		if (TestChoiceEdge(this, "Choice 2 text", ChoiceNode, 1, "Nested option", &NextNode))
		{
			TestTextNode(this, "Choice 2 node text type", NextNode, "NPC", "Some nested text with <Bounce>formatting</>");
			if (TestEdge(this, "Choice 2 node text edges", NextNode, 0, &NextNode))
			{
				if (TestChoiceNode(this, "Goto End node type", NextNode, 3))
				{
					auto NestedChoice = NextNode;
					if (TestChoiceEdge(this, "Nested choice 0 text", NestedChoice, 0, "Go to goodbye", &NextNode))
					{
						TestTextNode(this, "Nested choice 0 text type", NextNode, "Player", "Gotta go!");
						if (TestEdge(this, "Nested Choice textnode edges", NextNode, 0, &NextNode))
						{
							// This was the goto which should have led to goodbye
							TestTextNode(this, "Check goto goodbye", NextNode, "NPC", "Bye!");
						}
					}

					if (TestChoiceEdge(this, "Nested choice 1 text", NestedChoice, 1, "Skip", &NextNode))
					{
						// This will go directly to secondchoice node
						TestTextNode(this, "Nested choice 1 goto", NextNode, "NPC", "Yep, this one too");
					}

					if (TestChoiceEdge(this, "Nested choice 2 text", NestedChoice, 2, "This is a <Bounce>mistake</>", &NextNode))
					{
						// there's a text node then a failed goto (nowhere)
						TestTextNode(this, "Nested choice 2 node", NextNode, "NPC", "Oh no");
						auto GotoEdge = NextNode->GetEdge(0);
						if (TestNotNull("Nested choice 2 node edge", GotoEdge))
						{
							TestFalse("Nested choice 2 node goes nowhere", GotoEdge->GetTargetNode().IsValid());
						}
						
					}
				}
			}
		}

		// Pick up the latter part (only reachable by goto)
		NextNode = Asset->GetNodeByLabel("secondchoice");
		if (TestTextNode(this, "secondchoice node", NextNode, "NPC", "Yep, this one too"))
		{
			if (TestEdge(this, "secondchoice node text edges", NextNode, 0, &NextNode))
			{
				if (TestChoiceNode(this, "secondchoice next choice", NextNode, 3))
				{
					auto EndChoiceNode = NextNode;
					if (TestChoiceEdge(this, "End choice 0 edge", EndChoiceNode, 0, "Go back to choice", &NextNode))
					{
						TestTextNode(this, "Node text", NextNode, "NPC", "Okay!");
						if (TestEdge(this, "Next node edges", NextNode, 0, &NextNode))
						{
							// Should go back to :choice node
							TestChoiceNode(this, "Check choice goto", NextNode, 2);
							TestChoiceEdge(this, "Check choice goto", NextNode, 0, "Go to end", &NextNode);
						}
					}
					if (TestChoiceEdge(this, "End choice 1 edge", EndChoiceNode, 1, "Return to the start", &NextNode))
					{
						TestTextNode(this, "Node text", NextNode, "NPC", "Gotcha");
						if (TestEdge(this, "Next node edges", NextNode, 0, &NextNode))
						{
							// Should go back to :start
							TestTextNode(this, "Check goto text", NextNode, "Player", "This is the start");
						}
					}
					if (TestChoiceEdge(this, "End choice 2 edge", EndChoiceNode, 2, "Alternative start, also with no text before", &NextNode))
					{
						// Should go directly back to :alsostart, same as :start
						TestTextNode(this, "Check goto text", NextNode, "Player", "This is the start");
					}
				}
			}
		}
	}


	

	// check goto labels
	auto GotoNode = Asset->GetNodeByLabel("start");
	TestTextNode(this, "Goto Start", GotoNode, "Player", "This is the start");
	GotoNode = Asset->GetNodeByLabel("alsostart");
	TestTextNode(this, "Goto alsostart", GotoNode, "Player", "This is the start");
	GotoNode = Asset->GetNodeByLabel("choice");
	TestChoiceNode(this, "Goto choice", GotoNode, 2);
	GotoNode = Asset->GetNodeByLabel("secondchoice");
	TestTextNode(this, "Goto secondchoice", GotoNode, "NPC", "Yep, this one too");
	GotoNode = Asset->GetNodeByLabel("goodbye");
	TestTextNode(this, "Goto goodbye", GotoNode, "NPC", "Bye!");

	// Test speakers
	TestEqual("Num speakers", Asset->GetSpeakers().Num(), 2);
	TestTrue("Speaker 1", Asset->GetSpeakers().Contains("Player"));
	TestTrue("Speaker 2", Asset->GetSpeakers().Contains("NPC"));

	return true;
}

const FString PartiallyLocalisedInput = R"RAWSUD(
Vagabond: Well met, fellow!
  * Er, hi?    
	Vagabond: Verily, 'tis wondrous to see such a fine fellow on the road this morn!
	[goto FriendlyChat]
  * Jog on, mate    @0001@
	Vagabond: Well, really! Good day then sir!    @0002@
	[goto end]

:FriendlyChat
Vagabond: Mayhaps we could travel together a while, and share a tale or two?    @0007@
What do you say?
)RAWSUD";

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestPartiallyLocalised,
								 "SUDSTest.TestPartiallyLocalised",
								 EAutomationTestFlags::EditorContext |
								 EAutomationTestFlags::ClientContext |
								 EAutomationTestFlags::ProductFilter)


bool FTestPartiallyLocalised::RunTest(const FString& Parameters)
{
	FSUDSMessageLogger Logger(false);
	FSUDSScriptImporter Importer;
	TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(PartiallyLocalisedInput), PartiallyLocalisedInput.Len(), "PartiallyLocalisedInput", &Logger, true));

	// Test the content of the parsing
	auto NextNode = Importer.GetNode(0);

	TestParsedText(this, "Start node", NextNode, "Vagabond", "Well met, fellow!");
	// Test that we generated unique textIDs for inserted lines that are unique
	// Should be after the last explicit one
	TestFalse("TextID should be populated", NextNode->TextID.IsEmpty());
	TestEqual("TextID should be correct", NextNode->TextID, "@0008@");
	TestGetParsedNextNode(this, "Next", NextNode, Importer, false, &NextNode);
	if (TestParsedChoice(this, "First choice", NextNode, 2))
	{
		TestFalse("TextID should be populated", NextNode->Edges[0].TextID.IsEmpty());
		TestEqual("TextID should be correct", NextNode->Edges[0].TextID, "@0009@");
		TestFalse("TextID should be populated", NextNode->Edges[1].TextID.IsEmpty());
		TestEqual("TextID should be correct", NextNode->Edges[1].TextID, "@0001@");

		TestParsedChoiceEdge(this, "First choice", NextNode, 0, "Er, hi?", Importer, &NextNode);
		TestParsedText(this, "Next node", NextNode, "Vagabond", "Verily, 'tis wondrous to see such a fine fellow on the road this morn!");
		TestFalse("TextID should be populated", NextNode->TextID.IsEmpty());
		TestEqual("TextID should be correct", NextNode->TextID, "@000a@");
		
		TestGetParsedNextNode(this, "Next", NextNode, Importer, false, &NextNode);
		TestParsedGoto(this, "Goto", NextNode, Importer, &NextNode);
		TestParsedText(this, "Next node", NextNode, "Vagabond", "Mayhaps we could travel together a while, and share a tale or two?\nWhat do you say?");
		TestEqual("TextID should be correct", NextNode->TextID, "@0007@");
	}

	NextNode = Importer.GetNode(0);
	TestGetParsedNextNode(this, "Next", NextNode, Importer, false, &NextNode);
	if (TestParsedChoice(this, "First choice", NextNode, 2))
	{
		TestEqual("TextID should be correct", NextNode->Edges[1].TextID, "@0001@");
		TestParsedChoiceEdge(this, "First choice", NextNode, 1, "Jog on, mate", Importer, &NextNode);
		TestParsedText(this, "Next node", NextNode, "Vagabond", "Well, really! Good day then sir!");
		TestEqual("TextID should be correct", NextNode->TextID, "@0002@");
	}
	
	return true;
}

const FString ProblemChoiceInput = R"RAWSUD(

NPC: Well, hello there. This is a test.
:choice
  * A test?
		Player: Yes!
		* This is a mistake; goto goes direct to another choice
			[goto choice]
NPC: Bye!
)RAWSUD";

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestParseChoiceProblem,
								 "SUDSTest.TestParseChoiceProblem",
								 EAutomationTestFlags::EditorContext |
								 EAutomationTestFlags::ClientContext |
								 EAutomationTestFlags::ProductFilter)


bool FTestParseChoiceProblem::RunTest(const FString& Parameters)
{
	FSUDSMessageLogger Logger(false);
	FSUDSScriptImporter Importer;
	TestFalse("Import should fail", Importer.ImportFromBuffer(GetData(ProblemChoiceInput), ProblemChoiceInput.Len(), "ProblemChoiceInput", &Logger, true));

	if (TestTrue("Logger should have registered errors", Logger.HasErrors()))
	{
		FText ErrMsg = Logger.GetErrorMessages()[0]->ToText();
		TestTrue("Error should contain 'Choices MUST show another speaker line'", ErrMsg.ToString().Contains("Choices MUST show another speaker line"));
	}
	return true;
}

const FString TrailingEventInput = R"RAWSUD(

NPC: Well, hello there. This is a test.
* Choice
	[event SomeEvent 1]
)RAWSUD";

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestTrailingEventProblem,
								 "SUDSTest.TestTrailingEventProblem",
								 EAutomationTestFlags::EditorContext |
								 EAutomationTestFlags::ClientContext |
								 EAutomationTestFlags::ProductFilter)


bool FTestTrailingEventProblem::RunTest(const FString& Parameters)
{
	FSUDSMessageLogger Logger(false);
	FSUDSScriptImporter Importer;
	TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(TrailingEventInput), TrailingEventInput.Len(), "TrailingEventInput", &Logger, true));

	return true;
}

const FString ConditionalChoicesNestedAndSiblingsInput = R"RAWSUD(

Player: Hello

[if {TopCondition} == 0]
    * Top level choice 1
        [goto outro]
[endif]

[if {TopCondition} == 1]
        * Top level choice 2
            [goto outro]
    [if {NestedCondition1} == 1]
        * Nested condition choice 1
            [goto outro]
    [endif]
    [if {NestedCondition2} == 0]
        * Nested condition choice 2
            [goto outro]
    [endif]
    [if {NestedCondition3} == 1]
        * Nested condition choice 3
            [goto outro]
    [endif]
[endif]

:outro
Player: Bye
)RAWSUD";


IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestConditionalChoicesNestedAndSiblings,
								 "SUDSTest.TestConditionalChoicesNestedAndSiblings",
								 EAutomationTestFlags::EditorContext |
								 EAutomationTestFlags::ClientContext |
								 EAutomationTestFlags::ProductFilter)


bool FTestConditionalChoicesNestedAndSiblings::RunTest(const FString& Parameters)
{
	FSUDSMessageLogger Logger(false);
	FSUDSScriptImporter Importer;
	TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(ConditionalChoicesNestedAndSiblingsInput), ConditionalChoicesNestedAndSiblingsInput.Len(), "ConditionalChoicesNestedAndSiblingsInput", &Logger, true));


	auto Asset = NewObject<USUDSScript>(GetTransientPackage(), "Test");
	const ScopedStringTableHolder StringTableHolder;
	Importer.PopulateAsset(Asset, StringTableHolder.StringTable);

	auto StartNode = Asset->GetFirstNode();
	if (!TestNotNull("Start node should be true", StartNode))
	{
		return false;
	}

	TestTextNode(this, "Start node", StartNode, "Player", "Hello");

	auto NextNode = StartNode;
	if (!TestEqual("Start node edges", NextNode->GetEdgeCount(), 1))
	{
		return false;
	}
	
	auto pEdge = NextNode->GetEdge(0);
	if (!TestNotNull("Start node edge", pEdge))
	{
		return false;
	}

	/* Node structure:
		T -> C1
		C1 -> S1 -> (if TopCondition == 0) C2 --> Top level choice 1
		C1 -> S2 -> (if TopCondition == 1) C3 -> Top Level choice 2
                                           |-> S3 -> (if NestedCondition1 == 1) -> C4 -> Nested condition choice 1
                                           |-> S4 -> (if NestedCondition2 == 0) -> C5 -> Nested condition choice 2
                                           |-> S5 -> (if NestedCondition3 == 0) -> C5 -> Nested condition choice 3
	 */
	
	NextNode = pEdge->GetTargetNode().Get();
	if (TestChoiceNode(this, "Start node next", NextNode, 2))
	{
		auto ChoiceNode = NextNode;
		// Top condition == 0 subtree
		if (TestChoiceEdge(this, "Select Path 1", ChoiceNode, 0, "", &NextNode))
		{
			if (TestSelectNode(this, "Select 1", NextNode, 1))
			{
				if (TestSelectEdge(this, "Select 1 Edge", NextNode, 0, "{TopCondition} == 0", &NextNode))
				{
					if (TestChoiceNode(this, "Choice 1 Leaf", NextNode, 1))
					{
						TestChoiceEdge(this, "Choice 1 Leaf Edge", NextNode, 0, "Top level choice 1", &NextNode);
					}
				}
			}
		}

		// Top condition == 1 subtree
		if (TestChoiceEdge(this, "Select Path 2", ChoiceNode, 1, "", &NextNode))
		{
			if (TestSelectNode(this, "Select 2", NextNode, 1))
			{
				if (TestSelectEdge(this, "Select 2 Edge", NextNode, 0, "{TopCondition} == 1", &NextNode))
				{
					// This choice node holds the non-nested condition choice, plus 3 more nested selects
					// Because each condition is a sibling they become separate select subtrees
					if (TestChoiceNode(this, "Choice 2 Mid", NextNode, 4))
					{
						auto ChoiceNode2 = NextNode;
						TestChoiceEdge(this, "Choice 2 Leaf Edge 1", ChoiceNode2, 0, "Top level choice 2", &NextNode);

						if (TestChoiceEdge(this, "Choice 2 To Select Edge 1", ChoiceNode2, 1, "", &NextNode))
						{
							if (TestSelectNode(this, "Select 2", NextNode, 1))
							{
								if (TestSelectEdge(this, "Select 2 Edge", NextNode, 0, "{NestedCondition1} == 1", &NextNode))
								{
									if (TestChoiceNode(this, "Choice 2 Leaf", NextNode, 1))
									{
										TestChoiceEdge(this, "Choice 2 Leaf Edge", NextNode, 0, "Nested condition choice 1", &NextNode);
									}
								}
							}
						}

						if (TestChoiceEdge(this, "Choice 2 To Select Edge 2", ChoiceNode2, 2, "", &NextNode))
						{
							if (TestSelectNode(this, "Select 3", NextNode, 1))
							{
								if (TestSelectEdge(this, "Select 3 Edge", NextNode, 0, "{NestedCondition2} == 0", &NextNode))
								{
									if (TestChoiceNode(this, "Choice 3 Leaf", NextNode, 1))
									{
										TestChoiceEdge(this, "Choice 3 Leaf Edge", NextNode, 0, "Nested condition choice 2", &NextNode);
									}
								}
							}
						}

						if (TestChoiceEdge(this, "Choice 2 To Select Edge 3", ChoiceNode2, 3, "", &NextNode))
						{
							if (TestSelectNode(this, "Select 4", NextNode, 1))
							{
								if (TestSelectEdge(this, "Select 4 Edge", NextNode, 0, "{NestedCondition3} == 1", &NextNode))
								{
									if (TestChoiceNode(this, "Choice 4 Leaf", NextNode, 1))
									{
										TestChoiceEdge(this, "Choice 4 Leaf Edge", NextNode, 0, "Nested condition choice 3", &NextNode);
									}
								}
							}
						}

					}
				}
			}
		}

		
	}
	return true;
}

// The deliberate mistake here is that the speaker lines are not indented underneath the choices,
// which when combined with the conditionals confuses the parser so that it leaves all but the
// first choice orphaned and unreachable. Import should report this to the user
const FString ErrorsReportedForBadChoiceIndentsInput = R"RAWSUD(

npc: hello
:chatchoices
    [if {npc_hasQuests}]
        * Any news?
        player: Any news?
    [endif]
    [if {npc_canTrade}]
        * Trade
        player: What do you have for sale?
    [endif]
    [if {npc_hasLore}]
        * How are you?
        player: How are things?
    [endif]
        * Nevermind
        npc: Alright. Come back anytime.
)RAWSUD";

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestErrorsReportedForBadChoiceIndents,
								 "SUDSTest.TestErrorsReportedForBadChoiceIndents",
								 EAutomationTestFlags::EditorContext |
								 EAutomationTestFlags::ClientContext |
								 EAutomationTestFlags::ProductFilter)


bool FTestErrorsReportedForBadChoiceIndents::RunTest(const FString& Parameters)
{
	FSUDSMessageLogger Logger(false);
	FSUDSScriptImporter Importer;
	TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(ErrorsReportedForBadChoiceIndentsInput), ErrorsReportedForBadChoiceIndentsInput.Len(), "ErrorsReportedForBadChoiceIndentsInput", &Logger, true));

	TestTrue("Warnings should have been logged", Logger.HasWarnings());
	return true;
}



UE_ENABLE_OPTIMIZATION