#ifndef _SP_MC_RING_BUFFER_H
#define _SP_MC_RING_BUFFER_H

#include <vector>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <new>
#include <memory>
#include <spinscale/sequenceLock.h>
#include <user/stimulusFrame.h>
#include <user/frameAssemblyDesc.h>
#include <user/senseApiDesc.h>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>

namespace smo {
namespace stim_buff {

/**
 * @brief Single-producer, multi-consumer ring buffer w/per-slot sequence locks
 *
 * A ring buffer that maintains data alignment constraints while providing
 * lock-free read access through per-slot sequence locks. The locks are kept
 * separate from the data to preserve alignment requirements for the input
 * engine.
 */
class SpMcRingBuffer
{
public:
	/**	EXPLANATION:
	 * Constructor initializes the ring buffer with FrameAssemblyDesc.
	 * Allocates frames vector with properly constructed StimulusFrame instances,
	 * each initialized with a SlotDesc from the FrameAssemblyDesc.
	 */
	explicit SpMcRingBuffer(
		const std::shared_ptr<FrameAssemblyDesc> &frameAssemblyDesc_,
		const SmoCallbacks& callbacks,
		cl_mem_flags flags)
	:
	nBuffers(frameAssemblyDesc_ ? frameAssemblyDesc_->slots.size() : 0),
	frameAssemblyDesc(frameAssemblyDesc_),
	slots(nBuffers),  // Default-construct all frames
	producerNextUsableIndex(0)
	{
		if (!frameAssemblyDesc)
		{
			throw std::invalid_argument(std::string(__func__)
				+ ": SpMcRingBuffer: frameAssemblyDesc must not be null");
		}

		if (nBuffers == 0)
		{
			throw std::invalid_argument(std::string(__func__)
				+ ": SpMcRingBuffer: frameAssemblyDesc must have at least one "
				"slot");
		}

		// Re-invoke constructors w/placement new on default-constructed frames
		for (size_t i = 0; i < nBuffers; ++i)
		{
			slots[i].~StimulusFrame();  // Destroy default-constructed object
			new (&slots[i]) StimulusFrame(
				frameAssemblyDesc->slots[i], callbacks, flags, i);
		}
	}

	~SpMcRingBuffer() = default;

	// Non-copyable, non-movable (slots are non-movable)
	SpMcRingBuffer(const SpMcRingBuffer&) = delete;
	SpMcRingBuffer& operator=(const SpMcRingBuffer&) = delete;
	SpMcRingBuffer(SpMcRingBuffer&&) = delete;
	SpMcRingBuffer& operator=(SpMcRingBuffer&&) = delete;

public:
	/**
	 * @brief Get a reference to the StimulusFrame at the specified slot
	 *
	 * @param slotIndex The index of the slot (0-based)
	 * @return Reference to StimulusFrame at the slot
	 * @throws std::out_of_range if slotIndex >= nBuffers
	 */
	StimulusFrame& getDataAtSlot(size_t slotIndex)
	{
		if (slotIndex >= nBuffers)
		{
			throw std::out_of_range(std::string(__func__)
				+ ": SpMcRingBuffer: slotIndex must be < nBuffers");
		}
		return slots[slotIndex];
	}

	sscl::SequenceLock& getSequenceLockAtSlot(size_t slotIndex)
	{
		if (slotIndex >= nBuffers)
		{
			throw std::out_of_range(std::string(__func__)
				+ ": SpMcRingBuffer: slotIndex must be < nBuffers");
		}
		return slots[slotIndex].lock;
	}

	/**
	 * @brief Get the next index to produce into, atomically incrementing it
	 *
	 * Uses sequence lock to perform an emulated fetch_add with modulo nBuffers
	 * applied, ensuring the returned index is always < nBuffers.
	 *
	 * @return The index to produce into (always < nBuffers)
	 */
	size_t getIndexToProduceInto()
	{
		producerNextUsableIndexLock.writeAcquire();
		size_t currentIndex = producerNextUsableIndex;
		size_t nextIndex = (currentIndex + 1) % nBuffers;
		producerNextUsableIndex = nextIndex;
		producerNextUsableIndexLock.writeRelease();
		return currentIndex;
	}

	/**
	 * @brief Abort production by setting the producer index to a specific value
	 *
	 * @param index The index to set (must be < nBuffers)
	 * @throws std::out_of_range if index >= nBuffers
	 */
	void abortProduction(size_t index)
	{
		if (index >= nBuffers)
		{
			throw std::out_of_range(std::string(__func__)
				+ ": SpMcRingBuffer: index must be < nBuffers");
		}
		producerNextUsableIndexLock.writeAcquire();
		producerNextUsableIndex = index;
		producerNextUsableIndexLock.writeRelease();
	}

public:
	// Layout/invariants
	size_t nBuffers;

private:
	// FrameAssemblyDesc describing the memory layout
	std::shared_ptr<FrameAssemblyDesc> frameAssemblyDesc;
	// Frames vector: each frame contains a sequence lock and SlotDesc
	std::vector<StimulusFrame> slots;
	sscl::SequenceLock producerNextUsableIndexLock;
	size_t producerNextUsableIndex;
};

} // namespace stim_buff
} // namespace smo

#endif // _SP_MC_RING_BUFFER_H

