/** \file
 *  Game Develop
 *  2008-2014 Florian Rival (Florian.Rival@gmail.com)
 */
#ifndef RUNTIMEOBJECT_H
#define RUNTIMEOBJECT_H

#include <string>
#include <vector>
#include <map>
#include "GDCpp/RuntimeVariablesContainer.h"
#include "GDCpp/Force.h"
namespace gd { class Automatism; }
namespace gd { class InitialInstance; }
namespace gd { class Object; }
namespace sf { class RenderTarget; }
class Polygon2d;
class RuntimeScene;

/**
 * \brief A RuntimeObject is something displayed on the scene.
 *
 * Games don't directly use this class: Extensions can provide object by deriving from this class, and redefining functions:
 * - An important function is RuntimeObject::Draw. It is called to render the object on the scene. This function take in parameter a reference to the target where render the object.
 * - RuntimeObject must be able to return their size, by redefining RuntimeObject::GetWidth and RuntimeObject::GetHeight
 * - RuntimeObject must be able to return the position where they have precisely drawn ( for example, Sprite can draw its image not exactly at the position of the object, if the origin point was moved ). It must also be able to return the position of their center. See RuntimeObject::GetDrawableX, RuntimeObject::GetDrawableY and RuntimeObject::GetCenterX, RuntimeObject::GetCenterY.
 * - If objects need to load resources ( for example textures at the loading ), it must be done inside the constructor creating the object from a gd::Object.
 * - When objects are placed at the start of the scene, scenes call RuntimeObject::ExtraInitializationFromInitialInstance, passing a gd::InitialInstance object in parameter ( containing information like the position where place the object ). Note that common information were already changed ( Position, angle, layer... ). You just need to setup the object with the information related to your object.
 * - Finally, objects can expose debugging features: RuntimeObject::GetPropertyForDebugger, RuntimeObject::ChangeProperty and RuntimeObject::GetNumberOfProperties
 *
 * See also gd::Object which is used to store the initial objects before they are put on the scene.
 *
 * \see gd::Object
 *
 * \ingroup GameEngine
 */
class GD_API RuntimeObject
{
public:
    /**
     * \brief Construct a RuntimeObject from an object.
     *
     * The default implementation already takes care of setting common properties
     * ( name, type, automatisms... ). Be sure to call the original constructor if you redefine it:
     * \code
     * MyRuntimeObject(RuntimeScene & scene, const gd::Object & object) :
     *     RuntimeObject(scene, object)
     * {
     *     //...
     * }
     * \endcode
     */
    RuntimeObject(RuntimeScene & scene, const gd::Object & object);

    /**
     * \brief Copy constructor. Calls Init().
     */
    RuntimeObject(const RuntimeObject & object) { Init(object); };

    /**
     * \brief Assignment operator. Calls Init().
     */
    RuntimeObject& operator=(const RuntimeObject & object) {if( (this) != &object ) Init(object); return *this; }

    /** \brief Default destructor
     */
    virtual ~RuntimeObject();

    /**
     * \brief Must return a pointer to a copy of the object. A such method is needed to do polymorphic copies.
     *
     * Just redefine this method in your derived object class like this:
     * \code
     * return new MyRuntimeObject(*this);
     * \endcode
     */
    virtual RuntimeObject * Clone() const { return new RuntimeObject(*this);}

    /**
     * \brief Called by RuntimeScene when creating the RuntimeObject from an initial instance.
     *
     * \note The RuntimeScene already takes care of setting common properties ( Position, Angle... )
     * according to the InitialInstance. You only need to initialize extra properties specific to your object.
     */
    virtual bool ExtraInitializationFromInitialInstance(const gd::InitialInstance & position) {return true;}

    /**
     * \brief Draw the object.
     * \param renderTarget The SFML Rendertarget where object must be drawn.
     */
    virtual bool Draw(sf::RenderTarget & renderTarget) {return true;};

    /** \name Object's variables
     * Members functions providing access to the object's variables.
     */
    ///@{

    /**
     * \brief Provide access to variables of the object.
     */
    inline const RuntimeVariablesContainer & GetVariables() const { return objectVariables; }

    /**
     * \brief Provide access to variables of the object.
     */
    inline RuntimeVariablesContainer & GetVariables() { return objectVariables; }

    ///@}


    /** \name Automatism related functions
     * Functions related to automatisms management.
     */
    ///@{
    /**
     * \brief Call each automatism so that they do their work before events
     */
    void DoAutomatismsPreEvents(RuntimeScene & scene);

    /**
     * \brief Call each automatism so that they do their work after the events were runn.
     */
    void DoAutomatismsPostEvents(RuntimeScene & scene);

    /**
     * Only used by GD events generated code
     */
    gd::Automatism* GetAutomatismRawPointer(const std::string & name);

    /**
     * Only used by GD events generated code
     */
    gd::Automatism* GetAutomatismRawPointer(const std::string & name) const;

    /**
     * \brief Return true if the object has the automatism with the specified name.
     */
    virtual bool HasAutomatismNamed(const std::string & name) const { return automatisms.find(name) != automatisms.end(); };
    ///@}

    /**
     * \brief Get the name of the object
     */
    inline const std::string & GetName() const { return name; };

    /**
     * \brief Get the type of the object
     */
    inline const std::string & GetType() const { return type; };

    /**
     * \brief Query the Z order of the object
     */
    inline int GetZOrder() const { return zOrder; }

    /**
     * \brief Change the Z order of the object
     */
    inline void SetZOrder(int zOrder_ ) { zOrder = zOrder_; }

    /**
     * \brief Return if the object is hidden or not
     */
    inline bool IsHidden() const {return hidden;};

    /**
     * \brief Return if the object is visible ( not hidden )
     */
    inline bool IsVisible() const {return !hidden;};

    /**
     * \brief Hide/Show the object
     */
    inline void SetHidden(bool hide = true) {hidden = hide;};

    /**
     * \brief Change the layer of the object
     */
    inline void SetLayer(const std::string & layer_) { layer = layer_;}

    /**
     * \brief Get the layer of the object
     */
    inline const std::string & GetLayer() const { return layer; }

    /**
     * \brief Check if the object is on a layer.
     */
    inline bool IsOnLayer(const std::string & layer_) const { return layer == layer_; }

    /**
     * \brief Get the object hitbox(es)
     * \note Default implementation returns a basic bounding box, according to the object width/height and angle.
     */
    virtual std::vector<Polygon2d> GetHitBoxes() const;

    /**
     * \brief Check collision between two objects using their hitboxes.
     * \note If bounding circles of objects are not colliding, hit boxes are not tested.
     * \param other The other object for collision to be tested against.
     */
    bool IsCollidingWith(RuntimeObject * other);

    /**
     * \brief Called at each frame so as to update internal object's things using time ( Such as animation for a sprite ).
     * \note The default implementation does nothing.
     */
    virtual void UpdateTime(float timeElapsed) {};

    /**
     * \brief Get the width of the object
     */
    virtual float GetWidth() const {return 0;};

    /**
     * \brief Get the height of the object
     */
    virtual float GetHeight() const {return 0;};

    /**
     * \brief Set the new width of the object.
     *
     * The width can be not changed if the object doesn't want/is not designed to.
     */
    virtual void SetWidth(float ) {};

    /**
     * \brief Set the new height of the object.
     *
     * The height can be not changed if the object doesn't want/is not designed to.
     */
    virtual void SetHeight(float ) {};

    /**
     * \brief Get the angle of the object, in degrees.
     *
     * The angle can be not changed if the object doesn't want/is not designed to.
     */
    virtual bool SetAngle(float ) {return false;};

    /**
     * \brief Get the angle of the object, in degrees.
     */
    virtual float GetAngle() const {return 0;};

    /**
     * \brief Get the X coordinate of the object in the layout.
     */
    inline float GetX() const { return X; }

    /**
     * \brief Get the Y coordinate of the object in the layout.
     */
    inline float GetY() const { return Y; }

    /**
     * \brief Change X position of the object.
     * \note This method cannot be redefined: Redefine OnPositionChanged() to do extra work if needed.
     */
    void SetX(float x_) { X = x_; OnPositionChanged(); }

    /**
     * \brief Change Y position of the object.
     * \note This method cannot be redefined: Redefine OnPositionChanged() to do extra work if needed.
     */
    void SetY(float y_) { Y = y_; OnPositionChanged(); }

    /**
     * Object can use this function to do special work
     * when position is changed.
     */
    virtual void OnPositionChanged() {};

    /**
     * \brief Get the real X position where is renderer the object.
     *
     * Most of the time, this will return the same value as GetX().<br>
     * However, some objects may allow the origin point to be moved: In this case, you need to redefine this method.
     */
    virtual float GetDrawableX() const {return GetX();};

    /**
     * \brief Get the real Y position where is renderer the object.
     *
     * Most of the time, this will return the same value as GetY().<br>
     * However, some objects may allow the origin point to be moved: In this case, you need to redefine this method.
     */
    virtual float GetDrawableY() const {return GetY();};

    /**
     * \brief Get the X position of the center, relative to the position returned by GetDrawableX().
     */
    virtual float GetCenterX() const {return GetWidth()/2;};

    /**
     * \brief Get the Y position of the center, relative to the position returned by GetDrawableY().
     */
    virtual float GetCenterY() const {return GetHeight()/2;};

    ///@}

    /** \name Forces
     * Members functions providing access to built-in force system
     * used to move the objects
     */
    ///@{

    Force force5; ///< \deprecated Old custom force used to manage collisions.

    /**
     * Automatically called at each frame so as to update forces applied on the object.
     */
    bool UpdateForce(float ElapsedTime);

    float TotalForceX() const;
    float TotalForceY() const;
    float TotalForceAngle() const;
    float TotalForceLength() const;
    ///@}

    /**
     * \brief Delete all forces applied to the object
     */
    bool ClearForce();

    #if defined(GD_IDE_ONLY)
    /** \name Others IDE related functions
     * Members functions used by the IDE
     */
    ///@{
    /**
     * \brief Called by the debugger so as to get a property value and name.
     *
     * Implementation example:
     * \code
     * if      ( propertyNb == 0 ) {name = _("MyObjectProperty");      value = ToString(GetSomeProperty());}
     * else if ( propertyNb == 1 ) {name = _("AnotherProperty");       value = GetAnotherPropety();}
     * \endcode
     */
    virtual void GetPropertyForDebugger (unsigned int propertyNb, std::string & name, std::string & value) const;

    /**
     * \brief Called by the debugger so as to update a property
     * \return true if property was changed, false if it not possible.
     *
     * Implementation example:
     * \code
     * if      ( propertyNb == 0 ) {return SetSomeProperty(ToFloat(newValue));}
     * else if ( propertyNb == 1 ) {return false;} //Changing property is not allowed: returning false.
     * \endcode
     */
    virtual bool ChangeProperty(unsigned int propertyNb, std::string newValue);

    /**
     * \brief Must return the number of available properties for the debugger.
     */
    virtual unsigned int GetNumberOfProperties() const;
    ///@}
    #endif

    /** \name Functions meant to be used by events generated code
     */
    ///@{
    void DeleteFromScene(RuntimeScene & scene);
    void PutAroundAPosition( float positionX, float positionY, float distance, float angleInDegrees );
    void AddForce( float x, float y, float clearing );
    void AddForceUsingPolarCoordinates( float angle, float length, float clearing );
    void AddForceTowardPosition( float positionX, float positionY, float length, float clearing );
    void AddForceToMoveAround( float positionX, float positionY, float angularVelocity, float distance, float clearing );
    void AddForceTowardObject( RuntimeObject * object, float length, float clearing );
    void AddForceToMoveAroundObject( RuntimeObject * object, float velocity, float length, float clearing );
    void PutAroundObject( RuntimeObject * object, float length, float angleInDegrees );

    static gd::Variable & ReturnVariable(gd::Variable & variable) { return variable; };
    bool VariableExists(const std::string & variable);
    static double GetVariableValue(const gd::Variable & variable) { return variable.GetValue(); };
    static const std::string& GetVariableString(const gd::Variable & variable) { return variable.GetString(); };
    static bool VariableChildExists(const gd::Variable & variable, const std::string & childName);
    static void VariableRemoveChild(gd::Variable & variable, const std::string & childName);
    
    void SetXY( const char* xOperator, float xValue, const char* yOperator, float yValue );

    void Duplicate( RuntimeScene & scene, std::map <std::string, std::vector<RuntimeObject*> *> pickedObjectLists );
    void ActivateAutomatism( const std::string & automatismName, bool activate = true );
    bool AutomatismActivated( const std::string & automatismName );

    bool IsStopped();
    bool TestAngleOfDisplacement( float angle, float tolerance );

    double GetSqDistanceWithObject( RuntimeObject * other );
    double GetDistanceWithObject( RuntimeObject * other );

    void SeparateFromObjects( std::map <std::string, std::vector<RuntimeObject*> *> pickedObjectLists);

    /** \deprecated
     */
    void SeparateObjectsWithoutForces( std::map <std::string, std::vector<RuntimeObject*> *> pickedObjectLists);

    /** \deprecated
     */
    void SeparateObjectsWithForces( std::map <std::string, std::vector<RuntimeObject*> *> pickedObjectLists);
    ///@}

protected:

    std::string                                             name; ///< The full name of the object
    std::string                                             type; ///< Which type is the object. ( To test if we can do something reserved to some objects with it )
    float                                                   X; ///<X position on the scene
    float                                                   Y; ///<Y position on the scene
    int                                                     zOrder; ///<Z order on the scene, to choose if an object is displayed before another object.
    bool                                                    hidden; ///<True to prevent the object from being rendered.
    std::string                                             layer; ///<Name of the layer on which the object is.
    std::map<std::string, gd::Automatism* >                 automatisms; ///<Contains all automatisms of the object. Automatisms are the ownership of the object
    RuntimeVariablesContainer                               objectVariables; ///<List of the variables of the object
    std::vector < Force >                                   forces; ///< Forces applied to the object

    /**
     * \brief Initialize object using another object. Used by copy-ctor and assign-op.
     * \warning Don't forget to update me if members were changed !
     */
    void Init(const RuntimeObject & object);
};

/**
 * As extensions, a function used to delete the object. ( Simply a "delete object;" )
 */
void DestroyBaseRuntimeObject(RuntimeObject * object);

/**
 * As extensions, a function used to create an object. ( Simply a "return new RuntimeObject(name);" )
 */
RuntimeObject * CreateBaseRuntimeObject(RuntimeScene & scene, const gd::Object & object);

#endif // RUNTIMEOBJECT_H
