#pragma once

#include "MayaFlux/Nodes/Generators/Generator.hpp"

#include "MayaFlux/Transitive/Memory/RingBuffer.hpp"

namespace MayaFlux::Nodes::Generator {

/**
 * @enum PolynomialMode
 * @brief Defines how the polynomial function processes input values
 */
enum class PolynomialMode : uint8_t {
    DIRECT, ///< Evaluates f(x) where x is the current phase/input
    RECURSIVE, ///< Evaluates using current and previous outputs: y[n] = f(y[n-1], y[n-2], ...)
    FEEDFORWARD ///< Evaluates using current and previous inputs: y[n] = f(x[n], x[n-1], ...)
};

class MAYAFLUX_API PolynomialContext : public NodeContext {
public:
    /**
     * @brief Constructs a PolynomialContext
     * @param value Current output value
     * @param mode Current polynomial mode
     * @param buffer_size Size of the input/output buffers
     * @param input_buffer Current input buffer contents
     * @param output_buffer Current output buffer contents
     * @param coefficients Current polynomial coefficients
     */
    PolynomialContext(double value,
        PolynomialMode mode,
        size_t buffer_size,
        std::span<double> input_buffer,
        std::span<double> output_buffer,
        const std::vector<double>& coefficients)
        : NodeContext(value)
        , m_mode(mode)
        , m_buffer_size(buffer_size)
        , m_input_buffer(input_buffer)
        , m_output_buffer(output_buffer)
        , m_coefficients(coefficients)
    {
    }

    /**
     * @brief Gets the current polynomial mode
     * @return Current polynomial mode
     */
    [[nodiscard]] PolynomialMode get_mode() const { return m_mode; }

    /**
     * @brief Gets the buffer size
     * @return Current buffer size
     */
    [[nodiscard]] size_t get_buffer_size() const { return m_buffer_size; }

    /**
     * @brief Gets the input buffer
     * @return Reference to the input buffer
     */
    [[nodiscard]] std::span<double> get_input_buffer() const { return m_input_buffer; }

    /**
     * @brief Gets the output buffer
     * @return Reference to the output buffer
     */
    [[nodiscard]] std::span<double> get_output_buffer() const { return m_output_buffer; }

    /**
     * @brief Gets the polynomial coefficients
     * @return Reference to the coefficient vector
     */
    [[nodiscard]] const std::vector<double>& get_coefficients() const { return m_coefficients; }

private:
    PolynomialMode m_mode; ///< Current processing mode
    size_t m_buffer_size; ///< Size of the buffers
    std::span<double> m_input_buffer; ///< Copy of input buffer
    std::span<double> m_output_buffer; ///< Copy of output buffer
    const std::vector<double>& m_coefficients; ///< Copy of polynomial coefficients

    friend class Polynomial;
};

class MAYAFLUX_API PolynomialContextGpu
    : public PolynomialContext,
      public GpuVectorData {
public:
    PolynomialContextGpu(
        double value,
        PolynomialMode mode,
        size_t buffer_size,
        std::span<double> input_buffer,
        std::span<double> output_buffer,
        const std::vector<double>& coefficients,
        std::span<const float> gpu_data)
        : PolynomialContext(value, mode, buffer_size, input_buffer, output_buffer, coefficients)
        , GpuVectorData(gpu_data)
    {
    }

    friend class Polynomial;

    std::vector<float> gpu_float_buffer;
};

/**
 * @class Polynomial
 * @brief Generator that produces values based on polynomial functions
 *
 * The Polynomial generator creates a signal based on mathematical functions
 * that can operate in different modes:
 * - Direct mode: evaluates f(x) where x is the current phase or input
 * - Recursive mode: evaluates using current and previous outputs
 * - Feedforward mode: evaluates using current and previous inputs
 *
 * This flexible approach allows implementing various types of polynomial
 * equations, difference equations, and recurrence relations.
 */
class MAYAFLUX_API Polynomial : public Generator {
public:
    /**
     * @brief Function type for direct polynomial evaluation
     *
     * Takes a single input value and returns the computed output.
     */
    using DirectFunction = std::function<double(double)>;

    /**
     * @brief Function type for recursive/feedforward polynomial evaluation
     *
     * Takes a reference to a buffer of values and returns the computed output.
     * For recursive mode, the buffer contains previous outputs.
     * For feedforward mode, the buffer contains current and previous inputs.
     */
    using BufferFunction = std::function<double(std::span<double>)>;

    /**
     * @class PolynomialContext
     * @brief Context object for polynomial node callbacks
     *
     * Extends the base NodeContext with polynomial-specific information
     * such as mode, buffer contents, and coefficients.
     */
    ;

    /**
     * @brief Constructs a Polynomial generator in direct mode with coefficient-based definition
     * @param coefficients Vector of polynomial coefficients (highest power first)
     *
     * Creates a polynomial generator that evaluates a standard polynomial function
     * defined by the provided coefficients. The coefficients are ordered from
     * highest power to lowest (e.g., [2, 3, 1] represents 2x² + 3x + 1).
     */
    explicit Polynomial(const std::vector<double>& coefficients);

    /**
     * @brief Constructs a Polynomial generator in direct mode with a custom function
     * @param function Custom function that maps input to output value
     *
     * Creates a polynomial generator that evaluates a custom mathematical function
     * provided by the user.
     */
    explicit Polynomial(DirectFunction function);

    /**
     * @brief Constructs a Polynomial generator in recursive or feedforward mode
     * @param function Function that processes a buffer of values
     * @param mode Processing mode (RECURSIVE or FEEDFORWARD)
     * @param buffer_size Number of previous values to maintain
     *
     * Creates a polynomial generator that evaluates using current and previous values.
     * In recursive mode, the buffer contains previous outputs.
     * In feedforward mode, the buffer contains current and previous inputs.
     */
    Polynomial(BufferFunction function, PolynomialMode mode, size_t buffer_size);

    ~Polynomial() override = default;

    /**
     * @brief Processes a single sample
     * @param input Input value
     * @return The next sample value from the polynomial function
     *
     * Computes the next output value based on the polynomial function and current mode.
     */
    double process_sample(double input = 0.) override;

    /**
     * @brief Processes multiple samples at once
     * @param num_samples Number of samples to generate
     * @return Vector of generated samples
     *
     * This method is more efficient than calling process_sample() repeatedly
     * when generating multiple samples at once.
     */
    std::vector<double> process_batch(unsigned int num_samples) override;

    /**
     * @brief Resets the generator to its initial state
     *
     * Clears the internal buffers and resets to initial conditions.
     */
    void reset();

    /**
     * @brief Sets the polynomial coefficients (for direct mode)
     * @param coefficients Vector of polynomial coefficients (highest power first)
     *
     * Updates the polynomial function to use the specified coefficients.
     * This allows dynamically changing the polynomial during operation.
     */
    void set_coefficients(const std::vector<double>& coefficients);

    /**
     * @brief Sets a custom direct function
     * @param function Custom function that maps input to output value
     *
     * Updates the generator to use the specified custom function.
     */
    void set_direct_function(DirectFunction function);

    /**
     * @brief Sets a custom buffer function
     * @param function Function that processes a buffer of values
     * @param mode Processing mode (RECURSIVE or FEEDFORWARD)
     * @param buffer_size Number of previous values to maintain
     *
     * Updates the generator to use the specified buffer function and mode.
     */
    void set_buffer_function(BufferFunction function, PolynomialMode mode, size_t buffer_size);

    /**
     * @brief Sets initial conditions for recursive mode
     * @param initial_values Vector of initial output values
     *
     * Sets the initial values for the output buffer in recursive mode.
     * The values are ordered from newest to oldest.
     */
    void set_initial_conditions(const std::vector<double>& initial_values);

    /**
     * @brief Creates a polynomial function from coefficients
     * @param coefficients Vector of polynomial coefficients (highest power first)
     * @return Function that evaluates the polynomial
     *
     * Generates a function that evaluates the polynomial defined by the
     * specified coefficients.
     * The cofficients are ordered from highest power to lowest.
     */
    DirectFunction create_polynomial_function(const std::vector<double>& coefficients);

    /**
     * @brief Sets the input node to generate polynomial values from
     * @param input_node Node providing the input values
     *
     * Configures the node to receive input from another node
     */
    inline void set_input_node(const std::shared_ptr<Node>& input_node) { m_input_node = input_node; }

    /**
     * @brief Gets the current polynomial mode
     * @return Current polynomial mode
     */
    [[nodiscard]] PolynomialMode get_mode() const { return m_mode; }

    /**
     * @brief Gets the buffer size
     * @return Current buffer size
     */
    [[nodiscard]] size_t get_buffer_size() const { return m_buffer_size; }

    /**
     * @brief Gets the polynomial coefficients
     * @return Reference to the coefficient vector
     */
    [[nodiscard]] const std::vector<double>& get_coefficients() const { return m_coefficients; }

    /**
     * @brief Gets the input buffer
     * @return Reference to the input buffer
     */
    [[nodiscard]] std::span<double> get_input_buffer() { return m_history.linearized_view(); }

    /**
     * @brief Gets the output buffer
     * @return Reference to the output buffer
     */
    [[nodiscard]] std::span<double> get_output_buffer() { return m_history.linearized_view(); }

    /**
     * @brief Sets the scaling factor for the output values
     */
    inline void set_amplitude(double amplitude) override { m_scale_factor = amplitude; }

    /**
     * @brief Gets the current amplitude scaling factor
     * @return Current amplitude scaling factor
     *
     * This method retrieves the scaling factor applied to the output values,
     * which controls the overall amplitude of the generated signal.
     */
    [[nodiscard]] inline double get_amplitude() const override { return m_scale_factor; }

    void save_state() override;
    void restore_state() override;

    /**
     * @brief Uses an external buffer context for processing
     * @param buffer_view Span representing the external buffer
     *
     * Configures the generator to use an external buffer context
     * for its internal processing, allowing integration with
     * external data sources.
     */
    void set_buffer_context(std::span<double> buffer_view)
    {
        m_external_buffer_context = buffer_view;
        m_use_external_context = true;
    }

    /**
     * @brief Clear external buffer context, resume internal accumulation
     */
    inline void clear_buffer_context()
    {
        m_use_external_context = false;
        m_external_buffer_context = {};
    }

    [[nodiscard]] inline bool using_external_context() const
    {
        return m_use_external_context;
    }

    /**
     * @brief Retrieves the last created context object
     * @return Reference to the last PolynomialContext object
     *
     * This method provides access to the most recent PolynomialContext object
     * created by the polynomial generator. This context contains information
     * about the generator's state at the time of the last output generation.
     */
    NodeContext& get_last_context() override;

    void set_gpu_compatible(bool compatible) override
    {
        Node::set_gpu_compatible(compatible);
        if (compatible) {
            m_node_capability |= NodeCapability::VECTOR;
        } else {
            m_node_capability &= ~NodeCapability::VECTOR;
        }
    }

    /**
     * @brief Retrieves the current modulators connected to this node
     * @return Vector of pairs containing the modulator role and the corresponding node
     */
    [[nodiscard]] std::vector<std::pair<ModulatorRole, std::shared_ptr<Node>>> get_modulators() const override;

protected:
    /**
     * @brief Updates the context object with the current node state
     * @param value The current sample value
     */
    void update_context(double value) override;

private:
    /**
     * @brief Converts coefficient vector to a polynomial function
     * @param coefficients Vector of polynomial coefficients (highest power first)
     * @return Function that evaluates the polynomial
     *
     * Creates a function that evaluates the polynomial defined by the
     * specified coefficients.
     */
    // DirectFunction create_polynomial_function(const std::vector<double>& coefficients);

    PolynomialMode m_mode; ///< Current processing mode
    DirectFunction m_direct_function; ///< Function for direct mode
    BufferFunction m_buffer_function; ///< Function for recursive/feedforward mode
    std::vector<double> m_coefficients; ///< Polynomial coefficients (if using coefficient-based definition)
    std::span<double> m_external_buffer_context; ///< View into external buffer

    Memory::HistoryBuffer<double> m_history; ///< Ring buffer for input/output history
    std::vector<double> m_linear_view; ///< Linearized view of history for easy access

    std::vector<double> m_saved_history_state; ///< Saved state of the history buffer

    size_t m_buffer_size {}; ///< Maximum size of the buffers
    double m_scale_factor; ///< Scaling factor for output
    std::shared_ptr<Node> m_input_node; ///< Input node for processing
    size_t m_current_buffer_position {}; // Where we are in the external buffer

    double m_saved_last_output {};
    bool m_use_external_context {}; // Whether to use it

    PolynomialContext m_context;
    PolynomialContextGpu m_context_gpu;

    std::span<double> external_context_view(double input);
};

} // namespace MayaFlux::Nodes::Generator
