#pragma once

#include <functional>
#include <queue>

#include "Array.h"
#include "Mat3.h"
#include "Point.h"
#include "Rect2.h"

namespace Framework
{
    typedef Vec2<float> Vertex;
    class Image;

    struct WorldInfo
    {
        float airResistance;
        bool hasSize;
        bool circular;
        Point size;
    };

    class Object2D : public virtual ReferenceCounter
    {
    protected:
        std::queue<std::function<void()>> actions;
        Vertex position;
        Vertex speed;
        float rSpeed;
        float rotation;
        float size;
        bool collision;

    public:
        DLLEXPORT Object2D();
        DLLEXPORT virtual ~Object2D();
        //! Passes a void function pointer to an action that should be executed
        //! once by the main thread. (Happens after tick)
        DLLEXPORT void postAction(std::function<void()> action);
        //! Adds a thrust in the propagation direction of the explosion to
        //! the object's movement \param worldPos The position of the explosion
        //! origin \param intensity The intensity of the explosion
        DLLEXPORT void explosion(Vertex worldPos, float intensity);
        //! Applies an impulse to the object that affects both speed and
        //! rotation speed \param start The start position of the impulse
        //! in the world \param speed The speed of the impulse in the world
        //! \param strength Strength of the impact
        DLLEXPORT virtual void impuls(
            Vertex start, Vertex speed, float strength = 1.f);
        //! Sets the speed of the object in the world
        //! \param speed Number of coordinates traveled per second
        DLLEXPORT void setSpeed(Vertex speed);
        //! Sets the speed of the object in the world
        //! \param x Number of x coordinates traveled per second
        //! \param y Number of y coordinates traveled per second
        DLLEXPORT void setSpeed(float x, float y);
        //! Sets the position of the object in the world
        //! \param pos The position in world coordinates
        DLLEXPORT void setPosition(Vertex pos);
        //! Sets the position of the object in the world
        //! \param x The X position in world coordinates
        //! \param y The Y position in world coordinates
        DLLEXPORT void setPosition(float x, float y);
        //! Sets the rotation speed in radians per second
        //! \param ds The new rotation speed
        DLLEXPORT void setRotationSpeed(float ds);
        //! Sets the counter-clockwise rotation of the model
        //! \param rotation The angle in radians
        DLLEXPORT void setRotation(float rotation);
        //! Adds to the current rotation angle
        //! \param rotation The angle in radians to add
        DLLEXPORT void addRotation(float rotation);
        //! Sets the scaling of the model
        //! \param size The scaling factor
        DLLEXPORT void setSize(float size);
        //! Adds a value to the scaling
        //! \param size The value to add to the scaling
        DLLEXPORT void addSize(float size);
        //! Sets whether other objects can collide with this object
        //! \param handle 0 if no collisions exist
        DLLEXPORT void setCollision(bool handle);
        //! Checks if a collision with another object exists and adjusts
        //! the speeds of both objects accordingly
        DLLEXPORT virtual bool handleCollision(Object2D* obj);
        //! Processes elapsed time and updates the position and rotation
        //! of the object in the world \param zeit The elapsed time in seconds
        DLLEXPORT virtual bool tick(const WorldInfo& info, double zeit);
        //! Draws the object into an image
        //! \param kamMat The camera matrix that converts a point from
        //! world coordinates to screen coordinates \param zRObj The image
        //! to draw into \param ignoreTransparentFlag if 1, collisions with
        //! transparent polygons are also considered
        virtual void render(
            Mat3<float>& kamMat, Image& zRObj, const char* ignoreTransparentFlag)
            = 0;
        //! Returns whether a point is inside the object
        //! \param p The point
        //! \param ignoreTransparentFlag if 1, collisions with transparent
        //! polygons are also considered
        DLLEXPORT virtual bool isPointInside(
            Vertex p, bool ignoreTransparentFlag = 0) const;
        //! Checks whether a line is inside the object
        //! \param a The start point of the line
        //! \param b The end point of the line
        //! \param ignoreTransparentFlag if 1, collisions with transparent
        //! polygons are also considered
        DLLEXPORT virtual bool isLineInside(
            Vertex a, Vertex b, bool ignoreTransparentFlag = 0) const;
        //! Checks whether the object intersects with another
        //! \param zObj A pointer to the other object without increased reference
        //! counter \param sp A pointer to a point where the intersection
        //! point is stored \param end 0 if all corners of both objects should be
        //! checked. 1 if only the points of this model should be searched
        //! in the other
        DLLEXPORT virtual bool isModelInside(const Object2D* zObj,
            Vertex* sp = 0,
            bool end = 0,
            bool ignoreTransparent = 0) const;
        //! Returns a matrix that converts a point from object coordinates
        //! to screen coordinates
        DLLEXPORT Mat3<float> getObjectMatrix() const;
        //! Returns a matrix that converts a point from screen coordinates
        //! to object coordinates
        DLLEXPORT Mat3<float> getInverseObjectMatrix() const;
        //! Converts a point from world coordinates to object coordinates
        //! \param worldPos The position of the point in the world
        DLLEXPORT Vertex getObjectPos(Vertex worldPos) const;
        //! Converts a direction from world coordinates to object coordinates
        //! \param worldDir The direction in world coordinates
        DLLEXPORT Vertex getObjectDir(Vertex worldDir) const;
        //! Converts a point from object coordinates to world coordinates
        //! \param worldPos The position of the point in object coordinates
        DLLEXPORT Vertex getWorldPos(Vertex objectPos) const;
        //! Converts a direction from object coordinates to world coordinates
        //! \param worldDir The direction in object coordinates
        DLLEXPORT Vertex getWorldDir(Vertex objectDir) const;
        //! Returns the speed of the object
        DLLEXPORT Vertex getSpeed() const;
        //! Returns the position of the object
        DLLEXPORT Vertex getPosition() const;
        //! Returns the rotation speed of the object
        DLLEXPORT float getRotationSpeed() const;
        //! Returns the rotation of the object
        DLLEXPORT float getRotation() const;
        //! Returns the scaling of the object
        DLLEXPORT float getSize() const;
        //! Returns a bounding box containing all points of the object
        //! (in world coordinates)
        DLLEXPORT virtual Rect2<float> getBoundingBox() const = 0;
        //! Determines the hit point of a ray emitted from pos in the
        //! direction dir. \param pos The support vector of the line
        //! \param dir The direction vector of the line
        //! \param hitPoint A reference to the variable where the
        //! intersection point should be stored \return 1 if an
        //! intersection point exists
        DLLEXPORT virtual bool calcHitPoint(
            Vertex pos, Vertex dir, Vertex& hitpoint) const;
        //! Determines the area of the object perpendicular to the
        //! movement vector
        DLLEXPORT virtual float getAirResistance() const;
        //! Returns the mass of the object
        DLLEXPORT virtual float getMass() const;
        //! Returns whether other objects can collide with this object
        //! \return 0 if no collisions exist
        DLLEXPORT bool canCollide();
    };

    class World2D : public virtual ReferenceCounter
    {
    private:
        RCArray<Object2D>* objects;
        WorldInfo info;

        void render(Mat3<float>& kamMat,
            Point size,
            Image& zRObj,
            int xOffset,
            int yOffset,
            const char* kamName);

    public:
        DLLEXPORT World2D();
        DLLEXPORT ~World2D();
        DLLEXPORT void setAirResistance(float resistance);
        DLLEXPORT void setSize(int width, int height);
        DLLEXPORT void setSize(bool hasSize);
        DLLEXPORT void setCircular(bool circular);
        DLLEXPORT Object2D* zObjectAt(
            int x, int y, bool ignoreTransparentFlag = 0);
        DLLEXPORT Object2D* getObjectAt(
            int x, int y, bool ignoreTransparentFlag = 0);
        DLLEXPORT void addObject(Object2D* obj);
        DLLEXPORT void removeObject(Object2D* zObj);
        DLLEXPORT void removeAll();
        DLLEXPORT void explosion(
            Vertex worldPos, float intensity, float maxRad);
        DLLEXPORT void impuls(Vertex worldPos, Vertex worldDir);
        DLLEXPORT bool tick(double zeit);
        DLLEXPORT void render(
            Mat3<float>& kamMat, Point size, Image& zRObj, const char* kamName);
        DLLEXPORT const WorldInfo& getWorldInfo() const;
        DLLEXPORT ArrayIterator<Object2D*> getMembers();
    };
} // namespace Framework