#include <psemek/audio/effect/compressor.hpp>
#include <psemek/audio/detail/smooth.hpp>
#include <psemek/math/math.hpp>
#include <psemek/math/interval.hpp>
#include <psemek/math/contains.hpp>

namespace psemek::audio
{

	namespace
	{

		struct compressor_impl
			: stream
		{
			compressor_impl(stream_ptr stream, float volume_threshold, float strength, float envelope_attack, float envelope_release, float knee)
				: stream_(std::move(stream))
				, volume_threshold_log_(std::log(volume_threshold))
				, strength_(strength)
				, envelope_attack_multiplier_(smoothness_to_multiplier(envelope_attack))
				, envelope_release_multiplier_(smoothness_to_multiplier(envelope_release))
				, knee_range_(math::expand(math::interval<float>::singleton(volume_threshold_log_), std::log(knee) * 0.5f))
			{}

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

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

				for (std::size_t i = 0; i < count; i += 2)
				{
					float v = std::max(std::abs(samples[i]), std::abs(samples[i + 1]));

					float multiplier = (v > envelope_) ? envelope_attack_multiplier_ : envelope_release_multiplier_;

					envelope_ += (v - envelope_) * multiplier;

					float strength = strength_;

					float envelope_log = std::log(envelope_);

					if (math::contains(knee_range_, envelope_log))
						strength *= math::unlerp(knee_range_, envelope_log);

					float log_gain = strength * std::min(0.f, volume_threshold_log_ - envelope_log);

					float gain = std::exp(log_gain);

					samples[i + 0] *= gain;
					samples[i + 1] *= gain;
				}

				return count;
			}

			std::size_t played() const override
			{
				return stream_->played();
			}

		private:
			stream_ptr stream_;
			float volume_threshold_log_;
			float strength_;
			float envelope_attack_multiplier_;
			float envelope_release_multiplier_;
			math::interval<float> knee_range_;
			float envelope_ = 0.f;
		};

	}

	stream_ptr compressor(stream_ptr stream, float volume_threshold, float strength, float envelope_attack, float envelope_release, float knee)
	{
		return std::make_shared<compressor_impl>(std::move(stream), volume_threshold, strength, envelope_attack, envelope_release, knee);
	}

	stream_ptr limiter(stream_ptr stream, float volume_threshold, float envelope_attack, float envelope_release, float knee)
	{
		return compressor(std::move(stream), volume_threshold, 1.f, envelope_attack, envelope_release, knee);
	}

}
