

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

namespace	cmlabs {


// Built-in Collaborative Cognitive Map catalog 
// Queries: Get
// Parameters: none
int8 CCMCatalog(PsyAPI* api) {
	std::string myName = api->getModuleName();
	uint32 systemID = (uint32)api->getParameterInt("SystemID");

	uint32 maxKeep = (uint32)api->getParameterInt("MaxKeep");
	if (!maxKeep)
		maxKeep = 1000;

	std::string ccmDir = api->getParameterString("StorageDir");
	if (!ccmDir.length())
		ccmDir = "CCMData";

	CCMDataStorage* dataStorage = new CCMDataStorage(api);
	std::string errStr = dataStorage->init(ccmDir.c_str(), maxKeep);
	if (errStr.length()) {
		api->logPrint(0, "Unable to initialise catalog storage: ", errStr.c_str());
		return -1;
	}

	XMLResults xmlResults;
	std::string xml = api->getParameterString("componentsetup");
	XMLNode node, subNode, mainNode = XMLNode::parseString(xml.c_str(), "setup", &xmlResults);

	// display error message (if any)
	if (xmlResults.error != eXMLErrorNone)
	{
		if (xmlResults.error == eXMLErrorFirstTagNotFound) {
			api->logPrint(0, "Catalog setup error, main tag '<setup>' not found");
			return -1;
		}
		else {
			api->logPrint(0, "Catalog setup parsing error at line %i, column %i:\n   %s\n",
				xmlResults.nLine, xmlResults.nColumn, XMLNode::getError(xmlResults.error));
			return -1;
		}
	}

	if (!(node = mainNode.getChildNode("vantagepoints")).isEmpty()) {
		if (!dataStorage->vantagePoints.init(node)) {
			api->logPrint(0, "VantagePoints couldn't be initialised from XML");
		}
	}
	else {
		api->logPrint(0, "No VantagePoints provided");
	}

	uint32 count, id;
	const char* name, *image, *face, *type, *parent, *offset, *view, *nickname, *data;
	int n = 0;
	if (!(node = mainNode.getChildNode("namedpoints")).isEmpty()) {
		while (!(subNode = node.getChildNode("point", n++)).isEmpty()) {
			if ((name = subNode.getAttribute("label"))
				&& (data = subNode.getAttribute("data"))) {
				dataStorage->namedPoints[name] = data;
			}
		}
	}

	n = 0;
	if (!(node = mainNode.getChildNode("roles")).isEmpty()) {
		while (!(subNode = node.getChildNode("role", n++)).isEmpty()) {
			if ((name = subNode.getAttribute("name"))
				&& (type = subNode.getAttribute("type"))) {
				dataStorage->addRole(name, type);
			}
		}
	}

	n = 0;
	while (!(node = mainNode.getChildNode("map", n++)).isEmpty()) {
		if (name = node.getAttribute("name")) {
			parent = node.getAttribute("parent");
			offset = node.getAttribute("offset");
			view = node.getAttribute("view");
			image = node.getAttribute("image");

			errStr = dataStorage->addMap(name, parent, offset, view, image, node.getAttribute("invertx"), node.getAttribute("inverty"));
			if (errStr.length()) {
				api->logPrint(0, "Unable to add map '%s': %s", name, errStr.c_str());
				return -1;
			}
		}
	}

	std::list<std::string> faces;
	n = 0;
	while (!(node = mainNode.getChildNode("identity", n++)).isEmpty()) {
		if ((name = node.getAttribute("name")) &&
			(type = node.getAttribute("type"))) {
			nickname = node.getAttribute("nickname");
			image = node.getAttribute("image");
			faces.clear();
			face = node.getAttribute("face");
			if (face)
				faces.push_back(face);
			for (int n = 1; n <= 10; n++) {
				face = node.getAttribute(utils::StringFormat("face%d", n).c_str());
				if (face)
					faces.push_back(face);
			}
			if (image && !faces.size())
				faces.push_back(image);

			id = utils::Ascii2Uint32(node.getAttribute("id"));

			errStr = dataStorage->addIdentity(id, name, type, nickname, image, faces);
			if (errStr.length()) {
				api->logPrint(0, "Unable to add identity '%s': %s", name, errStr.c_str());
				return -1;
			}
		}
	}

	api->addPsyProbeCustomView("Cognitive Map", "elements/ccmcatalog.html");

	//std::map<DataMessage*, std::string> queuedPosts;

	//api->logPrint(1, "Component ready...");

	std::string json, dataStr;
	DataMessage* inMsg, *replyMsg;
	const char* triggerName, *query, *role;
	uint32 lastObjectID = 0;
	bool webCall = false;
	uint32 s;
	uint8 status;
	uint32 querySystemID = 0;
	uint32 objID;

	//for (int nn = 1; nn <= 20; nn++) {
	//	id = dataStorage->vantagePoints.reserveNextPoint(1);
	//	if (!id)
	//		api->logPrint(1, "Couldn't get vantage point [%u] id", nn);
	//	else {
	//		data = dataStorage->vantagePoints.getPointData(id);
	//		if (!data)
	//			api->logPrint(1, "Couldn't get vantage point [%u] data", nn);
	//		else
	//			api->logPrint(1, "Got vantage point [%u] data: %s", nn, data);
	//		dataStorage->vantagePoints.occupyPoint(id, 1);
	//	}
	//	utils::Sleep(1000);
	//}

	while (api->shouldContinue()) {
		//printf("."); fflush(stdout);
		if (inMsg = api->waitForNewMessage(100, triggerName)) {
			if (triggerName && stricmp(triggerName, "DefaultComponentTrigger") == 0) {
				//api->logPrint(2, "Got wakeup message...");
			}
			else if (inMsg->getType() == PsyAPI::CTRL_QUERY) {
				//api->logPrint(1, "%s\n", inMsg->getUserEntriesAsString().c_str());
				webCall = inMsg->hasInteger("HTTP_OPERATION");
				//if (inMsg->hasInteger("SystemID"))
				if (inMsg->getSystemID()) {
					querySystemID = inMsg->getSystemID();
					if (querySystemID == systemID)
						api->logPrint(1, "*** WARNING: Remote system claims to have the same System ID: %u", systemID);
				}
				else if (!webCall) {
					querySystemID = systemID;
					inMsg->setSystemID(systemID);
				}

				if (querySystemID)
					dataStorage->addRemoteSystemIdentity(querySystemID, inMsg);
				
				replyMsg = NULL;
				status = QUERY_FAILED;
				query = inMsg->getString("Query");
				//api->logPrint(1, "CCM got query '%s' from system %u, processing...", query, querySystemID);
				//printf("%s: CCM got query '%s' from system %u, processing...\n", PrintTimeNowString().c_str(), query, querySystemID);
				if (webCall && (!query || (stricmp(query, "all") == 0))) {
					json = dataStorage->getFullJSON();
					if (json.length()) {
						replyMsg = new DataMessage();
						status = QUERY_SUCCESS;
						// add output JSON to replyMsg
						replyMsg->setString("JSON", json.c_str());
					}
					else
						status = QUERY_FAILED;
				}
				else if (!query) {
					api->queryReply((uint32)inMsg->getReference(), QUERY_NOT_AVAILABLE);
					continue;
				}
				else if (stricmp(query, "CCMHeartbeat") == 0) {
					replyMsg = dataStorage->addHeartbeat(inMsg);
					status = QUERY_SUCCESS;
				}
				else if (stricmp(query, "GetLocalSystemID") == 0) {
					replyMsg = new DataMessage();
					replyMsg->setSystemID(systemID);
					status = QUERY_SUCCESS;
				}
				else if (stricmp(query, "CreateObject") == 0) {
					uint64 firstSeen = inMsg->getTime("FirstSeen");
					if (!firstSeen) firstSeen = inMsg->getCreatedTime();
					uint64 lastSeen = inMsg->getTime("LastSeen");
					if (!lastSeen) lastSeen = firstSeen;
					id = dataStorage->createObject(inMsg->getString("Type"), (uint32)inMsg->getInt("Identity"),
						firstSeen, lastSeen, inMsg->getAsBool("PresentNow"), querySystemID);

					if (id) {
						replyMsg = new DataMessage();
						replyMsg->setInt("ObjectID", id);
						status = QUERY_SUCCESS;
					}
					else
						status = QUERY_FAILED;
				}
				else if (stricmp(query, "UpdateObject") == 0) {
					if (dataStorage->updateObject((uint32)inMsg->getInt("ObjectID"), inMsg->getString("Type"), (uint32)inMsg->getInt("Identity"),
						inMsg->getTime("FirstSeen"), inMsg->getTime("LastSeen"), inMsg->getAsBool("PresentNow"), querySystemID))
						status = QUERY_SUCCESS;
					else
						status = QUERY_FAILED;
				}
				else if (stricmp(query, "AddDataToObject") == 0) {
					status = QUERY_FAILED;
					// ########### Why is this failing ?????
					if (objID = (uint32)inMsg->getInt("ObjectID")) {
						inMsg->deleteEntry("Query");
						inMsg->deleteEntry("ObjectID");
						if (dataStorage->addDataToObject(objID, inMsg))
							status = QUERY_SUCCESS;
					}
				}
				else if (stricmp(query, "AddStreamToObject") == 0) {
					if (dataStorage->addStreamToObject((uint32)inMsg->getInt("ObjectID"),
						inMsg->getString("Name"), querySystemID, inMsg->getString("ComponentName"),
						inMsg->getString("PrivateDataName"), inMsg->getString("Mimetype")
						))
						status = QUERY_SUCCESS;
					else
						status = QUERY_FAILED;
				}
				else if (stricmp(query, "AddImage") == 0) {
					status = QUERY_FAILED;
					data = inMsg->getData("Image", s);
					if (data && s) {
						Bitmap* image = new Bitmap();
						if (image->readFromMemory(data, s)) {
							if (dataStorage->addImage((uint32)inMsg->getInt("ObjectID"), querySystemID, image))
								status = QUERY_SUCCESS;
						}
						else
							delete image;
					}
				}
				else if (stricmp(query, "UpdateMap") == 0) {
					status = QUERY_FAILED;
					data = inMsg->getData("Image", s);
					if (data && s) {
						errStr = dataStorage->updateMap(
							(uint32)inMsg->getInt("MapID"),
							data, s,
							(uint32)inMsg->getInt("Width"),
							(uint32)inMsg->getInt("Height"),
							(uint32)inMsg->getInt("Step"),
							inMsg->getString("Encoding"),
							inMsg->getFloat("Scale"),
							inMsg->getFloat("OriginX"),
							inMsg->getFloat("OriginY")
						);
						if (errStr.length()) {
						}
						else
							status = QUERY_SUCCESS;
					}
				}
				else if (stricmp(query, "AddObservations") == 0) {
					// This message contains N observation objects, numbered from 1 to N
					count = (uint32)inMsg->getInt("ObsCount");
					for (uint32 n = 1; n <= count; n++) {
						data = inMsg->getData(n, "OBS", s);
						if (data && s) {
							dataStorage->addObservation(data, s);
						}
					}
					// extract each one in turn
					// determine type
					// add to the appropriate obsMap
					status = QUERY_SUCCESS;
				}
				else if (stricmp(query, "post") == 0) {
					// /api/query?from=CCMCatalog&query=post&name=postname&id=1
					id = (uint32)inMsg->getAsInt("id");
					name = inMsg->getString("name");
					if (!id) {
						if (api->postOutputMessage(name, inMsg->copy()))
							if(dataStorage->addQueuedPost(id, name, inMsg))
								status = QUERY_SUCCESS;
					}
					else if (id == systemID) {
						if (api->postOutputMessage(name, inMsg->copy()))
							status = QUERY_SUCCESS;
					}
					else if (dataStorage->addQueuedPost(id, name, inMsg))
						status = QUERY_SUCCESS;
					//status = PostToSystem(id, name, myName.c_str(), inMsg, api, dataStorage);
				}
				else if (stricmp(query, "GetNamedPoint") == 0) {
					//api->logPrint(1, "Query for next vantage point, searching...");
					status = QUERY_FAILED;
					name = inMsg->getString("name");
					if (!name)
						name = inMsg->getString("label");
					// find() not operator[]: a lookup for an absent point name must
					// not insert an empty entry (unbounded map growth from queries).
					std::map<std::string, std::string>::iterator npi;
					if (name &&
						((npi = dataStorage->namedPoints.find(name)) != dataStorage->namedPoints.end()) &&
						(dataStr = npi->second).length() ) {
						std::vector<std::string> list = utils::TextListSplit(dataStr.c_str(), ",");
						if (list.size() > 3) {
							replyMsg = new DataMessage();
							replyMsg->setFloat("X", utils::Ascii2Float64(list.at(0).c_str()));
							replyMsg->setFloat("Y", utils::Ascii2Float64(list.at(1).c_str()));
							replyMsg->setFloat("OZ", utils::Ascii2Float64(list.at(2).c_str()));
							replyMsg->setFloat("OW", utils::Ascii2Float64(list.at(3).c_str()));
							replyMsg->setString("Label", name);
							status = QUERY_SUCCESS;
							api->logPrint(1, "Replied with point name %s", name);
						}
					}
					if (status == QUERY_FAILED)
						api->logPrint(1, "Unknown point name %s", name);
				}
				else if (stricmp(query, "GetVantagePoint") == 0) {
					//api->logPrint(1, "Query for next vantage point, searching...");
					status = QUERY_FAILED;
					if ((id = dataStorage->vantagePoints.reserveNextPoint(querySystemID)) &&
						(data = dataStorage->vantagePoints.getPointData(id))) {
						std::vector<std::string> list = utils::TextListSplit(data, ",");
						if (list.size() > 3) {
							replyMsg = new DataMessage();
							replyMsg->setInt("ID", id);
							replyMsg->setFloat("X", utils::Ascii2Float64(list.at(0).c_str()));
							replyMsg->setFloat("Y", utils::Ascii2Float64(list.at(1).c_str()));
							replyMsg->setFloat("OZ", utils::Ascii2Float64(list.at(2).c_str()));
							replyMsg->setFloat("OW", utils::Ascii2Float64(list.at(3).c_str()));
							replyMsg->setString("Label", dataStorage->vantagePoints.getPointLabel(id));
							status = QUERY_SUCCESS;
							api->logPrint(1, "Replied with next vantage point: %s", dataStorage->vantagePoints.getPointLabel(id));
						}
						else
							api->logPrint(1, "Next vantage point format error: %s", data);
					}
					else {
						if (dataStorage->vantagePoints.state == INMOTION)
							api->logPrint(1, "Couldn't get next vantage point - someone is currently in motion");
						else if (dataStorage->vantagePoints.count == 0)
							api->logPrint(1, "No vantage points have been specified");
						else
							api->logPrint(1, "No vantage points are in an available state");
					}
				}
				else if (stricmp(query, "SetVantagePoint") == 0) {
					//api->logPrint(1, "Setting vantage point...");
					id = (uint32)inMsg->getAsInt("id");
					if (dataStorage->vantagePoints.occupyPoint(id, querySystemID))
						status = QUERY_SUCCESS;
					else
						status = QUERY_FAILED;
					api->logPrint(1, "Vantage point for system %u set to %u!", querySystemID, id);
				}
				else if (stricmp(query, "FreeVantagePoint") == 0) {
					//api->logPrint(1, "Freeing vantage point...");
					id = (uint32)inMsg->getAsInt("id");
					if (dataStorage->vantagePoints.freePoint(id, querySystemID))
						status = QUERY_SUCCESS;
					else
						status = QUERY_FAILED;
					api->logPrint(1, "Vantage point for system %u id %u freed!", querySystemID, id);
				}
				else if (stricmp(query, "map") == 0) {
					// /api/query?from=CCMCatalog&query=map&datatype=image&id=1
					// /api/query?from=CCMCatalog&query=map&datatype=image&id=2
					// /api/query?from=CCMCatalog&query=map&id=1
					replyMsg = new DataMessage();
					if (dataStorage->queryMap(inMsg, replyMsg))
						status = QUERY_SUCCESS;
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
					}
				}
				else if (stricmp(query, "identity") == 0) {
					// /api/query?from=CCMCatalog&query=identity&datatype=image&name=face&id=2
					replyMsg = new DataMessage();
					if (dataStorage->queryIdentity(inMsg, replyMsg))
						status = QUERY_SUCCESS;
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
					}
				}
				else if (stricmp(query, "identities") == 0) {
					// msg api
					replyMsg = new DataMessage();
					if (dataStorage->queryIdentities(inMsg, replyMsg))
						status = QUERY_SUCCESS;
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
					}
				}
				else if (stricmp(query, "object") == 0) {
					// /api/query?from=CCMMaster&query=object&id=1&datatype=image
					replyMsg = new DataMessage();
					if (dataStorage->queryObject(inMsg, replyMsg))
						status = QUERY_SUCCESS;
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
					}
				}
				else if (stricmp(query, "objects") == 0) {
					// /api/query?from=CCMMaster&query=objects&type=human
					replyMsg = new DataMessage();
					if (dataStorage->queryObjects(inMsg, replyMsg))
						status = QUERY_SUCCESS;
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
					}
				}




				// roles
				else if (stricmp(query, "takerole") == 0) {
					replyMsg = new DataMessage();

					if (dataStorage->takeRole(inMsg, replyMsg)) {
						status = QUERY_SUCCESS;
						dataStorage->addQueuedPost(0, "RoleAssigned", replyMsg->copy());
						api->postOutputMessage("RoleAssigned", replyMsg->copy());
						//PostToSystem(0, "RoleAssigned", myName.c_str(), replyMsg->copy(), api, dataStorage);
					}
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
					}
				}
				else if (stricmp(query, "leaverole") == 0) {
					replyMsg = new DataMessage();
					if (dataStorage->leaveRole(inMsg, replyMsg)) {
						status = QUERY_SUCCESS;
						dataStorage->addQueuedPost(0, "RoleLeft", replyMsg->copy());
						api->postOutputMessage("RoleLeft", replyMsg->copy());
						//PostToSystem(0, "RoleLeft", myName.c_str(), replyMsg->copy(), api, dataStorage);
					}
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
					}
				}
				else if (stricmp(query, "giverole") == 0) {
					replyMsg = new DataMessage();
					if (dataStorage->giveRole(inMsg, replyMsg)) {
						status = QUERY_SUCCESS;
						dataStorage->addQueuedPost(0, "RoleGiven", replyMsg->copy());
						api->postOutputMessage("RoleGiven", replyMsg->copy());
						//PostToSystem(0, "RoleGiven", myName.c_str(), replyMsg->copy(), api, dataStorage);
					}
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
					}
				}



				// tasks
				else if (stricmp(query, "createtask") == 0) {
					replyMsg = new DataMessage();
					if (dataStorage->createTask(inMsg, replyMsg)) {
						uint64 taskID = (uint64)replyMsg->getInt("TaskID");
						name = inMsg->getString("Name");
						status = QUERY_SUCCESS;
						dataStorage->addQueuedPost(0, "TaskCreated", replyMsg->copy());
						api->postOutputMessage("TaskCreated", replyMsg->copy());
						//PostToSystem(0, "TaskCreated", myName.c_str(), replyMsg->copy(), api, dataStorage);
						// check if we should assign it now
						if (id = (uint32)inMsg->getInt("Assignee"))
							api->logPrint(1, "Task '%s' [%llu] created, assigning to system ID %u by assignee request",
								name, taskID, id);
						if (!id && (role = inMsg->getString("Role"))) {
							CCMRole* ccmRole = dataStorage->getRole(role);
							if (ccmRole && ccmRole->currentAssignments.size()) {
								id = ccmRole->currentAssignments.begin()->first;
								api->logPrint(1, "Task '%s' [%llu] created, assigning to system ID %u based on role '%s'",
									name, taskID, id, role);
							}
						}
						if (id) {
							if (dataStorage->assignTask(taskID, id, querySystemID, replyMsg)) {
								api->logPrint(1, "Task '%s' [%llu] assigned to system ID %u",
									name, taskID, id);
								dataStorage->addQueuedPost(0, "TaskAssigned", replyMsg->copy());
								api->postOutputMessage("TaskAssigned", replyMsg->copy());
							}
						}
					}
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
						api->logPrint(1, "Failed to create task named '%s'", inMsg->getString("Name"));
					}
				}
				else if (stricmp(query, "canceltask") == 0) {
					replyMsg = new DataMessage();
					if (dataStorage->cancelTask(inMsg, replyMsg)) {
						api->logPrint(1, "Task ID %lld cancelled", inMsg->getInt("TaskID"));
						status = QUERY_SUCCESS;
						dataStorage->addQueuedPost(0, "TaskCancelled", replyMsg->copy());
						api->postOutputMessage("TaskCancelled", replyMsg->copy());
						//PostToSystem(0, "TaskAssigned", myName.c_str(), replyMsg->copy(), api, dataStorage);
					}
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
						api->logPrint(1, "Task ID %lld cancel failed", inMsg->getInt("TaskID"));
					}
				}
				else if (stricmp(query, "assigntask") == 0) {
					replyMsg = new DataMessage();
					if (dataStorage->assignTask(inMsg, replyMsg)) {
						status = QUERY_SUCCESS;
						dataStorage->addQueuedPost(0, "TaskAssigned", replyMsg->copy());
						api->postOutputMessage("TaskAssigned", replyMsg->copy());
						//PostToSystem(0, "TaskAssigned", myName.c_str(), replyMsg->copy(), api, dataStorage);
						api->logPrint(1, "Task ID %lld assigned", inMsg->getInt("TaskID"));
					}
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
						api->logPrint(1, "Task ID %lld assign failed", inMsg->getInt("TaskID"));
					}
				}
				else if (stricmp(query, "accepttask") == 0) {
					replyMsg = new DataMessage();
					if (dataStorage->acceptTask(inMsg, replyMsg)) {
						status = QUERY_SUCCESS;
						dataStorage->addQueuedPost(0, "TaskAccepted", replyMsg->copy());
						api->postOutputMessage("TaskAccepted", replyMsg->copy());
						//PostToSystem(0, "TaskAccepted", myName.c_str(), replyMsg->copy(), api, dataStorage);
						api->logPrint(1, "Task ID %lld accepted", inMsg->getInt("TaskID"));
					}
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
						api->logPrint(1, "Task ID %lld accept failed", inMsg->getInt("TaskID"));
					}
				}
				else if (stricmp(query, "updatetask") == 0) {
					replyMsg = new DataMessage();
					if (dataStorage->updateTask(inMsg, replyMsg)) {
						status = QUERY_SUCCESS;
						dataStorage->addQueuedPost(0, "TaskUpdated", replyMsg->copy());
						api->postOutputMessage("TaskUpdated", replyMsg->copy());
						//PostToSystem(0, "TaskUpdated", myName.c_str(), replyMsg->copy(), api, dataStorage);
						api->logPrint(1, "Task ID %lld updated", inMsg->getInt("TaskID"));
					}
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
						api->logPrint(1, "Task ID %lld update failed", inMsg->getInt("TaskID"));
					}
				}
				else if (stricmp(query, "completetask") == 0) {
					replyMsg = new DataMessage();
					if (dataStorage->completeTask(inMsg, replyMsg)) {
						status = QUERY_SUCCESS;
						dataStorage->addQueuedPost(0, "TaskCompleted", replyMsg->copy());
						api->postOutputMessage("TaskCompleted", replyMsg->copy());
						//PostToSystem(0, "TaskCompleted", myName.c_str(), replyMsg->copy(), api, dataStorage);
						api->logPrint(1, "Task ID %lld completed", inMsg->getInt("TaskID"));
					}
					else {
						status = QUERY_FAILED;
						delete replyMsg;
						replyMsg = NULL;
						api->logPrint(1, "Task ID %lld complete failed", inMsg->getInt("TaskID"));
					}
				}



				else {
					// /api/query?from=CCMCatalog&query=range&datatype=location&id=1&fromtime=x&totime=x&fromx=x&fromy=x&fromy=y&toy=y&systemid=x&topicid=x
					// /api/query?from=CCMCatalog&query=value&datatype=float&id=1&name=battery
					replyMsg = new DataMessage();
					if (webCall) {
						//printf("%s\n", inMsg->getUserEntriesAsString().c_str());
						std::list<std::string> jsonList;
						if (dataStorage->query(inMsg, NULL, &jsonList)) {
							status = QUERY_SUCCESS;
							// add output JSON to replyMsg
							replyMsg->setString("JSON", utils::StringFormat("{ \"count\": %u, \"observations\": [%s] }\n", jsonList.size(), utils::TextJoin(jsonList, ",").c_str()).c_str());
						}
						else {
							status = QUERY_FAILED;
							delete replyMsg;
							replyMsg = NULL;
						}
					}
					else {
						if (dataStorage->query(inMsg, replyMsg))
							status = QUERY_SUCCESS;
						else {
							status = QUERY_FAILED;
							delete replyMsg;
							replyMsg = NULL;
						}
					}
				}
				//printf("*"); fflush(stdout);
				//if (querySystemID)
				//	dataStorage->addRemoteSystemIdentity(querySystemID, inMsg);

				//api->logPrint(1, "Replying to query '%s': %s", query, (status == QUERY_SUCCESS) ? "Success" : "Failed");
				//printf("%s: Replying to query '%s': %s\n", PrintTimeNowString().c_str(), query, (status == QUERY_SUCCESS) ? "Success" : "Failed");
				api->queryReply((uint32)inMsg->getReference(), status, replyMsg);
				//if (replyMsg)
				//	api->queryReply((uint32)inMsg->getReference(), QUERY_SUCCESS, replyMsg);
				//else
				//	api->queryReply((uint32)inMsg->getReference(), status);
			}
			else if (api->hasCurrentPostName(triggerName)) {
				// This message is for distribution to all other systems
				// but don't repost locally as it is already here
				dataStorage->addQueuedPost(0, triggerName, inMsg);
			}
		}

		// Check for timed out tasks
		// ################
		while (replyMsg = dataStorage->getNextTimedOutTask()) {
			dataStorage->addQueuedPost(0, "TaskTimeout", replyMsg->copy());
			api->postOutputMessage("TaskTimeout", replyMsg);
		}

	}

	return 0;
}


// Built-in Collaborative Cognitive Map proxy catalog 
// Exists in Psyclones where a real CCM doesn't and is used for posting messages to these systems from the master CCM
// Must be named the same as the master CCM in every other system
// Only the POST trigger will be handled locally, all others are forwarded to the actual Master in the other system
int8 CCMProxyCatalog(PsyAPI* api) {
	uint32 systemID = (uint32)api->getParameterInt("SystemID");
	std::string myName = api->getModuleName();
	const char* name;
	DataMessage* inMsg, *replyMsg, *msg, *postMsg = NULL;
	const char* triggerName, *query;
	uint8 status;
	uint64 lastHeartbeat = 0;
	int32 heartbeatInterval = (int32)api->getParameterInt("HeartbeatInterval");
	if (!heartbeatInterval)
		heartbeatInterval = 500;

	std::map<int64, DataMessage*> postMessages;
	std::map<int64, DataMessage*>::iterator i, e;
	std::map<int64, std::string> postNames;
	std::string postName;
	uint32 postCount = 0;

	api->addPsyProbeCustomView("Cognitive Map (remote)", "elements/ccmcatalog.html");
	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(100, triggerName)) {
			if (triggerName && stricmp(triggerName, "DefaultComponentTrigger") == 0) {
				//api->logPrint(2, "Got wakeup message...");
			}
			// Only the POST trigger will be handled locally
			else if (inMsg->getType() == PsyAPI::CTRL_QUERY) {
				replyMsg = NULL;
				query = inMsg->getString("Query");
				// for now, really only expect the 'post' query from the master CCM
				status = QUERY_FAILED;
				if (stricmp(query, "local_post") == 0) {
					name = inMsg->getString("name");
					if (name) {
						int32 res = api->postOutputMessage(name, new DataMessage(*inMsg));
						if (res > 0)
							status = QUERY_SUCCESS;
					}
					api->queryReply((uint32)inMsg->getReference(), status);
				}
				else if (stricmp(query, "GetLocalSystemID") == 0) {
					replyMsg = new DataMessage();
					replyMsg->setSystemID(systemID);
					status = QUERY_SUCCESS;
					api->queryReply((uint32)inMsg->getReference(), status, replyMsg);
				}
				// all others are forwarded to the actual Master in the other system
				else {
					// api->logPrint(1, "Got local query '%s', forwarding to CCMMaster...", query);
					// forward message as is to the master catalog
					msg = new DataMessage(*inMsg);
					//msg->setInt("SystemID", systemID);
					msg->setSystemID(systemID);
					status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
					//if (status == QUERY_SUCCESS)
					//	api->logPrint(1, "Got remote reply (SUCCESS) for local query '%s', responding to local module...", query);
					//else if (status == QUERY_FAILED)
					//	api->logPrint(1, "Got remote reply (FAILED) for local query '%s', responding to local module...", query);
					if ((status == QUERY_SUCCESS) || (status == QUERY_FAILED)) {
					}
					else if (status == QUERY_TIMEOUT)
						api->logPrint(1, "*** Timeout getting reply for local query '%s', responding to local module ***", query);
					else if (status == QUERY_NOT_REACHABLE)
						api->logPrint(1, "*** Remote system not responding for local query '%s', responding to local module ***", query);
					else
						api->logPrint(1, "*** Problem %u getting reply for local query '%s', responding to local module ***", status, query);
					// and reply with that to the request
					api->queryReply((uint32)inMsg->getReference(), status, replyMsg);
				}
			}
			else if (api->hasCurrentPostName(triggerName)) {
				// This message is for distribution to all other systems
				// but don't repost locally as it is already here
				if (!postMsg) {
					postMsg = new DataMessage();
					postCount = 0;
				}
				postMsg->setAttachedMessage(++postCount, "Messages", inMsg->copy());
				postMsg->setString(postCount, "Postnames", triggerName);
				// force heartbeat to be sent immediately
				lastHeartbeat = 0;
			}
		}

		if (!lastHeartbeat || GetTimeAgeMS(lastHeartbeat) > heartbeatInterval) {
			status = QUERY_FAILED;
			if (!postMsg)
				postMsg = new DataMessage();
			else
				api->logPrint(1, "Preparing Heartbeat, got %u messages to post to others", postCount);
			postMsg->setString("Query", "CCMHeartbeat");
			postMsg->setSystemID(systemID);
			status = api->queryCatalog(&replyMsg, "Master", postMsg, 5000);
			if (status == QUERY_SUCCESS) {
				if (replyMsg->getUserCount() > 0) {
					postMessages = replyMsg->getAttachedMessageArray("Messages");
					api->logPrint(1, "Heartbeat successful, got %u messages to post locally", (uint32)postMessages.size());
					if (postMessages.size()) {
						postNames = replyMsg->getStringArray("Postnames");
						if (postNames.size() == postMessages.size()) {
							i = postMessages.begin();
							e = postMessages.end();
							while (i != e) {
								postName = postNames[i->first];
								if (i->second && postName.length()) {
									api->postOutputMessage(postName.c_str(), i->second);
								}
								i++;
							}
						}
					}
				}
			}
			else if (status == QUERY_TIMEOUT) {
				api->logPrint(1, "Heartbeat timed out");
			}
			else {
				api->logPrint(1, "Heartbeat failed");
			}
			postMsg = NULL;

			lastHeartbeat = GetTimeNow();
		}
	}

	return 0;
}










// Built-in Collaborative Cognitive Map role negotiator catalog 
int8 CCMRoleNegotiator(PsyAPI* api) {
	std::string myName = api->getModuleName();
	const char* name;
	DataMessage* inMsg, *replyMsg, *msg;
	const char* triggerName;
	uint8 status;
	//api->addPsyProbeCustomView("Cognitive Map (remote)", "elements/ccmcatalog.html");
	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(100, triggerName)) {
			name = NULL;
			if (stricmp(triggerName, "TakeRole") == 0) {
				name = inMsg->getString("name");
			}
			else if (stricmp(triggerName, "LeaveRole") == 0) {
				name = inMsg->getString("name");
			}
			else if (stricmp(triggerName, "GiveRole") == 0) {
				name = inMsg->getString("name");
			}

			if (name) {
				replyMsg = NULL;
				status = QUERY_FAILED;
				msg = new DataMessage(*inMsg);
				msg->setString("Query", triggerName);
				status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
				if (status == QUERY_SUCCESS) {
					//api->logPrint(1, "Got remote reply (SUCCESS) for local query '%s', responding to local module...", triggerName);
					api->postOutputMessage(utils::StringFormat("%sSuccess", triggerName).c_str(), replyMsg);
				}
				else if (status == QUERY_FAILED) {
					api->logPrint(1, "Got remote reply (FAILED) for local query '%s', responding to local module...", triggerName);
					api->postOutputMessage(utils::StringFormat("%sFailed", triggerName).c_str(), replyMsg);
				}
				else {
					api->logPrint(1, "*** Problem %u getting reply for local query '%s', responding to local module ***", status, triggerName);
					api->postOutputMessage(utils::StringFormat("%sOther", triggerName).c_str(), replyMsg);
				}
			}
			else
				api->postOutputMessage(utils::StringFormat("%sFailed", triggerName).c_str());
		}
	}

	return 0;
}


int8 RoleNegotiator(PsyAPI* api) {

	std::string defaultRole = api->getParameterString("DefaultRole");
	std::string primaryRole = api->getParameterString("PrimaryRole");
	std::string secondaryRole = api->getParameterString("SecondaryRole");

	//bool logPrint = api->getParameterAsBool("LogPrint");

	std::string currentRole;

	uint32 systemID = 0, roleSystemID;

	DataMessage* inMsg, *replyMsg, *msg;
	const char* triggerName, *roleName;
	uint8 status;
	uint64 lastNoRoleCheck = 0;
	int32 noRoleCheckTimeoutMS = 1000;
	std::string privData;

	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(100, triggerName)) {
			if (!systemID) {
				replyMsg = NULL;
				msg = new DataMessage();
				msg->setString("Query", "GetLocalSystemID");
				status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
				if (replyMsg)
					systemID = replyMsg->getSystemID();
				delete replyMsg;
			}
			if (stricmp(triggerName, "UpgradeRole") == 0) {
				if (stricmp(currentRole.c_str(), defaultRole.c_str()) == 0) {
					// Try Primary role
					api->logPrint(1, "Trying to upgrade to primary role: %s", primaryRole.c_str());
					replyMsg = NULL;
					msg = new DataMessage();
					msg->setString("Query", "TakeRole");
					msg->setString("Name", primaryRole.c_str());
					status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
					// Reply doesn't matter, other trigger will tell us what happened...
					delete replyMsg;
					privData = utils::StringFormat("<span style='white - space: nowrap'><b>Upgrading role to: %s</b> <I>(%s)</I></span>", primaryRole.c_str(), PrintTimeNowString(true, false, false).c_str());
					api->setPrivateData("Action", privData.c_str(), privData.length(), "text/html");
				}
			}
			else if (stricmp(triggerName, "DowngradeRole") == 0) {
				if (stricmp(currentRole.c_str(), defaultRole.c_str()) != 0) {
					// Try to downgrade role
					api->logPrint(1, "Trying to downgrade to default role: %s", defaultRole.c_str());
					replyMsg = NULL;
					msg = new DataMessage();
					msg->setString("Query", "TakeRole");
					msg->setString("Name", defaultRole.c_str());
					status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
					// Reply doesn't matter, other trigger will tell us what happened...
					privData = utils::StringFormat("<span style='white - space: nowrap'><b>Downgrading role to: %s</b> <I>(%s)</I></span>", defaultRole.c_str(), PrintTimeNowString(true, false, false).c_str());
					api->setPrivateData("Action", privData.c_str(), privData.length(), "text/html");
					delete replyMsg;
				}
			}
			else if ((stricmp(triggerName, "RoleAssigned") == 0) ||
						(stricmp(triggerName, "RoleGiven") == 0)) {
				// Check if I am the role assignee
				roleSystemID = (uint32)inMsg->getInt("RoleSystemID");
				roleName = inMsg->getString("RoleName");
				if (systemID && (systemID == roleSystemID)) {
					api->logPrint(1, "Got assigned a new role: %s", roleName);
					privData = utils::StringFormat("<span style='white - space: nowrap'><b>%s</b> <I>(%s)</I></span>", roleName, PrintTimeNowString(true, false, false).c_str());
					api->setPrivateData("Role", privData.c_str(), privData.length(), "text/html");
					api->setPrivateData("Action", "None", 4, "text/html");
					// I have been assigned this role
					if (stricmp(currentRole.c_str(), roleName) != 0) {
						if (currentRole.length()) {
							// leave current role first
							api->logPrint(1, "Leaving current role first: %s", currentRole.c_str());
							replyMsg = NULL;
							msg = new DataMessage();
							msg->setString("Query", "LeaveRole");
							msg->setString("Name", currentRole.c_str());
							status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
							// Reply doesn't matter, other trigger will tell us what happened...
							delete replyMsg;
						}

						if (stricmp(defaultRole.c_str(), roleName) == 0) {
							api->logPrint(1, "Now confirmed in default role: %s", defaultRole.c_str());
							api->postOutputMessage("NowDefaultRole", inMsg->copy());
							currentRole = roleName;
						}
						else if (stricmp(primaryRole.c_str(), roleName) == 0) {
							api->logPrint(1, "Now confirmed in primary role: %s", primaryRole.c_str());
							api->postOutputMessage("NowPrimaryRole", inMsg->copy());
							currentRole = roleName;
						}
						else if (stricmp(secondaryRole.c_str(), roleName) == 0) {
							api->logPrint(1, "Now confirmed in secondary role: %s", secondaryRole.c_str());
							api->postOutputMessage("NowSecondaryRole", inMsg->copy());
							currentRole = roleName;
						}
						else
							currentRole.clear();
					}
				}
				// else did someone else take the primary role
				else if (roleName && (stricmp(primaryRole.c_str(), roleName) == 0)) {
					// take the secondary role
					api->logPrint(1, "Other system got primary role, trying to upgrade to secondary role: %s", secondaryRole.c_str());
					replyMsg = NULL;
					msg = new DataMessage();
					msg->setString("Query", "TakeRole");
					msg->setString("Name", secondaryRole.c_str());
					status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
					// Reply doesn't matter, other trigger will tell us what happened...
					delete replyMsg;
				}
				else {
					api->logPrint(1, "Other system got role: %s, no action here", roleName);
				}
			}
			else if (stricmp(triggerName, "RoleLeft") == 0) {
				// Check if I am the role leaver
				roleSystemID = (uint32)inMsg->getInt("RoleSystemID");
				roleName = inMsg->getString("RoleName");
				if (systemID && (systemID == roleSystemID)) {
					// I have left this role
					if (stricmp(currentRole.c_str(), roleName) == 0) {
						api->logPrint(1, "I have left my current role: %s", currentRole.c_str());
						// If this was my current role, clear this which will
						// trigger me into the default role below
						if (stricmp(defaultRole.c_str(), roleName) == 0)
							currentRole.clear();
						else if (stricmp(primaryRole.c_str(), roleName) == 0)
							currentRole.clear();
						else if (stricmp(secondaryRole.c_str(), roleName) == 0)
							currentRole.clear();
					}
					else {
						// this is a notification that I left myself above
					}
				}
				// else did someone else leave the primary role
				else if (roleName && (stricmp(primaryRole.c_str(), roleName) == 0)) {
					// if we are currently in the secondary role
					if (stricmp(currentRole.c_str(), secondaryRole.c_str()) == 0) {
						// take the default role too
						api->logPrint(1, "Other system left primary role, going to default role too: %s", defaultRole.c_str());
						replyMsg = NULL;
						msg = new DataMessage();
						msg->setString("Query", "TakeRole");
						msg->setString("Name", defaultRole.c_str());
						status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
						// Reply doesn't matter, other trigger will tell us what happened...
						delete replyMsg;
					}
				}
			}
		}

		if (!currentRole.length() && defaultRole.length() && (!lastNoRoleCheck || (GetTimeAgeMS(lastNoRoleCheck) > noRoleCheckTimeoutMS))) {
			// Choose default role
			replyMsg = NULL;
			msg = new DataMessage();
			msg->setString("Query", "TakeRole");
			msg->setString("Name", defaultRole.c_str());
			status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
			lastNoRoleCheck = GetTimeNow();
			delete replyMsg;
		}
	}

	return 0;
}













// Built-in Collaborative Cognitive Map role negotiator catalog 
int8 CCMTaskNegotiator(PsyAPI* api) {
	std::string myName = api->getModuleName();
	uint64 taskID = 0;
	DataMessage* inMsg, *replyMsg, *msg;
	const char* triggerName;
	uint8 status;
	//api->addPsyProbeCustomView("Cognitive Map (remote)", "elements/ccmcatalog.html");
	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(100, triggerName)) {
			taskID = 0;
			if (stricmp(triggerName, "CreateTask") == 0) {
			}
			else if (stricmp(triggerName, "AssignTask") == 0) {
				taskID = (uint64)msg->getInt("TaskID");
			}
			else if (stricmp(triggerName, "AcceptTask") == 0) {
				taskID = (uint64)msg->getInt("TaskID");
			}
			else if (stricmp(triggerName, "UpdateTask") == 0) {
				taskID = (uint64)msg->getInt("TaskID");
			}
			else if (stricmp(triggerName, "CompleteTask") == 0) {
				taskID = (uint64)msg->getInt("TaskID");
			}

			if (taskID || (stricmp(triggerName, "CreateTask") == 0)) {
				replyMsg = NULL;
				status = QUERY_FAILED;
				msg = new DataMessage(*inMsg);
				msg->setString("Query", triggerName);
				status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
				if (status == QUERY_SUCCESS) {
					//api->logPrint(1, "Got remote reply (SUCCESS) for local query '%s', responding to local module...", triggerName);
					api->postOutputMessage(utils::StringFormat("%sSuccess", triggerName).c_str(), replyMsg);
				}
				else if (status == QUERY_FAILED) {
					api->logPrint(1, "Got remote reply (FAILED) for local query '%s', responding to local module...", triggerName);
					api->postOutputMessage(utils::StringFormat("%sFailed", triggerName).c_str(), replyMsg);
				}
				else {
					api->logPrint(1, "*** Problem %u getting reply for local query '%s', responding to local module ***", status, triggerName);
					api->postOutputMessage(utils::StringFormat("%sOther", triggerName).c_str(), replyMsg);
				}
			}
			else
				api->postOutputMessage(utils::StringFormat("%sFailed", triggerName).c_str());
		}
	}

	return 0;
}





int8 TaskNegotiator(PsyAPI* api) {
	std::string myName = api->getModuleName();
	uint64 taskID = 0;
	DataMessage* inMsg, *replyMsg, *msg;
	const char* triggerName, *name;
	uint8 status;

	TaskTracker* task;
	std::map<uint64, TaskTracker*> currentTasks;
	std::map<uint64, TaskTracker*>::iterator i, e = currentTasks.end();
	std::string html;

	//api->addPsyProbeCustomView("Cognitive Map (remote)", "elements/ccmcatalog.html");
	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(100, triggerName)) {
			taskID = 0;
			if (stricmp(triggerName, "PerformTask") == 0) {
				if (name = inMsg->getString("Name")) {
					replyMsg = NULL;
					status = QUERY_FAILED;
					msg = inMsg->copy();
					msg->setString("Query", "CreateTask");
					status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
					if (status == QUERY_SUCCESS) {
						if (replyMsg) {
							taskID = (uint64)replyMsg->getInt("TaskID");
							api->logPrint(1, "Task '%s' created successfully with task ID %llu", name, taskID);
							task = new TaskTracker;
							task->id = taskID;
							task->name = name;
							task->status = "Created";
							task->createdTime = task->lastUpdatedTime = GetTimeNow();
							task->completedTime = 0;
							task->assignee = 0;
							currentTasks[taskID] = task;
						}
					}
					else if (status == QUERY_TIMEOUT) {
						api->logPrint(1, "Timed out when attempting to create task '%s'", name);
					}
					else {
						api->logPrint(1, "Failed creating task '%s'", name);
					}
				}
				else {
					api->logPrint(1, "Cannot create task without a name");
				}
			}
			else if (stricmp(triggerName, "CancelTask") == 0) {
				if (taskID = (uint64)inMsg->getInt("TaskID")) {
					replyMsg = NULL;
					status = QUERY_FAILED;
					msg = inMsg->copy();
					msg->setString("Query", "CancelTask");
					status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
					if (status == QUERY_SUCCESS) {
						if (task = currentTasks[taskID]) {
							api->logPrint(1, "Cancelling task '%s' ID %llu", task->name.c_str(), taskID);
						}
						else
							api->logPrint(1, "Cancelling task %llu", taskID);
					}
					else if (status == QUERY_TIMEOUT) {
						api->logPrint(1, "Timed out when attempting to cancel task %llu", taskID);
					}
					else {
						api->logPrint(1, "Failed cancelling task %llu", taskID);
					}
				}
				else
					api->logPrint(1, "Unable to cancel task withouta TaskID");
			}
			else if (stricmp(triggerName, "TaskCancelled") == 0) {
				taskID = (uint64)inMsg->getInt("TaskID");
				if (taskID && (task = currentTasks[taskID])) {
					if (!task->completedTime) {
						task->status = "Cancelled";
						task->completedTime = task->lastUpdatedTime = GetTimeNow();
						api->logPrint(1, "Task '%s' ID %llu marked as cancelled", task->name.c_str(), taskID);
					}
					else
						api->logPrint(1, "Unable to mark completed task '%s' ID %llu as cancelled", task->name.c_str(), taskID);
				}
				else if (taskID)
					api->logPrint(1, "Other or previous task ID %llu marked as cancelled", taskID);
			}
			else if (stricmp(triggerName, "TaskAccepted") == 0) {
				taskID = (uint64)inMsg->getInt("TaskID");
				if (taskID && (task = currentTasks[taskID]) && !task->completedTime) {
					task->status = "Accepted";
					task->lastUpdatedTime = GetTimeNow();
					task->assignee = (uint32)inMsg->getInt("Assignee");
					api->logPrint(1, "Task '%s' ID %llu marked as accepted", task->name.c_str(), taskID);
				}
				else if (taskID)
					api->logPrint(1, "Other or previous task ID %llu marked as accepted", taskID);
			}
			else if (stricmp(triggerName, "TaskUpdated") == 0) {
				taskID = (uint64)inMsg->getInt("TaskID");
				if (taskID && (task = currentTasks[taskID]) && !task->completedTime) {
					task->status = "Ongoing";
					task->lastUpdatedTime = GetTimeNow();
					api->logPrint(1, "Task '%s' ID %llu marked as updated", task->name.c_str(), taskID);
				}
				else if (taskID)
					api->logPrint(1, "Other or previous task ID %llu marked as updated", taskID);
			}
			else if (stricmp(triggerName, "TaskCompleted") == 0) {
				taskID = (uint64)inMsg->getInt("TaskID");
				if (taskID && (task = currentTasks[taskID]) && !task->completedTime) {
					task->status = "Completed";
					task->completedTime = task->lastUpdatedTime = GetTimeNow();
					api->logPrint(1, "Task '%s' ID %llu marked as completed", task->name.c_str(), taskID);
				}
				else if (taskID)
					api->logPrint(1, "Other or previous task ID %llu marked as completed", taskID);
			}
			else if (stricmp(triggerName, "TaskTimeout") == 0) {
				taskID = (uint64)inMsg->getInt("TaskID");
				if (taskID && (task = currentTasks[taskID]) && !task->completedTime) {
					task->status = "Timed out";
					task->completedTime = task->lastUpdatedTime = GetTimeNow();
					api->logPrint(1, "Task '%s' ID %llu marked as timed out", task->name.c_str(), taskID);
				}
				else if (taskID)
					api->logPrint(1, "Other or previous task ID %llu marked as timed out", taskID);
			}
		}

		std::map<uint64, TaskTracker*>::reverse_iterator ri, re = currentTasks.rend();
		int64 age;
		html = "<table cellspacing=10>";
		html += "<tr><td><b>Task ID</b></td><td><b>Name</b></td><td><b>Status</b></td><td><b>Age</b></td><td><b>Last Updated</b></td><td><b>Assignee</b></td></tr>";
		ri = currentTasks.rbegin();
		uint32 c = 0;
		bool nullsFound = false;
		while (ri != re) {
			if (task = ri->second) {
				age = GetTimeAge(task->lastUpdatedTime);
				html += utils::StringFormat("<tr><td>%llu</td><td><b>%s</b></td><td><b><i>%s</i></b></td><td>%s</td><td>%s</td><td><b>%u</b></td></tr>",
					task->id, task->name.c_str(), task->status.c_str(),
					PrintTimeDifString(GetTimeAge(task->createdTime), false, true).c_str(),
					PrintTimeDifString(age, false, true).c_str(), task->assignee);
				/*	if (age > (5 * MINUTE_US)) {
				ri++;
				currentTasks.erase(task->id);
				delete task;
				}
				else*/
				ri++;
				if (++c > 20)
					break;
			}
			else {
				nullsFound = true;
				ri++;
			}
		}
		html += "</table>";
		api->setPrivateData("Current Tasks", html.c_str(), html.length(), "text/html");

		if (nullsFound) {
			i = currentTasks.begin();
			while (i != e) {
				if (!i->second)
					i = currentTasks.erase(i);
				else
					i++;
			}
		}
	}

	return 0;
}

int8 TaskExecutor(PsyAPI* api) {
	std::string myName = api->getModuleName();
	uint64 taskID = 0;
	DataMessage* inMsg, *replyMsg, *msg;
	const char* triggerName, *name, *statusText;
	uint8 status;
	uint32 systemID = 0, assigneeID, creatorID;
	
	TaskTracker* task;
	std::map<uint64, TaskTracker*> currentTasks;
	std::map<uint64, TaskTracker*>::iterator i, e = currentTasks.end();
	std::string html, searchTask, postName;
	uint64 now;

	//api->addPsyProbeCustomView("Cognitive Map (remote)", "elements/ccmcatalog.html");
	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(100, triggerName)) {
			now = GetTimeNow();
			taskID = 0;
			if (!systemID) {
				replyMsg = NULL;
				msg = new DataMessage();
				msg->setString("Query", "GetLocalSystemID");
				status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
				if (replyMsg)
					systemID = replyMsg->getSystemID();
				delete replyMsg;
			}

			if (stricmp(triggerName, "TaskAssigned") == 0) {
				// A task has been assigned, check if it's for me
				assigneeID = (uint32)inMsg->getInt("Assignee");
				if (systemID && (assigneeID == systemID)) {
					taskID = (uint64)inMsg->getInt("TaskID");
					name = inMsg->getString("TaskName");
					creatorID = (uint32)inMsg->getInt("CreatorID");
					statusText = inMsg->getString("Status");

					// do I know this task
					if (!api->hasCurrentPostName(name))
						api->logPrint(1, "Unknown task '%s'", name);
					else {
						replyMsg = NULL;
						msg = new DataMessage();
						msg->setString("Query", "AcceptTask");
						msg->setInt("TaskID", (int64)taskID);
						status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
						if (replyMsg) delete replyMsg;
						if (status == QUERY_SUCCESS) {
							// trigger msg contains any parameters relevant for the task
							// so post this out with the task name post
							api->postOutputMessage(name, inMsg->copy());
							// and wait for status
							task = new TaskTracker;
							task->id = taskID;
							task->name = name;
							task->status = "Accepted";
							task->createdTime = now;
							task->lastUpdatedTime = now;
							task->completedTime = 0;
							task->assignee = assigneeID;
							currentTasks[taskID] = task;
							api->logPrint(1, "Task '%s' ID %llu accepted by me", name, taskID);
						}
						else
							api->logPrint(1, "Error accepting task '%s'", name);
					}
				}
			}
			else if (stricmp(triggerName, "TaskTimeout") == 0) {
				taskID = (uint64)inMsg->getInt("TaskID");
				if (taskID && (task = currentTasks[taskID])) {
					task->status = "Timed out";
					task->completedTime = now;
					task->lastUpdatedTime = now;
					postName = utils::StringFormat("%sCancel", task->name.c_str());
					if (api->hasCurrentPostName(postName.c_str())) {
						api->logPrint(1, "Task '%s' ID %llu timed out, sending cancel post", task->name.c_str(), taskID);
						api->postOutputMessage(postName.c_str());
					}
					else
						api->logPrint(1, "Task '%s' ID %llu timed out, no cancel post provided", task->name.c_str(), taskID);
				}
				else if (taskID)
					api->logPrint(1, "Unknown (or previous) task ID '%d' timed out", taskID);
			}
			else if (stricmp(triggerName, "TaskCancelled") == 0) {
				taskID = (uint64)inMsg->getInt("TaskID");
				if (taskID && (task = currentTasks[taskID])) {
					task->status = "Cancelled";
					task->completedTime = now;
					task->lastUpdatedTime = now;
					if (api->hasCurrentPostName(postName.c_str())) {
						api->logPrint(1, "Task '%s' ID %llu cancelled, sending cancel post", task->name.c_str(), taskID);
						api->postOutputMessage(postName.c_str());
					}
					else
						api->logPrint(1, "Task '%s' ID %llu cancelled, no cancel post provided", task->name.c_str(), taskID);
				}
				else if (taskID)
					api->logPrint(1, "Unknown (or previous) task ID '%d' cancelled", taskID);
			}
			else {
				// look for any of the supported task names from the posts
				i = currentTasks.begin();
				if (utils::TextEndsWith(triggerName, "Success", false)) {
					searchTask = std::string(triggerName, strlen(triggerName)-7);
					while (i != e) {
						if (i->second && !i->second->completedTime) {
							if (stricmp(searchTask.c_str(), i->second->name.c_str()) == 0) {
								i->second->status = "Success";
								i->second->completedTime = now;
								i->second->lastUpdatedTime = now;
								replyMsg = NULL;
								msg = inMsg->copy();
								msg->setString("Query", "CompleteTask");
								msg->setInt("TaskID", (int64)(i->second->id));
								msg->setString("Completion", "Success");
								status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
								if (replyMsg) delete replyMsg;
								if (status == QUERY_SUCCESS)
									api->logPrint(1, "Reported successful task '%s' as complete", i->second->name.c_str());
								else
									api->logPrint(1, "Error reporting successful task '%s' as complete", i->second->name.c_str());
								break;
							}
						}
						i++;
					}
					if (i == e)
						api->logPrint(1, "Error finding successful task '%s' to report as complete", searchTask.c_str());
				}
				else if (utils::TextEndsWith(triggerName, "Failed", false)) {
					searchTask = std::string(triggerName, strlen(triggerName) - 6);
					while (i != e) {
						if (i->second && !i->second->completedTime) {
							if (stricmp(searchTask.c_str(), i->second->name.c_str()) == 0) {
								i->second->status = "Failed";
								i->second->completedTime = now;
								i->second->lastUpdatedTime = now;
								replyMsg = NULL;
								msg = inMsg->copy();
								msg->setString("Query", "CompleteTask");
								msg->setInt("TaskID", (int64)(i->second->id));
								msg->setString("Completion", "Failed");
								status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
								if (replyMsg) delete replyMsg;
								if (status == QUERY_SUCCESS)
									api->logPrint(1, "Reported failed task '%s' as complete", i->second->name.c_str());
								else
									api->logPrint(1, "Error reporting failed task '%s' as complete", i->second->name.c_str());
								break;
							}
						}
						i++;
					}
					if (i == e)
						api->logPrint(1, "Error finding failed task '%s' to report as complete", searchTask.c_str());
				}
				else if (utils::TextEndsWith(triggerName, "Status", false)) {
					searchTask = std::string(triggerName, strlen(triggerName) - 6);
					while (i != e) {
						if (i->second && !i->second->completedTime) {
							if (stricmp(searchTask.c_str(), i->second->name.c_str()) == 0) {
								i->second->status = "Ongoing";
								i->second->lastUpdatedTime = now;
								replyMsg = NULL;
								msg = inMsg->copy();
								msg->setString("Query", "UpdateTask");
								msg->setInt("TaskID", (int64)(i->second->id));
								msg->setString("Completion", "Success");
								status = api->queryCatalog(&replyMsg, "Master", msg, 5000);
								if (replyMsg) delete replyMsg;
								if (status == QUERY_SUCCESS) {
									api->logPrint(1, "Reported update on task '%s'", i->second->name.c_str());
								}
								else
									api->logPrint(1, "Error reporting update on task '%s'", i->second->name.c_str());
								break;
							}
						}
						i++;
					}
					if (i == e)
						api->logPrint(1, "Error finding successful task '%s' to report update for", searchTask.c_str());
				}
				else {
					api->logPrint(1, "Error finding task for trigger message '%s'", triggerName);
				}
			}
		}

		std::map<uint64, TaskTracker*>::reverse_iterator ri, re = currentTasks.rend();
		int64 age;
		html = "<table cellspacing=10>";
		html += "<tr><td><b>Task ID</b></td><td><b>Name</b></td><td><b>Status</b></td><td><b>Age</b></td><td><b>Last Updated</b></td><td><b>Assignee</b></td></tr>";
		ri = currentTasks.rbegin();
		uint32 c = 0;
		bool nullsFound = false;
		while (ri != re) {
			if (task = ri->second) {
				age = GetTimeAge(task->lastUpdatedTime);
				html += utils::StringFormat("<tr><td>%llu</td><td><b>%s</b></td><td><b><i>%s</i></b></td><td>%s</td><td>%s</td><td><b>%u</b></td></tr>",
					task->id, task->name.c_str(), task->status.c_str(),
					PrintTimeDifString(GetTimeAge(task->createdTime), false, true).c_str(),
					PrintTimeDifString(age, false, true).c_str(), task->assignee);
			/*	if (age > (5 * MINUTE_US)) {
					ri++;
					currentTasks.erase(task->id);
					delete task;
				}
				else*/
					ri++;
					if (++c > 20)
						break;
			}
			else {
				nullsFound = true;
				ri++;
			}
		}
		html += "</table>";
		api->setPrivateData("Current Tasks", html.c_str(), html.length(), "text/html");

		if (nullsFound) {
			i = currentTasks.begin();
			while (i != e) {
				if (!i->second)
					i = currentTasks.erase(i);
				else
					i++;
			}
		}
	}

	return 0;
}







//uint8 PostToSystem(uint32 systemID, const char* postName, const char* myName, DataMessage* msg, PsyAPI* api, CCMDataStorage* dataStorage) {
//	int32 res;
//	uint8 status = QUERY_FAILED;
//	RemoteSystemEntry* entry;
//	std::map<uint32, RemoteSystemEntry*>::iterator iRemote, eRemote = dataStorage->remoteSystems.end();
//	if (!systemID) {
//		// post to all, first the rest
//		iRemote = dataStorage->remoteSystems.begin();
//		while (iRemote != eRemote) {
//			if (iRemote->second->identity) {
//			}
//		}
//
//		if ((iRemote = remoteSystems.find(systemID)) != eRemote) {
//			entry = iRemote->second;
//
//
//
//		std::map<uint32, CCMRemoteSystemIdentity*>::iterator sI, sE = dataStorage->remoteSystemIdentities.end();
//		sI = dataStorage->remoteSystemIdentities.begin();
//		while (sI != sE) {
//			if (sI->second) {
//				// now try to forward the query to the remote system
//				msg->setString("Query", "local_post");
//				DataMessage* resultMsg = NULL;
//				res = api->queryRemoteCatalog(&resultMsg, sI->second->chosenAddress, myName, sI->second->systemAddresses, sI->second->systemAddressCount, sI->second->systemPort, msg->copy());
//				delete resultMsg;
//				status = (uint8)res;
//			}
//
//			sI++;
//		}
//		// then my own system
//		res = api->postOutputMessage(postName, new DataMessage(*msg));
//		if (res > 0)
//			status = QUERY_SUCCESS;
//	}
//	else {
//		uint32 sysID = dataStorage->getObjectSystemID(systemID);
//		if (postName && sysID) {
//			CCMRemoteSystemIdentity* sysIden;
//			if (dataStorage->localSystemID && (dataStorage->localSystemID == sysID)) {
//				// id is me, post from here
//				res = api->postOutputMessage(postName, new DataMessage(*msg));
//				if (res > 0)
//					status = QUERY_SUCCESS;
//			}
//			else if (sysIden = dataStorage->remoteSystemIdentities[sysID]) {
//				// now try to forward the query to the remote system
//				msg->setString("Query", "local_post");
//				DataMessage* resultMsg = NULL;
//				res = api->queryRemoteCatalog(&resultMsg, sysIden->chosenAddress, myName, sysIden->systemAddresses, sysIden->systemAddressCount, sysIden->systemPort, msg->copy());
//				delete resultMsg;
//				status = (uint8)res;
//			}
//		}
//	}
//	return status;
//}


// Built-in Collaborative Cognitive Map catalog 
// Queries: Get
// Parameters: none
int8 CCMCollector(PsyAPI* api) {
	std::string myName = api->getModuleName();
	//api->logPrint(2, "Started running...");

	//std::map<ObsEntry, std::map<std::string, std::string>, cmpObsEntry > obsMappings;
	std::list<ObsEntry*> obsEntries;
	std::list<ObsEntry*>::iterator oI, oE = obsEntries.end();

	XMLResults xmlResults;
	std::string xml = api->getParameterString("componentsetup");
	XMLNode node, subNode, mainNode = XMLNode::parseString(xml.c_str(), "setup", &xmlResults);

	// display error message (if any)
	if (xmlResults.error != eXMLErrorNone)
	{
		if (xmlResults.error == eXMLErrorFirstTagNotFound) {
			api->logPrint(0, "Catalog setup error, main tag '<setup>' not found");
			// return XMLNode::emptyXMLNode;
		}
		else {
			api->logPrint(0, "Catalog setup parsing error at line %i, column %i:\n   %s\n",
				xmlResults.nLine, xmlResults.nColumn, XMLNode::getError(xmlResults.error));
			// return XMLNode::emptyXMLNode;
		}
	}

	ObsEntry* obsEntry;
	int n = 0, m;
	const char* name, *type, *trigger, *entry, *key;
	while (!(node = mainNode.getChildNode("obs", n++)).isEmpty()) {
		if ((name = node.getAttribute("name")) &&
			(type = node.getAttribute("obstype")) &&
			(trigger = node.getAttribute("trigger"))) {
			obsEntry = new ObsEntry();
			obsEntry->name = name;
			obsEntry->type = type;
			obsEntry->trigger = trigger;
			m = 0;
			//std::map<std::string, std::string>* subMap = &obsMappings[obsEntry];
			while (!(subNode = node.getChildNode("mapping", m++)).isEmpty()) {
				if ((entry = subNode.getAttribute("entry")) &&
					(key = subNode.getAttribute("key")))
					obsEntry->mappings[entry] = key;
			}
			obsEntries.push_back(obsEntry);
		}
	}

	DataMessage* inMsg, *outMsg, *resultMsg; // , *replyMsg;
	const char* triggerName;
//	std::map<ObsEntry, std::map<std::string, std::string>, cmpObsEntry >::iterator i, e = obsMappings.end();

	uint32 count;
	uint8 status;
	uint64 lastID = 0;
	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(100, triggerName)) {
			if (triggerName && stricmp(triggerName, "DefaultComponentTrigger") == 0) {
			//	api->logPrint(2, "Got wakeup message...");
			}
			else {
				count = 0;
				lastID = 0;
				outMsg = new DataMessage();
				oI = obsEntries.begin();
				while (oI != oE) {
					if (stricmp((*oI)->trigger.c_str(), triggerName) == 0) {
						if (ProcessObservationTrigger(outMsg, inMsg, (*oI), ++lastID)) {
							count++;
						}
						else
							api->logPrint(0, "Unable to extract observation data for %s trigger %s...\n%s", (*oI)->name.c_str(), (*oI)->trigger.c_str(), inMsg->getUserEntriesAsString().c_str());
						//api->logPrint(1, "[%u] Object %u ( %.3f , %.3f )",
						//	(uint32)inMsg->getInt("SystemID"),
						//	(uint32)inMsg->getInt("ObjectID"),
						//	inMsg->getFloat("x"),
						//	inMsg->getFloat("y")
						//);
					}
					oI++;
				}
				if (count) {
					outMsg->setString("Query", "AddObservations");
					outMsg->setInt("ObsCount", count);
					status = api->queryCatalog(&resultMsg, "Master", outMsg);
					if (status != QUERY_SUCCESS)
						api->logPrint(0, "Failed to add observations from trigger %s to CCM...", triggerName);
					//else
					//	api->logPrint(1, "Added %u observation(s) from trigger %s to CCM...", count, triggerName);
				}
				else {
					api->logPrint(1, "No observation data found for trigger %s...", triggerName);
					delete outMsg;
				}
			}
		}
	}
	oI = obsEntries.begin();
	while (oI != oE) {
		delete(*oI);
		oI++;
	}
	obsEntries.clear();

	return 0;
}




int8 CCMTester(PsyAPI* api) {
	std::string myName = api->getModuleName();

	std::vector<uint32> identities;
	std::vector<uint32>::iterator i, e;

	int64 intervalMS = api->getParameterInt("Interval");
	if (!intervalMS)
		intervalMS = 1000;

	bool shouldPrintSuccess = api->getParameterAsBool("PrintSuccess");

	int64 navigationTime = 5000;
	int64 perchingTime = 15000;

	uint64 lastQueryTest = GetTimeNow();

	float64 x, y;
	const char* label;
	uint64 navigationInProgress = 0;
	uint64 occupyTime = 0;
	uint32 currentID = 0;
	uint32 entityID = 0;
	uint32 id;
	//const char* name;
	DataMessage* inMsg, *resultMsg, *msg;
	const char* triggerName; // , *query;
	//uint8 status;
	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(100, triggerName)) {
			if (triggerName && stricmp(triggerName, "DefaultComponentTrigger") == 0) {
			}
		}

		if (!identities.size()) {
			msg = new DataMessage();
			msg->setString("Query", "Identities");
			msg->setString("Type", "Human");
			if ((api->queryCatalog(&resultMsg, "Master", msg, 5000) != QUERY_SUCCESS) || !resultMsg) {
				delete resultMsg;
				api->logPrint(1, "Couldn't get identities from CCM");
			}
			else {
				std::map<int64, std::string> resultMap = resultMsg->getStringArray("Name");
				std::map<int64, std::string>::iterator mapI = resultMap.begin(), mapE = resultMap.end();
				while (mapI != mapE) {
					if (mapI->first && mapI->second.length())
						identities.push_back((uint32)mapI->first);
					mapI++;
				}
				delete resultMsg;
				api->logPrint(1, "Read %u human identities from CCM", identities.size());
			}

			api->logPrint(1, "Creating self in CCM...");
			msg = new DataMessage();
			msg->setString("Query", "CreateObject");
			msg->setInt("Identity", 1);
			msg->setString("Type", "Robot");
			msg->setInt("PresentNow", 1);
			// Create new object and retrieve the new ID
			if ((api->queryCatalog(&resultMsg, "Master", msg, 5000) != QUERY_SUCCESS) || !resultMsg) {
				delete resultMsg;
				api->logPrint(1, "Couldn't create self in CCM");
				return -1;
			}
			entityID = (uint32)resultMsg->getInt("ObjectID");
			delete resultMsg;
			if (!entityID) {
				api->logPrint(1, "Couldn't get ID of newly created self");
			}
		}

		if (entityID) {
			msg = new DataMessage();
			msg->setString("Query", "AddDataToObject");
			msg->setInt("ObjectID", entityID);
			msg->setString("Current position", utils::StringFormat("%.3f , %.3f", 1.0, 1.0).c_str());
			if ((api->queryCatalog(&resultMsg, "Master", msg, 5000) != QUERY_SUCCESS)) {
				api->logPrint(1, "Couldn't add data to self in CCM");
			}
			delete resultMsg;
		}

		if (GetTimeAgeMS(lastQueryTest) > intervalMS) {
			if (!navigationInProgress && (!occupyTime || (GetTimeAgeMS(occupyTime) > perchingTime))) {
				api->logPrint(1, "Getting next navigation point from CCM...");
				msg = new DataMessage();
				msg->setString("Query", "GetVantagePoint");
				//msg->setInt("SystemID", systemID);
				uint8 status = api->queryCatalog(&resultMsg, "Master", msg, 5000);
				if ((status != QUERY_SUCCESS) || !resultMsg) {
					delete resultMsg;
					if (status != QUERY_FAILED)
						api->logPrint(1, "***** Error getting next navigation point from CCM *****");
					else
						api->logPrint(1, "----- Next navigation point not yet availalble from CCM -----");
					occupyTime = GetTimeNow();
				}
				else {
					currentID = (uint32)resultMsg->getInt("ID");
					label = resultMsg->getString("Label");
					x = resultMsg->getFloat("X");
					y = resultMsg->getFloat("Y");
					api->logPrint(1, "Robot go to vantage point %u: %s (%.3f,%.3f)     ---    NAVIGATING", currentID, label, x, y);
					navigationInProgress = GetTimeNow();
					occupyTime = 0;
				}
			}
			else {
				msg = new DataMessage();
				msg->setString("Query", "Identity");
				msg->setString("Datatype", "Image");
				id = identities.at((uint32)utils::RandomInt(0, identities.size() - 1));
				msg->setInt("ID", id);
				if ((api->queryCatalog(&resultMsg, "Master", msg, 5000) != QUERY_SUCCESS) || !resultMsg) {
					api->logPrint(1, "Couldn't get identity photo of identity %u from CCM", id);
				}
				else {
					if (shouldPrintSuccess)
						api->logPrint(1, "Retrieved identity photo of identity %u from CCM", id);
				}
				delete resultMsg;
			}
			lastQueryTest = GetTimeNow();

		}

		if (navigationInProgress && (GetTimeAgeMS(navigationInProgress) > navigationTime) && !occupyTime) {
			msg = new DataMessage();
			msg->setString("Query", "SetVantagePoint");
			msg->setInt("ID", currentID);
			if ((api->queryCatalog(&resultMsg, "Master", msg, 5000) != QUERY_SUCCESS)) {
				api->logPrint(1, "***** Couldn't occupy navigation point from CCM *****");
				occupyTime = GetTimeNow(); // prevent from asking until +5 seconds
			}
			else {
				api->logPrint(1, "Now occupying navigation point %u in CCM     ---    STOPPED", currentID);
				occupyTime = GetTimeNow();
			}
			currentID = 0;
			navigationInProgress = 0;
		}

	}

	return 0;
}





bool ProcessObservationTrigger(DataMessage* outMsg, DataMessage* inMsg, ObsEntry *obsEntry, uint64 id) {
	// We have our trigger and the message containing the data
	// now extract the fields from the msg into the observation and add to message

	bool result = false;
	if (stricmp(obsEntry->type.c_str(), "Location") == 0) {
		GenericObservation<ObsLocation>* location = new GenericObservation<ObsLocation>();
		if (location->extractDataFromMessage(inMsg, obsEntry->mappings)) {
			utils::strcpyavail(location->header.name, obsEntry->name.c_str(), 1023, true);
			result = location->writeToMessage(outMsg, id);
		}
		delete location;
	}
	else if (stricmp(obsEntry->type.c_str(), "Dimension") == 0) {
		GenericObservation<ObsDimension>* dimension = new GenericObservation<ObsDimension>();
		if (dimension->extractDataFromMessage(inMsg, obsEntry->mappings)) {
			utils::strcpyavail(dimension->header.name, obsEntry->name.c_str(), 1023, true);
			result = dimension->writeToMessage(outMsg, id);
		}
		delete dimension;
	}
	else if (stricmp(obsEntry->type.c_str(), "Integer") == 0) {
		GenericObservation<ObsInteger>* integer = new GenericObservation<ObsInteger>();
		if (integer->extractDataFromMessage(inMsg, obsEntry->mappings)) {
			utils::strcpyavail(integer->header.name, obsEntry->name.c_str(), 1023, true);
			result = integer->writeToMessage(outMsg, id);
		}
		delete integer;
	}
	else if (stricmp(obsEntry->type.c_str(), "Float") == 0) {
		GenericObservation<ObsFloat>* floatData = new GenericObservation<ObsFloat>();
		if (floatData->extractDataFromMessage(inMsg, obsEntry->mappings)) {
			utils::strcpyavail(floatData->header.name, obsEntry->name.c_str(), 1023, true);
			result = floatData->writeToMessage(outMsg, id);
		}
		delete floatData;
	}
	else if (stricmp(obsEntry->type.c_str(), "Time") == 0) {
		GenericObservation<ObsTime>* time = new GenericObservation<ObsTime>();
		if (time->extractDataFromMessage(inMsg, obsEntry->mappings)) {
			utils::strcpyavail(time->header.name, obsEntry->name.c_str(), 1023, true);
			result = time->writeToMessage(outMsg, id);
		}
		delete time;
	}
	else if (stricmp(obsEntry->type.c_str(), "String") == 0) {
		GenericObservation<ObsString>* string = new GenericObservation<ObsString>();
		if (string->extractDataFromMessage(inMsg, obsEntry->mappings)) {
			utils::strcpyavail(string->header.name, obsEntry->name.c_str(), 1023, true);
			result = string->writeToMessage(outMsg, id);
		}
		delete string;
	}

	return result;
}










CCMDataStorage::CCMDataStorage(PsyAPI* api) {
	this->api = api;
	lastMapID = 0;
	lastIdentityID = 0;
	lastObjectID = 0;
	lastImageID = 0;
	lastObsID = 0;
	localSystemID = 0;
	lastTaskID = 0;
}

CCMDataStorage::~CCMDataStorage() {
	std::map<uint32, CCMMap*>::iterator mapsI = maps.begin(), mapsE = maps.end();
	while (mapsI != mapsE) {
		delete mapsI->second;
		mapsI++;
	}
	maps.clear();

	std::map<uint32, CCMIdentity*>::iterator identitiesI = identities.begin(), identitiesE = identities.end();
	while (identitiesI != identitiesE) {
		delete identitiesI->second;
		identitiesI++;
	}
	identities.clear();

	std::map<uint32, CCMObject*>::iterator objectsI = objects.begin(), objectsE = objects.end();
	while (objectsI != objectsE) {
		delete objectsI->second;
		objectsI++;
	}
	objects.clear();

	std::map<uint32, CCMBaseTypedStorage*>::iterator obsMapI = obsMap.begin(), obsMapE = obsMap.end();
	while (obsMapI != obsMapE) {
		delete obsMapI->second;
		obsMapI++;
	}
	obsMap.clear();

	std::map<uint32, CCMImage*>::iterator imagesI = images.begin(), imagesE = images.end();
	while (imagesI != imagesE) {
		delete imagesI->second;
		imagesI++;
	}
	images.clear();

	std::map<uint32, RemoteSystemEntry*>::iterator entryI = remoteSystems.begin(), entryE = remoteSystems.end();
	while (entryI != entryE) {
		entryI->second->clear();
		delete entryI->second;
		entryI++;
	}
	remoteSystems.clear();
}

std::string CCMDataStorage::init(const char* ccmDir, uint32 maxKeep) {

	if (!utils::DoesADirExist(ccmDir)) {
		if (!utils::CreateADir(ccmDir))
			return utils::StringFormat("Couldn't create storage directory: %s", ccmDir);
	}
	
	obsMap[OBSID_LOC] = new CCMTypedStorage<ObsLocation>(maxKeep);
	obsMap[OBSID_DIM] = new CCMTypedStorage<ObsDimension>(maxKeep);
	obsMap[OBSID_INT] = new CCMTypedStorage<ObsInteger>(maxKeep);
	obsMap[OBSID_FLOAT] = new CCMTypedStorage<ObsFloat>(maxKeep);
	obsMap[OBSID_TIME] = new CCMTypedStorage<ObsTime>(maxKeep);
	obsMap[OBSID_STRING] = new CCMTypedStorage<ObsString>(maxKeep);

	return "";
}

bool CCMDataStorage::addRemoteSystemIdentity(uint32 systemID, DataMessage* inMsg) {
	// Check if systemID is a local or remote system
	if (!systemID || (localSystemID == systemID)) {
		// ID is either invalid or our own local system ID
		return true;
	}

	// This ID is new, find it if it is a local or remote ID
	const char* iden = inMsg->getString("INTERSYSTEM_IDENTIFICATION");
	if (!iden) {
		// Nope, it local, so update local
		localSystemID = systemID;
		return true;
	}

	RemoteSystemEntry* entry;
	std::map<uint32, RemoteSystemEntry*>::iterator iRemote, eRemote = remoteSystems.end();
	if ((iRemote = remoteSystems.find(systemID)) != eRemote) {
		entry = iRemote->second;
		if (entry->identity) {
			// ID is already known as a remote system ID
			return true;
		}
	}
	else {
		entry = new RemoteSystemEntry;
		entry->systemID = systemID;
		entry->lastSeenTime = GetTimeNow();
		entry->identity = NULL;
		remoteSystems[systemID] = entry;
	}

	entry->identity = new CCMRemoteSystemIdentity(systemID);
	entry->identity->setFromString(iden);
	return true;
}

DataMessage* CCMDataStorage::addHeartbeat(DataMessage* inMsg) {

	uint32 sysID = inMsg->getSystemID();
	if (!sysID)
		return NULL;

	RemoteSystemEntry* entry;
	std::map<uint32, RemoteSystemEntry*>::iterator i, e = remoteSystems.end();
	if ((i = remoteSystems.find(sysID)) != e)
		entry = i->second;
	else {
		entry = new RemoteSystemEntry;
		entry->systemID = sysID;
		entry->lastSeenTime = GetTimeNow();
		entry->identity = NULL;
		remoteSystems[sysID] = entry;
	}

	if (!entry)
		return NULL;

	// check for messages to post locally and to others
	std::map<int64, DataMessage*> postMessages;
	std::map<int64, DataMessage*>::iterator pi, pe;
	std::map<int64, std::string> postNames;
	std::string postName;
	if (inMsg->getUserCount() > 0) {
		postMessages = inMsg->getAttachedMessageArray("Messages");
		if (postMessages.size()) {
			LogPrint(0, 0, 1, "Heartbeat received for system %u, have %u messages to post from it to me and others", sysID, (uint32)postMessages.size());
			postNames = inMsg->getStringArray("Postnames");
			if (postNames.size() == postMessages.size()) {
				pi = postMessages.begin();
				pe = postMessages.end();
				while (pi != pe) {
					postName = postNames[pi->first];
					if (i->second && postName.length()) {
						addQueuedPostToOthers(sysID, postName.c_str(), pi->second);
						api->postOutputMessage(postName.c_str(), pi->second->copy());
					}
					pi++;
				}
			}
		}
	}

	DataMessage* replyMsg = new DataMessage();

	uint32 n = 1;
	std::map<DataMessage*, std::string>::iterator postI = entry->queuedPosts.begin(), postE = entry->queuedPosts.end();
	if (entry->queuedPosts.size() > 0)
		LogPrint(0,0,1, "Heartbeat received for system %u, have %u messages to post back to it", sysID, (uint32)entry->queuedPosts.size());
	while (postI != postE) {
		replyMsg->setAttachedMessage(n, "Messages", postI->first);
		replyMsg->setString(n, "Postnames", postI->second.c_str());
		delete postI->first;
		postI++;
		n++;
	}
	entry->queuedPosts.clear();
	return replyMsg;
}

bool CCMDataStorage::addQueuedPost(uint32 sysID, const char* name, DataMessage* msg) {
	if (!name || !msg)
		return false;

	RemoteSystemEntry* entry;
	std::map<uint32, RemoteSystemEntry*>::iterator i, e = remoteSystems.end();
	if (sysID) {
		if ((i = remoteSystems.find(sysID)) != e)
			entry = i->second;
		else
		{
			entry = new RemoteSystemEntry;
			entry->systemID = sysID;
			entry->lastSeenTime = 0;
			entry->identity = NULL;
			remoteSystems[sysID] = entry;
		}
		if (entry)
			entry->queuedPosts[msg->copy()] = name;
	}
	else {
		i = remoteSystems.begin();
		while (i != e) {
			if (i->second)
				i->second->queuedPosts[msg->copy()] = name;
			i++;
		}
	}
	return true;
}


bool CCMDataStorage::addQueuedPostToOthers(uint32 sysID, const char* name, DataMessage* msg) {
	if (!name || !msg)
		return false;

	std::map<uint32, RemoteSystemEntry*>::iterator i, e = remoteSystems.end();
	i = remoteSystems.begin();
	while (i != e) {
		if ((i->first != sysID) && (i->second))
			i->second->queuedPosts[msg->copy()] = name;
		i++;
	}
	return true;
}

CCMImage* CCMDataStorage::getLastImage(uint32 objectID, uint32 systemID) {
	if (!images.size())
		return NULL;

	std::map<uint32, CCMImage*>::reverse_iterator imagesRI = images.rbegin(), imagesRE = images.rend();

	for (; imagesRI != imagesRE; imagesRI++) {
		if (objectID && (imagesRI->second->objectID != objectID))
			continue;
		if (systemID && (imagesRI->second->systemID != systemID))
			continue;
		if (!imagesRI->second->image)
			continue;
		return imagesRI->second;
	}
	return NULL;
}

bool CCMDataStorage::addImage(uint32 objectID, uint32 systemID, Bitmap* image) {
	if (!image)
		return false;

	CCMImage* img = new CCMImage();
	img->objectID = objectID;
	img->systemID = systemID;
	img->createdTime = GetTimeNow();
	img->image = image;

	img->id = ++lastImageID;
	images[img->id] = img;
	return true;
}


std::string CCMDataStorage::addMap(const char* name, const char* parent, const char* offset, const char* view, const char* image, const char* invertX, const char* invertY) {
	if (!name || !strlen(name))
		return "Name and size are required parameters for maps";

	std::vector<std::string> list;

	Bitmap* bitmap = NULL;
	if (image && strlen(image)) {
		if (!utils::DoesAFileExist(image)) {
			return utils::StringFormat("Image file '%s' doesn't exist", image);
		}
		bitmap = new Bitmap(image);
		if (!bitmap->width) {
			delete bitmap;
			return utils::StringFormat("Image file '%s' is not a Bitmap file", image);
		}
	}

	CCMMap* map = new CCMMap();
	map->name = name;
	map->view = 0;
	if (view)
		map->view = utils::Ascii2Float64(view);
	if (!view)
		map->view = 10;

	if (bitmap) {
		// map defaults to viewing from below, swap to view from above
		bitmap->mirror(false, true);
		map->image = bitmap;
	}

	if (parent && strlen(parent)) {
		std::map<uint32, CCMMap*>::iterator mapsI = maps.begin(), mapsE = maps.end();
		while (mapsI != mapsE) {
			if (stricmp(mapsI->second->name.c_str(), parent) == 0) {
				map->parent = mapsI->first;
				break;
			}
			mapsI++;
		}
		if (map->parent) {
			if (offset) {
				list = utils::TextListSplit(offset, ",", false, true);
				if (list.size() == 2) {
					map->offset.x = (double)utils::Ascii2Float64(list.at(0).c_str());
					map->offset.y = (double)utils::Ascii2Float64(list.at(1).c_str());
				}
			}
			//map->scale.x = 1;
			//map->scale.y = 1;
			//if (scale) {
			//	list = utils::TextListSplit(scale, ",", false, true);
			//	if (list.size() == 2) {
			//		map->scale.x = (double)utils::Ascii2Float64(list.at(0).c_str());
			//		map->scale.y = (double)utils::Ascii2Float64(list.at(1).c_str());
			//	}
			//}
		}
	}

	map->id = ++lastMapID;
	maps[map->id] = map;
	return "";
}

std::string CCMDataStorage::updateMap(uint32 id, const char* imageData, uint32 imageDataSize, uint32 imageWidth, uint32 imageHeight,
	uint32 imageStep, const char* imageEncoding, float64 scale, float64 originX, float64 originY) {
	if (!id || !imageData || !imageDataSize || !imageWidth || !imageHeight || !imageEncoding || !scale)
		return utils::StringFormat("Map input data error");

	std::map<uint32, CCMMap*>::iterator mapsI = maps.find(id);
	if (mapsI == maps.end())
		return utils::StringFormat("Map ID %u doesn't exist", id);

	Bitmap* bitmap = new Bitmap(imageData, imageDataSize, imageWidth, imageHeight, imageEncoding, imageStep);
	if (!bitmap->width) {
		delete bitmap;
		return utils::StringFormat("Image data is not valid");
	}
	bitmap->mirror(false, true);

	(*mapsI).second->image = bitmap;
	(*mapsI).second->scale = scale;
	(*mapsI).second->origin.x = originX;
	(*mapsI).second->origin.y = originY;
	return "";
}

std::string CCMDataStorage::addIdentity(uint32 id, const char* name, const char* type, const char* nickname, const char* image, std::list<std::string>& faces) {
	if (!name || !type || !strlen(name) || !strlen(type))
		return "Name and type are required parameters for identities";

	Bitmap* bitmap = NULL;
	if (image && strlen(image)) {
		if (!utils::DoesAFileExist(image)) {
			return utils::StringFormat("Image file '%s' doesn't exist", image);
		}
		bitmap = new Bitmap(image);
		if (!bitmap->width) {
			delete bitmap;
			return utils::StringFormat("Image file '%s' is not a Bitmap file", image);
		}
	}

	CCMIdentity* identity = new CCMIdentity();
	identity->name = name;
	identity->type = type;
	identity->image = bitmap;

	int n = 0;
	Bitmap* bitmapFace = NULL;
	std::list<std::string>::iterator i = faces.begin(), e = faces.end();
	while (i != e) {
		if ((*i).length()) {
			if (!utils::DoesAFileExist((*i).c_str())) {
				return utils::StringFormat("Face file '%s' doesn't exist", (*i).c_str());
			}
			bitmapFace = new Bitmap((*i).c_str());
			if (!bitmapFace->width) {
				delete bitmapFace;
				return utils::StringFormat("Face file '%s' is not a Bitmap file", (*i).c_str());
			}
			identity->faces[n] = bitmapFace;
			n++;
		}
		i++;
	}

	if (nickname)
		identity->nickname = nickname;

	if (id) {
		identity->id = id;
		lastIdentityID = id;
	}
	else
		identity->id = ++lastIdentityID;

	identities[identity->id] = identity;
	return "";
}

uint32 CCMDataStorage::getObjectSystemID(uint32 id) {
	std::map<uint32, CCMObject*>::iterator objectsI = objects.find(id);
	if (objectsI != objects.end())
		return objectsI->second->systemID;
	else
		return 0;
}

uint32 CCMDataStorage::createObject(const char* type, uint32 identity, uint64 firstSeen, uint64 lastSeen, bool presentNow, uint32 systemID) {
	if (!type || !strlen(type))
		return 0;

	// Check if an object with this identity already exists...
	std::map<uint32, CCMObject*>::iterator objectsI = objects.begin(), objectsE = objects.end();
	while (objectsI != objectsE) {
		if ((objectsI->second->identity == identity) && (objectsI->second->presentNow)) {
			return objectsI->second->id;
		}
		objectsI++;
	}

	CCMObject* object = new CCMObject();
	object->type = type;
	object->identity = identity;
	object->firstSeen = firstSeen ? firstSeen : GetTimeNow();
	object->lastSeen = lastSeen ? lastSeen : object->firstSeen;
	object->presentNow = presentNow;
	object->systemID = systemID;

	//object->staticData.setString("Created", PrintTimeNowString().c_str());

	// for testing only
	//object->staticData.setString("Created", PrintTimeNowString().c_str());
	//object->addMediaStream("USB Camera", 2, "RobotSelf", "USB", "image/bmp");
	//object->addMediaStream("Color Camera", 2, "RobotSelf", "Color", "image/bmp");
	//object->addMediaStream("Depth Camera", 2, "RobotSelf", "Depth", "image/bmp");
	//object->addMediaStream("Registered Camera", 2, "RobotSelf", "Registered", "image/bmp");

	object->id = ++lastObjectID;
	objects[object->id] = object;
	return object->id;
}

bool CCMDataStorage::addDataToObject(uint32 id, DataMessage* msg) {
	if (!id || !msg)
		return false;
	std::map<uint32, CCMObject*>::iterator objectsI = objects.find(id);
	if (objectsI == objects.end())
		return false;
	CCMObject* object = objectsI->second;
	if (!object)
		return false;

	object->staticData.copyUserEntriesFromMessage(msg);
	return true;
}

bool CCMDataStorage::addStreamToObject(uint32 id, const char* name, uint32 systemID, const char* componentName, const char* privateDataName, const char* mimetype) {
	if (!id || !name || !systemID || !componentName || !privateDataName || !mimetype ||
		!strlen(name) || !strlen(componentName) || !strlen(privateDataName) || !strlen(mimetype))
		return false;
	std::map<uint32, CCMObject*>::iterator objectsI = objects.find(id);
	if (objectsI == objects.end())
		return false;
	CCMObject* object = objectsI->second;
	if (!object)
		return false;

	return object->addMediaStream(name, systemID, componentName, privateDataName, mimetype);
}


bool CCMDataStorage::updateObject(uint32 id, const char* type, uint32 identity, uint64 firstSeen, uint64 lastSeen, bool presentNow, uint32 systemID) {
	if (!id)
		return false;

	std::map<uint32, CCMObject*>::iterator objectsI = objects.find(id);
	if (objectsI == objects.end())
		return false;

	CCMObject* object = objectsI->second;
	if (!object)
		return false;

	if (type)
		object->type = type;
	if (identity)
		object->identity = identity;
	if (firstSeen)
		object->firstSeen = firstSeen;
	if (lastSeen)
		object->lastSeen = lastSeen;
	object->presentNow = presentNow;
	return true;
}


bool CCMDataStorage::query(DataMessage* qMsg, DataMessage* resultMsg, std::list<std::string>* jsonList) {
	// Find type
	std::map<uint32, CCMBaseTypedStorage*>::iterator i, e = obsMap.end();

	uint32 type = (uint32)qMsg->getAsInt("DataType");
	if (!type) {
		const char* typeStr = qMsg->getString("DataType");
		if (typeStr) {
			if (stricmp(typeStr, "location") == 0)
				type = OBSID_LOC;
			else if (stricmp(typeStr, "dimension") == 0)
				type = OBSID_DIM;
			else if (stricmp(typeStr, "integer") == 0)
				type = OBSID_INT;
			else if (stricmp(typeStr, "float") == 0)
				type = OBSID_FLOAT;
			else if (stricmp(typeStr, "time") == 0)
				type = OBSID_TIME;
			else if (stricmp(typeStr, "string") == 0)
				type = OBSID_STRING;
		}
	}
	if (type) {
		i = obsMap.find(type);
		if ((i == e) || !i->second)
			return false;
		return i->second->query(qMsg, resultMsg, jsonList, lastObjectID);
	}
	// Query is for all datatypes
	i = obsMap.begin();
	while (i != e) {
		if (i->second) {
			if (!i->second->query(qMsg, resultMsg, jsonList, lastObjectID))
				return false;
		}
		i++;
	}
	return true;
}

bool CCMDataStorage::addObservation(const char* cdata, uint32 size) {
	if (!cdata || !size) return false;
	uint32 dataType = GetObsTypeFromRawData(cdata, size);
	std::map<uint32, CCMBaseTypedStorage*>::iterator i = obsMap.find(dataType);
	if ((i == obsMap.end()) || !i->second)
		return false;
	return i->second->addObservation(++lastObsID, cdata, size);
}



std::string CCMDataStorage::getFullJSON() {
	std::string json;
	std::list<std::string> list;

	std::map<uint32, CCMMap*>::iterator mapsI = maps.begin(), mapsE = maps.end();
	while (mapsI != mapsE) {
		list.push_back(mapsI->second->toJSON());
		mapsI++;
	}

	json = utils::StringFormat("\"maps\": [%s]", utils::TextJoin(list, ",").c_str());
	list.clear();

	std::map<uint32, CCMIdentity*>::iterator identitiesI = identities.begin(), identitiesE = identities.end();
	while (identitiesI != identitiesE) {
		list.push_back(identitiesI->second->toJSON());
		identitiesI++;
	}
	json = utils::StringFormat("%s, \"identities\": [%s]", json.c_str(), utils::TextJoin(list, ",").c_str());
	list.clear();

	std::map<uint32, CCMObject*>::iterator objectsI = objects.begin(), objectsE = objects.end();
	while (objectsI != objectsE) {
		list.push_back(objectsI->second->toJSON());
		objectsI++;
	}
	json = utils::StringFormat("%s, \"objects\": [%s]", json.c_str(), utils::TextJoin(list, ",").c_str());
	list.clear();

	std::map<std::string, CCMRole*>::iterator rolesI = roles.begin(), rolesE = roles.end();
	while (rolesI != rolesE) {
		list.push_back(rolesI->second->toJSON());
		rolesI++;
	}
	json = utils::StringFormat("%s, \"roles\": [%s]", json.c_str(), utils::TextJoin(list, ",").c_str());
	list.clear();

	std::map<uint64, CCMTask*>::iterator tasksI = tasks.begin(), tasksE = tasks.end();
	while (tasksI != tasksE) {
		list.push_back(tasksI->second->toJSON());
		tasksI++;
	}
	json = utils::StringFormat("%s, \"tasks\": [%s]", json.c_str(), utils::TextJoin(list, ",").c_str());
	list.clear();

	//std::map<uint32, CCMBaseTypedStorage*>::iterator obsMapI = obsMap.begin(), obsMapE = obsMap.end();
	//while (obsMapI != obsMapE) {
	//	obsMapI++;
	//}
	//json = utils::StringFormat("%s, \"identities\": [%s]", utils::TextJoin(list, ","));
	//list.clear();

	return utils::StringFormat("{ %s }\n", json.c_str());
}

bool CCMDataStorage::queryMap(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	uint32 id = (uint32)msg->getAsInt("id");
	const char* datatype = msg->getString("datatype");
	std::map<uint32, CCMMap*>::iterator mapsI, mapsE = maps.end();

	if (!id && !datatype) {
		std::list<std::string> list;
		mapsI = maps.begin();
		while (mapsI != mapsE) {
			list.push_back(mapsI->second->toJSON());
			mapsI++;
		}
		std::string json = utils::StringFormat("{ \"maps\": [%s] }\n", utils::TextJoin(list, ",").c_str());
		replyMsg->setString("JSON", json.c_str());
		return true;
	}

	if ((mapsI = maps.find(id)) == mapsE)
		return false;

	if (!datatype) {
		std::string json = utils::StringFormat("{ \"map\": %s }\n", mapsI->second->toJSON().c_str());
		replyMsg->setString("JSON", json.c_str());
		return true;
	}
	else if (stricmp(datatype, "image") == 0) {
		if (!mapsI->second->image)
			return false;
		uint32 size;
		char* data = mapsI->second->image->toBitmapFileFormat(size);
		if (!data || !size) {
			delete[] data;
			return false;
		}
		//utils::WriteAFile("d:/mapfile.bmp", data, size, true);
		replyMsg->setData("BINARY", data, size);
		replyMsg->setString("MIMETYPE", "image/bmp");
		delete[] data;
		return true;
	}

	return false;
}

bool CCMDataStorage::queryIdentity(DataMessage* msg, DataMessage* replyMsg) {
	// /api/query?from=CCMCatalog&query=identity&datatype=image&name=face&id=2

	if (!msg || !replyMsg)
		return false;
	uint32 id = (uint32)msg->getAsInt("id");
	const char* datatype = msg->getString("datatype");
	const char* name = msg->getString("name");
	std::map<uint32, CCMIdentity*>::iterator identitiesI, identitiesE = identities.end();

	if (!id && !datatype) {
		std::list<std::string> list;
		identitiesI = identities.begin();
		while (identitiesI != identitiesE) {
			list.push_back(identitiesI->second->toJSON());
			identitiesI++;
		}
		std::string json = utils::StringFormat("{ \"identities\": [%s] }\n", utils::TextJoin(list, ",").c_str());
		replyMsg->setString("JSON", json.c_str());
		return true;
	}

	if ((identitiesI = identities.find(id)) == identitiesE)
		return false;

	if (!datatype) {
		std::string json = utils::StringFormat("{ \"identity\": %s }\n", identitiesI->second->toJSON().c_str());
		replyMsg->setString("JSON", json.c_str());
		return true;
	}
	else if (stricmp(datatype, "image") == 0) {
		uint32 size;
		char* data;
		Bitmap* image = NULL;
		if (name) {
			if (stricmp(name, "faces") == 0) {
				for (int n = 0; n < 10; n++) {
					if (identitiesI->second->faces[n]) {
						data = identitiesI->second->faces[n]->toBitmapFileFormat(size);
						if (!data || !size) {
							delete[] data;
							return false;
						}
						replyMsg->setData(n + 1, "Image", data, size);
						delete[] data;
					}
				}
				return true;
			}
			else if (stricmp(name, "face") == 0) {
				if ((stricmp(name, "face") == 0) && (identitiesI->second->faces[0]))
					image = identitiesI->second->faces[0];
			}
			else if (utils::TextStartsWith(name, "face")) {
				uint32 faceN = utils::Ascii2Uint32(name + 4);
				if ((faceN > 0) && (faceN < 10) && (identitiesI->second->faces[faceN-1]))
					image = identitiesI->second->faces[faceN-1];
			}
			else
				return false;
			//if ((stricmp(name, "face") == 0) && (identitiesI->second->face1))
			//	image = identitiesI->second->face1;
			//else if ((stricmp(name, "face1") == 0) && (identitiesI->second->face1))
			//	image = identitiesI->second->face1;
			//else if ((stricmp(name, "face2") == 0) && (identitiesI->second->face2))
			//	image = identitiesI->second->face2;
			//else if ((stricmp(name, "face3") == 0) && (identitiesI->second->face3))
			//	image = identitiesI->second->face3;
			//if (!image)
			//	return false;
		}
		else {
			if (!identitiesI->second->image)
				return false;
			image = identitiesI->second->image;
		}
		data = image->toBitmapFileFormat(size);
		if (!data || !size) {
			delete[] data;
			return false;
		}
		replyMsg->setData("BINARY", data, size);
		replyMsg->setString("MIMETYPE", "image/bmp");
		delete[] data;
		return true;
	}

	return false;
}

bool CCMDataStorage::queryIdentities(DataMessage* msg, DataMessage* replyMsg) {
	// /api/query?from=CCMCatalog&query=identities

	if (!msg || !replyMsg)
		return false;
	const char* type = msg->getString("Type");

	// list all identities
	uint32 count = 0;
	std::map<uint32, CCMIdentity*>::iterator identitiesI = identities.begin(), identitiesE = identities.end();
	while (identitiesI != identitiesE) {
		if (type && (stricmp(identitiesI->second->type.c_str(), type) != 0)) {
			identitiesI++;
			continue;
		}
		replyMsg->setString(identitiesI->second->id, "Name", identitiesI->second->name.c_str());
		replyMsg->setString(identitiesI->second->id, "Nickname", identitiesI->second->nickname.c_str());
		count++;
		identitiesI++;
	}
	replyMsg->setInt("Count", count);
	return true;
}

bool CCMDataStorage::queryObject(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	uint32 id = (uint32)msg->getAsInt("id");
	const char* type = msg->getString("type");
	const char* datatype = msg->getString("datatype");
	//uint32 systemID = (uint32)msg->getAsInt("systemid");
	uint32 systemID = msg->getSystemID();
	std::map<uint32, CCMObject*>::iterator objectsI, objectsE = objects.end();

	if (!id) {
		std::list<std::string> list;
		objectsI = objects.begin();
		while (objectsI != objectsE) {
			if (!type || (stricmp(objectsI->second->type.c_str(), type) == 0) )
				list.push_back(objectsI->second->toJSON());
			objectsI++;
		}
		std::string json = utils::StringFormat("{ \"objects\": [%s] }\n", utils::TextJoin(list, ",").c_str());
		replyMsg->setString("JSON", json.c_str());
		return true;
	}

	if ((objectsI = objects.find(id)) == objectsE)
		return false;
	if (type && (stricmp(objectsI->second->type.c_str(), type) != 0))
		return false;

	if (stricmp(datatype, "image") == 0) {
		CCMImage* img = getLastImage(id, systemID);
		if (!img || !img->image)
			return false;
		uint32 size;
		char* data = img->image->toBitmapFileFormat(size);
		if (!data || !size) {
			delete[] data;
			return false;
		}
		replyMsg->setData("BINARY", data, size);
		replyMsg->setString("MIMETYPE", "image/bmp");
		delete[] data;
		return true;
	}
	else {
		std::string json = utils::StringFormat("{ \"object\": %s }\n", objectsI->second->toJSON().c_str());
		replyMsg->setString("JSON", json.c_str());
		return true;
	}

	return false;
}

bool CCMDataStorage::queryObjects(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	const char* type = msg->getString("type");
	const char* format = msg->getString("format");
	//uint32 systemID = (uint32)msg->getAsInt("systemid");
	uint32 systemID = msg->getSystemID();
	std::map<uint32, CCMObject*>::iterator objectsI, objectsE = objects.end();

	std::list<std::string> list;
	objectsI = objects.begin();
	while (objectsI != objectsE) {
		if (!type || (stricmp(objectsI->second->type.c_str(), type) == 0)) {
			if (stricmp(format, "message") == 0) {
				replyMsg->setInt(objectsI->first, "Identity", objectsI->second->identity);
				replyMsg->setInt(objectsI->first, "PresentNow", objectsI->second->presentNow ? 1 : 0);
			}
			else
				list.push_back(objectsI->second->toJSON());
		}
		objectsI++;
	}
	std::string json = utils::StringFormat("{ \"objects\": [%s] }\n", utils::TextJoin(list, ",").c_str());
	replyMsg->setString("JSON", json.c_str());
	return true;
}












// roles

CCMRole* CCMDataStorage::getRole(const char* name) {
	if (!name)
		return NULL;
	std::map<std::string, CCMRole*>::iterator i = roles.find(name), e = roles.end();
	if (i == e)
		return NULL;
	return i->second;
}

bool CCMDataStorage::addRole(const char* name, const char* type) {
	if (!name || !type)
		return false;
	CCMRole* role = new CCMRole;
	role->name = name;
	if (stricmp(type, "exclusive") == 0)
		role->type = CCMRole::EXCLUSIVE;
	else
		role->type = CCMRole::SHARED;
	roles[name] = role;
	return true;
}

bool CCMDataStorage::takeRole(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	const char* name = msg->getString("name");
	if (!name)
		return false;
	uint32 systemID = msg->getSystemID();
	std::map<std::string, CCMRole*>::iterator i = roles.find(name), e = roles.end();
	if (i == e)
		return false;
	CCMRole* role = i->second;
	if (!role)
		return false;

	if ((role->type == CCMRole::EXCLUSIVE) && (role->currentAssignments.size())) {
		role->addCurrentData(replyMsg);
		return false;
	}

	if (role->currentAssignments.find(systemID) == role->currentAssignments.end()) {
		uint64 now = GetTimeNow();
		CCMLog logEntry;
		replyMsg->setInt("RoleSystemID", systemID);
		replyMsg->setString("RoleName", role->name.c_str());
		role->currentAssignments[systemID] = now;
		logEntry.setData(now, systemID, 0, 1, utils::StringFormat("Role assigned to system ID %u", systemID).c_str() );
		role->historyLog[now] = logEntry;
		role->addCurrentData(replyMsg);
		return true;
	}
	role->addCurrentData(replyMsg);
	return false;
}

bool CCMDataStorage::leaveRole(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	const char* name = msg->getString("name");
	if (!name)
		return false;
	uint32 systemID = msg->getSystemID();
	std::map<std::string, CCMRole*>::iterator i = roles.find(name), e = roles.end();
	if (i == e)
		return false;
	CCMRole* role = i->second;
	if (!role)
		return false;

	if (!role->currentAssignments.erase(systemID)) {
		role->addCurrentData(replyMsg);
		return false;
	}
	
	uint64 now = GetTimeNow();
	CCMLog logEntry;
	replyMsg->setInt("RoleSystemID", systemID);
	replyMsg->setString("RoleName", role->name.c_str());
	logEntry.setData(now, systemID, 1, 0, utils::StringFormat("Role unsigned from system ID %u", systemID).c_str());
	role->historyLog[now] = logEntry;
	role->addCurrentData(replyMsg);
	return true;
}

bool CCMDataStorage::giveRole(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	const char* name = msg->getString("name");
	if (!name)
		return false;
	uint32 systemID = msg->getSystemID();
	uint32 otherSystemID = (uint32)msg->getInt("OtherSystemID");
	if (!otherSystemID)
		return false;
	std::map<std::string, CCMRole*>::iterator i = roles.find(name), e = roles.end();
	if (i == e)
		return false;
	CCMRole* role = i->second;
	if (!role)
		return false;

	if ((role->type == CCMRole::EXCLUSIVE) && (role->currentAssignments.size())) {
		role->addCurrentData(replyMsg);
		return false;
	}

	if (role->currentAssignments.find(otherSystemID) == role->currentAssignments.end()) {
		uint64 now = GetTimeNow();
		CCMLog logEntry;
		role->currentAssignments[otherSystemID] = now;
		replyMsg->setInt("RoleSystemID", systemID);
		replyMsg->setString("RoleName", role->name.c_str());
		logEntry.setData(now, otherSystemID, 0, 1, utils::StringFormat("Role assigned to system ID %u by system ID %u", otherSystemID, systemID).c_str());
		role->historyLog[now] = logEntry;
		role->addCurrentData(replyMsg);
		return true;
	}
	role->addCurrentData(replyMsg);
	return false;
}







// tasks
bool CCMDataStorage::createTask(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	const char* name = msg->getString("name");
	const char* type = msg->getString("type");
	uint32 timeoutMS = (uint32)msg->getInt("timeout");
	if (!name)
		return false;
	uint32 systemID = msg->getSystemID();

	CCMTask* task = new CCMTask;
	task->id = (++lastTaskID);
	task->creatorID = systemID;
	task->localRefID = msg->getInt("ReferenceID");
	task->name = name;
	task->creationMsg = msg->copy();
	task->createdTime = GetTimeNow();
	task->timeoutMS = timeoutMS;
	if (type && stricmp(type, "exclusive") == 0)
		task->type = CCMTask::EXCLUSIVE;
	else
		task->type = CCMTask::SHARED;
	task->status = CCMTask::CREATED;
	tasks[task->id] = task;
	task->addCurrentData(replyMsg);
	return true;
}

bool CCMDataStorage::assignTask(uint64 taskID, uint32 assigneeID, uint32 sysID, DataMessage* replyMsg) {
	std::map<uint64, CCMTask*>::iterator i = tasks.find(taskID), e = tasks.end();
	if (i == e)
		return false;
	CCMTask* task = i->second;
	if (!task)
		return false;

	if ((task->type == CCMTask::EXCLUSIVE) && (task->currentAssignments.size())) {
		task->addCurrentData(replyMsg);
		return false;
	}
	if (task->currentAssignments.find(assigneeID) == task->currentAssignments.end()) {
		uint64 now = GetTimeNow();
		CCMLog logEntry;
		task->currentAssignments[assigneeID] = CCMTask::ASSIGNED;
		logEntry.setData(now, sysID, 0, 1, utils::StringFormat("Task assigned to system ID %u by system ID %u", assigneeID, sysID).c_str());
		task->historyLog[now] = logEntry;
		if (task->status < CCMTask::ASSIGNED)
			task->status = CCMTask::ASSIGNED;
		replyMsg->setInt("Assignee", assigneeID);
		task->addCurrentData(replyMsg);
		return true;
	}
	return false;

}

bool CCMDataStorage::cancelTask(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	uint64 id = (uint64)msg->getInt("TaskID");
	if (!id)
		return false;
	uint32 systemID = msg->getSystemID();
	std::map<uint64, CCMTask*>::iterator i = tasks.find(id), e = tasks.end();
	if (i == e)
		return false;
	CCMTask* task = i->second;
	if (!task)
		return false;

	if (task->status < CCMTask::SUCCESS)
		task->status = CCMTask::CANCELLED;
	task->addCurrentData(replyMsg);
	return true;
}

bool CCMDataStorage::assignTask(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	uint64 taskID = (uint64)msg->getInt("TaskID");
	if (!taskID)
		return false;
	uint32 systemID = msg->getSystemID();
	uint32 assigneeID = (uint32)msg->getInt("Assignee");
	return assignTask(taskID, assigneeID, systemID, replyMsg);
}

bool CCMDataStorage::acceptTask(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	uint64 id = (uint64)msg->getInt("TaskID");
	if (!id)
		return false;
	uint32 systemID = msg->getSystemID();
	std::map<uint64, CCMTask*>::iterator i = tasks.find(id), e = tasks.end();
	if (i == e)
		return false;
	CCMTask* task = i->second;
	if (!task)
		return false;

	std::map<uint32, uint8>::iterator iA = task->currentAssignments.find(systemID);
	if (iA == task->currentAssignments.end())
		return false;

	if (iA->second > CCMTask::ASSIGNED)
		return false;

	task->currentAssignments[systemID] = CCMTask::ACCEPTED;
	task->addCurrentData(replyMsg);
	if (task->status < CCMTask::ACCEPTED)
		task->status = CCMTask::ACCEPTED;
	replyMsg->setInt("Assignee", systemID);
	return true;
}



bool CCMDataStorage::updateTask(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	uint64 id = (uint64)msg->getInt("TaskID");
	if (!id)
		return false;
	uint32 systemID = msg->getSystemID();
	std::map<uint64, CCMTask*>::iterator i = tasks.find(id), e = tasks.end();
	if (i == e)
		return false;
	CCMTask* task = i->second;
	if (!task)
		return false;

	if (task->status < CCMTask::ACCEPTED)
		return false;

	std::map<uint32, uint8>::iterator iA = task->currentAssignments.find(systemID);
	if (iA == task->currentAssignments.end())
		return false;

	if (iA->second < CCMTask::ACCEPTED)
		return false;

	if (task->status < CCMTask::STATUS)
		task->status = CCMTask::STATUS;
	task->addCurrentData(replyMsg);
	return true;
}


bool CCMDataStorage::completeTask(DataMessage* msg, DataMessage* replyMsg) {
	if (!msg || !replyMsg)
		return false;
	uint64 id = (uint64)msg->getInt("TaskID");
	if (!id)
		return false;
	uint32 systemID = msg->getSystemID();
	const char* completion = msg->getString("Completion");
	std::map<uint64, CCMTask*>::iterator i = tasks.find(id), e = tasks.end();
	if (i == e)
		return false;
	CCMTask* task = i->second;
	if (!task)
		return false;

	if (task->status < CCMTask::ACCEPTED)
		return false;

	std::map<uint32, uint8>::iterator iA = task->currentAssignments.find(systemID);
	if (iA == task->currentAssignments.end())
		return false;

	if (iA->second < CCMTask::ACCEPTED)
		return false;

	if (completion && stricmp(completion, "success") == 0)
		task->status = CCMTask::SUCCESS;
	else
		task->status = CCMTask::FAILED;

	replyMsg->copyUserEntriesFromMessage(msg);

	task->addCurrentData(replyMsg);

//	std::string str = replyMsg->toXML();
//	printf("\n\n%s\n\n", str.c_str());

	return true;
}


DataMessage* CCMDataStorage::getNextTimedOutTask() {
	CCMTask* task;
	std::map<uint64, CCMTask*>::iterator i = tasks.begin(), e = tasks.end();
	while (i != e) {
		if (task = i->second) {
			if (task->status < CCMTask::SUCCESS) {
				if (task->timeoutMS && (GetTimeAgeMS(task->createdTime) > task->timeoutMS)) {
					//LogPrint(0, 0, 0, "Task %llu '%s' timed out, deleting... status: %u   timeout: %d   age: %s",
					//	task->id, task->name.c_str(), (uint8)task->status, task->timeoutMS, PrintTimeDifString(GetTimeAgeMS(task->createdTime)).c_str());
					task->status = CCMTask::TIMEDOUT;
					DataMessage* replyMsg = new DataMessage();
					task->addCurrentData(replyMsg);
					delete task->creationMsg;
					delete task;
					tasks.erase(i);
					return replyMsg;
				}
			}
			else {
				//LogPrint(0, 0, 0, "Task %llu '%s' completed, deleting... status: %u   timeout: %d   age: %s",
				//	task->id, task->name.c_str(), (uint8)task->status, task->timeoutMS, PrintTimeDifString(GetTimeAgeMS(task->createdTime)).c_str());
				delete task->creationMsg;
				delete task;
				i = tasks.erase(i);
				continue;
			}
		}
		i++;
	}
	return NULL;
}


} // namespace cmlabs
