

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK 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/
//_/_/
//_/_/  and is released under the following
//_/_/  
//_/_/  EXTENDED BSD License
//_/_/
//_/_/  Redistribution and use in source and binary forms, with or without
//_/_/  modification, is permitted provided that the following conditions 
//_/_/  are met:
//_/_/
//_/_/  - Redistributions of source code must retain the above copyright 
//_/_/    and collaboration notice, this list of conditions and the following 
//_/_/    disclaimer.
//_/_/
//_/_/  - Redistributions in binary form must reproduce the above copyright 
//_/_/    notice, this list of conditions and the following disclaimer in the 
//_/_/    documentation and/or other materials provided with the distribution.
//_/_/
//_/_/  - Neither the name of its copyright holders nor the names of its 
//_/_/    contributors may be used to endorse or promote products derived from 
//_/_/    this software without specific prior written permission.
//_/_/
//_/_/  - 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. 
//_/_/
//_/_/  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.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*! @file MessageIndex.h
	@brief In-memory, multi-key index over DataMessages with TTL-based expiry — the storage engine behind whiteboards.

	A MessageIndex keeps DataMessages ordered by time, type and any number of
	user-declared index keys (time, string, integer or float entries), so
	subscription retrievals (RetrieveSpec) can be answered with range queries
	instead of scans. Messages are evicted when their end-of-life (creation
	time + TTL, or a default TTL) passes, during periodic doMaintenance()
	calls. Optionally messages can be spooled to file storage.
	@ingroup Messaging
*/

#if !defined(_MESSAGEINDEX_H_)
#define _MESSAGEINDEX_H_

#include "DataMessage.h"
#include "PsyTime.h"
#include "Subscriptions.h"

namespace	cmlabs {


/*! @brief Multi-key in-memory index and lifetime manager for DataMessages.

	Ownership: addMessage() takes ownership of the message — the index deletes
	it when it expires or the index is destroyed. Results returned by
	queryMessages() are a newly allocated flat buffer of message copies owned
	by the caller.
	@warning Not internally synchronised; callers (e.g. the whiteboard
	component) must serialise access from multiple threads.
	@ingroup Messaging
*/
class MessageIndex {
public:
	//! Run the built-in self test. @return true if all tests pass.
	static bool UnitTest();

	//! Create an empty index with no extra index keys and no file storage.
	MessageIndex();
	//! Destroys the index and deletes all messages it still holds.
	~MessageIndex();

	/*! Set the TTL applied to messages that carry none of their own.
	@param defaultTTL default time-to-live in microseconds
	@return true if successful */
	bool setDefaultTTL(uint64 defaultTTL);

	/*! Start indexing messages by the value of a named user entry.
	Subsequently added messages containing this entry become retrievable by
	value/range queries on it.
	@param key user entry name to index on
	@param type entry interpretation: INDEX_TIME, INDEX_STRING, INDEX_INTEGER or INDEX_FLOAT
	@return true if successful */
	bool addIndexKey(const char* key, uint8 type);
	/*! Remove a previously added index key (the index over that entry is dropped;
	the messages themselves remain).
	@param key user entry name
	@param type the type the key was registered with
	@return true if successful */
	bool removeIndexKey(const char* key, uint8 type);

	/*! Enable file-backed storage of indexed messages.
	@param dir directory to store message files in
	@param buffertime how long (ms) messages are buffered before being flushed
	@param indexfilename name of the index database file inside @p dir
	@return true if successful */
	bool setFileStorage(const char* dir, uint32 buffertime, const char* indexfilename = "index.db");

	/*! Add a message to the index; the index TAKES OWNERSHIP of @p msg and will
	delete it on expiry.
	@param msg the message to index
	@return true if successful */
	bool addMessage(DataMessage* msg);

	/*! Execute a retrieval query and return matching messages as one flat buffer.
	The buffer contains the binary blocks of the matching messages back-to-back
	and is owned by the caller (delete[]).
	@param spec the retrieval specification (type, key, ranges, maxcount, maxage, ...)
	@param size receives the byte size of the returned buffer
	@param count receives the number of messages in the buffer
	@return the buffer, or NULL if nothing matched */
	char* queryMessages(RetrieveSpec* spec, uint32 &size, uint32 &count);

	//! @return the number of messages currently held by the index.
	uint32 getCount();

	/*! Perform periodic housekeeping: evict expired messages (per TTL/EOL) and
	flush file storage buffers. Call regularly, e.g. once per second.
	@return true if successful */
	bool doMaintenance();

private:

	bool addMessageToTime(DataMessage* msg);
	bool addMessageToString(DataMessage* msg);
	bool addMessageToInteger(DataMessage* msg);
	bool addMessageToFloat(DataMessage* msg);

	template <typename T>
	bool deleteIndexKey(const char* key, std::map<std::string, std::multimap<T, DataMessage*> > &maps);

	template <typename T>
	bool removeMessage(DataMessage* msg, std::multimap<T, DataMessage*> &map, T &val);

	template <typename T>
	char* queryMessages(uint32 from, uint32 to, PsyType &type, uint32 maxcount, uint64 maxage, uint32 &size, uint32 &count, std::multimap<T, DataMessage*> &map);
	template <typename T>
	char* queryMessages(T start, uint32 from, uint32 to, PsyType &type, uint32 maxcount, uint64 maxage, uint32 &size, uint32 &count, std::multimap<T, DataMessage*> &map);
	template <typename T>
	char* queryMessages(T start, T end, uint32 from, uint32 to, PsyType &type, uint32 maxcount, uint64 maxage, uint32 &size, uint32 &count, std::multimap<T, DataMessage*> &map);

	typedef std::pair <uint64, DataMessage*> Time_Pair;
	typedef std::pair <std::string, DataMessage*> String_Pair;
	typedef std::pair <int64, DataMessage*> Int_Pair;
	typedef std::pair <float64, DataMessage*> Float_Pair;
	typedef std::pair <PsyType, DataMessage*> Type_Pair;

	std::map<std::string, std::multimap<uint64, DataMessage*> > timeMaps;
	std::map<std::string, std::multimap<std::string, DataMessage*> > stringMaps;
	std::map<std::string, std::multimap<int64, DataMessage*> > integerMaps;
	std::map<std::string, std::multimap<float64, DataMessage*> > floatMaps;

	std::multimap<PsyType, DataMessage*>* typeMap;
	std::multimap<uint64, DataMessage*>* timeMap;
	std::multimap<uint64, DataMessage*> eolMap;
	uint64 lastMaintenance;

	std::string dir;
	std::string indexfilename;
	int32 buffertime;
	int32 maintenanceInterval;

	uint64 defaultTTL;
};


} // namespace cmlabs

#endif // _MESSAGEINDEX_H_
