//
//  Action.hpp
//  Meteor
//
//  Created by Cesar Parent on 21/08/2015.
//  Copyright © 2015 Cesar Parent. All rights reserved.
//

#ifndef Action_hpp
#define Action_hpp

#include "../defines.h"
#include "../CPFoundation.h"
#include "../Geometry.h"
#include "Easing.h"
#include <cmath>
#include <functional>
#include <initializer_list>

NS_BEGIN

class Node;

/*!
 * @class           Action
 * @ingroup         GameObjects
 * @brief           An action is an object that defines a behaviour executed by a node in the
 *                  scene graph.
 * @details         Actions are most often used to change a node's appearance or physical
 *                  properties, but can also affect the game in other ways.
 *                  
 *                  You create an action by calling the static class method that suits your need.
 *                  You can then run the action on any subclass of Node displayed in the scene
 *                  graph.
 *
 *                  Some actions have actions as chidren:
 *                  - Action::repeatForever stores a single child action. When the child action
 *                      completes, it is restarted.
 *                  - Action::sequence has multiple child actions. Each action in the
 *                      sequence begins after the previous action ends.
 *
 * @warning         You should not subclass Action. If you require a custom behaviour, you should
 *                  compose actions instead using _repeat_ and _sequence_ actions and create
 *                  runBlock actions.
 */
class Action : public CPObject {
    
public:
    
    // MARK: Static Action Creators
    
    /**
     * @name        Creating Actions That Move Nodes
     * @{
     */
    
    /*!
     * @brief       Creates an action that moves a node relative to its current position.
     * @param       distance    Vector, in pixels, to add to the node's position.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new move action.
     */
    static ARC<Action>
    moveBy(const vec2& distance, float duration, Easing ease = Ease::linear);
    
    /*!
     * @brief       Creates an action that moves a node horizontally.
     * @param       x           The x-value for the node's new position.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new move action.
     */
    static ARC<Action>
    moveToX(float x, float duration, Easing ease = Ease::linear);
    
    /*!
     * @brief       Creates an action that moves a node vertically.
     * @param       y           The y-value for the node's new position.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new move action.
     */
    static ARC<Action>
    moveToY(float y, float duration, Easing ease = Ease::linear);
    
    /*!
     * @brief       Creates an action that moves a node to a new position.
     * @param       destination The coordinates for the node's new position.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new move action.
     */
    static ARC<Action>
    moveTo(const vec2& destination, float duration, Easing ease = Ease::linear);
    
    /*!
     * @brief       Creates an action that moves a node towards another node.
     * @param       destination The node to move towards.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new move action.
     */
    static ARC<Action>
    moveToNode(ARC<Node> destination, float duration, Easing ease = Ease::linear);
    
    /**
     * @}
     * @name        Creating Actions That Rotate Nodes
     * @{
     */
    
    /*!
     * @brief       Creates an action that rotates the node by a relative value.
     * @param       angle       The amount to rotate the node, in radians.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new rotate action.
     */
    static ARC<Action>
    rotateBy(float angle, float duration, Easing ease = Ease::linear);
    
    /*!
     * @brief       Creates an action that rotates the node to an absolute angle.
     * @param       angle       The angle to rotate the node to, in radians.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new rotate action.
     */
    static ARC<Action>
    rotateTo(float angle, float duration, Easing ease = Ease::linear);
    
    /**
     * @}
     * @name        Creating Actions That Change a Node's Scale
     * @{
     */
    
    /*!
     * @brief       Creates an action that adds relative values to the x and y scale values of a node.
     * @param       sx          The amount to add to the node's x scale value.
     * @param       sy          The amount to add to the node's y scale value.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new scale action.
     */
    static ARC<Action>
    scaleBy(float sx, float sy, float duration, Easing ease = Ease::linear);
    
    /*!
     * @brief       Creates an action that changes the x scale value of a node to a new value.
     * @param       sx          The new value for the node's x scale value.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new scale action.
     */
    static ARC<Action>
    scaleXTo(float sx, float duration, Easing ease = Ease::linear);
    
    /*!
     * @brief       Creates an action that changes the y scale value of a node to a new value.
     * @param       sy          The new value for the node's y scale value.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new scale action.
     */
    static ARC<Action>
    scaleYTo(float sy, float duration, Easing ease = Ease::linear);
    
    /**
     * @}
     * @name        Creating Actions That Change a Node's Opacity
     * @{
     */
    
    /*!
     * @brief       Creates an action that adjusts the alpha value of a node by a relative value.
     * @param       alpha       The amount to add to the node's alpha value.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new scale action.
     */
    static ARC<Action>
    fadeAlphaTo(float alpha, float duration, Easing ease = Ease::linear);
    
    /*!
     * @brief       Creates an action that adjusts the alpha value of a node to a new value.
     * @param       alpha       The new value of the node's alpha.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new scale action.
     */
    static ARC<Action>
    fadeAlphaBy(float alpha, float duration, Easing ease = Ease::linear);
    
    /**
     * @}
     * @name        Creating Actions That Animate a SpriteNode
     * @{
     */
    
    static ARC<Action>
    animate(const std::initializer_list<int>& frames, double interval);
    
    /**
     * @}
     * @name        Creating Actions That Combine or Repeat Other Actions
     * @{
     */
    
    /*!
     * @brief       Creates an action that runs a collection of actions sequentially.
     * @param       actions     A braced list of Action objects
     * @return      A new sequence action objects.
     */
    static ARC<Action>
    sequence(const std::initializer_list<ARC<Action>>& actions);
    
    /*!
     * @brief       Creates an action that repeats another action forever.
     * @param       action      The action to execute.
     * @return      A new action object.
     */
    static ARC<Action>
    repeatForever(const ARC<Action>& action);
    
    /*!
     * @brief       Creates an action that waits for a given duration.
     * @param       duration    The duration of the animation in seconds.
     * @return      A new action object.
     */
    static ARC<Action>
    waitForDuration(float duration);
    
    /**
     * @}
     * @name        Playing Sound Effects
     * @{
     */
    
    /*!
     * @brief       Creates an action that plays a sound.
     * @param       soundFile   The name of a sound file.
     * @return      A new action object.
     */
    static ARC<Action>
    playSoundFileNamed(const std::string& soundFile);
    
    /**
     * @}
     * @name        Creating Actions That Run Code
     * @{
     */
    
    /*!
     * @brief       Creates an action that removes the node from its parent.
     * @return      A new action object.
     */
    static ARC<Action>
    removeFromParent();
    
    /*!
     * @brief       Creates an action that executes a block over a duration.
     * @param       block       The block to run.
     * @param       duration    The duration of the action, in seconds.
     * @return      A new action object.
     */
    static ARC<Action>
    customActionWithBlock(std::function<void(Node*, float)> block, float duration);
    
    /*!
     * @brief       Creates an action that executes a block.
     * @param       block       The block to run.
     * @return      A new action object.
     */
    static ARC<Action>
    runBlock(std::function<void()> block);
    
    /**
     * @}
     */
    
    // MARK: -
    // MARK: Action interface.
    
    
    /*!
     * @internal
     * @brief       Creates an action with a given node as target.
     * @param       target      Node the action acts one.
     */
    Action() : _target(nullptr) {};
    
    /*!
     * @internal
     * @brief       Action's destructor.
     */
    virtual ~Action() {};
    
    /*!
     * @internal
     * @brief       Registers a node as the target. This should only be called by the
     *              target node itself.
     */
    virtual void
    registerTarget(Node* target) { _target = target; }
    
    /*!
     * @internal
     * @brief       called once the action has been registered with its target.
     */
    virtual void
    start() {}
    
    /*!
     * @internal
     * @brief       Returns wether the action has finished running.
     * @return      True if the action has finished running, false otherwise.
     */
    virtual bool
    done() const = 0;
    
    /*!
     * @internal
     * @brief       Called by the node running the action every frame.
     * @param       delta       Time elapsed since last frame in seconds.
     */
    virtual void
    update(double delta) = 0;
    
    /*!
     * @internal
     * @brief       Called when an action is stopped, before it is removed from its target.
     */
    virtual void
    stop() {};
    
protected:
    
    Node*       _target;
    
};




NS_END

#endif /* Action_hpp */
