#include <psemek/audio/effect/pitch.hpp>
#include <psemek/audio/detail/resampler.hpp>

#include <cmath>

namespace psemek::audio
{

	namespace
	{

		struct pitch_control_impl
			: pitch_control
		{
			pitch_control_impl(stream_ptr stream, float ratio, float smoothness)
				: stream_(std::move(stream))
				, resampler_(1.f / ratio, smoothness)
			{}

			// N.B. resampler ratio is in terms of sampling frequency, while
			// pitch control ratio is in terms of sound frequency, which
			// is the opposite

			float pitch() const override
			{
				return 1.f / resampler_.ratio();
			}

			float pitch(float ratio) override
			{
				return 1.f / resampler_.ratio(1.f / ratio);
			}

			float smoothness() const override
			{
				return resampler_.smoothness();
			}

			float smoothness(float value) override
			{
				return resampler_.smoothness(value);
			}

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

			std::size_t read(util::span<float> samples) override
			{
				std::size_t count = 0;

				while (count < samples.size())
				{
					if (resampler_pos_ < resampler_.result().size())
					{
						std::size_t size = std::min(samples.size() - count, resampler_.result().size() - resampler_pos_);
						std::copy(resampler_.result().data() + resampler_pos_, resampler_.result().data() + resampler_pos_ + size, samples.begin() + count);
						count += size;
						resampler_pos_ += size;
						played_ += size;
					}
					else
					{
						std::size_t request_size = std::max<std::size_t>(samples.size(), std::ceil(resampler_.ratio() * samples.size() / 2.f) * 2);
						source_buffer_.resize(request_size);
						auto source_count = stream_->read({source_buffer_.data(), request_size});

						if (source_count == 0)
							break;

						resampler_pos_ = 0;

						resampler_.feed({source_buffer_.data(), source_buffer_.data() + source_count});
					}
				}

				return count;
			}

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

		private:
			stream_ptr stream_;

			std::vector<float> source_buffer_;

			resampler resampler_;
			std::size_t resampler_pos_{0};

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

	}

	std::shared_ptr<pitch_control> pitch(stream_ptr stream, float ratio, float smoothness)
	{
		return std::make_shared<pitch_control_impl>(std::move(stream), ratio, smoothness);
	}

}
