#ifndef _ATTACHMENT_SUPPORT_STIMULUS_FRAME_H
#define _ATTACHMENT_SUPPORT_STIMULUS_FRAME_H

#include <cstdint>
#include <memory>
#include <spinscale/sequenceLock.h>
#include <user/frameAssemblyDesc.h>
#include <user/compute.h>
#include <user/senseApiDesc.h>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>

namespace smo {
namespace stim_buff {

/**	EXPLANATION:
 * A simultaneity stamp is a timestamp that is used to determine whether two
 * stimulus frames occured simultaneously. Its purpose is adamantly *NOT* to
 * record or denote the "absolute" time of the stimulus frames. I cannot stress
 * this enough. The simultaneity stamp is NOT used to record "when" the stimulus
 * frame occured. It is used *SOLELY* to record that two or more stimulus frames
 * occured at the same time.
 *
 * The SMO has absolutely no notion of "absolute" time. It only has a notion of
 * simultaneity among stimulus frames. Any notions of "absolute" time are built
 * up consciously and volitionally by the running mind, and not baked into the
 * underlying software (i.e: Salmanoff).
 *
 * We need about 36 bits of unique simultaneity per year, assuming that we only
 * expect to capture 1000 stim frames per second. 1000 is a lot of stim frames
 * per second. If we use a 64 bit integer, that leaves us with 2^28 years
 * before our simultaneity stamps roll over. That's 256 million years.
 *
 * The calculation we used to arrive at 36 bits is as follows:
 *	hex(86400 * 400 * 1000) = 0x80befc000
 *	* 86400 = seconds per day.
 *	* 400 = days per year.
 *	* 1000 = stim frames per second.
 * As you can see, our extremely cautious calculation resulted in 36 bits.
 * If we use a UUID (128 bits), we can basically be fairly sure we won't
 * rollover for ...aeons. Now the question is: should we use a UUID or a 64 bit
 * integer?
 *
 * It's important to note that simultaneity stamps are not used in all mental
 * entities. They're only used in raw chronomena recordings, and possibly
 * also in artificed memory chronomena. Among the artificed chronomena, their
 * simultaneity lifetime is usually self-contained. Only the raw, observed
 * chronomena have to retain a lifetime that is basically "the person's
 * lifespan" (though not even necessarily that long).
 * 
 * It may not even necessarily need to be lifespan-unique because the purpose of
 * simultaneity stamps is to denote simultaneity among the stim frames that are
 * __actually stored__ in the mind's memories. So if we forgot all stim frames
 * with simultaneity stamps that older than say, 1000, then we can re-use all
 * the simultaneity stamps that are numerically less than 1000. So there's some
 * dynamic recycling, and we can prolly keep track of the oldest simultaneity
 * stamp that we are currently using.
 *
 * Also, since simultaneity stamps are *NOT* used to record "when" the stimulus
 * frame occured, we can also periodically run a reclaiming daemon process on
 * our stored memories, which will try to defragment the simultaneity stamps
 * in use by currently stored chronomena. Or we can silently mutate the
 * simultaneity stamps of chronomena when committing them to backing storage;
 * as well as when loading them from backing storage.
 */
typedef uint64_t SimultaneityStamp;

class StimulusFrame
{
public:
	/**	EXPLANATION:
	 * Default constructor creates uninitialized frame.
	 * Must be properly initialized using placement new with the parameterized constructor.
	 */
	StimulusFrame() = default;

	StimulusFrame(
		const FrameAssemblyDesc::SlotDesc& slotDesc_,
		const SmoCallbacks& callbacks,
		cl_mem_flags flags,
		size_t ringBufferIndex_)
	: slotDesc(slotDesc_),
	ringBufferIndex(ringBufferIndex_)
	{
		if (!callbacks.ComputeManager_createUseHostPtrBuffer)
		{
			throw std::runtime_error(std::string(__func__)
				+ ": StimulusFrame: ComputeManager_createUseHostPtrBuffer "
				"callback is null");
		}

		clBuffer = callbacks.ComputeManager_createUseHostPtrBuffer(
			slotDesc.vaddr, slotDesc.nBytes, flags);

		if (!clBuffer)
		{
			throw std::runtime_error(std::string(__func__)
				+ ": StimulusFrame: failed to create clBuffer");
		}

//		std::cout << __func__ << ": StimulusFrame: created clBuffer with size " << slotDesc.nBytes << " bytes @ " << (const void*)slotDesc.vaddr << std::endl;
	}

	~StimulusFrame() = default;

	// Non-copyable, movable
	StimulusFrame(const StimulusFrame&) = delete;
	StimulusFrame& operator=(const StimulusFrame&) = delete;
	StimulusFrame(StimulusFrame&&) = default;
	StimulusFrame& operator=(StimulusFrame&&) = default;

public:
	sscl::SequenceLock lock;
	SimultaneityStamp simultaneityStamp;
	FrameAssemblyDesc::SlotDesc slotDesc;
	std::shared_ptr<smo::compute::ClBuffer> clBuffer;
	size_t ringBufferIndex;
};

} // namespace stim_buff
} // namespace smo

#endif // _ATTACHMENT_SUPPORT_STIMULUS_FRAME_H
