#include <psemek/audio/track.hpp>
#include <psemek/util/exception.hpp>

#include <atomic>

namespace psemek::audio
{

	namespace
	{

		struct data_holder
		{
			std::vector<float> storage;
			util::blob blob_storage;
			util::span<float const> samples;

			data_holder(util::span<float const> samples)
				: samples(samples)
			{}

			data_holder(std::vector<float> storage)
				: storage(std::move(storage))
				, samples(this->storage)
			{}

			data_holder(util::blob storage)
				: blob_storage(std::move(storage))
				, samples(util::cast<float const>(util::span<char const>(blob_storage)))
			{}
		};

		struct raw_stream_impl
			: stream
		{
			raw_stream_impl(std::shared_ptr<data_holder> data_holder)
				: data_holder_(std::move(data_holder))
			{}

			std::optional<std::size_t> length() const override
			{
				return data_holder_->samples.size();
			}

			std::size_t read(util::span<float> samples) override
			{
				auto played = played_.load();

				auto count = std::min(data_holder_->samples.size() - played, samples.size());
				std::copy(data_holder_->samples.begin() + played, data_holder_->samples.begin() + played + count, samples.begin());
				played_.fetch_add(count);
				return count;
			}

			std::size_t played() const override
			{
				return played_;
			}


		private:
			std::shared_ptr<data_holder> data_holder_;
			std::atomic<std::size_t> played_{0};
		};

		struct raw_track_impl
			: track
		{
			raw_track_impl(std::shared_ptr<data_holder> data_holder)
				: data_holder_(std::move(data_holder))
			{}

			stream_ptr stream() const override
			{
				return std::make_shared<raw_stream_impl>(data_holder_);
			}

			std::optional<std::size_t> length() const override
			{
				return data_holder_->samples.size();
			}

		private:
			std::shared_ptr<data_holder> data_holder_;
		};

	}

	track_ptr load_raw(util::span<float const> samples)
	{
		if ((samples.size() % 2) != 0)
			throw util::exception("Sample count must be even");
		return std::make_shared<raw_track_impl>(std::make_shared<data_holder>(samples));
	}

	track_ptr load_raw(util::blob samples)
	{
		if ((samples.size() % 4) != 0)
			throw util::exception("Byte count must be a multiple of 4");
		if ((samples.size() % 8) != 0)
			throw util::exception("Sample count must be even");
		return std::make_shared<raw_track_impl>(std::make_shared<data_holder>(std::move(samples)));
	}

	track_ptr load_raw(std::vector<float> samples)
	{
		if ((samples.size() % 2) != 0)
			throw util::exception("Sample count must be even");
		return std::make_shared<raw_track_impl>(std::make_shared<data_holder>(std::move(samples)));
	}

}
