

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  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 "InterSystemManager.h"

namespace	cmlabs{


InterSystemManager::InterSystemManager(Node* node) {
	this->node = node;
	logTraffic = false;
	
	uint32 count;
	uint32* addresses = utils::GetLocalIPAddresses(count);
	if (count) {
		std::stringstream ss;
		ss << addresses[0];
		for (uint32 n = 1; n < count; n++) {
			ss << ',' << addresses[n];
		}
		systemIdentificationString = utils::StringFormat("%s:%s:%u", utils::GetLocalHostnameString().c_str(), ss.str().c_str(), node->networkPort);
		//printf("\n\n ************** %s **************\n\n", systemIdentificationString.c_str());
	}
}
InterSystemManager::~InterSystemManager() {
	node = NULL;
}

bool InterSystemManager::init(bool logTraffic) {
	this->logTraffic = logTraffic;
	return true;
}

bool InterSystemManager::sendQueryReply(DataMessage* msg) {
	// Called via Node::runRequestQ()
	// We got a reply to a remote request, reply coming in via processQuery(),
	// now we need to send it back to the connection that made the request
	// with their reference number

	uint32 localReqID = (uint32)msg->getReference();

	// find()+erase under the lock: operator[] inserted a NULL on a miss, and this
	// map is touched from both the request (processQuery) and reply paths.
	incomingReqConIDsMutex.enter();
	std::map<uint32, uint64>::iterator ri = incomingReqConIDs.find(localReqID);
	uint64 conid = (ri != incomingReqConIDs.end()) ? ri->second : 0;
	if (ri != incomingReqConIDs.end())
		incomingReqConIDs.erase(ri);
	incomingReqConIDsMutex.leave();
	if (!conid)
		return false;

	uint32 remoteReqID;
	uint32 origin;
	uint32 source;
	if (!node->manager->dataMapsMemory->getRequestInfo(localReqID, origin, source, remoteReqID))
		return false;

	if (logTraffic)
		LogPrint(0, 0, 1, "Local component replied to remote request ID %u, sending it back across network to con ID %llu\n",
			remoteReqID, conid);

	msg->setReference(remoteReqID);
	node->networking->sendMessageToConnection(msg, conid);

	//DataMessage* replyMsg = new DataMessage(PsyAPI::CTRL_INTERSYSTEM_QUERY_REPLY, 0);
	//replyMsg->setReference(remoteReqID);
	//replyMsg->setStatus(msg->getStatus());
	//node->networking->sendMessageToConnection(replyMsg, conid);
	//delete(replyMsg);
	node->manager->dataMapsMemory->deleteRequest(localReqID);
	return true;
}

bool InterSystemManager::processQuery(DataMessage* msg, uint64 conid) {
	//printf("Remote request received, processing it now\n");
	// Called from Networking::receiveMessage()
	// A remote system has sent us a query, put in into motion
	// when a reply is ready it will be returned via sendQueryReply via Node::runRequestQ()

	if (!msg || !conid)
		return false;

	uint8 status = 0;
	uint32 remoteReqID = (uint32)msg->getReference();
	DataMessage* replyMsg = NULL;

	if (logTraffic) {
		const char* query = msg->getString("Query");
		if (query)
			LogPrint(0, 0, 1, "Remote request ID %u received from con ID %llu, processing query '%s' now...\n",
				remoteReqID, conid, query);
		else
			LogPrint(0, 0, 1, "Remote request ID %u received from con ID %llu, processing it now...\n",
				remoteReqID, conid);
	}

	// New incoming query, find out which component it is meant for by name
	const char* compName = msg->getString("INTERSYSTEM_SOURCENAME");
	uint32 compID = node->getComponentID(compName);
	if (!compName || !compID) {
		status = QUERY_COMPONENT_UNKNOWN;
		goto failed;
	}

	uint32 reqID;
	if (!node->manager->dataMapsMemory->createNewRequest(0, compID, remoteReqID, reqID)) {
		status = QUERY_FAILED;
		goto failed;
	}

	// Find out where this component is located
	uint16 otherNodeID, otherProcID;
	if (!node->manager->componentMemory->getComponentLocation(compID, otherNodeID, otherProcID)) {
		status = QUERY_COMPONENT_UNKNOWN;
		goto failed;
	}

	// reprogram query type to be local instead of inter system
	// otherwise it will be sent back out across the network again and again :-)
	msg->setType(PsyAPI::CTRL_QUERY);
	msg->setTo(compID);
	msg->setFrom(0);
	msg->setReference(reqID);

	if (otherNodeID != node->manager->getNodeID()) {
		node->manager->dataMapsMemory->setRequestStatus(reqID, REQ_PROCESSING_REMOTE);
		if (!node->manager->processMemory->addToReqQ(0, msg)) {
			node->manager->dataMapsMemory->deleteRequest(reqID);
			status = QUERY_FAILED;
			goto failed;
		}
	}
	else {
		node->manager->dataMapsMemory->setRequestStatus(reqID, REQ_PROCESSING_LOCAL);
		otherProcID = node->manager->componentMemory->getComponentProcessID(compID);
		if (!node->manager->processMemory->addToReqQ(otherProcID, msg)) {
			node->manager->dataMapsMemory->deleteRequest(reqID);
			status = QUERY_FAILED;
			goto failed;
		}
	}

	incomingReqConIDsMutex.enter();
	incomingReqConIDs[reqID] = conid;
	incomingReqConIDsMutex.leave();
	// request is now in motion, the result will be picked up in Node::runRequestQ()
	// which will call sendQueryReply(msg)
	return true;

failed:
	replyMsg = new DataMessage(PsyAPI::CTRL_INTERSYSTEM_QUERY_REPLY, 0);
	replyMsg->setReference(remoteReqID);
	replyMsg->setStatus(status);
	node->networking->sendMessageToConnection(replyMsg, conid);
	delete(replyMsg);
	return false;
}


bool InterSystemManager::sendQuery(DataMessage* msg) {
	//printf("Local request made, need to send it across network to remote system\n");
	// Called via Node::runRequestQ()
	// A local component has made a query meant for a remote system
	// we need to send it to the remote system now
	uint32 localReqID = (uint32)msg->getReference();

	uint32 ipHost = (uint32)msg->getInt("INTERSYSTEM_ADDRESS");
	uint16 ipPort = (uint16)msg->getInt("INTERSYSTEM_PORT");
	msg->setString("INTERSYSTEM_IDENTIFICATION", systemIdentificationString.c_str());
	if (!ipHost || !ipPort)
		return false;

	if (logTraffic) {

		const char* query = msg->getString("Query");
		if (query)
			LogPrint(0, 0, 1, "Local component request ID %u, sending query '%s' across network to %u.%u.%u.%u:%u...\n",
				localReqID, query, GETIPADDRESSQUAD(ipHost), ipPort);
		else
			LogPrint(0, 0, 1, "Local component request ID %u, sending across network to %u.%u.%u.%u:%u...\n",
				localReqID, GETIPADDRESSQUAD(ipHost), ipPort);
	}

	// now we need to connect to the remote system
	return node->networking->sendMessageToHost(msg, ipHost, ipPort);
}


bool InterSystemManager::processQueryReply(DataMessage* msg) {
	//printf("Local request reply has come back from remote system\n");
	// Called from Networking::receiveMessage(), coming from remote processQuery()
	// A local component has sent a query to a remote system, this is the reply
	// The reply message is attached so get this one

	// get our own request id
	uint32 id = (uint32)msg->getReference();
	uint8 status = (uint8)msg->getStatus();

	DataMessage* replyMsg = msg->getAttachedMessageCopy("DataMessage");
	//if (!replyMsg)
	//	return false;


	if (logTraffic) {
		if (replyMsg) {
			if (status == QUERY_SUCCESS)
				LogPrint(0, 0, 1, "Local component request ID %u reply has come back with reply message (%u entries), status: SUCCESS...\n", id, replyMsg->getUserCount());
			else if (status == QUERY_FAILED)
				LogPrint(0, 0, 1, "Local component request ID %u reply has come back with reply message (%u entries), status: FAILED...\n", id, replyMsg->getUserCount());
			else if (status == QUERY_TIMEOUT)
				LogPrint(0, 0, 1, "Local component request ID %u reply has come back with reply message (%u entries), status: TIMEOUT...\n", id, replyMsg->getUserCount());
			else
				LogPrint(0, 0, 1, "Local component request ID %u reply has come back with reply message (%u entries), status: %u...\n", id, replyMsg->getUserCount(), status);
		}
		else {
			if (status == QUERY_SUCCESS)
				LogPrint(0, 0, 1, "Local component request ID %u reply has come back, status: SUCCESS...\n", id);
			else if (status == QUERY_FAILED)
				LogPrint(0, 0, 1, "Local component request ID %u reply has come back, status: FAILED...\n", id);
			else if (status == QUERY_TIMEOUT)
				LogPrint(0, 0, 1, "Local component request ID %u reply has come back, status: TIMEOUT...\n", id);
			else
				LogPrint(0, 0, 1, "Local component request ID %u reply has come back, status: %u...\n", id, status);
		}
	}

	uint32 remoteID;
	uint32 origin;
	uint32 source;
	if (!node->manager->dataMapsMemory->getRequestInfo(id, origin, source, remoteID)) {
		LogPrint(0, LOG_SPACE, 0, "Couldn't find request info for local id %u", id);
		delete replyMsg;
		return false;
	}
	if (remoteID) {
		uint16 otherNodeID = node->manager->componentMemory->getComponentNodeID(origin);
		if (otherNodeID && (otherNodeID != node->manager->getNodeID())) {
			DataMessage* netMsg = new DataMessage(PsyAPI::CTRL_QUERY_REPLY, source, origin, 10000);
			netMsg->setReference(remoteID);
			netMsg->setStatus(status);
			if (replyMsg)
				netMsg->setAttachedMessage("DataMessage", replyMsg);
			node->manager->processMemory->addToReqQ(0, netMsg);
			delete replyMsg;
			delete netMsg;
			//LogPrint(procID, LOG_SPACE,3,"Replied to remote request %u (local %u) (source %u, origin %u)...", remoteID, id, source, origin);
			return node->manager->dataMapsMemory->deleteRequest(id);
		}
		else {
			delete replyMsg;
			LogPrint(0, LOG_SPACE, 0, "Couldn't find otherNode %u for remote request %u (local %u) (source %u, origin %u)...",
				otherNodeID, remoteID, id, source, origin);
			return false;
		}
	}

	uint64 msgID = 0;
	uint64 msgEOL = 0;
	uint8 reqStatus = 0;

	if (replyMsg) {
		replyMsg->setTTL(10000000);
		msgEOL = msg->getEOL();
		// decide on EOL ###################
		node->manager->temporalMemory->insertMessage(replyMsg, msgID);
		//delete(msg);
	}

	if (status == QUERY_SUCCESS) {
		if (msgID)
			reqStatus = REQ_SUCCESS_DATA_EOL;
		else
			reqStatus = REQ_SUCCESS;
	}
	else {
		if (msgID)
			reqStatus = REQ_FAILED_DATA_EOL;
		else
			reqStatus = REQ_FAILED;
	}
	delete replyMsg;
	return node->manager->dataMapsMemory->setRequestStatus(id, reqStatus, msgID, msgEOL);
}

}
