#pragma once

#include "Generator.hpp"

namespace MayaFlux::Nodes::Generator {

/**
 * @class Impulse
 * @brief Impulse generator node
 *
 * The Impulse class generates a single spike followed by zeros, which is a fundamental
 * signal used in digital signal processing. It produces a value of 1.0 (or specified amplitude)
 * at specified intervals, and 0.0 elsewhere.
 *
 * Key features:
 * - Configurable frequency, amplitude, and DC offset
 * - Support for frequency modulation (changing impulse rate)
 * - Support for amplitude modulation (changing impulse height)
 * - Automatic registration with the node graph manager (optional)
 * - Precise timing control for impulse generation
 *
 * Impulse generators are used extensively in audio and signal processing for:
 * - Triggering events at specific intervals
 * - Testing system responses (impulse response)
 * - Creating click trains and metronome-like signals
 * - Serving as the basis for more complex event-based generators
 * - Synchronization signals for other generators
 *
 * The implementation uses a phase accumulation approach similar to other oscillators
 * but only outputs a non-zero value at the beginning of each cycle.
 */
class MAYAFLUX_API Impulse : public Generator, public std::enable_shared_from_this<Impulse> {
public:
    /**
     * @brief Basic constructor with fixed parameters
     * @param frequency Impulse repetition rate in Hz (default: 1Hz, one impulse per second)
     * @param amplitude Impulse amplitude (default: 1.0)
     * @param offset DC offset added to the output (default: 0.0)
     *
     * Creates an impulse generator with fixed frequency and amplitude.
     */
    Impulse(float frequency = 1, double amplitude = 1, float offset = 0);

    /**
     * @brief Constructor with frequency modulation
     * @param frequency_modulator Node that modulates the frequency
     * @param frequency Base frequency in Hz (default: 1Hz)
     * @param amplitude Impulse amplitude (default: 1.0)
     * @param offset DC offset added to the output (default: 0.0)
     *
     * Creates an impulse generator with frequency modulation, where the actual frequency
     * is the base frequency plus the output of the modulator node.
     */
    Impulse(const std::shared_ptr<Node>& frequency_modulator, float frequency = 1, double amplitude = 1, float offset = 0);

    /**
     * @brief Constructor with amplitude modulation
     * @param frequency Impulse repetition rate in Hz
     * @param amplitude_modulator Node that modulates the amplitude
     * @param amplitude Base amplitude (default: 1.0)
     * @param offset DC offset added to the output (default: 0.0)
     *
     * Creates an impulse generator with amplitude modulation, where the actual amplitude
     * is the base amplitude multiplied by the output of the modulator node.
     */
    Impulse(float frequency, const std::shared_ptr<Node>& amplitude_modulator, double amplitude = 1, float offset = 0);

    /**
     * @brief Constructor with both frequency and amplitude modulation
     * @param frequency_modulator Node that modulates the frequency
     * @param amplitude_modulator Node that modulates the amplitude
     * @param frequency Base frequency in Hz (default: 1Hz)
     * @param amplitude Base amplitude (default: 1.0)
     * @param offset DC offset added to the output (default: 0.0)
     *
     * Creates an impulse generator with both frequency and amplitude modulation,
     * enabling complex timing and amplitude control.
     */
    Impulse(const std::shared_ptr<Node>& frequency_modulator, const std::shared_ptr<Node>& amplitude_modulator,
        float frequency = 1, double amplitude = 1, float offset = 0);

    /**
     * @brief Virtual destructor
     */
    ~Impulse() override = default;

    /**
     * @brief Processes a single input sample and generates an impulse sample
     * @param input Input sample (used for modulation when modulators are connected)
     * @return Generated impulse sample (1.0 at the start of each cycle, 0.0 elsewhere)
     *
     * This method advances the generator's phase and computes the next
     * sample of the impulse train, applying any modulation from connected nodes.
     */
    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 impulse 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 Prints a visual representation of the impulse train
     *
     * Outputs a text-based graph of the impulse pattern over time,
     * useful for debugging and visualization.
     */
    inline void printGraph() override { }

    /**
     * @brief Prints the current parameters of the impulse generator
     *
     * Outputs the current frequency, amplitude, offset, and modulation
     * settings of the generator.
     */
    inline void printCurrent() override { }

    /**
     * @brief Sets the generator's frequency
     * @param frequency New frequency in Hz
     *
     * Updates the generator's frequency, which controls how often impulses occur.
     */
    void set_frequency(float frequency) override;

    /**
     * @brief Gets the current base frequency
     * @return Current frequency in Hz
     */
    inline float get_frequency() const { return m_frequency; }

    /**
     * @brief Sets all basic parameters at once
     * @param frequency New frequency in Hz
     * @param amplitude New amplitude
     * @param offset New DC offset
     *
     * This is more efficient than setting parameters individually
     * when multiple parameters need to be changed.
     */
    inline void set_params(float frequency, double amplitude, float offset)
    {
        m_amplitude = amplitude;
        m_offset = offset;
        set_frequency(frequency);
    }

    /**
     * @brief Sets a node to modulate the generator's frequency
     * @param modulator Node that will modulate the frequency
     *
     * The modulator's output is added to the base frequency,
     * enabling dynamic control of impulse timing.
     */
    void set_frequency_modulator(const std::shared_ptr<Node>& modulator);

    /**
     * @brief Sets a node to modulate the generator's amplitude
     * @param modulator Node that will modulate the amplitude
     *
     * The modulator's output is multiplied with the base amplitude,
     * enabling dynamic control of impulse height.
     */
    void set_amplitude_modulator(const std::shared_ptr<Node>& modulator);

    /**
     * @brief Removes all modulation connections
     *
     * After calling this method, the generator will use only its
     * base frequency and amplitude without any modulation.
     */
    void clear_modulators();

    /**
     * @brief Resets the generator's phase and parameters
     * @param frequency New frequency in Hz (default: 1Hz)
     * @param amplitude New amplitude (default: 1.0)
     * @param offset New DC offset (default: 0)
     *
     * This method resets the generator's internal state and parameters,
     * effectively restarting it from the beginning of its cycle.
     */
    void reset(float frequency = 1, float amplitude = 1.0F, float offset = 0);

    /**
     * @brief Registers a callback for every impulse
     * @param callback Function to call when an impulse occurs
     *
     * This method allows external components to monitor or react to
     * every impulse produced by the generator. The callback
     * receives a GeneratorContext containing the generated value and
     * generator parameters like frequency, amplitude, and phase.
     */
    void on_impulse(const NodeHook& callback);

    /**
     * @brief Removes a previously registered callback
     * @param callback The callback function to remove
     * @return True if the callback was found and removed, false otherwise
     *
     * Unregisters a callback previously added with on_tick(), stopping
     * it from receiving further notifications about generated samples.
     */
    bool remove_hook(const NodeHook& callback) override;

    /**
     * @brief Removes all registered callbacks
     *
     * Clears all standard and conditional callbacks, effectively
     * disconnecting all external components from this generator's
     * notification system. Useful when reconfiguring the processing
     * graph or shutting down components.
     */
    inline void remove_all_hooks() override
    {
        m_callbacks.clear();
        m_conditional_callbacks.clear();
        m_impulse_callbacks.clear();
    }

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

protected:
    /**
     * @brief Notifies all registered callbacks about a new sample
     * @param value The newly generated sample
     *
     * This method is called internally whenever a new sample is generated,
     * creating the appropriate context and invoking all registered callbacks
     * that should receive notification about this sample.
     */
    void notify_tick(double value) override;

private:
    /**
     * @brief Phase increment per sample
     *
     * This value determines how much the phase advances with each sample,
     * controlling the generator's frequency.
     */
    double m_phase_inc {};

    /**
     * @brief DC offset added to the output
     */
    float m_offset;

    /**
     * @brief Node that modulates the frequency
     */
    std::shared_ptr<Node> m_frequency_modulator;

    /**
     * @brief Node that modulates the amplitude
     */
    std::shared_ptr<Node> m_amplitude_modulator;

    /**
     * @brief Updates the phase increment based on a new frequency
     * @param frequency New frequency in Hz
     *
     * This method calculates the phase increment needed to produce
     * impulses at the specified frequency at the current sample rate.
     */
    void update_phase_increment(double frequency);

    /**
     * @brief Collection of impulse-specific callback functions
     */
    std::vector<NodeHook> m_impulse_callbacks;

    bool m_impulse_occurred;

    double m_saved_phase {};
    float m_saved_frequency {};
    float m_saved_offset {};
    double m_saved_phase_inc {};
    double m_saved_last_output {};
};
}
