

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  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 Observations.tpl.h
    @brief Template implementations for GenericObservation<T> (declared in Observations.h).

    Included at the bottom of Observations.h; not meant for direct inclusion.
    Serialization layout is the raw ObsHeader immediately followed by the T
    payload (memcpy of the POD structs), stored as "OBS" data entries in a
    DataMessage. This raw-memory format is only portable between hosts of the
    same endianness and struct packing.

    The payload type @c T must be a POD struct providing: init(), getType(),
    getTypeName(), print(), toJSON(), isBetween(const T&, const T&) and
    extractDataFromMessage(DataMessage*, std::map\<std::string,std::string\>&).
    Because payloads are copied with memcpy, T must not contain pointers or
    non-trivial members. */
#include "Utils.h"

namespace cmlabs {

/*! Extract every "OBS" data entry of matching size from @a msg into new observations.

    Iterates ids 1,2,3,... until getData() finds no more entries; each blob whose size
    equals sizeof(ObsHeader)+sizeof(T) and whose type tag matches T is deserialized.
    Entries of the wrong observation type are skipped (readFromData() rejects them),
    but iteration stops at the first missing/short id, so ids must be contiguous from 1.

    @return List of heap-allocated observations; the caller owns and must delete each one.
    @code
    std::list<GenericObservation<MyObs>*> list = GenericObservation<MyObs>::ReadAllFromMessage(msg);
    for (GenericObservation<MyObs>* o : list) { use(o); delete o; }
    @endcode */
template <typename T>
std::list<GenericObservation<T>*> GenericObservation<T>::ReadAllFromMessage(DataMessage* msg) {
	uint32 n = 1;
	std::list<GenericObservation<T>*> list;
	GenericObservation<T>* obs;
	uint32 size;
	const char* data;
	while ((data = msg->getData(n, "OBS", size)) && size && (size == sizeof(ObsHeader) + sizeof(T))) {
		obs = new GenericObservation<T>();
		if (obs->readFromData(data, size))
			list.push_back(obs);
		else
			delete obs;
		n++;
	}
	return list;
}

/*! Bulk serialization counterpart of ReadAllFromMessage().
    @warning Not implemented; always returns false. Serialize observations one by one
    with writeToMessage() instead. */
template <typename T>
bool GenericObservation<T>::WriteAllToMessage(DataMessage* msg, std::list<GenericObservation<T>*>& list) {
	// #################
	return false;
}

/*! Construct an empty observation: zeroed header stamped with the current time and
    the payload's type tag (data.getType()), then T::init() on the payload. */
template <typename T>
GenericObservation<T>::GenericObservation() {
	memset(&header, 0, sizeof(ObsHeader));
	header.cid = OBSERVATIONID;
	header.size = sizeof(ObsHeader);
	header.timestamp = GetTimeNow();
	header.type = data.getType();
	data.init();
}

/*! Construct from a serialized blob (ObsHeader + T payload).
    Falls back to the default-constructed state (fresh header, T::init()) when @a cdata
    is NULL, the size does not match, or the type tag differs; no error is reported.
    @param cdata Serialized bytes (copied, not retained). @param size Byte count.
    @param id Optional id hint (currently unused by readFromData()). */
template <typename T>
GenericObservation<T>::GenericObservation(const char* cdata, uint32 size, uint64 id) {
	if (!readFromData(cdata, size, id)) {
		memset(&header, 0, sizeof(ObsHeader));
		header.cid = OBSERVATIONID;
		header.size = sizeof(ObsHeader);
		header.timestamp = GetTimeNow();
		header.type = data.getType();
		data.init();
	}
}


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

//template <typename T>
//bool GenericObservation<T>::init() {}

/*! Serialize this observation into @a msg as data entry @a id of type "OBS".

    The blob is header followed by a raw memcpy of the T payload (see the file-level
    note on portability). Fails if neither @a id nor header.id is set, or if the message
    already contains an "OBS" entry under that id (no overwrite).

    @param msg Target message (caller-owned). @param id Entry id; 0 means use header.id.
    @return true on success. Uses a temporary heap buffer; @a msg copies the bytes. */
template <typename T>
bool GenericObservation<T>::writeToMessage(DataMessage* msg, uint64 id) {
	if (!msg || (!header.id && !id)) return false;
	uint32 s;
	if (!id)
		id = header.id;
	if (msg->getData(id, "OBS", s))
		return false;
	// calculate size needed
	header.size = sizeof(ObsHeader) + sizeof(T);
	// create temp memory buffer
	char* buffer = new char[header.size];
	memcpy(buffer, &header, sizeof(ObsHeader));
	memcpy(buffer + sizeof(ObsHeader), &data, sizeof(T));
	bool res = msg->setData(id, "OBS", buffer, header.size);
	delete[] buffer;
	return res;
}

/*! Deserialize header and payload from a raw blob.

    Strict validation: @a size must equal sizeof(ObsHeader)+sizeof(T) and the blob's
    type tag must match this instantiation's type (set at construction), so reading a
    blob of another observation type fails cleanly rather than mis-interpreting bytes.

    @param cdata Serialized bytes (borrowed; copied into this object).
    @param size Byte count; must equal sizeof(ObsHeader)+sizeof(T).
    @param id Unused. @return true if the blob was accepted. */
template <typename T>
bool GenericObservation<T>::readFromData(const char* cdata, uint32 size, uint64 id) {
	if (!cdata || (size != (sizeof(ObsHeader) + sizeof(T))))
		return false;
	if (((ObsHeader*)cdata)->type != header.type)
		return false;
	memcpy(&header, cdata, sizeof(ObsHeader));
	memcpy(&data, cdata + sizeof(ObsHeader), sizeof(T));
	return true;
}

/*! Deserialize the "OBS" data entry stored under @a id in @a msg (via readFromData()).
    @return true if the entry exists and matches this observation type. */
template <typename T>
bool GenericObservation<T>::readFromMessage(DataMessage* msg, uint64 id) {
	if (!msg || !id) return false;
	uint32 s;
	const char* cdata = msg->getData(id, "OBS", s);
	return readFromData(cdata, s, id);
}

//template <typename T>
//bool GenericObservation<T>::buildFromQuery(std::string& query) {
//	return data.buildFromQuery(query.c_str());
//}

/*! Delegates to T::isBetween(): is this observation's payload between @a a's and @a b's
    (payload-defined ordering, e.g. spatial or temporal interpolation range)? */
template <typename T>
bool GenericObservation<T>::isBetween(GenericObservation<T>* a, GenericObservation<T>* b) {
	return data.isBetween(a->data, b->data);
}

/*! Populate payload and header fields from an arbitrary DataMessage.

    First lets the payload pull its own fields via T::extractDataFromMessage() using the
    caller-supplied name @a mappings (message field name to observation field name); on
    success, fills the header from the message's well-known entries (SystemID, ObjectID,
    Confidence, Consensus, creation time).
    @return true if the payload extraction succeeded. */
template <typename T>
bool GenericObservation<T>::extractDataFromMessage(DataMessage* msg, std::map<std::string, std::string>& mappings) {
	if (!data.extractDataFromMessage(msg, mappings))
		return false;
	// Extract header data
	//header.creatorID = (uint64)msg->getInt("SystemID");
	header.creatorID = msg->getSystemID();
	header.objectID = (uint64)msg->getInt("ObjectID");
	header.confidence = msg->getFloat("Confidence");
	header.consensus = msg->getFloat("Consensus");
	header.timestamp = msg->getCreatedTime();
	return true;
}

/*! Human-readable one-line description of the payload (delegates to T::print()). */
template <typename T>
std::string GenericObservation<T>::print() {
	return data.print();
}

/*! Render header metadata plus the payload's own JSON (T::toJSON()) as one JSON object.
    If the payload produces no JSON, a stub object with "datatype": "unknown" is emitted
    so consumers still see the header fields. @return JSON text ending in a newline. */
template <typename T>
std::string GenericObservation<T>::toJSON() {
	std::string json = data.toJSON();
	if (json.length())
		return utils::StringFormat("{ \"datatype\": \"%s\", \"name\": \"%s\", \"id\": %llu, \"timestamp\": %llu, \"timetext\": \"%s\", \"objectid\": %llu, \"systemid\": %llu, \"topicid\": %llu, \"confidence\": %.3f, \"consensus\": %.3f, %s}\n",
			data.getTypeName().c_str(), header.name, header.id, header.timestamp, PrintTimeString(header.timestamp).c_str(), header.objectID, header.creatorID, header.topicID, header.confidence, header.consensus, json.c_str());
	else
		return utils::StringFormat("{ \"datatype\": \"%s\", \"name\": \"%s\", \"id\": %llu, \"timestamp\": %llu, \"timetext\": \"%s\", \"objectid\": %llu, \"systemid\": %llu, \"topicid\": %llu, \"confidence\": %.3f, \"consensus\": %.3f, \"datatype\": \"unknown\"}\n",
			data.getTypeName().c_str(), header.name, header.id, header.timestamp, PrintTimeString(header.timestamp).c_str(), header.objectID, header.creatorID, header.topicID, header.confidence, header.consensus);
}

} // namespace cmlabs
