

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of PSYCLONE version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  PSYCLONE 2.1 is released under the GNU Lesser General Public License 
//_/_/  (LGPL) version 3.0, and is subject to the following
//_/_/  
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/    
//_/_/  A commercial license for PSYCLONE 2.1 is also available to 
//_/_/  any interested party from the copyright holder.
//_/_/  
//_/_/  You can redistribute this copy of PSYCLONE 2.1 and/or modify it under 
//_/_/  the terms of the GNU General Public License as published by the Free 
//_/_/  Software Foundation, either version 3 of the License, or (at your 
//_/_/  option) any later version.
//_/_/  
//_/_/  You should have received a copy of the GNU General Public License
//_/_/  and the GNU Lesser General Public License along with PSYCLONE 2.1.  
//_/_/  If not, see <http://www.gnu.org/licenses/>.
//_/_/  
//_/_/  PSYCLONE 2.1 is distributed in the hope that it will be useful,
//_/_/  but WITHOUT ANY WARRANTY; without even the implied warranty of
//_/_/  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//_/_/  GNU General Public License for more details.
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#include "Psyclone.h"

using namespace cmlabs;

namespace cmlabs { void Register_Psyclone_Tests(); }

bool ConvertAnnotationToMessageRecording(std::string annotationFile, std::string outputDir);

Node* node = NULL;

#if defined(__APPLE__) || defined(__linux__)
/* Run as early as possible in static init (low number = earlier). */
// static void early_init(void) __attribute__((constructor(1)));
// static void early_init(void) {
// 	fprintf(stderr, "Psyclone: early init\n");
// 	fflush(stderr);
// }
#endif

void PrintUsage(const char* progname) {
	printf("Usage: %s [] []\n\n", progname);
}

bool HasParam(int argc, char* argv[], const char* key) {
	char* str;
	char c;
	for (int n=0; n<argc; n++) {
		if ( (str = strstr(argv[n], key)) == argv[n] ) {
			c = str[strlen(key)];
			if ( (c=='=') || (c==0))
				return true;
		}
	}
	return false;
}

const char* GetParam(int argc, char* argv[], const char* key) {
	char* str;
	for (int n=0; n<argc; n++) {
		if ( (str = strstr(argv[n], key)) == argv[n] ) {
			str += strlen(key);
			if (*str=='=')
				return str+1;
		}
	}
	return NULL;
}

int GetParamInt(int argc, char* argv[], const char* key) {
	const char* str = GetParam(argc, argv, key);
	if (str == NULL)
		return -1;
	else
		return atoi(str);
}

int GetParamInt(int argc, char* argv[], const char* key, int defaultVal) {
	const char* str = GetParam(argc, argv, key);
	if (str == NULL)
		return defaultVal;
	else
		return (int)utils::Ascii2Int32(str);
}

double GetParamFloat(int argc, char* argv[], const char* key, double defaultVal) {
	const char* str = GetParam(argc, argv, key);
	if (str == NULL)
		return defaultVal;
	else
		return (double)utils::Ascii2Float64(str);
}

//bool testbeat(uint32 id, uint32 count) {
//	if (count == 10) {
//		printf("%s: Timer %u, count %u, exiting...\n", PrintTimeNowString().c_str(), id, count);
//		return false;
//	}
//	else
//		printf("%s: Timer %u, count %u...\n", PrintTimeNowString().c_str(), id, count);
//	return true;
//}

THREAD_RET THREAD_FUNCTION_CALL test(THREAD_ARG arg) {
	if (arg == NULL) thread_ret_val(1);
	utils::Event* event = (utils::Event*) arg;

	while (true) {
		if (event->waitNext(50))
			printf("  Got event: %s\n", PrintTimeNowString().c_str());
	}

	thread_ret_val(0);
}

THREAD_RET THREAD_FUNCTION_CALL TestThread(THREAD_ARG arg) {
	thread_ret_val(0);
}

#include "Bitmap.h"
#include "MessagePlayer.h"

int main(int argc, char* argv[]) {
	printf("\nStartine Psyclone...\n\n");
	fflush(stdout);


	//JSONM* jm = new JSONM();
	//jm->setJSON("{\"test\" = 1}");
	//jm->addData("data1", "abcdefg", 7, "text");
	//jm->addData("data2", "1234567890", 10, "text");

	//printf("%s\n\n", jm->getInfoString().c_str());

	//JSONM* jm2 = new JSONM(jm->jmData, jm->jmSize);
	//jm2->addData("data3", "ffffffffffff", 12, "binary");

	//printf("%s\n\n", jm2->getInfoString().c_str());

	//utils::WriteAFile("d:/jm1.bin", jm->jmData, (uint32)jm->jmSize, true);
	//utils::WriteAFile("d:/jm2.bin", jm2->jmData, (uint32)jm2->jmSize, true);

	//delete jm;
	//delete jm2;
	//exit(0);


	//std::string str = "Skip to main content Select language Skip to search MDN Web Docs Technologies  References & Guides  Feedback Sign in Search";
	//std::string str = "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef";
	//uint64 size = 0;

	//WebsocketData* wsData = new WebsocketData();
	//wsData->maskingKey = 2112143814;
	//wsData->setData(wsData->TEXT, true, str.c_str(), str.length());
	//const char* data = wsData->getRawData(size);

	//utils::WriteAFile("d:/ws-out.bin", data, size, true);

	//bool invalid = false;
	//WebsocketData* wsData2 = new WebsocketData();
	//wsData2->processHeader(data, size, invalid);

	//exit(0);

	//jsmn_parser parser;
	//jsmntok_t* tokens = new jsmntok_t[1024];
	//int tokenCount = 0;
	//std::string json = utils::ReadAFileString("d:/test.json");
	//jsmn_init(&parser);
	//if ((tokenCount = jsmn_parse(&parser, json.c_str(), json.length(), tokens, 256)) <= 0) {
	//	if (tokenCount == JSMN_ERROR_NOMEM)
	//		LogPrint(1, 1, 1, "JSON reply from FaceServer too long: %u", (uint32)json.length());
	//	else if (tokenCount == JSMN_ERROR_PART)
	//		LogPrint(1, 1, 1, "JSON reply from FaceServer not complete: %s", json.c_str());
	//	else
	//		LogPrint(1,1,1, "JSON reply from FaceServer not valid: %s", json.c_str());
	//	// No JSON found
	//	//delete[] tokens;
	//}













	// Fix for float number with incorrect decimal separator.
	setlocale(LC_NUMERIC, "C");

	//std::string teststr = "He becomes the third American to be detained by the North; one has been sentenced for spying, the other for trying to steal a sign from a hotel.\n\n The latest detention comes amid high tension on the peninsula, with the US warning its 'strategic patience' on the North's nuclear.\nA US naval battle group headed by an aircraft carrier, the USS Carl Vinson, and described by President Donald Trump as an 'armada', is expected to reach the Korean peninsula.\n";
	//std::string str = utils::TextListBreakLines(teststr.c_str(), 40, "<br>\n");
	//printf("%s\n", str.c_str());
	//exit(0);

	//uint32 size = 100000;
	//char* data = new char[size];
	//char* out = new char[2048];
	//memset(data, 0, size);
	//DataMessage* testMsg, *draft;
	//int64 serial = 0;

	//for (int nnn = 0; nnn < 10000000; nnn++) {
	//	testMsg = new DataMessage();
	//	testMsg->setSerial(++serial);
	//	testMsg->setData("AudioDataFrame", data, size);
	//	testMsg->setInt("AudioChannels", 1);
	//	testMsg->setInt("AudioRate", 16000);
	//	testMsg->setInt("AudioSamples", 1);
	//	testMsg->setInt("AudioBitsPerSample", 2);
	//	testMsg->setInt("TimeMS", 0);
	//	testMsg->setDouble("FilePosition", 0);
	//	testMsg->setDouble("VideoFPS", 0);
	//	draft = new DataMessage(*testMsg, 2048);
	//	memcpy(out, draft->data, draft->data->size);
	//	delete(draft);
	//	delete(testMsg);
	//}



	//std::string str = utils::ReadRegistryString("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\FA6", "ImagePath");
	//uint32 v32 = utils::ReadRegistryDWORD("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\FA6", "Start");

	//utils::WriteRegistryDWORD("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\FA62", "Test", 20);
	//utils::WriteRegistryString("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\FA62", "Test2", "Hello");

	//utils::DeleteRegistryEntry("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\FA62", "Test2");
	//utils::WriteRegistryString("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\FA62\\TestKey", "Test2", "Hello");
	//utils::CopyRegistryTree("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\FA6", "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\FA63");
	//utils::DeleteRegistryTree("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services", "FA62");

	//exit(0);

	//printf("Sizeof ReplayIndexEntry: %u\n", (uint32)sizeof(ReplayIndexEntry));

	//DataMessage* msg;
	//MessagePlayer* msgPlayer = new MessagePlayer();
	//msgPlayer->initRead("d:/c/CoCoMaps/Data/RobotStatusReplay", 0, true);
	//while (true) {
	//	msg = msgPlayer->waitForNextMessage(1000);
	//	if (msg) {
	//		printf("Trigger name: %s size: %u at %s\n",
	//			msgPlayer->getCurrentTriggerName().c_str(), msg->getSize(), PrintTimeString(msg->getCreatedTime()).c_str());
	//	}
	//	else
	//		printf("---\n");
	//}


	//uint32 size, length;
	//char* data = utils::ReadAFile("d:/32FC1.bin", size, true);

	//Bitmap* bitmap = new Bitmap(data, size, 640, 480, "32FC1", size / 480);
	//bitmap->saveToFile("d:/32FC1x.bmp");

	//char* bmpfile = Bitmap::DataToBitmapFileFormat(data, size, 640, 480, "32FC1", size/480, length);
	//utils::WriteAFile("d:/32FC1.bmp", bmpfile, length, true);

	//data = utils::ReadAFile("d:/16UC1.bin", size, true);
	//bmpfile = Bitmap::DataToBitmapFileFormat(data, size, 640, 480, "16UC1", size / 480, length);
	//utils::WriteAFile("d:/16UC1.bmp", bmpfile, length, true);

	//bitmap = new Bitmap(data, size, 640, 480, "16UC1", size / 480);
	//bitmap->saveToFile("d:/16UC1x.bmp");

	////Bitmap* bitmap = new Bitmap(data, 640, 480, size / 480);
	////bitmap->saveToFile("d:/2.bmp");

	//exit(0);


	//DataMessage* msg = new DataMessage();
	//msg->setTime("TestTime", GetTimeNow());
	//msg->setInt("TestInt", 1321);
	//msg->setInt(1, "TestIntArray", 1321);
	//msg->setInt(2, "TestIntArray", 1321);
	//msg->setInt(3, "TestIntArray", 1321);
	//msg->setString("TestString", "asdfasdf");
	//msg->setString("1", "TestString", "asdfasdf");
	//msg->setString("2", "TestString", "asdfasdf");
	//msg->setString("3", "TestString", "asdfasdf");
	//uint32 size = 231214;
	//char* data = new char[size];
	//msg->setData("TestData", data, size);

	//printf("Original message:\n%s\n\n", msg->toJSON().c_str());

	//DataMessage* msgDraft = new DataMessage(*msg, 1000);

	//printf("Draft message:\n%s\n\n", msgDraft->toJSON().c_str());
	//printf("Draft message:\n%s\n\n", msgDraft->toXML().c_str());

	//utils::Sleep(1000000);

	//DataMessage* msg = new DataMessage();
	//msg->setTime("1asdf", "Test", 1);
	//msg->setTime("2asdf", "Test", 2);
	//msg->setTime("3asdf", "Test", 3);
	//msg->setTime("5asdf", "Test", 5);
	//msg->setTime("6asdf", "Test", 6);

	//msg->setTime("Test2", GetTimeNow());
	//msg->setString("Test3", "Hello World");


	//int64 t;
	//t = msg->getTime("1asdf", "Test");
	//t = msg->getTime("3asdf", "Test");
	//t = msg->getTime("6asdf", "Test");

	//std::map<std::string, uint64> a = msg->getTimeMap("Test");

	//DataMessage* msg2 = new DataMessage();
	//msg2->setTimeMap("Test", a);

	//std::string str;
	//str = msg->toJSON();
	//printf(str.c_str());
	//str = msg2->toJSON();
	//printf(str.c_str());
	//str = msg->toXML();
	//printf(str.c_str());
	//str = msg2->toXML();
	//printf(str.c_str());
	//return 0;

//	MemoryRequestServer::UnitTest();
//	return 0;
//	MovingAverage::UnitTest();
//	return 0;

	//uint32 newID;
	//for (int ii = 0; ii < 10000; ii++) {
	//	if (!ThreadManager::CreateThread(TestThread, NULL, newID)) {
	//		return 0;
	//	}
	//}
	//newID = 0;

	#ifdef WINDOWS
		// Uncomment Standard.h lines 108-113
		// _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
	#else
		//	SIGABRT Abnormal termination 
		signal(SIGABRT, SIG_IGN);
		signal(SIGPIPE, SIG_IGN);
		signal(SIGBUS, SIG_IGN);
		signal(SIGHUP, signal_handler);
		//	SIGTERM Termination request 
		signal(SIGTERM, signal_handler);
		//	SIGINT CTRL+C signal 
		signal(SIGINT, signal_handler);
		signal(SIGABRT, signal_handler);
		//	SIGFPE Floating-point error 
		signal(SIGFPE, signal_handler);
		//	SIGILL Illegal instruction 
		signal(SIGILL, signal_handler);
		//	SIGSEGV Illegal storage access 
		signal(SIGSEGV, signal_handler);
	#endif

	//ThreadHandle tid;
	//uint32 osid;
	//utils::Event event;
	//utils::CreateThread(test, &event, tid, osid);
	//utils::CreateThread(test, &event, tid, osid);
	//utils::CreateThread(test, &event, tid, osid);
	//utils::CreateThread(test, &event, tid, osid);
	//utils::CreateThread(test, &event, tid, osid);

	//while (true) {
	//	printf("Signal event: %s\n", PrintTimeNowString().c_str());
	//	event.signal();
	//	utils::Sleep(0);
	//}

	//if (!utils::CreateDrumBeat(1, 50, testbeat, true)) {
	//	printf("Couldn't create beat 1...\n");
	//	return 0;
	//}
	//utils::Sleep(10);
	//if (!utils::CreateDrumBeat(2, 50, testbeat, true)) {
	//	printf("Couldn't create beat 1...\n");
	//	return 0;
	//}
	//utils::Sleep(10);
	//if (!utils::CreateDrumBeat(3, 50, testbeat, true)) {
	//	printf("Couldn't create beat 1...\n");
	//	return 0;
	//}

	//utils::Sleep(1000000000);


	//uint32* bittest = new uint32[8];
	//uint32 size = sizeof(uint32)*8;
	//memset(bittest, 255, size);

	//uint32 loc = 242;
	//bit check;
	//if (!utils::GetBit(loc, bittest, size, check)) {
	//	fprintf(stderr, "Bit location %u already in use\n", loc);
	//	return 0;
	//}
	//if ((check == BITOCCUPIED))
	//	fprintf(stderr, "Bit location %u occupied\n", loc);
	//else
	//	fprintf(stderr, "Bit location %u free\n", loc);

	//// Reserve it
	//utils::SetBit(loc, BITOCCUPIED, bittest, size);

	//if (!utils::GetBit(loc, bittest, size, check)) {
	//	fprintf(stderr, "Bit location %u already in use\n", loc);
	//	return 0;
	//}
	//if ((check == BITOCCUPIED))
	//	fprintf(stderr, "Bit location %u occupied\n", loc);
	//else
	//	fprintf(stderr, "Bit location %u free\n", loc);

	//return 0;



//	uint8 a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8;
//	uint8 b1 = 111, b2 = 234, b3 = 124, b4 = 246, b5 = 24, b6 = 19, b7 = 20, b8 = 21;
//	uint8 x = 255;
//
//	uint64 conA = TYPEGEN(a1, a2, a3, a4, a5, a6, a7, a8);
////	uint64 conB = CONTEXT(b1, b2, b3, b4, b5, b6, b7, b8);
//	uint64 conB = CONTEXT(a1, a2, a3, a4, a5, x, x, x);
//
//	if (MATCH(conA, conB)) {
//		printf("Works\n");
//		return 0;
//	}
//	else {
//		printf("****** Error ******\n");
//		return 0;
//	}

//	uint8 type = TYPELEVEL(conA, 3);
//	printf("Level: %u\n", type);

	//ReplyCheck* replyCheck = new ReplyCheck();
	//uint32 newID = replyCheck->newEntry();
	//replyCheck->addReplier(newID, 1);
	//replyCheck->addReplier(newID, 2);
	//replyCheck->addReplier(newID, 3);
	//replyCheck->addReplier(newID, 4);

	//ReplyCheckEntry* entry;
	//uint8 res;
	//if ((res = replyCheck->waitForReplies(newID, 1000, &entry, false)) != SYNC_TIMEOUT)
	//	return 0;
	//replyCheck->addReply(newID, 1, true);
	//replyCheck->addReply(newID, 2, true);
	//replyCheck->addReply(newID, 3, true);
	//if ((res = replyCheck->waitForReplies(newID, 1000, &entry, false)) != SYNC_TIMEOUT)
	//	return 0;
	//replyCheck->addReply(newID, 4, false);
	//if ((res = replyCheck->waitForReplies(newID, 1000, &entry)) != SYNC_SUCCESS)
	//	return 0;

	utils::SetCommandLine(argc, argv);

	const char* procname = argv[0];
	int spaceid = GetParamInt(argc, argv, "space");
	const char* name = GetParam(argc, argv, "name");

	const char* logdir = GetParam(argc, argv, "logdir");
	const char* configfile = GetParam(argc, argv, "spec");
	const char* htmldir = GetParam(argc, argv, "html");

	uint32 len;
	char* configXML = NULL;
	if (configfile && strlen(configfile)) {
		configXML = utils::ReadAFile(configfile, len);
		if (configXML == NULL) {
			LogPrint(0, LOG_SYSTEM, 0, "Error reading PsySpec file '%s'...", configfile);
			exit(0);
		}
	}

	int port = GetParamInt(argc, argv, "port");
	if (port <= 0) {
		// see if we can find it in the first line of the PsySpec
		if (configXML && strlen(configXML)) {
			std::vector<std::string> lines = utils::TextListSplitLines(configXML, false);
			if (lines.size()) {
				std::string firstLine = lines.front();
				const char* portStr = strstr(firstLine.c_str(), "port=");
				if (portStr) portStr += 6; // move past 'port="'
				port = utils::Ascii2Int32(portStr);
				if (port >= 0)
					LogPrint(0, LOG_SYSTEM, 0, "Found main port in PsySpec: %d", port);
			}
		}
		if (port <= 0)
			port = 10000;
	}

	std::string verboseCmdline;
	int val;
	for (int n=0; n<SUBJECT_COUNT; n++) {
		if ( (val = GetParamInt(argc, argv, VerboseSubjects[n])) >= 0) {
			if (!n)
				LogSystem::SetLogLevelVerbose(val);
			else
				LogSystem::SetLogLevelVerbose(n, val);
			verboseCmdline += utils::StringFormat("%s=%u ", VerboseSubjects[n], (uint32)val);
		}
		if ( (val = GetParamInt(argc, argv, DebugSubjects[n])) >= 0) {
			if (!n)
				LogSystem::SetLogLevelDebug(val);
			else
				LogSystem::SetLogLevelDebug(n, val);
			verboseCmdline += utils::StringFormat("%s=%u ", DebugSubjects[n], (uint32)val);
		}
	}

	const char* test;
	if (HasParam(argc, argv, "test")) {
		test = GetParam(argc, argv, "test");
		// Unified CMSDK unit-test framework routing.
		bool        utVerbose = GetParamInt(argc, argv, "verbose", 0) != 0;
		bool        utFork    = GetParamInt(argc, argv, "fork", 1) != 0;  // fork=0 runs tests in-process (e.g. under a debugger)
		const char* utCompare = GetParam(argc, argv, "compare");
		const char* utOut     = GetParam(argc, argv, "out");
		const char* utOutDir  = GetParam(argc, argv, "outdir");

		// test=cmsdk is the CMSDK layer's OWN unit suite and must stay pure: it
		// only exercises CMSDK objects and must not stand up a Psyclone node.
		// Psyclone-system tests (which launch nodes/spaces/external execs) are
		// registered ONLY for the by-name / list paths below, never for the
		// cmsdk|sdk|all suite run. Psyclone-system testing lives under psytest.
		if (test == NULL || stricmp(test, "cmsdk") == 0 || stricmp(test, "sdk") == 0 ||
		    stricmp(test, "all") == 0) {
			bool ok = RunCMSDKTests("cmsdk", utCompare, utOut, utOutDir, utVerbose, false, utFork);
			exit(ok ? 0 : 1);
		}

		// Beyond this point a specific test (or a listing) may be a Psyclone
		// test, so make those available now.
		Register_Psyclone_Tests();

		if (stricmp(test, "list") == 0) {
			RunCMSDKTests(NULL, NULL, NULL, NULL, utVerbose, true, utFork);
			exit(0);
		}
		else if (stricmp(test, "http") == 0) {
			// Convenience alias: run only the HTTP round-trip test(s). network_https
			// is only registered in SSL builds, so include it when present.
			bool ok = RunCMSDKTests("network_http", utCompare, utOut, utOutDir, utVerbose, false, utFork);
			if (IsCMSDKTest("network_https"))
				ok = RunCMSDKTests("network_https", utCompare, utOut, utOutDir, utVerbose, false, utFork) && ok;
			exit(ok ? 0 : 1);
		}
		else if (IsCMSDKTest(test)) {
			bool ok = RunCMSDKTests(test, utCompare, utOut, utOutDir, utVerbose, false, utFork);
			exit(ok ? 0 : 1);
		}
		else if (stricmp(test, "perf") == 0) {
			PerfTest_CMSDK();
			exit(0);
		}
		else if (stricmp(test, "timer") == 0) {
			utils::UnitTest_Timer();
			exit(0);
		}
		else if (stricmp(test, "time") == 0) {
			PsyTime_UnitTest();
			exit(0);
		}
		else if (stricmp(test, "messageindex") == 0) {
			MessageIndex::UnitTest();
			exit(0);
		}
		else if (stricmp(test, "utils") == 0) {
			utils::UtilsTest();
			exit(0);
		}
		else if (stricmp(test, "server") == 0) {
			NetworkTest_TCPServer((uint16)GetParamInt(argc, argv, "port"));
			exit(0);
		}
		else if (stricmp(test, "client") == 0) {
			NetworkTest_TCPClient(GetParam(argc, argv, "server"), (uint16)GetParamInt(argc, argv, "port"));
			exit(0);
		}
		else if (stricmp(test, "network") == 0) {
			NetworkManager::UnitTest();
			exit(0);
		}
		else if (stricmp(test, "rest") == 0) {
			RESTParser::UnitTest();
			exit(0);
		}
		else if (stricmp(test, "requestgateway") == 0) {
			uint8 netlog = (uint8)GetParamInt(argc, argv, "netlog", 0);
			if (netlog)
				LogSystem::SetLogLevelVerbose(LOG_NETWORK, netlog);
			RequestGateway::UnitTest(
				(uint16)GetParamInt(argc, argv, "port", 3000),
				(uint32)GetParamInt(argc, argv, "executors", 1),
				(uint32)GetParamInt(argc, argv, "gateways", 1),
				(uint32)GetParamInt(argc, argv, "clients", 0),
				(uint32)GetParamInt(argc, argv, "webclients", 0),
				(uint32)GetParamInt(argc, argv, "websockets", 0),
				GetParamFloat(argc, argv, "sendfreq", 0.2),
				(int32)GetParamInt(argc, argv, "reconnect", 0),
				(uint32)GetParamInt(argc, argv, "payload", 1024*50),
				(uint32)GetParamInt(argc, argv, "processtime", 100),
				(uint32)GetParamInt(argc, argv, "runtime", 0)
				);
			return 0;
		}
		else if (stricmp(test, "delayedconnect") == 0) {
			NetworkManager::UnitTestDelayedConnect();
			exit(0);
		}
		else if (stricmp(test, "requestlogin") == 0) {
			RequestClient::TestServerLogin(
				GetParam(argc, argv, "server"),
				(uint16)GetParamInt(argc, argv, "port"),
				GetParam(argc, argv, "username"),
				GetParam(argc, argv, "password"),
				GetParam(argc, argv, "cmd")
				);
			exit(0);
		}
		else if (stricmp(test, "http") == 0) {
			NetworkManager::UnitTestHTTP();
			exit(0);
		}
		else if (stricmp(test, "websockets") == 0) {
			NetworkManager::WebsocketTest();
			exit(0);
		}
		else if (stricmp(test, "httpserver") == 0) {
			std::vector<std::string> urls;
			urls.push_back("/main.css");
			urls.push_back("/main.html");
			urls.push_back("/main.js");
			urls.push_back("/main_template.xml");
			urls.push_back("/psyutils.js");
			urls.push_back("/readme.html");
			urls.push_back("/simplepage_template.html");
			urls.push_back("/system.css");
			urls.push_back("/system.html");
			urls.push_back("/system.js");
			urls.push_back("/test.xml");
			NetworkManager::TestHTTP("localhost", 10000, urls);
			//utils::Sleep(100000000);
			exit(0);
		}
		else if (stricmp(test, "https") == 0) {
			NetworkManager* manager = new NetworkManager();
			HTTPReply* reply = manager->makeHTTPRequest("https://www.ssllabs.com/ssltest/", 10000);
			utils::Sleep(100000000);
			exit(0);
		}
		else if (stricmp(test, "queues") == 0) {
			ProcessMemory::UnitTest();
		//	MemoryQueues::UnitTestQueues();
			exit(0);
		}
		else if (stricmp(test, "cpu") == 0) {
			uint64 t1 = GetTimeNow();
			uint64 cpu1;
			utils::GetCPUTicks(cpu1);
			for (uint64 kk = 0; kk < 1000000; kk++) {
				double aa = sqrt(sqrt(sqrt(100019.0 * kk)));
			}
			uint64 cpu2;
			utils::GetCPUTicks(cpu2);
			uint64 t2 = GetTimeNow();

			uint64 tt = t2 - t1;
			uint64 cc = cpu2 - cpu1;

			printf("Time usage: %llu - %llu = %llu\nCPU usage: %llu - %llu = %llu\n\n",
			t2, t1, tt, cpu2, cpu1, cc);
			exit(0);
		}
		else {
			LogPrint(0, LOG_SYSTEM, 0, "Test '%s' is not recognised\n", test);
			exit(0);
		}
	}	

	if (HasParam(argc, argv, "annotate")) {
		const char* annotateFile, *outputDir;
		annotateFile = GetParam(argc, argv, "annotate");
		outputDir = GetParam(argc, argv, "outputdir");
		if (!annotateFile || !outputDir) {
			LogPrint(0, LOG_SYSTEM, 0, "Usage: Psyclone annotate=file outputdir=dir\n");
			exit(0);
		}
		ConvertAnnotationToMessageRecording(annotateFile, outputDir);
		exit(0);
	}

	if (HasParam(argc, argv, "listmsg")) {
		MessagePlayer* msgPlayer = new MessagePlayer();
		if (!msgPlayer->initRead(GetParam(argc, argv, "listmsg"))) {
			LogPrint(0, LOG_SYSTEM, 0, "Dir '%s' doesn't contain any message recordings\n", GetParam(argc, argv, "listmsg"));
			exit(0);
		}
		printf("\n%s\n", msgPlayer->printAllString(GetParam(argc, argv, "format")).c_str());
		exit(0);
	}

	if (HasParam(argc, argv, "exportmsg")) {
		MessagePlayer* msgPlayer = new MessagePlayer();
		if (!msgPlayer->initRead(GetParam(argc, argv, "exportmsg"))) {
			LogPrint(0, LOG_SYSTEM, 0, "Dir '%s' doesn't contain any message recordings\n", GetParam(argc, argv, "exportmsg"));
			exit(0);
		}
		const char* filename = GetParam(argc, argv, "filename");
		if (filename && strlen(filename))
			msgPlayer->exportToCSVFile(filename, GetParam(argc, argv, "separator"));
		else
			printf("%s", msgPlayer->exportToCSV(GetParam(argc, argv, "separator")).c_str());
		exit(0);
	}

	//Node* node2;
	// psyclone psytest
	//    run single node on local computer
	// psyclone psytest=2
	//    run two nodes on local computer, other port auto-detect
	// psyclone psytest=N
	//    run N nodes on local computer, other ports auto-detect
	// psyclone psytest=N nodes=192.168.1.12:5000,192.168.1.13:8000
	//    nodes: other nodes already running
	//    N: will start N - count(nodes) local nodes, at least one
	

	std::string cmdline;
	std::list<uint32> autoStartedNodeProcIDs;
	uint32 procID;
	int32 nodeCount = 0;
	uint32 nodeIndex = 2;
	uint32 port2 = 0;
	std::string nodesXML;

	while (true) {
		cmdline.clear();
		autoStartedNodeProcIDs.clear();
		procID = 0;
		nodeCount = 0;
		nodeIndex = 2;
		port2 = 0;

		if (HasParam(argc, argv, "psytest")) {
			nodeCount = GetParamInt(argc, argv, "psytest");
			if (nodeCount <= 0)
				nodeCount = 1;
			nodesXML = utils::StringFormat("	<node name=\"Main\" address=\"localhost\" port=\"%u\" />\n", port);
			if (nodeCount > 1) {
				const char* nodes = GetParam(argc, argv, "nodes");
				// Build up nodes list
				if (nodes) {
					std::multimap<std::string, std::string> nodeMap = utils::TextMultiMapSplit(nodes, ",", ":");
					std::multimap<std::string, std::string>::iterator iN = nodeMap.begin(), eN = nodeMap.end();
					while (iN != eN) {
						nodesXML += utils::StringFormat("	<node name=\"Node%u\" address=\"%s\" port=\"%s\" />\n",
							nodeIndex++, iN->first.c_str(), iN->second.c_str());
						iN++;
					}
				}
				uint16 lastPort = port, nodePort = 0;
				if (nodeCount - nodeIndex + 1 > 20) {
					LogPrint(0, LOG_SYSTEM, 0, "Too many nodes (%d) being auto started", nodeCount - nodeIndex);
					exit(0);
				}

				uint32 rows = 3;
				uint32 cols = (uint32)ceilf((float)((nodeCount + 1.0) / rows));
				uint32 dWidth, dHeight;
				uint16 w = 400, h = 300;
				if (utils::GetDesktopSize(dWidth, dHeight)) {
					w = dWidth / cols;
					h = dHeight / rows;
				}
				utils::RenameConsoleWindow("Psyclone Node 1", true);
				utils::Sleep(100);
				utils::MoveConsoleWindow(0, 0, w, dHeight);
				while (nodeCount - nodeIndex + 1 > 0) {
					if (utils::GetNextAvailableLocalPort(lastPort, nodePort)) {
						nodesXML += utils::StringFormat("	<node name=\"Node%u\" address=\"localhost\" port=\"%u\" />\n",
							nodeIndex, nodePort);
						// Auto start local node
						cmdline = utils::StringFormat("%s port=%u %s",
							utils::GetCommandLineExecutable().c_str(),
							nodePort,
							verboseCmdline.c_str());
						utils::Sleep(500);
						if (procID = utils::NewProcess(cmdline.c_str(), NULL,
							utils::StringFormat("Psyclone Node %u", nodeIndex).c_str(), w * ((int)((nodeIndex - 2) / rows) + 1), h * ((nodeIndex - 2) % rows), w, (int)(h * 0.9))) {
							autoStartedNodeProcIDs.push_back(procID);
							LogPrint(0, LOG_SYSTEM, 0, "Started Node %u [%u]: %s", nodeIndex, nodePort, cmdline.c_str());
						}
						else {
							LogPrint(0, LOG_SYSTEM, 0, "Couldn't autostart local node %u...", nodeIndex);
							delete[] configXML;
							exit(0);
						}

						lastPort = nodePort;
						nodeIndex++;
					}
				}
			}

			uint8 mode = 0;
			if (HasParam(argc, argv, "stay"))
				mode |= STAYAFTERFINISH;
			if (HasParam(argc, argv, "long"))
				mode |= LONGPINGTEST | NOTIMETEST;
			if (HasParam(argc, argv, "short"))
				mode |= SHORTTEST;
			if (HasParam(argc, argv, "cont"))
				mode |= CONTINUOUSTEST;

			if (!configXML)
				configXML = buildPsyTestSpec(len, nodeCount, nodesXML, mode);
			LogPrint(0, LOG_SYSTEM, 3, "Using PsySpec:\n%s\n", configXML);
		}

		if (spaceid > 0) {
			if (name)
				LogPrint(0, LOG_SYSTEM, 0, "Starting space '%s' ID: %u...", name, spaceid);
			else
				LogPrint(0, LOG_SYSTEM, 0, "Starting space ID %u...", spaceid);

			PsySpace* space = new PsySpace(name, false, spaceid, false);
			if (!space->connect(port, true)) {
				LogPrint(0, LOG_SYSTEM, 0, "Space ID %u couldn't initialise...", spaceid);
				delete[] configXML;
				exit(0);
			}
			if (!space->start()) {
				LogPrint(0, LOG_SYSTEM, 0, "Space ID %u couldn't start...", spaceid);
				delete[] configXML;
				exit(0);
			}
			LogPrint(0, LOG_SYSTEM, 0, "Space ID %u has started successfully...", spaceid);

			while (!space->hasShutdown())
				utils::Sleep(100);
			LogPrint(0, LOG_SYSTEM, 0, "Space ID %u has shutdown...", spaceid);
			delete(space);
			delete[] configXML;
			exit(0);
		}

		node = new Node(logdir, procname);
		if (HasParam(argc, argv, "logintersystem"))
			node->logIntersystemTraffic = true;
		// Set the spec file before init so %specdir% is available during config parsing (T1.4)
		if (configfile && strlen(configfile))
			node->setConfigFile(configfile);
		else if (HasParam(argc, argv, "psytest"))
			node->setConfigFile("PsyTest (auto)");
		if (!node->init(port, configXML, htmldir)) {
			LogPrint(0, LOG_SYSTEM, 0, "Could not initialise local Node...\n");
			utils::Sleep(1000);
			delete(node);
			delete[] configXML;
			exit(0);
		}

		int8 status = node->run();

		if (!node->terminate()) {
			LogPrint(0, LOG_SYSTEM, 0, "Could not terminate local Node...\n");
			delete(node);
			delete[] configXML;
			exit(0);
		}
		//printf("-9-"); fflush(stdout);

		// Shutdown autostarted nodes
		std::list<uint32>::iterator i = autoStartedNodeProcIDs.begin(), e = autoStartedNodeProcIDs.end();
		while (i != e) {
			utils::EndProcess(*i);
			i++;
		}
		autoStartedNodeProcIDs.clear();

		delete(node);

		if (status != NODE_EXIT_RESTART)
			break;

		utils::Sleep(1000);
	}
	delete[] configXML;

	//printf("-10-"); fflush(stdout);
	exit(0);
}




std::string addTimePsyTestSpec(uint16 nodeCount, std::string context, std::string nextContext) {

	// top and main node 
	std::string xml = utils::StringFormat(
"	<!-- ************** Time Test ************** -->\n\
	\n\
	<module name=\"TimeMaster\" node=\"Main\">\n\
		<parameter name=\"Count\" type=\"Integer\" value=\"%%TimeCount%%\" />\n\
		<parameter name=\"Nodes\" type=\"Integer\" value=\"%%Nodes%%\" />\n\
		<context name=\"%s\">\n\
			<trigger name=\"Start\" context=\"%s\" />\n\
			<trigger name=\"InputMsg\" type=\"time.1\" />\n\
			<crank name=\"TimeMaster\" function=\"Examples::PsyTest_TimeMaster\" />\n\
			<post name=\"OutputMsg\" type=\"time.2\" ttl=\"1000000\" />\n\
			<post name=\"Done\" context=\"%s\" />\n\
		</context>\n\
	</module>\n", context.c_str(), context.c_str(), nextContext.c_str());

	if (nodeCount == 1) {
		xml += utils::StringFormat("\n\
	<module name=\"TimeSlave2\" node=\"Main\">\n\
		<context name=\"%s\">\n\
			<trigger name=\"InputMsg\" type=\"time.2\" />\n\
			<crank name=\"TimeSlave2\" function=\"Examples::PsyTest_TimeSlave\" />\n\
			<post name=\"OutputMsg\" type=\"time.1\" ttl=\"1000000\" />\n\
		</context>\n\
	</module>\n\n", context.c_str());
	}
	else {
		for (uint16 n=2; n<=nodeCount; n++) {
			xml += utils::StringFormat("\n\
	<module name=\"TimeSlave%u\" node=\"Node%u\">\n\
		<context name=\"%s\">\n\
			<trigger name=\"InputMsg\" type=\"time.2\" />\n\
			<crank name=\"TimeSlave%u\" function=\"Examples::PsyTest_TimeSlave\" />\n\
			<post name=\"OutputMsg\" type=\"time.1\" ttl=\"1000000\" />\n\
		</context>\n\
	</module>\n\n", n, n, context.c_str(), n);
		}
	}

	return xml;
}

std::string addPingPsyTestSpec(uint16 nodeCount, std::string context, std::string nextContext) {

	// top and main node 
	std::string xml = utils::StringFormat(
"	<!-- ************** Ping Test ************** -->\n\
	\n\
	<module name=\"PingMaster\" node=\"Main\">\n\
		<customview name=\"MyStatus\" template=\"elements/mystatus.html\" />\n\
		<parameter name=\"Count\" type=\"Integer\" value=\"%%PingCount%%\" />\n\
		<parameter name=\"Cycles\" type=\"Integer\" value=\"%%PingCycles%%\" />\n\
		<parameter name=\"Nodes\" type=\"Integer\" value=\"%%Nodes%%\" />\n\
		<context name=\"%s\">\n\
			<trigger name=\"Start\" context=\"%s\" />\n\
			<trigger name=\"InputMsg\" type=\"ping.1\" />\n\
			<crank name=\"PingMaster\" function=\"Examples::PsyTest_PingMaster\" />\n\
			<post name=\"OutputMsg\" type=\"ping.2\" />\n\
			<post name=\"Done\" context=\"%s\" />\n\
		</context>\n\
	</module>\n", context.c_str(), context.c_str(), nextContext.c_str());

	if (nodeCount == 1) {
		xml += utils::StringFormat("\n\
	<module name=\"PingSlave2\" node=\"Main\">\n\
		<context name=\"%s\">\n\
			<trigger name=\"InputMsg\" type=\"ping.2\" />\n\
			<crank name=\"PingSlave2\" function=\"Examples::PsyTest_PingSlave\" />\n\
			<post name=\"OutputMsg\" type=\"ping.1\" />\n\
		</context>\n\
	</module>\n\n", context.c_str());
	}
	else {
		for (uint16 n=2; n<=nodeCount; n++) {
			xml += utils::StringFormat("\n\
	<module name=\"PingSlave%u\" node=\"Node%u\">\n\
		<context name=\"%s\">\n\
			<trigger name=\"InputMsg\" type=\"ping.%u\" />\n\
			<crank name=\"PingSlave%u\" function=\"Examples::PsyTest_PingSlave\" />\n\
			<post name=\"OutputMsg\" type=\"ping.%u\" />\n\
		</context>\n\
	</module>\n\n", n, n, context.c_str(), n, n, (n < nodeCount) ? n+1 : 1);
		}
	}

	return xml;
}

std::string addPingUDPPsyTestSpec(uint16 nodeCount, std::string context, std::string nextContext) {

	// top and main node 
	std::string xml = utils::StringFormat(
"	<!-- ************** PingUDP Test ************** -->\n\
	\n\
	<module name=\"PingUDPMaster\" node=\"Main\">\n\
		<parameter name=\"Count\" type=\"Integer\" value=\"%%PingCount%%\" />\n\
		<parameter name=\"Cycles\" type=\"Integer\" value=\"%%PingCycles%%\" />\n\
		<parameter name=\"Nodes\" type=\"Integer\" value=\"%%Nodes%%\" />\n\
		<customview name=\"MyStatus\" template=\"elements/mystatus.html\" />\n\
		<context name=\"%s\">\n\
			<trigger name=\"Start\" context=\"%s\" />\n\
			<trigger name=\"InputMsg\" type=\"ping.1\" />\n\
			<crank name=\"PingUDPMaster\" function=\"Examples::PsyTest_PingMaster\" />\n\
			<post name=\"OutputMsg\" type=\"ping.2\" guaranteed=\"no\" />\n\
			<post name=\"Done\" context=\"%s\" />\n\
		</context>\n\
	</module>\n", context.c_str(), context.c_str(), nextContext.c_str());

	if (nodeCount == 1) {
		xml += utils::StringFormat("\n\
	<module name=\"PingUDPSlave2\" node=\"Main\">\n\
		<context name=\"%s\">\n\
			<trigger name=\"InputMsg\" type=\"ping.2\" />\n\
			<crank name=\"PingUDPSlave2\" function=\"Examples::PsyTest_PingSlave\" />\n\
			<post name=\"OutputMsg\" type=\"ping.1\" guaranteed=\"no\" />\n\
		</context>\n\
	</module>\n\n", context.c_str());
	}
	else {
		for (uint16 n=2; n<=nodeCount; n++) {
			xml += utils::StringFormat("\n\
	<module name=\"PingUDPSlave%u\" node=\"Node%u\">\n\
		<context name=\"%s\">\n\
			<trigger name=\"InputMsg\" type=\"ping.%u\" />\n\
			<crank name=\"PingUDPSlave%u\" function=\"Examples::PsyTest_PingSlave\" />\n\
			<post name=\"OutputMsg\" type=\"ping.%u\" guaranteed=\"no\" />\n\
		</context>\n\
	</module>\n\n", n, n, context.c_str(), n, n, (n < nodeCount) ? n+1 : 1);
		}
	}

	return xml;
}

std::string addSignalPsyTestSpec(uint16 nodeCount, std::string context, std::string nextContext) {

	// top and main node 
	std::string xml = utils::StringFormat(
"	<!-- ************** Signal Test ************** -->\n\
	\n\
	<module name=\"SignalMaster\" node=\"Main\">\n\
		<parameter name=\"Count\" type=\"Integer\" value=\"%%SignalCount%%\" />\n\
		<parameter name=\"Cycles\" type=\"Integer\" value=\"%%SignalCycles%%\" />\n\
		<parameter name=\"Nodes\" type=\"Integer\" value=\"%%Nodes%%\" />\n\
		<context name=\"%s\">\n\
			<trigger name=\"Start\" context=\"%s\" />\n\
			<signal name=\"Input\" type=\"signal.1\" />\n\
			<crank name=\"SignalMaster\" function=\"Examples::PsyTest_SignalMaster\" />\n\
			<signal name=\"Output\" type=\"signal.2\" />\n\
			<post name=\"Done\" context=\"%s\" />\n\
		</context>\n\
	</module>\n", context.c_str(), context.c_str(), nextContext.c_str());

	if (nodeCount == 1) {
		xml += utils::StringFormat("\n\
	<module name=\"SignalSlave2\" node=\"Main\">\n\
		<context name=\"%s\">\n\
			<trigger name=\"Start\" context=\"%s\" />\n\
			<signal name=\"Input\" type=\"signal.2\" />\n\
			<crank name=\"SignalSlave2\" function=\"Examples::PsyTest_SignalSlave\" />\n\
			<signal name=\"Output\" type=\"signal.1\" />\n\
		</context>\n\
	</module>\n\n", context.c_str(), context.c_str());
	}
	else {
		for (uint16 n=2; n<=nodeCount; n++) {
			xml += utils::StringFormat("\n\
	<module name=\"SignalSlave%u\" node=\"Node%u\">\n\
		<context name=\"%s\">\n\
			<trigger name=\"Start\" context=\"%s\" />\n\
			<signal name=\"Input\" type=\"signal.%u\" />\n\
			<crank name=\"SignalSlave%u\" function=\"Examples::PsyTest_SignalSlave\" />\n\
			<signal name=\"Output\" type=\"signal.%u\" />\n\
		</context>\n\
	</module>\n\n", n, n, context.c_str(), context.c_str(), n, n, (n < nodeCount) ? n+1 : 1);
		}
	}

	return xml;
}

std::string addRetrievePsyTestSpec(uint16 nodeCount, std::string context, std::string nextContext) {

	// top and main node 
	std::string xml = utils::StringFormat(
"	<!-- ************** Catalogs Test ************** -->\n\
	\n\
	<whiteboard name=\"WB1\" node=\"Main\" key=\"count\" keytype=\"integer\" />\n\
	\n\
	<whiteboard name=\"WB2\" node=\"Main\" key=\"count\" keytype=\"integer\">\n\
		<trigger name=\"Test1\" type=\"test.*\" />\n\
	</whiteboard>\n\
	\n\
	<catalog name=\"MyFiles\" node=\"Main\" type=\"FileCatalog\" root=\"./\">\n\
		<parameter name=\"ReadOnly\" type=\"String\" value=\"no\" />\n\
	</catalog>\n\
	\n\
	<catalog node=\"Main\" name=\"MyData\" type=\"DataCatalog\" interval=\"2000\" root=\"./mydata.dat\" />\n\
	\n\
	<module name=\"RetrieveTest1\" node=\"Main\">\n\
		<parameter name=\"Count\" type=\"Integer\" value=\"%%RequestCount%%\" />\n\
		<context name=\"%s\">\n\
			<trigger name=\"Start\" context=\"%s\" />\n\
			<retrieve name=\"r1\" source=\"WB1\" maxcount=\"100\" />\n\
			<retrieve name=\"r2\" source=\"WB1\" key=\"count\" keytype=\"integer\" />\n\
			<retrieve name=\"r3\" source=\"WB2\" key=\"count\" keytype=\"integer\" />\n\
			<query name=\"MyFiles\" source=\"MyFiles\" subdir=\"test\" ext=\"txt\" binary=\"yes\" />\n\
			<query name=\"MyData\" source=\"MyData\" />\n\
			<crank name=\"RetrieveTest1\" function=\"Examples::PsyTest_RetrieveTest\" />\n\
			<post name=\"OutputMsg\" to=\"WB1\" type=\"test.1\" ttl=\"2000000\" />\n\
			<post name=\"Done\" context=\"%s\" />\n\
			<setup>Hello world Multi</setup>\n\
		</context>\n\
	</module>\n", context.c_str(), context.c_str(), (nodeCount == 1) ? nextContext.c_str() : utils::StringFormat("%s.2", context.c_str()).c_str() );

	for (uint16 n=2; n<=nodeCount; n++) {
		xml += utils::StringFormat("\n\
	<module name=\"RetrieveTest%u\" node=\"Node%u\">\n\
		<parameter name=\"Count\" type=\"Integer\" value=\"%%RequestCount%%\" />\n\
		<context name=\"%s.%u\">\n\
			<trigger name=\"Start\" context=\"%s.%u\" />\n\
			<retrieve name=\"r1\" source=\"WB1\" maxcount=\"100\" />\n\
			<retrieve name=\"r2\" source=\"WB1\" key=\"count\" keytype=\"integer\" />\n\
			<retrieve name=\"r3\" source=\"WB2\" key=\"count\" keytype=\"integer\" />\n\
			<query name=\"MyFiles\" source=\"MyFiles\" subdir=\"test\" ext=\"txt\" binary=\"yes\" />\n\
			<query name=\"MyData\" source=\"MyData\" />\n\
			<crank name=\"RetrieveTest1\" function=\"Examples::PsyTest_RetrieveTest\" />\n\
			<post name=\"OutputMsg\" to=\"WB1\" type=\"test.%u\" ttl=\"2000000\" />\n\
			<post name=\"Done\" context=\"%s\" />\n\
			<setup>Hello world Multi</setup>\n\
		</context>\n\
	</module>\n", n, n, context.c_str(), n, context.c_str(), n, n,
			(n == nodeCount) ? nextContext.c_str() : utils::StringFormat("%s.%u", context.c_str(), n+1).c_str());
	}

	return xml;
}





std::string addTriggerGroupPsyTestSpec(uint16 nodeCount, std::string context, std::string nextContext) {
	// TGMaster (Main): kicks the round (broadcast tggroup.kick), hosts the
	// <triggergroup> joining one member per node (tggroup.m1..mN), reports and
	// advances the chain once the full set joins. In multi-node runs the members
	// arrive from different nodes -> exercises the cross-node join delivery path.
	std::string members;
	for (uint16 n = 1; n <= nodeCount; n++)
		members += utils::StringFormat("\t\t\t\t<trigger name=\"m%u\" type=\"tggroup.m%u\" />\n", n, n);

	std::string xml = utils::StringFormat(
"	<!-- ************** Trigger Group Test ************** -->\n\
	\n\
	<module name=\"TGMaster\" node=\"Main\">\n\
		<parameter name=\"Nodes\" type=\"Integer\" value=\"%%Nodes%%\" />\n\
		<context name=\"%s\">\n\
			<trigger name=\"Start\" context=\"%s\" />\n\
			<triggergroup name=\"TG\" maxage=\"4000\">\n\
%s\
			</triggergroup>\n\
			<crank name=\"TGMaster\" function=\"Examples::PsyTest_TGMaster\" />\n\
			<post name=\"Kick\" type=\"tggroup.kick\" ttl=\"1000000\" />\n\
			<post name=\"Done\" context=\"%s\" />\n\
		</context>\n\
	</module>\n", context.c_str(), context.c_str(), members.c_str(), nextContext.c_str());

	// One member module per node (Main is node 1). Each triggers on the kick and
	// posts its own member type tggroup.mN.
	for (uint16 n = 1; n <= nodeCount; n++) {
		std::string nodeName = (n == 1) ? "Main" : utils::StringFormat("Node%u", n);
		xml += utils::StringFormat("\n\
	<module name=\"TGMember%u\" node=\"%s\">\n\
		<context name=\"%s\">\n\
			<trigger name=\"Kick\" type=\"tggroup.kick\" />\n\
			<crank name=\"TGMember%u\" function=\"Examples::PsyTest_TGMember\" />\n\
			<post name=\"Member\" type=\"tggroup.m%u\" ttl=\"1000000\" />\n\
		</context>\n\
	</module>\n", n, nodeName.c_str(), context.c_str(), n, n);
	}

	return xml;
}


std::string addExternalPsyTestSpec(uint16 nodeCount, std::string context, std::string nextContext) {

	// External-module auto-start stage (T1.4). ExtMaster (node side) pings
	// ext.ping and counts ext.pong replies; the pong crank runs in the
	// ExternalModules helper process that Psyclone auto-launches via the
	// <executable> on ExtPong. Proves auto-launch + space connect + a real
	// round-trip across a separate OS process. ExtMaster's watchdog fails soft
	// (reports FAIL and advances the chain) if the helper never connects, so a
	// missing/partial build cannot hang the suite. Single-node stage (the helper
	// process attaches to the Main node's local shared memory).
	std::string xml = utils::StringFormat(
"	<!-- ************** External Module Test (T1.4 auto-start) ************** -->\n\
	\n\
	<space name=\"MySpace\" type=\"external\" />\n\
	\n\
	<module name=\"ExtMaster\" node=\"Main\">\n\
		<parameter name=\"Count\" type=\"Integer\" value=\"20\" />\n\
		<parameter name=\"ConnectTimeout\" type=\"Integer\" value=\"15000\" />\n\
		<context name=\"%s\">\n\
			<trigger name=\"Start\" context=\"%s\" />\n\
			<trigger name=\"Pong\" type=\"ext.pong\" />\n\
			<crank name=\"ExtMaster\" function=\"Examples::PsyTest_External\" />\n\
			<post name=\"Ping\" type=\"ext.ping\" />\n\
			<post name=\"Done\" context=\"%s\" />\n\
		</context>\n\
	</module>\n\
	\n\
	<module name=\"ExtPong\" space=\"MySpace\">\n\
		<executable output=\"psyclonelog\" autorestart=\"no\">%%execdir%%ExternalModules %%port%% Test</executable>\n\
		<trigger name=\"Ping\" type=\"ext.ping\" />\n\
		<crank name=\"Test\" />\n\
		<post name=\"Ball\" type=\"ext.pong\" />\n\
	</module>\n", context.c_str(), context.c_str(), nextContext.c_str());

	return xml;
}

char* buildPsyTestSpec(uint32& len, uint16 nodeCount, std::string nodesXML, uint8 mode) {

	std::string configXML;

	uint32 timeCount = (mode & NOTIMETEST) ? 1 : 5;
	uint32 pingCount = (mode & LONGPINGTEST) ? 100000 : 10;
	uint32 pingCycles = 10;
	uint32 signalCount = 10;
	uint32 signalCycles = 10;
	uint32 requestCount = 10;

	if (mode & SHORTTEST) {
		timeCount = 1;
		pingCount = 2;
		pingCycles = 2;
		signalCount = 2;
		signalCycles = 2;
		requestCount = 2;
	}

	std::string endGame = "type=\"Psyclone.Shutdown\"";
	if (mode & STAYAFTERFINISH)
		endGame = "type=\"Test.Finished\"";
	else if (mode & CONTINUOUSTEST)
		endGame = "context=\"Test.TimeTest\"";

	configXML = utils::StringFormat(
"<psySpec>\n%s\n\
	<library name=\"Examples\" library=\"Examples\" /> <!-- universal library on all nodes -->\n\
	\n\
	<variable name=\"Nodes\" value=\"%u\" />\n\
	<variable name=\"TimeCount\" value=\"%u\" />\n\
	<variable name=\"PingCount\" value=\"%u\" />\n\
	<variable name=\"PingCycles\" value=\"%u\" />\n\
	<variable name=\"SignalCount\" value=\"%u\" />\n\
	<variable name=\"SignalCycles\" value=\"%u\" />\n\
	<variable name=\"RequestCount\" value=\"%u\" />\n\
	\n\
	<!-- ************** Startup ************** -->\n\
	\n\
	<module name=\"Startup\">\n\
		<trigger name=\"Ready\" type=\"Psyclone.Ready\" />\n\
		<crank name=\"Start\" function=\"Simple\" />\n\
		<post name=\"Done\" context=\"Test.TimeTest\" />\n\
	</module>\n\
	\n%s%s%s%s%s%s%s\n\
	\n\
	<!-- ************** Shutdown ************** -->\n\
	\n\
	<module name=\"Shutdown\" node=\"Main\">\n\
		<context name=\"Test.Done\">\n\
			<trigger name=\"Start\" context=\"Test.Done\" />\n\
			<crank name=\"Pong\" function=\"Simple\" />\n\
		<post name=\"Done\" %s />\n\
		</context>\n\
	</module>\n\
	\n\
</psySpec>\n",
	nodesXML.c_str(), nodeCount,
	timeCount, pingCount, pingCycles, signalCount, signalCycles, requestCount,
	addTimePsyTestSpec(nodeCount, "Test.TimeTest", "Test.TriggerGroupTest").c_str(),
	addTriggerGroupPsyTestSpec(nodeCount, "Test.TriggerGroupTest", "Test.PingTest").c_str(),
	addPingPsyTestSpec(nodeCount, "Test.PingTest", "Test.PingUDPTest").c_str(),
	addPingUDPPsyTestSpec(nodeCount, "Test.PingUDPTest", "Test.SignalTest").c_str(),
	addSignalPsyTestSpec(nodeCount, "Test.SignalTest", "Test.ExternalTest").c_str(),
	addExternalPsyTestSpec(nodeCount, "Test.ExternalTest", "Test.CatalogTest").c_str(),
	addRetrievePsyTestSpec(nodeCount, "Test.CatalogTest", "Test.Done").c_str(),
	endGame.c_str());

	len = (uint32)(configXML.size() + 1);
	char* xml = new char[len];
	utils::strcpyavail(xml, configXML.c_str(), len, true);

	// printf("%s", xml);
	//exit(0);

	return xml;
}


int CRTLCCount = 0;
void signal_handler(int sig) {
	//	SIGABRT Abnormal termination 
	//	SIGFPE Floating-point error 
	//	SIGILL Illegal instruction 
	//	SIGINT CTRL+C signal 
	//	SIGSEGV Illegal storage access 
	//	SIGTERM Termination request 

	signal(sig, SIG_IGN);

	switch (sig) {
	case SIGABRT:
		printf("\n*******************************************************\n");
		printf("         Received Abnormal Termination Signal    \n");
		printf("*******************************************************\n");
		if (node)
			node->terminate();
		signal(sig, signal_handler);
		break;
	case SIGTERM:
		printf("\n*******************************************************\n");
		printf("            Received Termination Signal    \n");
		printf("*******************************************************\n");
		if (node)
			node->terminate();
		CRTLCCount++;
		if (CRTLCCount > 3)
			exit(0);
		signal(sig, signal_handler);
		break;
	case SIGINT:
		printf("\n*******************************************************\n");
		printf("           Received Shutdown Signal (CTRL+c)           \n");
		printf("*******************************************************\n");
		if (node)
			node->shutdown(3000);
		CRTLCCount++;
		if (CRTLCCount > 3)
			exit(0);
		signal(sig, signal_handler);
		break;
#ifdef WIN32
#else
	case SIGHUP:
		printf("\n*******************************************************\n");
		printf("              Received Reset Signal    \n");
		printf("*******************************************************\n");
		if (node)
			node->terminate();
		signal(sig, signal_handler);
		break;
#endif // WIN32
	case SIGFPE:
	case SIGILL:
	case SIGSEGV:
		// case SIG_ERR:
		printf("\n*******************************************************\n");
		printf("            Received Termination Signal    \n");
		printf("*******************************************************\n");
		printf("\n");
		printf("\
Psyclone or a module running inside Psyclone has encountered a problem and needs to close.\
We are sorry for the inconvenience. If this problem persists, please contact Communicative\
Machines at support@cmlabs.com.\n");
		printf("\n");
		exit(0);
		break;
	default:
		signal(sig, signal_handler);
		break;
	}
}





bool ConvertAnnotationToMessageRecording(std::string annotationFile, std::string outputDir) {
	std::string inputData = utils::ReadAFileString(annotationFile);
	if (!inputData.length()) {
		LogPrint(0, LOG_SYSTEM, 0, "Input file '%s' empty or not there\n", annotationFile.c_str());
		return false;
	}

	MessagePlayer* msgPlayer = new MessagePlayer();
	if (!msgPlayer->initWrite(outputDir.c_str(), 0, 1000000000)) {
		LogPrint(0, LOG_SYSTEM, 0, "Output dir '%s' couldn't be created\n", outputDir.c_str());
		return false;
	}

	std::vector<std::string> lines = utils::TextListSplitLines(inputData.c_str(), false, true);
	std::vector<std::string>::iterator i = lines.begin(), e = lines.end();
	std::vector<std::string> items;
	std::vector<std::string> times;

	DataMessage* msg = new DataMessage();
	uint64 startTime = GetTimeNow(), t;
	uint32 l = 0;
	uint32 hh, mm, ss, ms;
	while (i != e) {
		items = utils::TextListSplit((*i).c_str(), "\t", true, true);
		if (items.size() == 6) {
			// 0: Marker name    2: timestamp  'HH;MM;SS;mmm'
			times = utils::TextListSplit(items.at(2).c_str(), ":");
			if (times.size() < 3)
				times = utils::TextListSplit(items.at(2).c_str(), ";");
			if (times.size() < 3) {
				i++;
				continue;
			}
			hh = utils::Ascii2Uint32(times.at(0).c_str());
			mm = utils::Ascii2Uint32(times.at(1).c_str());
			ss = utils::Ascii2Uint32(times.at(2).c_str());
			if (times.size() > 3)
				ms = utils::Ascii2Uint32(times.at(3).c_str());
			else ms = 0;
			t = startTime + ((uint64)((hh * 3600) + (mm * 60) + ss) * 1000000) + (ms * 1000);
			msg->setCreatedTime(t);
			msg->setSendTime(t);
			msg->setRecvTime(t);
			msgPlayer->addMessage(items.at(0).c_str(), msg);
		}
		else {
			LogPrint(0, LOG_SYSTEM, 0, "Line %u in file '%s' ignored, has %u entries...\n", l+1, annotationFile.c_str(), items.size());
		}
		i++;
	}

	msgPlayer->flushToDisk();

	printf("%s\n", msgPlayer->printAllString("text").c_str());

	return true;
}


