#pragma once
#include "util/cortex_math.hh"
#include "util/math.hh"
#include <array>
#include <cstdint>
#include <type_traits>

template<typename SampleType, int UsedBits = sizeof(SampleType) * 8, int NumChannels = 2>
struct AudioFrame {
	std::array<SampleType, NumChannels> chan;

public:
	static constexpr inline unsigned kSampleTypeBits = sizeof(SampleType) * 8;
	static constexpr inline unsigned kMaxValue = MathTools::ipow(2, UsedBits - 1);
	static constexpr inline float kOutScaling = static_cast<float>(kMaxValue);

	SampleType sign_extend_chan(unsigned chan_num) const {
		return sign_extend(chan[chan_num]);
	}

	float scale_input_chan(unsigned chan_num) const {
		return scaleInput(chan[chan_num]);
	}

	void set_scaled_output(unsigned chan_num, float val) {
		chan[chan_num] = scaleOutput(val);
	}

	static constexpr float scaleInput(SampleType val) {
		return sign_extend(val) / kOutScaling;
	}

	static constexpr SampleType scaleOutput(float val) {

		if constexpr (std::is_signed_v<SampleType>) {

			if constexpr (kSampleTypeBits > UsedBits) {
				auto v = static_cast<SampleType>(val * kOutScaling);
				return MathTools::signed_saturate(v, 24);
			} else {
				const float v = MathTools::constrain(val, -1.f, (kOutScaling - 1.f) / kOutScaling);
				return static_cast<SampleType>(v * kOutScaling);
			}

		} else {
			const float v = MathTools::constrain(val * 0.5f + 0.5f, 0.f, 1.0f);
			return v * (MathTools::ipow(2, UsedBits) - 1);
		}
	}

	static constexpr SampleType sign_extend(const SampleType &v) noexcept {
		static_assert((sizeof(SampleType) * 8u) >= UsedBits, "SampleType is smaller than the specified width");
		if constexpr ((sizeof(SampleType) * 8u) == UsedBits)
			return v;
		else {
			using S = struct {
				signed Val : UsedBits;
			};
			return reinterpret_cast<const S *>(&v)->Val;
		}
	}
};
