//
//  EmitterNode.hpp
//  Meteor
//
//  Created by Cesar Parent on 01/09/2015.
//  Copyright © 2015 Cesar Parent. All rights reserved.
//

#ifndef EmitterNode_hpp
#define EmitterNode_hpp

#include "../defines.h"
#include "../Graphics.h"
#include "Node.h"
#include "Particle.h"
#include <vector>
#include <string>

NS_BEGIN

class Particle;

/*!
 * @class           EmitterNode
 * @ingroup         GameObjects
 * @brief           A node that creates and renders particles.
 * @details         An <code>EmitterNode</code> object is a node that automatically creates, renders
 *                  and manages small particle sprites. Particle sprites are owned by the emitter
 *                  and are not <code>Node</code> subclasses: you cannot access them directly and
 *                  manipulate them in the same way you would normal Scene Graph objects.
 *                  
 *                  To control the way the effect works, the emitter is edited instead. It provides
 *                  many wasy to control the behavior of the particles, including:
 *
 *                  - The emission rate and the particles' lifetime. You also choose how many
 *                      particles can be alive at once when you create the emitter;
 *                  - The speed, angle, size and randomness with which particles are emitted;
 *                  - The changes applied to particles over their lifetime: the speed at which
 *                      the partices grow or shrink, the gravity vector applied to their motion,
 *                      the color at emission and death…
 */
class EmitterNode : public Node {
public:
    
    /// Defines how particles move when the emitter is moved.
    enum class ParticleMotionType {
        /// @brief  Particles are drawn relative to the emitter, and move with it.
        Grouped,
        /// @brief  Particles ared draw relative to the emitter's parent layer.
        Loose,
    };
    
    /*!
     * @name        Creating and Saving Emitters
     * @{
     */
    
    /*!
     * @brief       Creates a particle emitter with the given texture and particle poolsize.
     * @param       sprite      Particle texture's filename.
     * @param       poolSize    The maximum number of particles alive at any one point. Defaults
     *                          to 512.
     */
    EmitterNode(const std::string& sprite, int poolSize = 512);
    
    /*!
     * @brief       Creates a particle emitter from the data contained in a
     *              <code>DecoderProtocol</code> object
     * @param       coder       Decoder that contains a serialized node.     
     */
    EmitterNode(DecoderProtocol* coder);
    
    /*!
     * @brief       Serializes the emitter to an <code>EncoderProtocol</code> object.
     * @param       coder       Encoder object to serialize to.
     */
    void
    encodeWithCoder(EncoderProtocol* coder) override;
    
    /*!
     * @}
     */
    
    virtual ~EmitterNode();
    
    // MARK: -
    // MARK: Getters
    
    /*!
     * @name        Inspecting the Emitter's general properties
     * @{
     */
    
    /*!
     * @brief       Returns the particles' blending mode.
     * @return      The particles' blending mode.
     */
    TextureProtocol::BlendMode
    blendMode() const { return _texture->blendMode(); }
    
    /*!
     * @brief       Returns the particles' motion type.
     * @return      The particles' motion type.
     */
    ParticleMotionType
    motionType() const { return _motionType; }
    
    /*!
     * @brief       Returns the particles' lifetime.
     * @return      The particles' lifetime.
     */
    float
    lifetime() const { return _lifetime; }
    
    
    /*!
     * @}
     */
    
    /*!
     * @name        Inspecting the Emitter's Emission properties
     * @{
     */
    
    /*!
     * @brief       Returns the emitter's emission rate.
     * @return      The emitter's emission rate.
     */
    int
    emissionRate() const { return _emissionRate; }
    
    /*!
     * @brief       Returns the angle at which particles are emitted.
     * @return      The angle at which particles are emitted.
     */
    float
    emissionAngle() const { return _angle; }
    
    /*!
     * @brief       Returns the speed at which particles are emitted.
     * @return      The speed at which particles are emitted.
     */
    float
    emissionSpeed() const { return _speed; }
    
    /*!
     * @brief       Returns the rotation with which particles are emitted.
     * @return      The rotation with which particles are emitted.
     */
    float
    emissionRotation() const { return _emissionRotation; }
    
    /*!
     * @brief       Returns the particles' scale at emission.
     * @return      The particles' scale at emission.
     */
    float
    emissionScale() const { return _size; }
    
    /*!
     * @brief       Returns the particles' color at emission.
     * @return      The particles' color at emissiond.
     */
    Color
    emissionColor() const { return _startColor; }
    
    /*!
     * @}
     */
    
    /*!
     * @name        Inspecting the particles' evolution properties
     * @{
     */
    
    /*!
     * @brief       Returns the gravity applied to particles.
     * @return      The gravity vector applied to particles.
     */
    vec2
    gravity() const { return _gravity; }
    
    /*!
     * @brief       Returns the particles' scaling speed.
     * @return      The particles' scaling speed.
     */
    float
    scalingSpeed() const { return _sizeSpeed; }
    
    /*!
     * @brief       Returns the particles' rotation speed.
     * @return      The particles' rotation speed.
     */
    float
    rotationSpeed() const { return _rotationSpeed; }
    
    /*!
     * @brief       Returns the particles' color at death.
     * @return      The particles' color at death.
     */
    Color
    endColor() const { return _endColor; }
    
    /*!
     * @brief       Returns the particles' tangential acceleration.
     * @return      The particles' tangential acceleration.
     */
    float
    tangentialAcceleration() const { return _tangentialAccel; }
    
    /*!
     * @brief       Returns the particles' radial acceleration.
     * @return      The particles' radial acceleration.
     */
    float
    radialAcceleration() const { return _radialAccel; }
    
    /*!
     * @}
     */
    
    // MARK: -
    // MARK: Setters
    
    /*!
     * @name        Changing the Emitter's general properties
     * @{
     */
    
    /*!
     * @brief       Sets wether particles are being emitted.
     * @param       emit            Wether particles are emitted.
     */
    void
    setEmit(bool emit) { _emit = emit; }
    
    /*!
     * @brief       Set wether particles should move relative to the emitter or the parent layer.
     * @param       mode            Particles' position mode.
     */
    void
    setMotionType(ParticleMotionType mode) { _motionType = mode; }
    
    /*!
     * @brief       Set the particles' blending mode.
     * @param       blendMode   The particles' blending mode.
     */
    void
    setBlendMode(TextureProtocol::BlendMode blendMode) {
        _texture->setBlendMode(blendMode);
    }
    
    /*!
     * @brief       Set the particle's lifetime and its var.
     * @param       lifetime    Time taken by a particle to fade away.
     * @param       var         Range over which lifetime varies.
     */
    void
    setLifetime(float lifetime, float var = 0) { _lifetime = lifetime; _lifetimeVar = var; }
    
    /*!
     * @brief       Set the color ramp applied to the particles.
     * @param       start       Particles' color at the emission point.
     */
    void
    setColorRamp(const Color& start) { _startColor = start; _endColor = start; }
    
    /*!
     * @brief       Set the color ramp applied to the particles.
     * @param       start       Particles' color at the emission point.
     * @param       end         Particles' color at their death.
     */
    void
    setColorRamp(const Color& start, const Color& end) { _startColor = start; _endColor = end; }
    
    /*!
     * @}
     */
    
    /*!
     * @name        Changing the Emitter's emission properties
     * @{
     */
    
    /*!
     * @brief       Set the number of particles emitted per second.
     * @param       emissionRate    Number of particles emitted per second.
     */
    void
    setEmissionRate(int emissionRate) { _emissionRate = emissionRate; }
    
    /*!
     * @brief       Set the angle at which particles are emitted.
     * @param       angle       Angle at which particles are emitted.
     * @param       var         Range over which angle varies.
     */
    void
    setEmissionAngle(float angle, float var = 0) { _angle = angle; _angleVar = var; }
    
    /*!
     * @brief       Set the variance in the emission's location
     * @param       range       Range over which particles are emitted.
     */
    void
    setEmissionRange(const vec2& range = 0) { _positionVar = range; }
    
    /*!
     * @brief       Set the speed at which particles are emitted.
     * @param       speed       Speed at which particles are emitted.
     * @param       var         Range over which speed varies.
     */
    void
    setEmissionSpeed(float speed, float var = 0) { _speed = speed; _speedVar = var; }
    
    /*!
     * @brief       Set the rotation with which particles are emitted.
     * @param       speed       Rotation with which particles are emitted.
     * @param       var         Range over which rotation varies.
     */
    void
    setEmissionRotation(float rotation, float var = 0) { _emissionRotation = rotation; _emissionRotationVar = var; }
    
    
    /*!
     * @brief       Set the particles's scale when they are emitted.
     * @param       scale       Particles's scale when they are emitted.
     * @param       var         Range over which scale varies.
     */
    void
    setEmissionScale(float scale, float var = 0) { _size = scale; _sizeVar = var; }
    
    /*!
     * @}
     */
    
    /*!
     * @name        Changing the Particles' evolution properties
     * @{
     */
    
    /*!
     * @brief       Set the gravity attraction applied to particles
     * @param       gx          Horizontal component of gravity.
     * @param       gy          Horizontal component of gravity.
     */
    void
    setGravity(float gx, float gy) { setGravity({gx, gy}); }
    
    /*!
     * @brief       Set the gravity attraction applied to particles
     * @param       gravity     Gravity vector.
     */
    void
    setGravity(const vec2& gravity) { _gravity = gravity; }
    
    /*!
     * @brief       Set the speed at which particles scale up or down. A positive speed means
     *              particles grow over time, a negative speed means they shrink.
     * @param       speed       Particles scaling speed.
     * @param       var         Range over which the scaling speed varies.
     */
    void
    setScalingSpeed(float speed, float var = 0) { _sizeSpeed = speed; _sizeSpeedVar = var; }
    
    /*!
     * @brief       Set the particles' rotation speed.
     * @param       speed       Particles' rotation speed.
     * @param       var         range over which the rotation speed varies.
     */
    void
    setRotationSpeed(float speed, float var = 0) { _rotationSpeed = speed; _rotationSpeedVar = var; }
    
    /*!
     * @brief       Set the particles' tangential acceleration.
     * @param       speed       Paarticles' tangential acceleration.
     * @param       var         range over which the acceleration varies.
     */
    void
    setTangentialAcceleration(float accel, float var = 0) { _tangentialAccel = accel; _tangentialAccelVar = var; }
    
    /*!
     * @brief       Set the particles' radial acceleration.
     * @param       speed       Paarticles' radial acceleration.
     * @param       var         range over which the acceleration varies.
     */
    void
    setRadialAcceleration(float accel, float var = 0) { _radialAccel = accel; _radialAccelVar = var; }
    
    /*!
     * @}
     */
    
    // MARK: -
    // MARK: Update and Drawing
    
    /*!
     * @internal
     * @brief       Updates each alive particle, and spawns new ones if needed.
     * @param       delta       Time elapsed since last frame in seconds.
     */
    void
    _update(double delta) override;
    
    /*!
     * @internal
     * @brief       Draws each alive particles on the renderer's current target.
     * @param       renderer    Renderer used for drawing.
     */
    void
    draw(RendererProtocol& renderer) override;
    
private:
    
    /*!
     * @brief       Pulls a new particle from the shared pool and initialises it according
     *              to the emission parameters.
     * @param       position    Position at which the particle should be crated.
     */
    void
    createParticle(const vec2& position);
    
    /*!
     * @brief       Returns a random double value in inside a range of size <code>var</code>,
     *              centred on zero.
     * @param       var         length of the range.
     * @return      A random value between <code>-var/2</code> and <code>var/2</code>
     */
    double
    randomDouble(double var = 1) { return ((double)rand()/(double)RAND_MAX - 0.5) * var; }
    
    ParticleMotionType _motionType;
    
    std::vector<Particle> _pool;
    
    int         _poolSize;
    
    Particle*   _firstAvailable;
    
    vec2        _previousPosition;
    
    // MARK: -
    // MARK: Emitter Properties
    
    int         _emissionRate;
    
    
    bool        _emit;
    
    
    double      _lifetime;
    double      _lifetimeVar;
    
    float       _emissionRotation;
    float       _emissionRotationVar;
    
    float       _angle;
    float       _angleVar;
    
    float       _speed;
    float       _speedVar;
    
    vec2        _gravity;
    
    float       _size;
    float       _sizeVar;
    
    float       _sizeSpeed;
    float       _sizeSpeedVar;
    
    
    float       _rotationSpeed;
    float       _rotationSpeedVar;
    
    float       _tangentialAccel;
    float       _tangentialAccelVar;
    
    float       _radialAccel;
    float       _radialAccelVar;
    
    Color       _startColor;
    Color       _endColor;
    
    
    vec2        _positionVar;
    
    std::string _textureName;
    
    ARC<TextureProtocol> _texture;
    
    float       _emissionAccumulator;
};

NS_END

#endif /* EmitterNode_hpp */
