

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of PSYCLONE version 2.0.0.170915_Beta4 (15 September 2017)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2017
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  PSYCLONE 2.0 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.0 is also available to 
//_/_/  any interested party from the copyright holder.
//_/_/  
//_/_/  You can redistribute this copy of PSYCLONE 2.0 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.0.  
//_/_/  If not, see <http://www.gnu.org/licenses/>.
//_/_/  
//_/_/  PSYCLONE 2.0 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();
	api->logPrint(2, "Started running...");

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

	CCMDataStorage* dataStorage = new CCMDataStorage();
	std::string errStr = dataStorage->init(ccmDir.c_str());
	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;
		}
	}

	const char* name, *size, *image, *face, *type, *parent, *offset, *scale, *nickname;
	int n = 0;
	while (!(node = mainNode.getChildNode("map", n++)).isEmpty()) {
		if ((name = node.getAttribute("name")) &&
			(size = node.getAttribute("size"))) {
			parent = node.getAttribute("parent");
			offset = node.getAttribute("offset");
			scale = node.getAttribute("scale");
			image = node.getAttribute("image");

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

	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");
			face = node.getAttribute("face");

			errStr = dataStorage->addIdentity(name, type, nickname, image, face);
			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::string json;
	DataMessage* inMsg, *replyMsg;
	const char* triggerName, *query;
	uint32 lastObjectID = 0;
	uint32 count, id;
	bool webCall = false;
	const char* data;
	uint32 s;
	uint8 status;
	while (api->shouldContinue()) {
		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) {
				//printf("%s\n", inMsg->getUserEntriesAsString().c_str());
				replyMsg = NULL;
				status = QUERY_FAILED;
				webCall = inMsg->hasInteger("HTTP_OPERATION");
				query = inMsg->getString("Query");
				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, "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"));

					if (id) {
						replyMsg = new DataMessage();
						replyMsg->setInt("ObjectID", id);
						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"), (uint32)inMsg->getInt("SystemID"), image))
								status = QUERY_SUCCESS;
						}
						else
							delete image;
					}
				}
				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, "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, "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 {
					// /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;
						}
					}
				}

				//api->logPrint(1, "Replying to query '%s': %s", query, (status == QUERY_SUCCESS) ? "Success" : "Failed");
				if (replyMsg)
					api->queryReply((uint32)inMsg->getReference(), QUERY_SUCCESS, replyMsg);
				else
					api->queryReply((uint32)inMsg->getReference(), status);
			}
		}
	}

	return 0;
}



// 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;

	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;
	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.name = name;
			obsEntry.type = type;
			obsEntry.trigger = trigger;
			if (!(subNode = node.getChildNode()).isEmpty()) {
				if ((entry = subNode.getAttribute("entry")) &&
					(key = subNode.getAttribute("key")))
					obsMappings[obsEntry][entry] = key;
			}
		}
	}

	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();
				i = obsMappings.begin();
				while (i != e) {
					if (stricmp(i->first.trigger.c_str(), triggerName) == 0) {
						if (ProcessObservationTrigger(outMsg, inMsg, i->first, i->second, ++lastID)) {
							count++;
						}
						else
							api->logPrint(0, "Unable to extract observation data for %s...", i->first.name.c_str());
						//api->logPrint(1, "[%u] Object %u ( %.3f , %.3f )",
						//	(uint32)inMsg->getInt("SystemID"),
						//	(uint32)inMsg->getInt("ObjectID"),
						//	inMsg->getFloat("x"),
						//	inMsg->getFloat("y")
						//);
					}
					i++;
				}
				if (count) {
					outMsg->setString("Query", "AddObservations");
					outMsg->setInt("ObsCount", count);
					status = api->queryCatalog(&resultMsg, "MasterLocal", 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;
				}
			}
		}
	}
	return 0;
}

bool ProcessObservationTrigger(DataMessage* outMsg, DataMessage* inMsg, const ObsEntry &obsEntry, std::map<std::string, std::string>& mappings, 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, mappings))
			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, mappings))
			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, mappings))
			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, mappings))
			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, mappings))
			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, mappings))
			result = string->writeToMessage(outMsg, id);
		delete string;
	}

	return result;
}










CCMDataStorage::CCMDataStorage() {
	lastMapID = 0;
	lastIdentityID = 0;
	lastObjectID = 0;
	lastImageID = 0;
	lastObsID = 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::string CCMDataStorage::init(const char* ccmDir) {

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

	return "";
}

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* size, const char* parent, const char* offset, const char* scale, const char* image) {
	if (!name || !size || !strlen(name))
		return "Name and size are required parameters for maps";

	std::vector<std::string> list = utils::TextListSplit(size, ",", false, true);
	if (list.size() != 2)
		return "Map size incorrectly formatted, should be 'x,y'";

	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->size.w = (double)utils::Ascii2Float64(list.at(0).c_str());
	map->size.h = (double)utils::Ascii2Float64(list.at(1).c_str());
	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::addIdentity(const char* name, const char* type, const char* nickname, const char* image, const char* face) {
	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);
		}
	}

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

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

	if (nickname)
		identity->nickname = nickname;

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

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

	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->id = ++lastObjectID;
	objects[object->id] = object;
	return object->id;
}


bool CCMDataStorage::query(DataMessage* qMsg, DataMessage* resultMsg, std::list<std::string>* jsonList) {
	// Find type
	uint32 type = (uint32)qMsg->getAsInt("DataType");
	if (type) {
		std::map<uint32, CCMBaseTypedStorage*>::iterator i = obsMap.find(type);
		if ((i == obsMap.end()) || !i->second)
			return false;
		return i->second->query(qMsg, resultMsg, jsonList, lastObjectID);
	}
	// Query is for all datatypes
	std::map<uint32, CCMBaseTypedStorage*>::iterator i = obsMap.begin(), e = obsMap.end();
	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<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;
		}
		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) {
		Bitmap* image = NULL;
		if (name) {
			if ((stricmp(name, "face") == 0) && (identitiesI->second->face))
				image = identitiesI->second->face;
			if (!image)
				return false;
		}
		else {
			if (!identitiesI->second->image)
				return false;
			image = identitiesI->second->image;
		}
		uint32 size;
		char* 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::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");
	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;
}


} // namespace cmlabs
