/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#include <glmCoreAPI.h>
#include <glmMemory.h>
#include <glmVector3.h>

namespace glm
{
    class ClassifiedRay3;
    class GLM_CORE_API FastBoundingBox
    {
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);
        /**
		* \brief Minimum vertex of the box.
		*/
        Vector3 _min = Vector3(FLT_MAX);

        /**
		* \brief Maximum vertex of the box.
		*/
        Vector3 _max = Vector3(-FLT_MAX);

        mutable bool _empty = true;

        FastBoundingBox();

        FastBoundingBox(const Vector3& min, const Vector3& max);

        ~FastBoundingBox();

        /**
		* \brief computes and return the diagonal vector of the box.
		*/
        inline Vector3 getDelta() const;

        /**
		* \brief Computes and returns the center of the box.
		*/
        inline Vector3 getCenter() const;

        inline bool isEmpty() const;

        /**
		* \brief Updates the bounding box with a given point.
		*/
        inline void update(const Vector3& vertex);

        /**
		* \brief Computes the union of two boxes.
		*/
        FastBoundingBox operator+(const FastBoundingBox& box2) const;

        bool operator==(const FastBoundingBox& box2) const;

        inline float getVolume() const;

        inline bool contains(const Vector3& point, float precision = GLM_EPSILON) const;

        void clear();

        static bool areColliding(const FastBoundingBox& box1, const FastBoundingBox& box2, float precision = GLM_EPSILON);

        static float distance2(const FastBoundingBox& box1, const FastBoundingBox& box2);

        /**
		* \brief Check if a given ray intersects the box.
		*
		* \param[in] ray The ray.
		* \return True if the ray intersects the box.
		*/
        bool intersect(const ClassifiedRay3& ray) const;

        /**
		* \brief Check if a given ray intersects the box and computes the distance between the ray source and the box.
		*
		* \param[in] ray The ray.
		* \param[out] distance The distance between the ray source and the bow, if the source is inside the box this distance is 0.
		* \return True if the ray intersects the box.
		*/
        bool intersect(const ClassifiedRay3& ray, float& distance) const;
    };

    //-----------------------------------------------------------------------------
    inline Vector3 FastBoundingBox::getDelta() const
    {
        if (isEmpty())
        {
            return Vector3(0, 0, 0);
        }
        return _max - _min;
    }

    //-----------------------------------------------------------------------------
    inline Vector3 FastBoundingBox::getCenter() const
    {
        return (_min + _max) * 0.5f;
    }

    //-----------------------------------------------------------------------------
    inline bool FastBoundingBox::isEmpty() const
    {
        if (_empty)
        {
            _empty = _min.x > _max.x ||
                     _min.y > _max.y ||
                     _min.z > _max.z;
        }
        return _empty;
    }

    //-----------------------------------------------------------------------------
    inline void FastBoundingBox::update(const Vector3& vertex)
    {
        _empty = false;
        _min.x = glm::min(_min.x, vertex.x);
        _min.y = glm::min(_min.y, vertex.y);
        _min.z = glm::min(_min.z, vertex.z);
        _max.x = glm::max(_max.x, vertex.x);
        _max.y = glm::max(_max.y, vertex.y);
        _max.z = glm::max(_max.z, vertex.z);
    }

    //-----------------------------------------------------------------------------
    inline float FastBoundingBox::getVolume() const
    {
        if (isEmpty())
        {
            return 0;
        }
        Vector3 delta = getDelta();
        return fabs(delta.x) * fabs(delta.y) * fabs(delta.z);
    }

    //-----------------------------------------------------------------------------
    inline bool FastBoundingBox::contains(const Vector3& point, float precision) const
    {
        return !isEmpty() &&
               point.x > _min.x - precision &&
               point.x < _max.x + precision &&
               point.y > _min.y - precision &&
               point.y < _max.y + precision &&
               point.z > _min.z - precision &&
               point.z < _max.z + precision;
    }
} // namespace glm
