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

#ifndef TimedActions_hpp
#define TimedActions_hpp

#include "Action.h"
#include <functional>
#include "Easing.h"

NS_BEGIN


/*!
 * @internal
 * @brief           Timedaction defines an action that takes a given amount of time to execute.
 */
class TimedAction : public Action {
    
public:
    
    /*!
     * @internal
     * @brief       Creates a timed action with a given duration.
     * @param       duration        Action's duration.
     */
    TimedAction(float duration);
    
    /*!
     * @internal
     * @brief       Action destructor.
     */
    virtual ~TimedAction() {};
    
    virtual void
    start() { _elapsed = 0; }
    
    /*!
     * @internal
     * @brief       Method called by the target node, once per frame.
     * @param       delta       Time elapsed since last frame, in seconds.
     */
    virtual void
    update(double delta);
    
    /*!
     * @internal
     * @brief       Returns the progress of the animation, between 0 and 1.
     *
     */
    virtual double
    progress() const { return easing(_elapsed / _duration); }
    
    /*!
     * @internal
     * @brief
     */
    virtual bool
    done() const { return (_elapsed >= _duration); };
    
    
    /*!
     * @internal
     */
    Easing      easing;
    
protected:
    
    double      _duration;
    double      _elapsed;
    
};


class ActionMoveToNode : public TimedAction {
public:
    
    ActionMoveToNode(ARC<Node> destination, double duration);
    
    void
    start() override;
    
    void
    update(double delta) override;
    
protected:
    
    ARC<Node> _destination;
    
};


class ActionRun : public Action {
public:
    
    ActionRun(std::function<void()> block, const std::string& name = "") :  _ran(false), _block(block) {}
    
    bool
    done() const override { return _ran; }
    
    virtual void
    start() override { _ran = false; }
    
    void
    update(double delta) override {
        if(!_ran) {
            _block();
            _ran = true;
        }
    }
private:
    
    bool                _ran;
    
    std::function<void()> _block;
};


class CustomAction : public TimedAction {
    
public:
    
    CustomAction(std::function<void(Node*, float)> function, float duration) :
    TimedAction(duration),
    _block(function) {}
    
    void
    update(double delta) override {
        _block(_target, progress());
    }
    
private:
    
    std::function<void(Node*, float)> _block;
    
};


/*!
 * @internal
 * @brief           Tween is a basic action that interpolates between to values of a property.
 *                  Subclasses must implement get() and set(), which allow the action to query
 *                  and edit the property the tween acts on.
 */
template<typename T>
class ActionTweenTo : public TimedAction {
public:
    
    /*!
     * @internal
     * @brief       Creates a tween that animates the target node's property to a given value
     *              over the given duration.
     * @param       to          Target value of the property.
     * @param       duration    Action's duration.
     */
    ActionTweenTo(T to, double duration) : TimedAction(duration), _to(to) {}
    
    virtual ~ActionTweenTo() {};
    
    /*!
     * @internal
     * @brief       Called when the action starts being ran by the target.
     */
    virtual void
    start() override {
        CPNullCheck(_target);
        TimedAction::start();
        _from = get();
        _offset = _to - _from;
    }
    
    /*!
     * @internal
     * @brief       Called every frame, animates the target's property.
     */
    virtual void
    update(double delta) override {
        TimedAction::update(delta);
        set(_from + progress()*_offset);
    }
    
protected:
    
    /*!
     * @internal
     * @brief       Get must return the target's property that the tween acts on.
     * @return      The target's property value.
     */
    virtual T
    get() = 0;
    
    /*!
     * @internal
     * @brief       Set must set the target's property that the tween acts on.
     * @param       value       The target's property new va
     */
    virtual void
    set(const T& value) = 0;
    
    T           _from, _to;
    T           _offset;
};


/*!
 * @internal
 * @brief           Tween is a basic action that interpolates between to values of a property.
 *                  Subclasses must implement get() and set(), which allow the action to query
 *                  and edit the property the tween acts on.
 */
template<typename T>
class ActionTweenBy : public TimedAction {
public:
    
    /*!
     * @internal
     * @brief       Creates a tween that animates the target node's property to a given value
     *              over the given duration.
     * @param       to          Target value of the property.
     * @param       duration    Action's duration.
     */
    ActionTweenBy(T by, double duration) : TimedAction(duration), _offset(by) {}
    
    virtual ~ActionTweenBy() {};
    
    /*!
     * @internal
     * @brief       Called when the action starts being ran by the target.
     */
    virtual void
    start() override {
        CPNullCheck(_target);
        TimedAction::start();
    }
    
    /*!
     * @internal
     * @brief       Called every frame, animates the target's property.
     */
    virtual void
    update(double delta) override {
        float prev = progress();
        TimedAction::update(delta);
        float now = progress();
        set(get() + _offset * (now-prev));
    }
    
protected:
    
    /*!
     * @internal
     * @brief       Get must return the target's property that the tween acts on.
     * @return      The target's property value.
     */
    virtual T
    get() = 0;
    
    /*!
     * @internal
     * @brief       Set must set the target's property that the tween acts on.
     * @param       value       The target's property new va
     */
    virtual void
    set(const T& value) = 0;
    
    T           _offset;
};

/*!
 * @internal
 * @brief           TweenBlock is an action that calls a lambda function each frame until the
 *                  action's duration has elapsed.
 */
class ActionTweenBlock : public TimedAction {
public:
    
    ActionTweenBlock(std::function<void(Node*, float)> block, float duration);
    
    virtual ~ActionTweenBlock() {}
    
    void update(double delta) override;
    
private:
    
    std::function<void(Node*, float)> _block;
    
};


NS_END

#endif /* TimedActions_hpp */
