

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

namespace cmlabs {

template <typename T>
CCMTypedStorage<T>::CCMTypedStorage(uint32 maxKeep) {
	if (maxKeep)
		maxCount = maxKeep;
	else
		maxCount = 1000;
}

template <typename T>
CCMTypedStorage<T>::~CCMTypedStorage() {
}

template <typename T>
bool CCMTypedStorage<T>::addObservation(uint64 id, const char* cdata, uint32 size) {
	if (!cdata || (size != (sizeof(ObsHeader) + sizeof(T)))) return false;
	GenericObservation<T>* obs = new GenericObservation<T>();
	if (!obs->readFromData(cdata, size)) {
		delete obs;
		return false;
	}
	obs->header.id = id;
	if (!mutex.enter(1000)) {
		delete obs;
		return false;
	}
	data.insert(std::make_pair(obs->header.timestamp, obs));
	while (data.size() > maxCount) {
		typename std::multimap<uint64, GenericObservation<T>* >::iterator i = data.begin();
		delete i->second;
		data.erase(i);
	}
	mutex.leave();
	//printf("*** Added object %llu observation: %s\n", obs->header.objectID, obs->print().c_str());
	//printf("DataMap type %u now contains %u observations", obs->header.type, data.size());
	return true;
}


template <typename T>
bool CCMTypedStorage<T>::query(DataMessage* msg, DataMessage* resultMsg, std::list<std::string>* jsonList, uint32 lastObjectID) {
	if (!msg) return false;
	// We expect this query message to contain the following information
	// type of query (range from-to, last N)
	// data type (OBSID_xxx)
	// possibly Object and/or System IDs
	// if range we must have time from-to and possibly data from-to
	T from, to;
	uint64 fromTime = msg->getAsTime("From");
	uint64 toTime = msg->getAsTime("to");
	uint64 objectID = (uint64)msg->getAsInt("ObjectID");
	uint64 creatorID = (uint64)msg->getAsInt("CreatorID");
	uint64 topicID = (uint64)msg->getAsInt("TopicID");
	const char* name = msg->getString("Name");

	const char* query = msg->getString("Query");
	if (stricmp(query, "range") == 0) {
		if (!fromTime) return false;
		// create a from and a to object
		if (!from.extractRangeFrom(msg) || !to.extractRangeTo(msg))
			return false;
		return getRange(fromTime, toTime, from, to, creatorID, objectID, topicID, name, resultMsg, jsonList);
	}
	else if (stricmp(query, "period") == 0) {
		if (!fromTime || !toTime) return false;
		return getPeriod(fromTime, toTime, creatorID, objectID, topicID, name, resultMsg, jsonList);
	}
	else if (stricmp(query, "since") == 0) {
		if (!fromTime) return false;
		// create a from object only
		return getPeriod(fromTime, 0, creatorID, objectID, topicID, name, resultMsg, jsonList);
	}
	else if (stricmp(query, "last") == 0) {
		uint32 count = (uint32)msg->getAsInt("Count");
		if (objectID)
			return getLast(count, creatorID, objectID, topicID, name, resultMsg, jsonList);
		else
			return getLastAllObjects(count, lastObjectID, creatorID, topicID, name, resultMsg, jsonList);
	}
	return false;
}

template <typename T>
bool CCMTypedStorage<T>::getRange(uint64 fromTime, uint64 toTime, T& from, T& to, uint64 creatorID, uint64 objectID, uint64 topicID, const char* name, DataMessage* resultMsg, std::list<std::string>* jsonList) {
	if (!mutex.enter(1000))
		return false;
	uint32 count = 0;
	// Find the first entry >= fromTime
	typename std::multimap<uint64, GenericObservation<T>* >::iterator i = data.lower_bound(fromTime), e = data.end();
	// Iterate until toTime or end if toTime = 0
	if (!toTime) {
		for (; i != e; i++) {
			// no need to check time anymore
			// For each check filters
			if (creatorID && (i->second->header.creatorID != creatorID))
				continue;
			if (objectID && (i->second->header.objectID != objectID))
				continue;
			if (topicID && (i->second->header.topicID != topicID))
				continue;
			if (name) {
				//if (!i->second->header.name)
				//	continue;
				if (stricmp(name, i->second->header.name) != 0)
					continue;
			}
			// For each check between from-to
			if (!i->second->data.isBetween(from,to))
				continue;
			// If fine renumber ID and add to output message
			if (jsonList)
				jsonList->push_back(i->second->toJSON());
			else
				i->second->writeToMessage(resultMsg, ++count);
		}
	}
	else {
		for (; i != e; i++) {
			// check time
			if (i->second->header.timestamp > toTime)
				break;
			// For each check filters
			if (creatorID && (i->second->header.creatorID != creatorID))
				continue;
			if (objectID && (i->second->header.objectID != objectID))
				continue;
			if (topicID && (i->second->header.topicID != topicID))
				continue;
			if (name) {
				//if (!i->second->header.name)
				//	continue;
				if (stricmp(name, i->second->header.name) != 0)
					continue;
			}
			// For each check between from-to
			if (!i->second->data.isBetween(from, to))
				continue;
			// If fine renumber ID and add to output message
			if (jsonList)
				jsonList->push_back(i->second->toJSON());
			else
				i->second->writeToMessage(resultMsg, ++count);
		}
	}
	mutex.leave();
	// Set count of how many added
	if (resultMsg)
		resultMsg->setInt("ObsCount", count);
	return true;
}

template <typename T>
bool CCMTypedStorage<T>::getPeriod(uint64 fromTime, uint64 toTime, uint64 creatorID, uint64 objectID, uint64 topicID, const char* name, DataMessage* resultMsg, std::list<std::string>* jsonList) {
	if (!mutex.enter(1000))
		return false;
	uint32 count = 0;
	// Find the first entry >= fromTime
	typename std::multimap<uint64, GenericObservation<T>* >::iterator i = data.lower_bound(fromTime), e = data.end();
	// Iterate until either toTime or end if toTime = 0
	if (!toTime) {
		for (; i != e; i++) {
			// no need to check time anymore
			// For each check filters
			if (creatorID && (i->second->header.creatorID != creatorID))
				continue;
			if (objectID && (i->second->header.objectID != objectID))
				continue;
			if (topicID && (i->second->header.topicID != topicID))
				continue;
			if (name) {
				//if (!i->second->header.name)
				//	continue;
				if (stricmp(name, i->second->header.name) != 0)
					continue;
			}
			// If fine renumber ID and add to output message
			if (jsonList)
				jsonList->push_back(i->second->toJSON());
			else
				i->second->writeToMessage(resultMsg, ++count);
		}
	}
	else {
		for (; i != e; i++) {
			// check time
			if (i->second->header.timestamp > toTime)
				break;
			// For each check filters
			if (creatorID && (i->second->header.creatorID != creatorID))
				continue;
			if (objectID && (i->second->header.objectID != objectID))
				continue;
			if (topicID && (i->second->header.topicID != topicID))
				continue;
			if (name) {
				//if (!i->second->header.name)
				//	continue;
				if (stricmp(name, i->second->header.name) != 0)
					continue;
			}
			// If fine renumber ID and add to output message
			if (jsonList)
				jsonList->push_back(i->second->toJSON());
			else
				i->second->writeToMessage(resultMsg, ++count);
		}
	}
	mutex.leave();
	// Set count of how many added
	if (resultMsg)
		resultMsg->setInt("ObsCount", count);
	return true;
}

template <typename T>
bool CCMTypedStorage<T>::getLast(uint32 maxCount, uint64 creatorID, uint64 objectID, uint64 topicID, const char* name, DataMessage* resultMsg, std::list<std::string>* jsonList) {
	if (!mutex.enter(1000))
		return false;
	// Get iterator to last entry
	uint32 count = 0;
	typename std::multimap<uint64, GenericObservation<T>* >::reverse_iterator i = data.rbegin(), e = data.rend();
	// Go back in time and check filters
	for (; i != e; i++) {
		// check count
		if (count >= maxCount)
			break;
		// For each check filters
		if (creatorID && (i->second->header.creatorID != creatorID))
			continue;
		if (objectID && (i->second->header.objectID != objectID))
			continue;
		if (topicID && (i->second->header.topicID != topicID))
			continue;
		if (name) {
			//if (!i->second->header.name)
			//	continue;
			if (stricmp(name, i->second->header.name) != 0)
				continue;
		}
		// If fine renumber ID and add to output message
		if (jsonList) {
			jsonList->push_back(i->second->toJSON());
			count++;
		}
		else
			i->second->writeToMessage(resultMsg, ++count);
	}
	mutex.leave();
	// Set count of how many added
	if (resultMsg)
		resultMsg->setInt("ObsCount", count);
	return true;
}

template <typename T>
bool CCMTypedStorage<T>::getLastAllObjects(uint32 maxCount, uint32 maxObjID, uint64 creatorID, uint64 topicID, const char* name, DataMessage* resultMsg, std::list<std::string>* jsonList) {
	if (!mutex.enter(1000))
		return false;
	// Get iterator to last entry
	uint32 count = 0;

	// Get the last N observations per objectID
	uint32 *objCount = new uint32[maxObjID + 1];
	memset(objCount, 0, (maxObjID + 1) * sizeof(uint32));

	bool done = false;
	uint64 curObjID;
	typename std::multimap<uint64, GenericObservation<T>* >::reverse_iterator i = data.rbegin(), e = data.rend();
	// Go back in time and check filters
	for (; i != e; i++) {
		curObjID = i->second->header.objectID;
		// check count
		if (objCount[curObjID] >= maxCount)
			continue;
		// For each check filters
		if (creatorID && (i->second->header.creatorID != creatorID))
			continue;
		if (topicID && (i->second->header.topicID != topicID))
			continue;
		if (name) {
			//if (!i->second->header.name)
			//	continue;
			if (stricmp(name, i->second->header.name) != 0)
				continue;
		}
		// If fine renumber ID and add to output message
		if (jsonList) {
			jsonList->push_back(i->second->toJSON());
			count++;
		}
		else
			i->second->writeToMessage(resultMsg, ++count);
		objCount[curObjID]++;
		if (objCount[curObjID] >= maxCount) {
			done = true;
			for (uint32 n = 1; n <= maxObjID; n++) {
				if (objCount[n] < maxCount)
					done = false;
			}
			if (done) break;
		}
	}
	mutex.leave();
	// Set count of how many added
	if (resultMsg)
		resultMsg->setInt("ObsCount", count);
	delete[] objCount;
	return true;
}


} // namespace cmlabs
