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

#pragma once

#include "glmMathBase.h"
#include "glmVector3.h"
#include "glmTriangle3.h"
#include "glmRay3.h"
#include "glmClassifiedRay3.h"
#include "glmVolume.h"

#include <iostream>
#include <iterator>
#include <algorithm>

namespace glm
{
	class RandomNumberGenerator;

	/**
	* \brief An axis-aligned box in 3D.
	*
	* \ingroup geometry3
	* \author Golaem <dev@golaem.com>
	*
	* \param float Manipulated scalar type (\ref Expression, double, float...)
	*/

	class GLM_CORE_API AxisAlignedBox3 : public Volume
	{
	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

		/**
		* \brief Default constructor.
		*/
		AxisAlignedBox3();

		/**
		* \brief Constructor form the bounds of the bounding box.
		*/
		AxisAlignedBox3(const Vector3 & min, const Vector3 & max);

		/**
		* \brief Copy constructor.
		*/
		AxisAlignedBox3(const AxisAlignedBox3 & box2);

		/**
		* \brief Destructor.
		*/
		virtual ~AxisAlignedBox3();

		/**
		* \name Requests
		*/
		//@{
		float getVolume() const;

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

		Vector3 getDeltaX() const;

		Vector3 getDeltaY() const;

		Vector3 getDeltaZ() const;

		/**
		* \see Volume::contains(const Vector3& point) const
		*/
		bool contains(const Vector3& point, float precision = GLM_NUMERICAL_PRECISION) const;

		/**
		* \see Volume::retrieveBoundingBox(Vector3 & min, Vector3 & max) const
		*/
		void retrieveBoundingBox(Vector3 & min, Vector3 & max) const;

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

		/**
		* \name Accessors
		*/
		//@{
		const Vector3& getMin() const;

		const Vector3& getMax() const;

		void setMin(const Vector3& min);

		void setMax(const Vector3& max);

		void setMinMax(const Vector3& min, const Vector3& max);

		bool isEmpty() const;
		//@}

		/**
		* \brief ensure that min and max are correctly ordered for each coordinate
		*/
		void makeValid();

		/**
		* \brief Computes the intersection of two boxes.
		*/
		AxisAlignedBox3 operator* (const AxisAlignedBox3 & box2) const;

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

		/**
		* \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;

		/**
		* \brief Translate the box following a given vector.
		*/
		void translate(const Vector3 & vector);

		/**
		* \brief Equality operator.
		*/
		bool operator==(const AxisAlignedBox3 & box2) const;

		/**
		* \brief Difference operator.
		*/
		bool operator!=(const AxisAlignedBox3 & box2) const;

		/**
		* \brief Compute and returns a random point inside this box.
		* \param rng The random number generator to use.
		*/
		Vector3 randomPoint(RandomNumberGenerator & rng) const;

		/**
		* \brief Clone the instance
		*/
		virtual Volume* clone() const;

	protected:
		/**
		* \brief Minimum vertex of the box.
		*/
		Vector3 _min;

		/**
		* \brief Maximum vertex of the box.
		*/
		Vector3 _max;

		/**
		* \brief True if the box is empty (if it is not valid, it is considered empty).
		*/
		bool _empty;

		/**
		* \brief Check if the bounding box is valid and set _empty accordingly.
		*/
		void validate();
	};

	std::ostream & operator<<(std::ostream& out, const AxisAlignedBox3& box);

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

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

	//-----------------------------------------------------------------------------
	inline const Vector3& AxisAlignedBox3::getMin() const
	{
		return _min;
	}

	//-----------------------------------------------------------------------------
	inline const Vector3& AxisAlignedBox3::getMax() const
	{
		return _max;
	}

	//-----------------------------------------------------------------------------
	inline void AxisAlignedBox3::setMin(const Vector3& min)
	{
		_min = min;
		validate();
	}

	//-----------------------------------------------------------------------------
	inline void AxisAlignedBox3::setMax(const Vector3& max)
	{
		_max = max;
		validate();
	}

	//-----------------------------------------------------------------------------
	inline void AxisAlignedBox3::setMinMax(const Vector3& min, const Vector3& max)
	{
		_min = min;
		_max = max;
		validate();
	}

	//-----------------------------------------------------------------------------
	inline bool AxisAlignedBox3::isEmpty() const
	{
		return _empty;
	}

	//-----------------------------------------------------------------------------
	inline Vector3 AxisAlignedBox3::getDelta() const
	{
		return _max - _min;
	}

	//-----------------------------------------------------------------------------
	inline Vector3 AxisAlignedBox3::getDeltaX() const
	{
		return Vector3(getDelta().x, 0.0, 0.0);
	}

	//-----------------------------------------------------------------------------
	inline Vector3 AxisAlignedBox3::getDeltaY() const
	{
		return Vector3(0.0, getDelta().y, 0.0);
	}

	//-----------------------------------------------------------------------------
	inline Vector3 AxisAlignedBox3::getDeltaZ() const
	{
		return Vector3(0.0, 0.0, getDelta().z);
	}

	//-----------------------------------------------------------------------------
	inline bool AxisAlignedBox3::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;
	}

	//-----------------------------------------------------------------------------
	inline void AxisAlignedBox3::retrieveBoundingBox(Vector3 & min, Vector3 & max) const
	{
		min = _min;
		max = _max;
	}
}

