#pragma once // Modern, simpler alternative to #ifndef guards

#include <cstdint> // For fixed-width integers like int16_t
#include <stdexcept>
#include <vector>

#include <mpi_fft_api.h>

// Use a namespace for constants to avoid polluting the global namespace
namespace K230_FFT_Defaults {
constexpr int FFT_SHIFT  = 0x555;
constexpr int IFFT_SHIFT = 0xaaa;
constexpr int POINT      = 64;
}

class FFT {
public:
    // Constructor takes a const reference to avoid copying the input vector
    FFT(const std::vector<int16_t>& real_input, int point = K230_FFT_Defaults::POINT, int shift = K230_FFT_Defaults::FFT_SHIFT);

    // Virtual destructor is important for base classes with virtual functions
    virtual ~FFT() = default;

    // The 'run' function is virtual to allow the IFFT class to override it
    virtual std::vector<int16_t> run();

    // Static function, as it doesn't depend on class instance data
    static std::vector<int32_t> calculate_frequencies(int point, int sample_rate);

    // Calculates amplitudes from a given complex data buffer (real and imaginary parts interleaved)
    std::vector<int32_t> calculate_amplitudes(const std::vector<int16_t>& complex_data);

    // Overload to calculate amplitudes from the last run() result
    std::vector<int32_t> calculate_amplitudes();

protected:
    // Store input data by value to ensure its lifetime
    std::vector<int16_t> m_input_real;
    // The output buffer for real and imaginary parts (e.g., [r0,r1,...,i0,i1,...])
    std::vector<int16_t> m_output_buffer;

    int m_point;
    int m_shift;

private:
    // A private helper function is safer than a macro
    void check_point(int point);
};

class IFFT : public FFT {
public:
    // The IFFT can be initialized with separate real and imaginary parts
    IFFT(const std::vector<int16_t>& real_input, const std::vector<int16_t>& imag_input, int point = K230_FFT_Defaults::POINT,
         int shift = K230_FFT_Defaults::IFFT_SHIFT);

    // Override the base class's run method
    std::vector<int16_t> run() override;

private:
    // Store imaginary input separately
    std::vector<int16_t> m_input_imag;
};
