

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

namespace	cmlabs {

// Built-in file catalog 
// Queries: ReadAsciiFile, ReadBinaryFile, WriteAsciiFile, WriteBinaryFile, GetFileInfo
// Parameters: root (root dir)
int8 FileCatalog(PsyAPI* api) {
	char* myName = new char[256];
	api->getModuleName(myName, 256);

	api->logPrint(2,"Started running...");
	
	char* root = new char[MAXKEYNAMELEN+1];
	if (!api->getParameter("root", root, MAXKEYNAMELEN) || !strlen(root) || (stricmp(root, ".") == 0))
		utils::strcpyavail(root, "./", MAXKEYNAMELEN, true);
	else if (!utils::TextEndsWith(root, "/") && !utils::TextEndsWith(root, "\\"))
		snprintf(root, MAXKEYNAMELEN, "%s/", root);
	char* ext = new char[MAXKEYNAMELEN+1];
	if (!api->getParameter("ext", ext, MAXKEYNAMELEN))
		ext[0] = 0;
	bool binary = false;
	char* binarytext = new char[MAXKEYNAMELEN+1];
	if (api->getParameter("binary", binarytext, MAXKEYNAMELEN))
		binary = (stricmp(binarytext, "yes") == 0);
	bool readonly = false;
	char* readonlytext = new char[MAXKEYNAMELEN+1];
	if (api->getParameter("ReadOnly", readonlytext, MAXKEYNAMELEN))
		readonly = (stricmp(readonlytext, "yes") == 0);

	DataMessage* inMsg;
	uint32 size, c = 0;
	char* data;
	const char* writedata;
	const char* triggerName;

	const char* str;
	const char* subdir;
	const char* reqext;
	bool reqbinary;
	bool reqwrite;

	std::string dirname;
	std::string filename;

	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) {
				writedata = NULL;
				subdir = reqext = NULL;
				reqbinary = binary;
				reqwrite = false;
				if (str = inMsg->getString("Subdir"))
					subdir = str;
				if (str = inMsg->getString("Ext"))
					reqext = str;
				if (str = inMsg->getString("Binary"))
					reqbinary = (stricmp(str, "yes") == 0);
				if ((str = inMsg->getString("Operation")))
					reqwrite = (stricmp(str, "write") == 0);
				
				if (reqwrite)
					writedata = inMsg->getData("Data", size);

				if ((str = inMsg->getString("Query")) && (!reqwrite || writedata)) {
					if (reqext && strlen(reqext))
						filename = utils::StringFormat("%s.%s", str, reqext);
					else if (strlen(ext))
						filename = utils::StringFormat("%s.%s", str, ext);
					else
						filename = str;

					if (subdir && strlen(subdir)) {
						if (!utils::TextEndsWith(subdir, "/") && !utils::TextEndsWith(subdir, "\\"))
							dirname = utils::StringFormat("%s%s/", root, subdir);
						else
							dirname = utils::StringFormat("%s%s", root, subdir);
					}
					
					if (reqwrite) {
						if (utils::WriteAFile(dirname.c_str(), filename.c_str(), writedata, size, reqbinary))
							api->queryReply((uint32)inMsg->getReference(), QUERY_SUCCESS);
						else {
							if (!utils::GetFileDetails(dirname.c_str()).doesExist) {
								if (utils::CreateADir(dirname.c_str())) {
									if (utils::WriteAFile(dirname.c_str(), filename.c_str(), writedata, size, reqbinary))
										api->queryReply((uint32)inMsg->getReference(), QUERY_SUCCESS);
									else {
										api->logPrint(0,"Couldn't write file %s in dir %s...", filename.c_str(), dirname.c_str());
										api->queryReply((uint32)inMsg->getReference(), QUERY_FAILED);
									}
								}
								else {
									api->logPrint(0,"Couldn't create dir %s...", dirname.c_str());
									api->queryReply((uint32)inMsg->getReference(), QUERY_FAILED);
								}
							}
							else if (!utils::GetFileDetails(dirname.c_str()).isDirectory) {
								api->logPrint(0,"Couldn't write file %s in dir %s...", filename.c_str(), dirname.c_str());
								api->queryReply((uint32)inMsg->getReference(), QUERY_FAILED);
							}
							else {
								api->logPrint(0,"Couldn't create dir %s as it is already a file...", dirname.c_str());
								api->queryReply((uint32)inMsg->getReference(), QUERY_FAILED);
							}
						}
					}
					else if (data = utils::ReadAFile(dirname.c_str(), filename.c_str(), size, reqbinary)) {
						api->queryReply((uint32)inMsg->getReference(), QUERY_SUCCESS, data, size, 0);
						delete [] data;
					}
					else {
						api->logPrint(0,"Couldn't read file %s in dir %s...", filename.c_str(), dirname.c_str());
						api->queryReply((uint32)inMsg->getReference(), QUERY_FAILED);
					}
				}
				else {
					api->queryReply((uint32)inMsg->getReference(), QUERY_FAILED);
				}
			}
			else {
				// Received another message, ignoring...
			}
		}
	}

	delete [] myName;
	delete [] root;
	delete [] ext;
	delete [] binarytext;
	delete [] readonlytext;

	return 0;
}

// Built-in data catalog for key(string) = value(object) 
// if file is provided the data will persist between runs 
// Queries: GetEntry, AddEntry, GetEntryInfo (createdTime, lastWrite, lastAccess, lastWriteComp, lastAccessComp)
// Parameters: root (db file)
int8 DataCatalog(PsyAPI* api) {
	std::map<std::string, DataStorageStruct*> dataMap;
	std::map<std::string, DataStorageStruct*>::iterator i, e;

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

	char* root = new char[MAXKEYNAMELEN+1];
	if (!api->getParameter("root", root, MAXKEYNAMELEN) || !strlen(root) || (stricmp(root, ".") == 0))
		snprintf(root, MAXKEYNAMELEN, "./%s.dat", myName);
	char* roottmp = new char[MAXKEYNAMELEN+1];
	snprintf(roottmp, MAXKEYNAMELEN, "%s.tmp", root);
	int64 val;
	int32 interval = 10000;
	if (api->getParameter("interval", val))
		interval = (int32) val;

	DataStorageFileHeader* fileHeader = NULL;
	DataStorageStruct* entry;
	uint32 length, n;
	int32 crc, origcrc;
	DataMessage* inMsg;
	const char* triggerName;
	const char* str;
	const char* name;
	const char* data;
	char* raw;
	uint32 size;
	bool reqwrite; //, reqbinary;
	uint64 lastWrite = GetTimeNow();

	fileHeader = (DataStorageFileHeader*) utils::ReadAFile(root, length, true);
	if (!fileHeader || !length || (fileHeader->size != length)) {
		delete [] fileHeader;
		fileHeader = NULL;
	}
	else {
		origcrc = fileHeader->crc;
		fileHeader->crc = 0;
		if (origcrc != (crc = utils::CRC32((char*)fileHeader, length))) {
			delete [] fileHeader;
			fileHeader = NULL;
		}
	}
	if (!fileHeader) {
		fileHeader = (DataStorageFileHeader*) utils::ReadAFile(roottmp, length, true);
		if (!fileHeader || !length || (fileHeader->size != length)) {
			delete [] fileHeader;
			fileHeader = NULL;
		}
		else {
			origcrc = fileHeader->crc;
			fileHeader->crc = 0;
			if (origcrc != (crc = utils::CRC32((char*)fileHeader, length))) {
				delete(fileHeader);
				fileHeader = NULL;
			}
		}
	}
	if (fileHeader) {
		raw = (((char*)fileHeader) + sizeof(DataStorageFileHeader));
		for (n = 0; n < fileHeader->count; n++) {
			entry = (DataStorageStruct*) malloc(((DataStorageStruct*)raw)->size);
			memcpy(entry, raw, ((DataStorageStruct*)raw)->size);
			dataMap[entry->name] = entry;
			raw += entry->size;
		}
		api->logPrint(2,"[%s] loaded database with %u entries...", myName, dataMap.size());
		delete [] fileHeader;
	}
	else
		api->logPrint(2,"[%s] started up with fresh database...", myName);

	bool hasBeenUpdated = false;
	while (api->shouldContinue()) {
		if ( inMsg = api->waitForNewMessage(200, triggerName)) {
			if (triggerName && stricmp(triggerName, "DefaultComponentTrigger") == 0)
				api->logPrint(2,"[%s] Got wakeup message...", myName);
			else if (inMsg->getType() == PsyAPI::CTRL_QUERY) {
				reqwrite = false;
				if ((str = inMsg->getString("Operation")))
					reqwrite = (stricmp(str, "write") == 0);
				//if (str = inMsg->getString("Binary"))
				//	reqbinary = (stricmp(str, "yes") == 0);

				if (reqwrite) {
					if ((name = inMsg->getString("Query")) && (data = inMsg->getData("Data", size))) {
						entry = (DataStorageStruct*) malloc(size + sizeof(DataStorageStruct));
						entry->size = size + sizeof(DataStorageStruct);
						entry->datasize = size;
						entry->time = GetTimeNow();
						//entry->type = reqbinary ? DataStorageStruct::BINARY : DataStorageStruct::STRING;
						utils::strcpyavail(entry->name, name, MAXKEYNAMELEN, true);
						memcpy(((char*)entry) + sizeof(DataStorageStruct), data, size);
						delete(dataMap[entry->name]);
						dataMap[entry->name] = entry;
						api->queryReply((uint32)inMsg->getReference(), QUERY_SUCCESS);
						hasBeenUpdated = true;
					}
					else {
						api->queryReply((uint32)inMsg->getReference(), QUERY_FAILED);
					}
				}
				else {
					// find() not operator[]: a query for an absent key must not
					// insert a NULL entry (unbounded map growth from query keys).
					std::map<std::string, DataStorageStruct*>::iterator di;
					if ((name = inMsg->getString("Query")) &&
					    ((di = dataMap.find(name)) != dataMap.end()) && (entry = di->second))
						api->queryReply((uint32)inMsg->getReference(), QUERY_SUCCESS, ((char*)entry)+sizeof(DataStorageStruct), entry->datasize, 0);
					else
						api->queryReply((uint32)inMsg->getReference(), QUERY_FAILED);
				}
			}
			else {
				// Received another message, ignoring...
			}
		}

		if (hasBeenUpdated && (GetTimeAgeMS(lastWrite) > interval)) {
			// calc size
			size = sizeof(DataStorageFileHeader);
			i = dataMap.begin();
			e = dataMap.end();
			while (i != e) {
				size += i->second->size;
				i++;
			}
			fileHeader = (DataStorageFileHeader*) malloc(size);
			fileHeader->count = (uint32)dataMap.size();
			fileHeader->size = size;
			fileHeader->crc = 0;
			raw = ((char*)fileHeader) + sizeof(DataStorageFileHeader);
			i = dataMap.begin();
			while (i != e) {
				memcpy(raw, i->second, i->second->size);
				raw += i->second->size;
				i++;
			}
			int32 crc1 = utils::CRC32((const char*) fileHeader, size);
			int32 crc2 = utils::CRC32((const char*) fileHeader, size);

			fileHeader->crc = utils::CRC32((const char*) fileHeader, size);

			utils::DeleteAFile(roottmp, true);
			if (utils::WriteAFile(roottmp, (const char*) fileHeader, size, true)) {
				if (!utils::MoveAFile(roottmp, root, true))
					api->logPrint(0,"[%s] Could not write permanent file '%s'", myName, root);
				else
					api->logPrint(0,"[%s] Wrote permanent file '%s'", myName, root);
			}
			else
				api->logPrint(0,"[%s] Could not write temporary file '%s'", myName, roottmp);

			free(fileHeader);
			lastWrite = GetTimeNow();
			hasBeenUpdated = false;
		}
	}

	i = dataMap.begin();
	e = dataMap.end();
	while (i != e) {
		delete(i->second);
		i++;
	}
	dataMap.clear();

	delete [] root;
	delete [] roottmp;
	delete [] myName;

	return 0;
}



// Built-in media catalog saving audio and video streams 
// Queries: NewRecording(name, type, filename, split), AddVideoFrame, AddAudioFrame, AddImage, GetVideoFrame, GetAudioFrame, GetImage 
// based on named Recordings (type, filename, split), framenumber carry through splits
// Parameters: root (root dir)
int8 MediaCatalog(PsyAPI* api) {
	return 0;
}

// Built-in Google Search catalog 
// Queries: Search, ImFeelingLucky 
// Parameters: root (which google engine)
int8 GoogleCatalog(PsyAPI* api) {
	return 0;
}











// Built-in ReplayCatalog 
// Queries: none
// Parameters: none
int8 ReplayCatalog(PsyAPI* api) {
	std::string myName = api->getModuleName();

	MessagePlayer* msgPlayer = new MessagePlayer();

	// Is this recording or playing back
	uint32 interval = (uint32)api->getParameterInt("interval");
	uint32 maxCount = (uint32)api->getParameterInt("maxcount");
	uint64 maxSize = (uint64)api->getParameterInt("maxsize");
	std::string rotateString = api->getParameterString("rotate");
	bool rotate = (stricmp(rotateString.c_str(), "yes") == 0);
	std::string root = api->getParameterString("root");
	if (!root.length())
		root = utils::StringFormat("./%s", myName.c_str());

	if (maxCount || maxSize) {
		if (!msgPlayer->initWrite(root.c_str(), maxCount, maxSize)) {
			delete(msgPlayer);
			return -1;
		}
	}
	else {
		if (!msgPlayer->initRead(root.c_str(), interval, rotate)) {
			delete(msgPlayer);
			return -1;
		}
	}

	DataMessage* msg;
	const char* triggerName;
	DataMessage* idMsg = api->retrieveAllSystemIDs();
	msgPlayer->setSystemIDs(idMsg);
	uint32 waitMS = 100;
	uint64 lastIDQueryTime = GetTimeNow();
	while (api->shouldContinue()) {
		if (msg = api->waitForNewMessage(waitMS, triggerName)) {
		//	printf("Message...\n");
			if (maxCount || maxSize) {
				// Store the message
				msgPlayer->addMessage(triggerName, msg);
			}
			// else ignore
		}
	//	else
	//		printf("No message...\n");
		if (!maxCount && !maxSize) {
			// plan to send out next message
			if (msg = msgPlayer->waitForNextMessage(0, triggerName, waitMS))
				api->postOutputMessage(triggerName, msg);
		}

		if (GetTimeAgeMS(lastIDQueryTime) > 10000) {
			if (idMsg)
				delete idMsg;
			idMsg = api->retrieveAllSystemIDs();
			msgPlayer->setSystemIDs(idMsg);
			lastIDQueryTime = GetTimeNow();
		}
	}
	delete(msgPlayer);
	return 0;
}


} // namespace cmlabs
