

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

namespace	cmlabs {


static std::map<std::string, std::list<MessageDataCatalogEntry*> > *MessageDataCatalogMap = NULL;

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

	std::list<MessageDataCatalogEntry*>* entryList = NULL;
	if (!MessageDataCatalogMap)
		MessageDataCatalogMap = new std::map<std::string, std::list<MessageDataCatalogEntry*> >;
	entryList = &((*MessageDataCatalogMap)[myName]);

	XMLResults xmlResults;
	std::string xml = api->getParameterString("componentsetup");
	XMLNode node, 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;
		}
	}

	MessageDataCatalogEntry* entry;
	std::list<MessageDataCatalogEntry*>::iterator i, e;
	int n = 0;
	const char* cdata, *tname;
	while (!(node = mainNode.getChildNode("store", n++)).isEmpty()) {
		if ((cdata = node.getAttribute("name")) && (tname = node.getAttribute("trigger"))) {
			//entry = NULL;
			//for (i = entryList->begin(), e = entryList->end(); i != e; i++) {
			//	if (stricmp((*i)->name.c_str(), cdata) == 0)
			//		entry = (*i);
			//}
			//if (!entry) {
			//	entry = new MessageDataCatalogEntry();
			//	entryList->push_back(entry);
			//}
			entry = new MessageDataCatalogEntry();
			entry->mutex.enter();
			entryList->push_back(entry);
			entry->reset();
			entry->name = cdata;
			entry->trigger = tname;
			if (cdata = node.getAttribute("key"))
				entry->key = cdata;
			if (cdata = node.getAttribute("keytype"))
				entry->keytype = cdata;
			if (cdata = node.getAttribute("datatype"))
				entry->datatype = cdata;
			if (cdata = node.getAttribute("mimetype"))
				entry->mimetype = cdata;
			entry->maxKeep = utils::Ascii2Int32(node.getAttribute("maxkeep"));
			if (node.isAttributeSet("maxfrequency")) {
				float64 f = utils::Ascii2Float64(node.getAttribute("maxfrequency"));
				if (f > 0)
					entry->minMSApart = (uint32)((1.0 / f) * 1000);
			}
			else if (node.isAttributeSet("minmsapart")) {
				uint32 u = utils::Ascii2Int32(node.getAttribute("minmsapart"));
				if (u > 0)
					entry->minMSApart = u;
			}
			if (!entry->maxKeep)
				entry->maxKeep = 1;
			entry->mutex.leave();
		}
	}

	DataMessage* inMsg, *replyMsg;
	const char* triggerName;
	const char* req;
	std::map<std::string, MessageDataCatalogEntry>::iterator it, et;

	int32 ms = 0;
	while (api->shouldContinue()) {
		if (inMsg = api->waitForNewMessage(200, triggerName)) {
			if (triggerName && stricmp(triggerName, "DefaultComponentTrigger") == 0)
				api->logPrint(2, "Got wakeup message...");
			else if (inMsg->getType() == PsyAPI::CTRL_QUERY) {
				if (req = inMsg->getString("Query")) {
					entry = NULL;
					for (i = entryList->begin(), e = entryList->end(); i != e; i++) {
						if (stricmp((*i)->name.c_str(), req) == 0) {
							entry = (*i);
							break;
						}
					}
					if (entry) {
						replyMsg = ProcessStoreRequest(entry);
						if (replyMsg)
							api->queryReply((uint32)inMsg->getReference(), QUERY_SUCCESS, replyMsg);
						else
							api->queryReply((uint32)inMsg->getReference(), QUERY_FAILED);
					}
					else
						api->queryReply((uint32)inMsg->getReference(), QUERY_NAME_UNKNOWN);
				}
				else if (req = inMsg->getString("List")) {
					entry = NULL;
					std::string listJSON;
					for (i = entryList->begin(), e = entryList->end(); i != e; i++) {
						if (listJSON.length())
							listJSON += utils::StringFormat(",\"%s\": { \"mimetype\": \"%s\", \"key\": \"%s\", \"maxkeep\": %u, \"minmsapart\": %u}\n",
								(*i)->name.c_str(), (*i)->mimetype.c_str(), (*i)->key.c_str(), (*i)->maxKeep, (*i)->minMSApart);
						else
							listJSON += utils::StringFormat("\"%s\": { \"mimetype\": \"%s\", \"key\": \"%s\", \"maxkeep\": %u, \"minmsapart\": %u}\n",
								(*i)->name.c_str(), (*i)->mimetype.c_str(), (*i)->key.c_str(), (*i)->maxKeep, (*i)->minMSApart);
					}
					replyMsg = new DataMessage();
					replyMsg->setString("JSON", utils::StringFormat("{ %s }\n", listJSON.c_str()).c_str());
					api->queryReply((uint32)inMsg->getReference(), QUERY_SUCCESS, replyMsg);
				}
				else
					api->queryReply((uint32)inMsg->getReference(), QUERY_FAILED);
			}
			else {
				// Received another message, storing...
				entry = NULL;
				for (i = entryList->begin(), e = entryList->end(); i != e; i++) {
					if (stricmp((*i)->trigger.c_str(), triggerName) == 0) {
						entry = (*i);
						entry->mutex.enter();
						// Check if we can store new item again already
						if (!entry->minMSApart || !entry->lastStored || ((ms = GetTimeAgeMS(entry->lastStored)) > (int32)entry->minMSApart)) {
							// Check if we need to remove the oldest entry
							while (entry->msgs.size() >= entry->maxKeep) {
								delete entry->msgs.back();
								entry->msgs.pop_back();
							}
							//entry->msgs.push_front(new DataMessage(*inMsg));
							entry->msgs.push_front(inMsg->copy());
							entry->lastStored = GetTimeNow();
						}
						entry->mutex.leave();
					}
				}
				// if no matches, we don't know about this, don't store, just ignore...
			}
		}
	}

	return 0;
}


DataMessage* ProcessStoreRequest(MessageDataCatalogEntry* entry) {
	if (!entry) return NULL;
	entry->mutex.enter();

	DataMessage* lastMsg = entry->getLastMessage();
	if (!lastMsg) {
		entry->mutex.leave();
		return NULL;
	}

	//	replyMsg->setString("XML", xml.toLatin1().data());
	//	replyMsg->setString("JSON", json.toLatin1().data());

	bool ok;
	uint32 size, width, height, step;
	char* data;
	const char* cdata, *encoding = NULL;
	if (((utils::TextEndsWith(entry->mimetype.c_str(), "bmp", false)) ||
		(utils::TextEndsWith(entry->mimetype.c_str(), "bitmap", false))) &&
		(stricmp(entry->datatype.c_str(), "raw") == 0)) {
		// We need to create a Bitmap from raw, expect entries for Width and Height
		if (!(width = (uint32)lastMsg->getInt((entry->key + ".width").c_str(), ok)))
			if (!(width = (uint32)lastMsg->getInt((entry->key + ".Width").c_str(), ok)))
				if (!(width = (uint32)lastMsg->getInt("width", ok)))
					width = (uint32)lastMsg->getInt("w", ok);
		if (!(height = (uint32)lastMsg->getInt((entry->key + ".height").c_str(), ok)))
			if (!(height = (uint32)lastMsg->getInt((entry->key + ".Height").c_str(), ok)))
				if (!(height = (uint32)lastMsg->getInt("height", ok)))
					height = (uint32)lastMsg->getInt("h", ok);
		if (!(step = (uint32)lastMsg->getInt((entry->key + ".step").c_str(), ok)))
			if (!(step = (uint32)lastMsg->getInt((entry->key + ".Step").c_str(), ok)))
				if (!(step = (uint32)lastMsg->getInt("step", ok)))
					step = (uint32)lastMsg->getInt("s", ok);
		if (!(encoding = lastMsg->getString((entry->key + ".encoding").c_str())))
			if (!(encoding = lastMsg->getString((entry->key + ".Encoding").c_str())))
				if (!(encoding = lastMsg->getString("encoding")))
					encoding = lastMsg->getString("enc");
		if (!width || !height) {
			entry->mutex.leave();
			return NULL;
		}
		cdata = lastMsg->getData(entry->key.c_str(), size);
		if (!cdata || !size) {
			entry->mutex.leave();
			return NULL;
		}
		
		if (encoding)
			data = Bitmap::DataToBitmapFileFormat(cdata, size, width, height, encoding, step ? step : size / height, size);
		else
			data = Bitmap::DataToBitmapFileFormat(cdata, size, width, height, step ? step : size / height, size);

		if (!data || !size) {
			entry->mutex.leave();
			delete[] data;
			return NULL;
		}
		//Bitmap* bmp;
		//if (encoding)
		//	bmp = new Bitmap(cdata, size, width, height, encoding, step ? step : size / height);
		//else
		//	bmp = new Bitmap(cdata, size, width, height, step ? step : size / height);
		// bmp->saveToFile(utils::StringFormat("d:/test_%s.bmp", encoding).c_str());
		
		//std::string filename;
		//for (uint32 n = 0; n < 1000; n++) {
		//	filename = utils::StringFormat("d:/bitmap_%03u.bmp", n);
		//	if (!utils::DoesAFileExist(filename.c_str())) {
		//		utils::WriteAFile(filename.c_str(), data, size, true);
		//		break;
		//	}
		//}
		
		DataMessage* msg = new DataMessage();
		msg->setData("BINARY", data, size);
		msg->setString("MIMETYPE", entry->mimetype.c_str());
		entry->mutex.leave();
		delete[] data;
		return msg;
	}
	else if (utils::TextEndsWith(entry->mimetype.c_str(), "json", false)) {
		if (entry->key.length()) {
			if (!(cdata = lastMsg->getString(entry->key.c_str()))) {
				entry->mutex.leave();
				return NULL;
			}
			DataMessage* msg = new DataMessage();
			msg->setString("JSON", cdata);
			entry->mutex.leave();
			return msg;
		}
		else {
			DataMessage* msg = new DataMessage();
			msg->setString("JSON", lastMsg->toJSON().c_str());
			entry->mutex.leave();
			return msg;
		}
	}
	else if (utils::TextEndsWith(entry->mimetype.c_str(), "xml", false)) {
		if (entry->key.length()) {
			if (!(cdata = lastMsg->getString(entry->key.c_str()))) {
				entry->mutex.leave();
				return NULL;
			}
			DataMessage* msg = new DataMessage();
			msg->setString("XML", cdata);
			entry->mutex.leave();
			return msg;
		}
		else {
			DataMessage* msg = new DataMessage();
			msg->setString("XML", lastMsg->toXML().c_str());
			entry->mutex.leave();
			return msg;
		}
	}
	else if (utils::TextEndsWith(entry->mimetype.c_str(), "html", false)) {
		if (entry->key.length()) {
			if (!(cdata = lastMsg->getString(entry->key.c_str()))) {
				entry->mutex.leave();
				return NULL;
			}
			DataMessage* msg = new DataMessage();
			msg->setString("HTML", cdata);
			entry->mutex.leave();
			return msg;
		}
		else {
			// getUserEntriesAsString() returns a temporary std::string; keep it in a
			// local so its buffer outlives the use below (calling .c_str() on the
			// temporary directly left a dangling pointer). c_str() is never NULL, so
			// test the string itself for "no entries".
			std::string entries = lastMsg->getUserEntriesAsString();
			if (entries.empty()) {
				entry->mutex.leave();
				return NULL;
			}
			DataMessage* msg = new DataMessage();
			msg->setString("HTML", entries.c_str());
			entry->mutex.leave();
			return msg;
		}
	}
	else if (utils::TextEndsWith(entry->mimetype.c_str(), "text", false)) {
		if (entry->key.length()) {
			std::string str = lastMsg->getAsString(entry->key.c_str());
			DataMessage* msg = new DataMessage();
			msg->setString("TEXT", str.c_str());
			entry->mutex.leave();
			return msg;
		}
		else {
			// See the HTML branch above: hold the temporary std::string in a local
			// (calling .c_str() on the temporary directly left a dangling pointer).
			std::string entries = lastMsg->getUserEntriesAsString();
			if (entries.empty()) {
				entry->mutex.leave();
				return NULL;
			}
			DataMessage* msg = new DataMessage();
			msg->setString("TEXT", entries.c_str());
			entry->mutex.leave();
			return msg;
		}
	}
	else {
		// the data is already in the right format
		cdata = lastMsg->getData(entry->key.c_str(), size);
		if (!cdata) {
			entry->mutex.leave();
			return NULL;
		}
		DataMessage* msg = new DataMessage();
		msg->setData("BINARY", cdata, size);
		msg->setString("MIMETYPE", entry->mimetype.c_str());
		entry->mutex.leave();
		return msg;
	}
}




} // namespace cmlabs
