//
//  Node.h
//  Meteor
//
//  Created by Cesar Parent on 13/07/2015.
//  Copyright (c) 2015 Cesar Parent. All rights reserved.
//

#ifndef __Meteor__Node__
#define __Meteor__Node__

#include <string>
#include <unordered_map>
#include "../CPFoundation.h"
#include "../Geometry.h"
#include "../Filesystem.h"

NS_BEGIN

class RendererProtocol;
class Layer;
class Action;

/*!
 * @class           Node
 * @ingroup         GameObjects
 * @brief           The base class from which all drawable Meteor content inherits.
 *
 * @details         The base object in a Meteor scene graph. The Node class doesn't draw any 
 *                  content. It provides an API used as a baseline for other node classes to
 *                  use in order to draw to the screen. Any visual element in a Meteor game must be
 *                  a subclass of Node.
 *
 *                  <h2>Node's basics</h2>
 *
 *                  Nodes are udpated and drawn each frame as soon as they are part
 *                  of the scene graph. A Node's visual properties can be changed using
 *                  <code>setPositon()</code>, <code>setRotation()</code> and
 *                  <code>setScale()</code> (<code>setXScale()</code>/<code>setYScale()</code>).
 *                  
 *                  As an abstraction from the frame-by-frame update model, Node and its subclasses
 *                  can have <code>Actions</code> attached to them, that can change their
 *                  properties over time.
 *
 *                  <h2>Subclassing Node</h2>
 *
 *                  Subclasses of Node must implement at least <code>draw()</code> to render
 *                  once added in a scene graph. <code>_draw()</code> and <code>_update()</code>
 *                  should not be overridden if not absolutely necessary. If they are, it is
 *                  mandatory to call Node's implementation using <code>_update()</code> and
 *                  <code>_draw()</code> calls.
 */
class Node : public CPObject, public CodingProtocol {
public:
    
    bool        __debug;
    
    /*!
     * @name        Creating and Saving Nodes
     * @{
     */
    
    /*!
     * @brief       Creates a new node.
     */
    Node();
    
    /*!
     * @brief       Creates a node from the data contained in a <code>DecoderProtocol</code> object.
     * @param       coder       Decoder that contains a serialized node.
     * @see         encodeWithCoder(EncoderProtocol*)
     */
    Node(DecoderProtocol* coder);
    
    /*!
     * @brief       Serializes the Node to an encoder object.
     * @param       coder       Encoder used for serialization.
     * @see         Node(DecoderProtocol* protocol)
     */
    virtual void
    encodeWithCoder(EncoderProtocol* coder) override;
    
    /*!
     * @}
     */
    
    virtual ~Node();
    
    // MARK: -
    // MARK: Getters
    
    /*!
     * @name        Inspecting the Node's physical properties
     * @{
     */
    
    /*!
     * @brief       Returns the node's visibility.
     * @return      The node's visibility.
     * @see         setAlpha(float)
     */
    bool
    hidden() const { return _hidden; }
    
    /*!
     * @brief       Returns the node's opacity.
     * @return      The node's opacity.
     * @see         setAlpha(float)
     */
    float
    alpha() const { return _alpha; }
    
    /*!
     * @brief       Returns the node's parent
     * @return      The node's parent, or Null if the node is orphan.
     * @see         setParent(Layer*)
     */
    Layer*
    parent() const { return _parent; }
    
    /*!
     * @brief       Returns the node's rotation in radians.
     * @return      The node's rotation in radians.
     * @see         setRotation(float)
     */
    float
    rotation() const { return _rotation; }
    
    /*!
     * @brief       Returns the node's horizontal scale.
     * @return      The node's horizontal scale.
     * @see         setXScale(float)
     * @see         setScale(float)
     */
    float
    xScale() const { return _scale.x; }
    
    /*!
     * @brief       Returns the node's vertical scale.
     * @return      The node's vertical scale.
     * @see         setYScale(float)
     * @see         setScale(float)
     */
    float
    yScale() const { return _scale.y; }
    
    /*!
     * @brief       Returns the node's position in its parent's coordinate system.
     * @return      The node's position in its parent's coordinate system.
     * @see         setPositon(const vec2&)
     */
    vec2
    position() const { return _position; }
    
    /*!
     * @brief       Returns the node's Z position.
     * @return      The node's Z position.
     * @see         setZPosition(int)
     */
    int
    zPosition() const { return _zPosition; }
    
    /*!
     * @brief       Returns the node-to-screen transformation matrix.
     * @return      the node-to-screen transformation matrix.
     * @see         localTransform()
     */
    virtual mat33
    absoluteTransform() const;
    
    /*!
     * @brief       Returns the node-to-parent transformation matrix.
     * @return      Node-to-parent transformation matrix.
     * @see         absoluteTransform()
     */
    virtual mat33
    localTransform() const;
    
    /*!
     * @brief       Invalidates cached transformations matrices.
     * @see         localTransform()
     * @see         absoluteTransform()
     */
    virtual void
    invalidateCachedTransform() const;
    
    /*!
     * @}
     */
    
    // MARK: -
    // MARK: Setters
    
    /*!
     * @name        Changing the Node's physical properties
     * @{
     */
    
    /*!
     * @brief       Sets the node's visibility.
     * @param       hidden      Node's visibility.
     * @see         hidden()
     */
    void
    setHidden(bool hidden) { _hidden = hidden; }
    
    /*!
     * @brief       Sets the node's opacity.
     * @param       alpha       Node's opacity.
     * @see         alpha()
     */
    void
    setAlpha(float alpha) { _alpha = clamp(alpha, 0.f, 1.f); }
    
    /*!
     * @brief       Sets the node's position in its parent's coordinate system.
     * @param       position    Node's position.
     * @see         position()
     */
    void
    setPosition(const vec2& position);
    
    /*!
     * @brief       Sets the node's position in its parent's coordinate system.
     * @param       x           Node's horizontal position.
     * @param       y           Node's vertical positon.
     * @see         position()
     */
    void
    setPosition(float x, float y);
    
    /*!
     * @brief       Sets the node's horizontal positon.
     * @param       x           Node's horizontal position.
     * @see         setPosition(vec2)
     * @see         position()
     */
    void
    setX(float x);
    
    /*!
     * @brief       Sets the node's vertical position.
     * @param       y           Node's horizontal position.
     * @see         setPosition(const vec2&)
     * @see         position()
     */
    void
    setY(float y);
    
    /*!
     * @brief       Sets the node's rotation in its parent's coordinate system.
     * @param       rotation    Node's rotation.
     * @see         rotation()
     */
    void
    setRotation(float rotation);
    
    /*!
     * @brief       Sets the node's horizontal scale.
     * @param       scale       Node's horizontal scale.
     * @see         setScale(float)
     * @see         xScale()
     * @see         yScale()
     */
    void
    setXScale(float scale);
    
    /*!
     * @brief       Sets the node's vertical scale.
     * @param       scale       Node's vertical scale.
     * @see         setScale(float)
     * @see         xScale()
     * @see         yScale()
     */
    void
    setYScale(float scale);
    
    /*!
     * @brief       Sets the node's scale on both axes.
     * @param       scale       Node's scale.
     * @see         xScale()
     * @see         yScale()
     */
    void
    setScale(float scale);
    
    /*!
     * @brief       Sets the node's z position.
     * @details     A node's Z position is only taken into account if its parent Layer is sorting
     *              its children.
     * @param       position    Node's Z position.
     * @see         zPosition()
     */
    void
    setZPosition(int position) { _zPosition = position; }
    
    /*!
     * @}
     */
    
    // MARK: -
    // MARK: Scene Graph handling.
    
    /*!
     * @name        Working with the Scene Graph
     * @{
     */
    
    /*!
     * @brief       Called when the node has been added to a layer.
     */
    virtual void
    didEnterParent() {};
    
    /*!
     * @brief       Called when the node will be removed from its parent
     */
    virtual void
    willMoveFromParent() {};
    
    /*!
     * @brief       Returns the node's name.
     * @return      The node's name.
     * @see         setName(std::string)
     */
    std::string
    name() const { return _name; }
    
    /*!
     * @brief       Sets the node's name, used to look it up in the scene graph.
     * @param       name        Node's name.
     * @see         name()
     */
    void
    setName(const std::string& name) {  _name = name; }
    
    /*!
     * @brief       Returns the node's removal tag.
     * @return      The node's removal tag.
     * @see         removeFromParent()
     */
    bool
    removed() const { return _removed; }
    
    
    /*!
     * @brief       Remove the node from its parent.
     * @see         setParent(Layer*)
     * @see         removeFromParent()
     */
    void
    removeFromParent();
    
    /*!
     * @brief       Sets the node's parent.
     * @param       parent      Node's new parent layer.
     * @see         removeFromParent()
     */
    void
    setParent(Layer* parent);
    
    /*!
     * @}
     */
    
    // MARK: -
    // MARK: Action Handling
    
    /*!
     * @name        Working with Actions
     * @{
     */
    
    /*!
     * @brief       Add an action to the node with a given key.
     * @param       action      The action to run on the node.
     * @param       key         A unique string identifying the action.
     * @see         stopAction(const std::string&)
     */
    void
    runAction(ARC<Action> action, const std::string& key);
    
    /*!
     * @brief       Returns a pointer to the action corresponding to the given key.
     * @param       key         Key of the action to look up.
     * @return      Pointer to the action for <code>key</code>, or <code>nullptr</code> if it does
     *              not exist.
     * @see         runAction(ARC<Action>, const std::string&)
     */
    ARC<Action>
    actionForKey(const std::string& key);
    
    /*!
     * @brief       Stops the action with the given key if it is running.
     * @param       key         Key of the action to stop.
     * @see         runAction(ARC<Action>, const std::string&)
     */
    void
    stopAction(const std::string& key);
    
    /*!
     * @brief       Stops all actions running on the node and deletes them.
     * @see         runAction(ARC<Action>, const std::string&)
     */
    void
    stopAllActions();
    
    /*!
     * @}
     */
    
    // MARK: -
    // MARK: Updating and Rendering
    
    /*!
     * @name        Updating and drawing the Node
     * @{
     */
    
    /*!
     * @brief       User update method, called once per frame.
     *
     * @details     Override this to implement game logic for a Node subclass.
     * @param       delta       Time elapsed since the last frame in seconds.
     */
    virtual void
    update(double delta) {};
    
    /*!
     * @brief       Fixed-time update method, called at fixed intervals.
     *
     * @details     Override this to implement game logic that requires fixed time steps (physics).
     */
    virtual void
    fixedUpdate() {};
    
    /*!
     * @internal
     * @brief       Draws the node on the screen.
     * @param       renderer    Renderer Protocol-compliant object used to draw the node.
     */
    virtual void
    draw(RendererProtocol& renderer) = 0;
    
    /*!
     * @}
     */
    
    
    /*!
     * @internal
     * @brief       Updates the node's actions once per frame.
     * @param       delta       Time elapsed since the last frame in seconds.
     */
    virtual void
    _update(double delta);
    
    /*!
     * @internal
     * @brief       Prepares the renderer to draw the node and calls <code>draw()</code>.
     *
     * @discussion  Override this to implement any logic that should happen before the node itself
     *              is drawn (draw children for example).
     * @param       renderer    Renderer Protocol-compliant object used to draw the node.
     */
    virtual void
    _draw(RendererProtocol& renderer);
    
protected:
    
    /*!
     * @brief       Wether the node should be removed from its parent
     */
    bool            _removed;
    
    /*!
     * @brief       Node's location relative to its parent's origin.
     */
    vec2            _position;
    
    /*!
     * @brief       Node's rotation relative to its parent.
     */
    float           _rotation;
    
    /*!
     * @brief       Node's scale
     */
    vec2           _scale;
    
    /*!
     * @brief       Opacity of the Node.
     */
    float           _alpha;
    
    /*!
     * @brief       A name used to find the node in the scene graph.
     */
    std::string     _name;
    
    /*!
     * @brief       Determines wether the node is drawn at all.
     */
    bool            _hidden;
    
    mutable mat33   _localTransform;
    
    
    mutable bool    _localTransformDirty;
    
    /*!
     * @brief       Cached version of the absolute 2d Transformation.
     */
    mutable mat33   _absoluteTransform;
    
    /*!
     * @brief       Absolute transform's dirty flag. Set if the Absolute transform needs to be
     *              re calculated.
     */
    mutable bool    _absoluteTransformDirty;
    
    /*!
     * @brief       Node's parent in the scene graph. The node is only rendered if the root node
     *              of its current graph is a scene. If the node is not in a scene graph, _parent
     *              will be a nullptr.
     */
    Layer*          _parent;
    
    /*!
     * @brief       Z position of the node. Will only be used if the parent has z-buffering enabled.
     */
    int             _zPosition;
    
private:
    
    /*!
     * @brief       Holds the node's currently running actions
     */
    std::unordered_map<std::string, ARC<Action>> _actions;
    
    /*!
     * @brief       Keeps track of the amount of <code>fixedUpdate()</code> calls required.
     */
    float           _timeAccumulator;
    
};

NS_END

#endif /* defined(__Meteor__Node__) */
