

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  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 PsySpace.h
	@brief The PsySpace process/node context: the runtime container in which Psyclone components (cranks) live and execute.

	A PsySpace represents one operating-system process participating in a Psyclone
	system. It owns the process-local MemoryManager (the gateway to the shared-memory
	fabric shared by all spaces on a node), a pool of worker threads that dispatch
	trigger messages to component callbacks, the local time-based scheduling queue,
	and the per-component PsyAPI handles.

	Relationship to PsyAPI (important for newcomers): a PsySpace is the *environment*
	a component runs inside; a PsyAPI is the *handle* an individual component (crank)
	uses to interact with that environment — posting messages, waiting for triggers,
	emitting signals, querying catalogs, and reading parameters. One PsySpace hosts
	many components, each with its own PsyAPI (see PsySpace::getCrankAPI()).

	Typical embedding usage:
	@code
	cmlabs::PsySpace space("MyProcess");
	if (!space.connect(1))            // join Psyclone system with sysid 1
		return 1;
	space.registerCrankCallback("MyModule.MyCrank", MyCrankFunction);
	space.start(5);                   // start with 5 pool threads
	// ... or drive a component manually:
	cmlabs::PsyAPI* api = space.getCrankAPI("MyModule.MyCrank");
	@endcode
*/
#if !defined(_PSYSPACE_H_)
#define _PSYSPACE_H_

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

namespace cmlabs {

class PsyAPI;
/*! Signature of a component (crank) entry function.

	A crank is the executable body of a Psyclone component. The space calls this
	function on a pool thread whenever the component is triggered; the function
	receives the component's PsyAPI handle and should loop on
	PsyAPI::shouldContinue() / PsyAPI::waitForNewMessage() to process input.

	@param api The PsyAPI handle for the component instance being run (owned by the space; do not delete).
	@return Implementation-defined status code; non-negative for success.
*/
typedef int8 (* CrankFunction)(PsyAPI* api);

/*! Log source id used by PsySpace when writing entries to the system log. */
#define LOG_SPACE 7

/*! @brief One recurring (or time-bounded) activation schedule managed by a TimeQueue.

	Describes a periodic event: every @c interval milliseconds between the optional
	@c start and @c end timestamps a message of type @c msgType / tag @c msgTag is
	generated to activate a module. Instances are owned by the TimeQueue they are
	added to (TimeQueue::addSchedule() takes ownership).
*/
class TimeQueueSchedule {
public:
	/*! Create a schedule.
		@param interval Repeat interval in milliseconds.
		@param start Optional first-activation time (PsyTime µs timestamp); 0 means start immediately.
		@param end Optional expiry time (PsyTime µs timestamp); 0 means never expires.
	*/
	TimeQueueSchedule(uint32 interval, uint64 start = 0, uint64 end = 0);
	/*! Destructor. No resources beyond the object itself. */
	~TimeQueueSchedule();

	uint32 id;          //!< Unique schedule id, used for removal via TimeQueue::removeSchedule().
	uint64 nextTrigger; //!< Next activation time (PsyTime µs timestamp), maintained by the TimeQueue.

	uint32 interval;    //!< Repeat interval in milliseconds.
	uint64 start;       //!< First-activation time (µs), or 0 for immediate.
	uint64 end;         //!< Expiry time (µs), or 0 for no expiry.

	PsyType msgType;    //!< Type of the message generated on each activation.
	uint32 msgTag;      //!< Tag attached to the generated message.
};

/*! @brief Time-binned activation queue used by a PsySpace to drive time-triggered modules.

	Holds a collection of TimeQueueSchedule entries and hands out activation
	messages in temporal order. A space thread blocks in waitForNextEvent() and
	dispatches the resulting DataMessage to the scheduled component.
	Thread-safe: internal mutex protects the schedule map; a semaphore wakes
	waiters when schedules change.
*/
class TimeQueue {
public:
	/*! Create an empty time queue with its own timer. */
	TimeQueue();
	/*! Destroy the queue and delete all remaining schedules (owned by the queue). */
	~TimeQueue();

	/*! Add a schedule to the queue.
		@param schedule Schedule to add; ownership transfers to the TimeQueue.
		@return true if the schedule was accepted.
	*/
	bool addSchedule(TimeQueueSchedule* schedule);
	/*! Remove (and delete) a schedule by id.
		@param id Id of the schedule (TimeQueueSchedule::id).
		@return true if a schedule with that id was found and removed.
	*/
	bool removeSchedule(uint32 id);

	/*! Block until the next scheduled event is due, or the timeout expires.
		@param ms Maximum time to wait, in milliseconds.
		@return A newly generated activation DataMessage (caller takes ownership), or NULL on timeout.
		@note Blocking call; intended to be run from a dedicated space thread.
	*/
	DataMessage* waitForNextEvent(uint32 ms);

private:
	utils::Timer* timer;
	std::map<uint32, TimeQueueSchedule*> schedules;

	utils::Mutex mutex;
	utils::Semaphore semaphore;
};

/*! @brief Per-signal bookkeeping for PsySpace signal distribution.

	One SignalStruct exists per signal PsyType that has listeners in this space.
	Holds the most recent signal message plus the event/mutex pair used to wake
	waiters in PsySpace::waitForSignal().
*/
struct SignalStruct {
	DataMessage* lastSignalMsg; //!< Most recently emitted message for this signal type (owned by the space).
	utils::Event event;         //!< Signalled whenever a new message arrives; wakes waiters.
	utils::Mutex mutex;         //!< Protects lastSignalMsg.
};

/*! @name Processing-statistics indices
	Indices into PsySpace::waitCounters / PsySpace::procCounters, tracking how many
	pool threads are waiting on / processing each kind of work item.
	@{ */
#define PROCMESSAGE		0 //!< Ordinary trigger-message processing.
#define PROCSIGNAL		1 //!< Signal processing.
#define PROCREQUEST		2 //!< Query/request processing.
/*! @} */

/*! @brief The runtime container (one OS process/node context) hosting Psyclone components.

	PsySpace is the flagship embedding object of the CMSDK. Creating a PsySpace and
	calling connect() joins the calling process to a Psyclone system: it attaches to
	(or creates) the node's shared memory via MemoryManager, registers the process,
	and starts the service threads. After start(), the space's thread pool waits for
	trigger messages and dispatches them to the registered crank callbacks through
	each component's PsyAPI handle.

	Ownership/lifetime: the PsySpace owns its MemoryManager, TimeQueue, loaded crank
	libraries and all PsyAPI objects it creates. Destroy the space only after
	shutdown() has completed (see hasShutdown()).

	Thread-safety: the public control methods (connect/start/shutdown/post/emit/
	query) are safe to call from application threads; internal state is protected by
	dedicated mutexes.

	@see PsyAPI for the component-facing API surface.
*/
class PsySpace : public Runnable, public LogReceiver {
	friend class PsyAPI;
public:
	//! Thread entry for the space's main service thread. @see PsySpaceRun()
	friend THREAD_RET THREAD_FUNCTION_CALL PsySpaceRun(THREAD_ARG arg);
	//! Thread entry for pool worker threads. @see PsySpacePoolRun()
	friend THREAD_RET THREAD_FUNCTION_CALL PsySpacePoolRun(THREAD_ARG arg);
	//! Thread entry for continuously running components. @see PsySpaceContinuousRun()
	friend THREAD_RET THREAD_FUNCTION_CALL PsySpaceContinuousRun(THREAD_ARG arg);

	// ***************** Constructors *****************
	// Create and destroy resources

	/*! Create a space (does not yet join a system; call connect()).
		@param name Human-readable process name shown in logs and PsyProbe; NULL for a generated name.
		@param isAdHoc true (default) if this process was started manually rather than launched by a Psyclone node supervisor.
		@param procID Pre-assigned process id, or 0 to be assigned one at connect time.
		@param isLocal true to run in purely local (single-process) mode without joining a networked system.
	*/
	PsySpace(const char* name = NULL, bool isAdHoc = true, uint16 procID = 0, bool isLocal = false);
	/*! Destructor. Releases the MemoryManager, PsyAPIs, libraries and queues.
		@warning Call shutdown() and wait for hasShutdown() before destroying a started space.
	*/
	~PsySpace();

	/*! Join a Psyclone system (attach to the node's shared memory).
		@param systemID Numeric id of the Psyclone system to join; used to locate the shared-memory segment on this node.
		@param isMaster true if this process should act as the system master (creates the shared memory and system structures).
		@param cmdline Optional command line / basedir string forwarded to components (see PsyAPI::getCommandlineBasedir()).
		@return true on success; false if the system could not be created/attached.
		@note Non-master processes may need to wait for the master; see isConnected().
	*/
	bool connect(uint16 systemID, bool isMaster = false, const char* cmdline = NULL);
	/*! Wait until the space is fully connected to the system.
		@param timeoutMS Maximum wait in milliseconds (default 5000).
		@return true if connected within the timeout. Blocking call.
	*/
	bool isConnected(uint32 timeoutMS = 5000);
	/*! Reset the space's runtime state (counters, queues) without disconnecting.
		@return true on success.
	*/
	bool reset();
	/*! Start processing: spin up the thread pool and begin dispatching triggers.
		@param threadCount Initial number of pool worker threads (default 5); the pool auto-grows/shrinks between internal thresholds.
		@return true if the threads were started.
	*/
	bool start(uint16 threadCount = 5);
	/*! Request an orderly shutdown of the space and all its components.
		@return true if the shutdown was initiated.
		@note Asynchronous; poll hasShutdown() to know when it has finished. Cranks
		observe the shutdown through PsyAPI::shouldContinue() turning false, so they
		must poll it regularly for shutdown to complete.
		@code
		space.shutdown();
		while (!space.hasShutdown())
			utils::Sleep(50);       // wait for pool threads and cranks to exit
		// now safe to destroy the space
		@endcode
	*/
	bool shutdown();
	/*! @return true once shutdown() has fully completed and all threads exited. */
	bool hasShutdown();
	/*! @return The process id of this space within the Psyclone system (assigned at connect()). */
	uint16 getID();

	/*! Post a raw message into the system for distribution to subscribers.
		@param msg Message to post; ownership transfers to the system (do not delete or reuse).
		@return true if the message was accepted for delivery.
		@note Components should normally use PsyAPI::postOutputMessage() instead, which applies the component's spec.
	*/
	bool postMessage(DataMessage* msg);

	// ***************** Signals *****************
	/*! Emit a system-wide signal of the given type.

		Signals are latest-value broadcasts: waiters receive the most recent message
		rather than a queued history.
		@param type Signal type to emit.
		@param msg Payload message; ownership transfers to the space.
		@return true if the signal was emitted.
	*/
	bool emitSignal(const PsyType& type, DataMessage* msg);
	/*! Block until a signal of the given type arrives (or has already arrived).
		@param type Signal type to wait for.
		@param timeout Maximum wait in milliseconds.
		@param lastReceivedTime Skip signals emitted at or before this PsyTime timestamp; 0 accepts the latest unread signal.
		@return A copy of the signal message (caller takes ownership), or NULL on timeout. Blocking call.
	*/
	DataMessage* waitForSignal(const PsyType& type, uint32 timeout, uint64 lastReceivedTime = 0);

	/*! Send a query message and wait for its reply.

		This is the synchronous request/reply primitive underlying
		PsyAPI::queryCatalog() and the retrieve() family: the message is delivered to
		the component addressed in its header, and the calling thread blocks until
		that component answers via queryReply() or the timeout expires.

		@code
		DataMessage* q = new DataMessage(PsyAPI::CTRL_QUERY, myCompID, targetCompID, 3000);
		q->setString("Query", "status");
		DataMessage* reply = NULL;
		uint8 status = space.query(q, &reply, 3000);
		delete q;                                      // caller keeps ownership of q
		if (status == QUERY_SUCCESS && reply) {
			// use reply ...
			delete reply;                              // caller owns the reply
		}
		@endcode

		@param msg Query message (addressing determined by its header). The message is
		copied into the request queue; the caller keeps ownership and should delete it
		after the call returns.
		@param result Receives the reply message on success (caller takes ownership); may be NULL-filled on failure.
		@param timeout Maximum wait in milliseconds.
		@return One of the QUERY_* status codes defined in PsyAPI.h (e.g. QUERY_SUCCESS, QUERY_TIMEOUT). Blocking call.
	*/
	uint8 query(DataMessage* msg, DataMessage** result, uint32 timeout);
	/*! Reply to a previously received query.
		@param id Id of the query being answered.
		@param status One of the QUERY_* status codes.
		@param result Reply payload; ownership transfers. May be NULL.
		@return true if the reply was dispatched.
	*/
	bool queryReply(uint32 id, uint8 status, DataMessage* result);

	/*! Look up the numeric component id for a component name.
		@param name Fully qualified component name.
		@return Component id, or 0 if unknown.
	*/
	uint32 getComponentID(const char* name);
//	PsyAPI* getComponentAPI(const char* name);
//	PsyAPI* getComponentAPI(uint32 compID);
//	bool registerComponentCallback(const char* name, CrankFunction func);
//	bool registerComponentCallback(uint32 compID, CrankFunction func);

	/*! Get (or create) the PsyAPI handle for a crank hosted in this space.

		This is how an embedding application drives a component manually: obtain the
		API handle and call PsyAPI::waitForNewMessage() / PsyAPI::postOutputMessage()
		from its own thread instead of registering a callback.
		@param name Fully qualified crank/component name.
		@return The PsyAPI handle (owned by the space; do not delete), or NULL if the name is unknown.
	*/
	PsyAPI* getCrankAPI(const char* name);
	/*! Register a C/C++ entry function for a crank, to be invoked by the thread pool when the component triggers.
		@param name Fully qualified crank/component name from the PsySpec.
		@param func Crank entry function (see CrankFunction).
		@return true if the callback was registered.
	*/
	bool registerCrankCallback(const char* name, CrankFunction func);

	/*! Add a custom PsyProbe view tab for a component (space-level variant of PsyAPI::addPsyProbeCustomView()).
		@param compID Component id.
		@param name Name of the view tab.
		@param templateURL Path/URL of the HTML template; pass "" to remove the view.
		@return true if the view was registered.
	*/
	bool addPsyProbeCustomView(uint32 compID, const char* name, const char* templateURL);

	/*! LogReceiver interface: accept a log entry produced within this space.
		@param entry Log entry; ownership handled by the logging framework.
		@return true if the entry was written/forwarded.
	*/
	bool logEntry(LogEntry* entry);

	//! The process-local memory manager: entry point to the node's shared-memory fabric. Owned by the space.
	MemoryManager* manager;
private:

	std::map<std::string, CrankFunction> internalCranks;

	bool threadPoolDispatch();
	bool runContinuousComponent();
	bool startContinuousComponent(uint32 compID);
	bool setThreadPoolSize(uint16 threadCount);
	CrankFunction loadCrankFromLibrary(const char* crankName, const char* libraryFilename);

	bool pullRemoteComponentData(uint32 compID, uint16 fromNodeID);

	bool run();

	std::string name;
	bool isLocal;
	bool isMaster;
	bool isAdHoc;
	bool finishedShuttingDown;
	uint64 currentInstID;

	uint16 procID;
	uint64 masterCreatedTime;

	uint32 waitCounters[3];
	uint32 procCounters[3];

	uint32 threadPoolWaitLowerThreshold;
	uint32 threadPoolWaitUpperThreshold;
	uint32 threadPoolWaitIncrement;
	uint64 lastThreadPoolCheck;
	uint64 threadPoolCheckInterval;
	utils::Mutex threadPoolMutex;

	TimeQueue* timeQ;

	uint16 threadTarget;

	std::map<uint16, CrankFunction> cranks;
	std::map<uint32, uint16> compCrankIDs;
	std::map<std::string, utils::Library*> libraries;
	std::map<uint32, PsyAPI*> psyAPIs;
	std::map<PsyType, SignalStruct*> signalList;
	utils::Mutex signalsMutex;

	std::map<uint32, uint8> threadPool;
	std::map<uint32, uint32> continuousComponentThreads;
};

//! Main service-thread entry: runs PsySpace::run() for the space passed in @a arg.
THREAD_RET THREAD_FUNCTION_CALL PsySpaceRun(THREAD_ARG arg);
//! Pool worker-thread entry: runs PsySpace::threadPoolDispatch() for the space passed in @a arg.
THREAD_RET THREAD_FUNCTION_CALL PsySpacePoolRun(THREAD_ARG arg);
//! Continuous-component thread entry: runs PsySpace::runContinuousComponent() for the space passed in @a arg.
THREAD_RET THREAD_FUNCTION_CALL PsySpaceContinuousRun(THREAD_ARG arg);

} // namespace cmlabs

#include "PsyAPI.h"
#include "PsyInternal.h"

#endif //_PSYSPACE_H_

