#include <psemek/audio/combine/mixer.hpp>

#include <memory>
#include <vector>
#include <atomic>
#include <mutex>

namespace psemek::audio
{

	namespace
	{

		struct mixer_impl final
			: mixer
			, std::enable_shared_from_this<mixer_impl>
		{
			channel_ptr add(stream_ptr stream) override;

			std::size_t stream_count() const override
			{
				return stream_count_.load();
			}

			std::size_t read(util::span<float> samples) override;

			std::optional<std::size_t> length() const override
			{
				return std::nullopt;
			}

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

		private:
			std::vector<channel_ptr> channels_;
			std::vector<channel_ptr> alive_channels_;

			std::vector<float> buffer_;

			std::mutex new_channels_mutex_;
			std::vector<channel_ptr> new_channels_;

			std::atomic<std::size_t> played_{0};
			std::atomic<std::size_t> stream_count_{0};
		};

		channel_ptr mixer_impl::add(stream_ptr stream)
		{
			auto result = std::make_shared<channel>(std::move(stream));

			{
				std::lock_guard lock{new_channels_mutex_};
				new_channels_.push_back(result);
			}

			return result;
		}

		std::size_t mixer_impl::read(util::span<float> samples)
		{
			{
				std::vector<channel_ptr> new_channels;
				{
					std::lock_guard lock{new_channels_mutex_};
					new_channels = std::move(new_channels_);
				}
				for (auto & ch : new_channels)
					channels_.push_back(std::move(ch));
			}

			std::fill(samples.begin(), samples.end(), 0.f);

			buffer_.resize(samples.size());

			for (auto & ch : channels_)
			{
				auto stream = ch->stream();
				if (!stream)
					continue;

				auto read = stream->read(buffer_);

				for (auto src = buffer_.data(), dst = samples.begin(); src != buffer_.data() + read; ++src, ++dst)
					*dst += *src;

				if (read < buffer_.size())
				{
					ch->stop();
					continue;
				}

				alive_channels_.push_back(std::move(ch));
			}

			stream_count_ = alive_channels_.size();

			std::swap(channels_, alive_channels_);
			alive_channels_.clear();

			played_.fetch_add(samples.size());

			return samples.size();
		}

	}

	mixer_ptr make_mixer()
	{
		return std::make_shared<mixer_impl>();
	}

	mixer_ptr mix(std::vector<stream_ptr> const & streams)
	{
		auto mixer = make_mixer();
		for (auto const & stream : streams)
			mixer->add(stream);
		return mixer;
	}

}
