

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  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.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
// Replicoder.cpp : Defines the entry point for the console application.
//

#include "ExternalModules.h"

#if defined(WINDOWS)
	#include <windows.h>
#endif

// Set when a shutdown has been requested (Windows console control event);
// both crank loops poll it so the helper exits promptly and cleanly.
static volatile bool gShutdownRequested = false;

#if defined(WINDOWS)
// Psyclone's ExternalExec stopChild() delivers a CTRL_BREAK to the helper's
// process group for a graceful stop (see Psyclone/src/ExternalExec.cpp and
// utils::SendProcessBreak). Without a handler the default action would only
// kill the process after the parent's stop timeout expires and it falls back
// to TerminateProcess. Catch CTRL_BREAK/CTRL_C/CTRL_CLOSE and request a clean
// loop exit instead.
static BOOL WINAPI ExternalModulesCtrlHandler(DWORD ctrlType) {
	switch (ctrlType) {
	case CTRL_BREAK_EVENT:
	case CTRL_C_EVENT:
	case CTRL_CLOSE_EVENT:
		gShutdownRequested = true;
		return TRUE;
	default:
		return FALSE;
	}
}
#endif


int RunTestCrank(uint16 sysid, std::string crankName);
int RunRetrieveTestCrank(uint16 sysid, std::string crankName);

void PrintUsage(const char* progname) {
	printf("Usage: %s <sysid>   (sysid: port number of local Psyclone Node)\n\n", progname);
}

int main(int argc, char* argv[]) {

#if defined(WINDOWS)
	SetConsoleCtrlHandler(ExternalModulesCtrlHandler, TRUE);
#endif

	uint16 sysid = (uint16) utils::Ascii2Uint32(argv[1]);

	std::string crankName = "Test";
	if (argc > 2)
		crankName = argv[2];

	if (sysid)
		LogPrint(0, LOG_SPACE, 1, "Connecting to Psyclone ID %u to run crank '%s'...", sysid, crankName.c_str());
	else {
		sysid = 10000;
		LogPrint(0, LOG_SPACE, 1, "Connecting to Psyclone on default ID 10000 to run crank '%s'...", crankName.c_str());
	}

	if (crankName.compare("Test") == 0)
		return RunTestCrank(sysid, crankName);
	else if (crankName.compare("RetrieveTest") == 0)
		return RunRetrieveTestCrank(sysid, crankName);

	return 0;

}

int RunTestCrank(uint16 systemID, std::string crank) {

	int sysid = 10000;
	std::string crankName = "Test";
	if (systemID) sysid = 10000;
	if (crank.length()) crankName = crank;

	PsyAPI* api = NULL;
	PsySpace* space = NULL;
	int64 counter = 0;
	DataMessage* msg;
	std::string triggerName;

	// Keep checking if Psyclone node is still available
	while (!gShutdownRequested) {
		if (space && api && space->isConnected()) {
			if (space->hasShutdown()) {
				// The node shut our space down (CTRL_SYSTEM_SHUTDOWN); exit
				// promptly so a graceful stop needs no force-terminate.
				printf("Psyclone shut the space down, exiting...\n");
				break;
			}
			msg = api->waitForNewMessage(100);
			if (msg) {
				triggerName = api->getCurrentTriggerName();
				api->getParameter("Counter", counter);
				api->logPrint(3, "Got trigger '%s' message %u...", triggerName.c_str(), (uint32)counter);
				api->tweakParameter("Counter", 1);
				api->postOutputMessage("Ball");
			}
		}
		else {
			if (!space)
				space = new PsySpace("MySpace");
			// try to connect
			if (!space->connect(sysid)) {
				printf("Waiting to connect to Psyclone ID %u...\n", sysid);
				utils::Sleep(1000);
			}
			else {
				if (!space->start()) {
					printf("Couldn't start Psyclone ID %u...\n", sysid);
					delete(space);
					space = NULL;
					utils::Sleep(1000);
				}
				// Get PsyAPI for Crank Test2
				if (!(api = space->getCrankAPI(crankName.c_str()))) {
					printf("Couldn't get Crank '%s' API...\n", crankName.c_str());
					delete(space);
					space = NULL;
					utils::Sleep(1000);
				}
				else
					printf("(Re)connected to Psyclone ID %u...\n", sysid);
			}
		}
	}

	return 0;
}



int RunRetrieveTestCrank(uint16 sysid, std::string crankName) {

	PsyAPI* api = NULL;
	PsySpace* space = NULL;
	int64 counter = 0;
	DataMessage* msg;
	std::string triggerName;

	uint32 count = 0;
	uint32 datasize = 0;
	char* data = NULL;
	int64 val;
	std::list<DataMessage*> retrievedMsgs;
	std::list<DataMessage*>::iterator i, e;

	// Keep checking if Psyclone node is still available
	while (!gShutdownRequested) {
		if (space && api && space->isConnected()) {
			if (space->hasShutdown()) {
				// The node shut our space down (CTRL_SYSTEM_SHUTDOWN); exit
				// promptly so a graceful stop needs no force-terminate.
				printf("Psyclone shut the space down, exiting...\n");
				break;
			}
			if (!space->hasShutdown()) {
				msg = api->waitForNewMessage(100);
				if (msg) {
					triggerName = api->getCurrentTriggerName();
					api->logPrint(0, "Got trigger message '%s'...", triggerName.c_str());
					if (++count < 10)
						api->postOutputMessage("Ball");
					else {
						// Execute retrieve
						uint8 status = api->retrieve(retrievedMsgs, "r1");
						if (status == QUERY_SUCCESS)
							api->logPrint(1, "Successfully retrieved %u messages...", retrievedMsgs.size());
						else if (status == QUERY_TIMEOUT)
							api->logPrint(0, "Retrieve 'r1' timed out...");
						else
							api->logPrint(0, "Retrieve 'r1' failed (%u)...", status);

						for (i = retrievedMsgs.begin(), e = retrievedMsgs.end(); i != e; i++) {
							if ((*i)->getInt("count", val))
								api->logPrint(2, "Count: %u", val);
							delete(*i);
						}
						retrievedMsgs.clear();

						status = api->retrieve(retrievedMsgs, "r2");
						if (status == QUERY_SUCCESS)
							api->logPrint(1, "Successfully retrieved %u messages...", retrievedMsgs.size());
						else if (status == QUERY_TIMEOUT)
							api->logPrint(0, "Retrieve 'r2' timed out...");
						else
							api->logPrint(0, "Retrieve 'r2' failed (%u)...", status);

						for (i = retrievedMsgs.begin(), e = retrievedMsgs.end(); i != e; i++) {
							if ((*i)->getInt("count", val))
								api->logPrint(2, "Count: %u", val);
							delete(*i);
						}
						retrievedMsgs.clear();

						data = utils::StringFormat(datasize, "Hello World");
						status = api->queryCatalog(&data, datasize, "MyFiles", "test", "write", data, datasize);
						if (status == QUERY_SUCCESS)
							api->logPrint(1, "Successfully wrote %u bytes to file catalog...", datasize);
						else if (status == QUERY_TIMEOUT)
							api->logPrint(0, "Query 'write' timed out for file catalog...");
						else
							api->logPrint(0, "Query 'write' failed (%u) for file catalog...", status);
						delete[] data;

						data = utils::StringFormat(datasize, "Hello World");
						status = api->queryCatalog(&data, datasize, "MyData", "test", "write", data, datasize);
						if (status == QUERY_SUCCESS)
							api->logPrint(1, "Successfully wrote %u bytes to data catalog...", datasize);
						else if (status == QUERY_TIMEOUT)
							api->logPrint(0, "Query 'write' timed out to data catalog...");
						else
							api->logPrint(0, "Query 'write' failed to data catalog (%u)...", status);
						delete[] data;

						api->postOutputMessage("Test");
					}
				}
			}
		}
		else {
			if (!space)
				space = new PsySpace("MySpace");
			// try to connect
			if (!space->connect(sysid)) {
				printf("Waiting to connect to Psyclone ID %u...\n", sysid);
				utils::Sleep(1000);
			}
			else {
				// Register callback
				if (!space->registerCrankCallback("RetrieveTest2", Crank2)) {
					LogPrint(0, LOG_SPACE, 0, "Couldn't register Crank 'RetrieveTest2'...");
					utils::Sleep(1000);
					delete(space);
					space = new PsySpace("MySpace");
					continue;
				}


				if (!space->start()) {
					printf("Couldn't start Psyclone ID %u...\n", sysid);
					delete(space);
					space = NULL;
					utils::Sleep(1000);
				}
				// Get PsyAPI for Crank Test2
				if (!(api = space->getCrankAPI(crankName.c_str()))) {
					printf("Couldn't get Crank '%s' API...\n", crankName.c_str());
					delete(space);
					space = NULL;
					utils::Sleep(1000);
				}
				else
					printf("(Re)connected to Psyclone ID %u...\n", sysid);
				// utils::Sleep(1000);
			}
		}
	}

	return 0;
}





int8 Crank2(PsyAPI* api) {
	char* myName = new char[256];
	api->getModuleName(myName, 256);

	api->logPrint(1,"started running...");

	DataMessage* inMsg;
	const char* triggerName;
	uint32 datasize = 0;
	char* data = NULL;
	int64 val;
	std::list<DataMessage*> retrievedMsgs;
	std::list<DataMessage*>::iterator i, e;
	while (api->shouldContinue()) {
		if ( inMsg = api->waitForNewMessage(100, triggerName)) {
			api->logPrint(1,"got trigger message '%s'...", triggerName);
			if (stricmp(triggerName, "Ball") == 0)
				api->postOutputMessage("Ball");
			else if (stricmp(triggerName, "Test") == 0) {
				// Execute retrieve

				uint8 status = api->retrieveIntegerParam(retrievedMsgs, "r3", 20, 30, 8);
				if (status == QUERY_SUCCESS)
					api->logPrint(1,"Successfully retrieved %u messages...", retrievedMsgs.size());
				else if (status == QUERY_TIMEOUT)
					api->logPrint(0,"Retrieve 'r3' timed out...");
				else
					api->logPrint(0,"Retrieve 'r3' failed (%u)...", status);

				for (i = retrievedMsgs.begin(), e = retrievedMsgs.end(); i != e; i++) {
					if ((*i)->getInt("count", val))
						api->logPrint(2,"Count: %u", val);
					delete(*i);
				}
				retrievedMsgs.clear();

				status = api->queryCatalog(&data, datasize, "MyFiles", "test", "read");
				if (status == QUERY_SUCCESS)
					api->logPrint(1,"Successfully retrieved %u bytes from file catalog...", datasize);
				else if (status == QUERY_TIMEOUT)
					api->logPrint(0,"Query 'read' timed out for file catalog...");
				else
					api->logPrint(0,"Query 'read' failed (%u) for file catalog...", status);
				delete (data);

				status = api->queryCatalog(&data, datasize, "MyData", "test", "read");
				if (status == QUERY_SUCCESS)
					api->logPrint(0,"Successfully read %u bytes from data catalog...", datasize);
				else if (status == QUERY_TIMEOUT)
					api->logPrint(0,"Query 'read' timed out from data catalog...");
				else
					api->logPrint(0,"Query 'read' failed (%u) from data catalog...", status);
				delete [] data;

				// Shutdown
				utils::Sleep(1000);
				api->postOutputMessage("Done");
			}
		}
	}
	return 0;
}
