#ifndef PHYSICS_ENTITY_HPP_
#define PHYSICS_ENTITY_HPP_

#include <Entity/Entity.hpp>
#include <BoundingBox/BoundingBox.hpp>
#include <Utilities/FixedPoint.hpp>
#include <set>

/**
 * @brief Represents an entity that has physics.
 */
class PhysicsEntity : public Entity
{
private:
    void updateVelocity();
    void handleWalkingOver();
    void checkWalkingOver(wfvec3 pos, const BoundingBox<wfixed> &bbox);
    std::optional<bool> isIntersectingBlock(const BoundingBox<wfixed> &box, glm::ivec3 blockPos);
    void playFallingSound(wfvec3 overallVelocity);
    void getIntersectedBlocks(std::set<Block*> &blocks);

public:
    wfvec3 pos;
    wfvec3 vel;
    wfvec3 restVel;
    bool isOnGround = false;

    PhysicsEntity(
        World &world,
        std::string typeId,
        uint64_t id,
        wfvec3 posArg = wfvec3(0.0f, 0.0f, 5.5f),
        wfvec3 velArg = wfvec3(0.0, 0.0, 0.0)
    );

    /**
     * @brief Checks if the entity can go to the given position.
     * @param pos position to check
     * @return true if the entity can go to the position, false if not, `std::nullopt`
     * if the chunk is unloaded.
     */
    virtual std::optional<bool> canGoTo(wfvec3 pos);

    /**
     * @brief Gets the bounding box of the entity.
     * @param pos - position of the entity
     */
    virtual BoundingBox<wfixed> getBoundingBox(wfvec3 pos) = 0;

    /**
     * @brief Called when this entity collides with something.
     * 
     * If this method returns `true`, the update will end; thus, you can set the
     * `pos` to whatever you want in this method and return `true` to handle the
     * collision.
     * 
     * If `false` is returned, the entity will continue moving as close as possible
     * to the destination without colliding.
     */
    virtual bool onCollision();

    /**
     * @brief Called when this entity enters an unloaded chunk.
     * 
     * When this function is called, the entity will not move into the chunk, and
     * instead float, repeatedly calling this function until the chunk is loaded.
     * 
     * Does nothing by default.
     */
    virtual void onEnteringUnloadedChunk();

    /**
     * @brief Get friction coefficients for the entity.
     * @return friction coefficients as a vector
     */
    virtual glm::vec3 getFrictionCoefficients() = 0;

    virtual void update() override;
    virtual bool wouldCollideWith(Block &block, glm::ivec3 pos) override;
};

#endif