/*
  ==============================================================================

    CircularBuffer.h
    Created: 4 Nov 2025 6:20:15pm
    Author:  mickl

  ==============================================================================
*/

#pragma once
#include <JuceHeader.h>

class CircularBuffer
{
public:
	CircularBuffer(int numChannels, int numSamples)
		: buffer(data, (size_t)numChannels, (size_t)numSamples)
	{
	}

	template <typename T>
	void push(dsp::AudioBlock<T> b)
	{
		jassert(b.getNumChannels() == buffer.getNumChannels());

		const auto trimmed = b.getSubBlock(b.getNumSamples()
			- std::min(b.getNumSamples(), buffer.getNumSamples()));

		const auto bufferLength = (int64)buffer.getNumSamples();

		for (auto samplesRemaining = (int64)trimmed.getNumSamples(); samplesRemaining > 0;)
		{
			const auto writeOffset = writeIx % bufferLength;
			const auto numSamplesToWrite = std::min(samplesRemaining, bufferLength - writeOffset);

			auto destSubBlock = buffer.getSubBlock((size_t)writeOffset, (size_t)numSamplesToWrite);
			const auto sourceSubBlock = trimmed.getSubBlock(trimmed.getNumSamples() - (size_t)samplesRemaining,
				(size_t)numSamplesToWrite);

			destSubBlock.copyFrom(sourceSubBlock);

			samplesRemaining -= numSamplesToWrite;
			writeIx += numSamplesToWrite;
		}
	}

	template <typename T>
	void push(Span<T> s)
	{
		auto* ptr = s.begin();
		dsp::AudioBlock<T> b(&ptr, 1, s.size());
		push(b);
	}

	void read(int64 readIx, dsp::AudioBlock<float> output) const
	{
		const auto numChannelsToUse = std::min(buffer.getNumChannels(), output.getNumChannels());

		jassert(output.getNumChannels() == buffer.getNumChannels());

		const auto bufferLength = (int64)buffer.getNumSamples();

		for (auto outputOffset = (size_t)0; outputOffset < output.getNumSamples();)
		{
			const auto inputOffset = (size_t)((readIx + (int64)outputOffset) % bufferLength);
			const auto numSamplesToRead = std::min(output.getNumSamples() - outputOffset,
				(size_t)bufferLength - inputOffset);

			auto destSubBlock = output.getSubBlock(outputOffset, numSamplesToRead)
				.getSubsetChannelBlock(0, numChannelsToUse);

			destSubBlock.copyFrom(buffer.getSubBlock(inputOffset, numSamplesToRead)
				.getSubsetChannelBlock(0, numChannelsToUse));

			outputOffset += numSamplesToRead;
		}
	}

	int64 getWriteIndex() const noexcept { return writeIx; }

private:
	HeapBlock<char> data;
	dsp::AudioBlock<float> buffer;
	int64 writeIx = 0;
};