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

#pragma once

#include "glmVector2.h"
#include "glmLine2.h"
#include "glmInterval.h"
#include "glmAttribute.h"

namespace glm
{
	/**
	* \brief This class defines 2D segments.
	* 
	* \ingroup geometry2
	* \author Golaem <dev@golaem.com>
	*
	*/
	class GLM_CORE_API Segment2 
	{
	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared)

		Vector2 _p1;
		Vector2 _p2;

		/** 
		* \brief Enumerated type defining the possible placement of point relative to a segment. 
		*/
		enum Side { 
			positiveSide=0, 
			negativeSide=1, 
			onLine=2 
		};

		Segment2();

		/** 
		* \brief Constructor
		*
		* \param p1 The first point of the segment
		* \param p2 The first point of the segment
		*/
		Segment2(const Vector2 & p1, const Vector2 & p2);

		/**
		* \brief Copy constructor
		*/
		Segment2(const Segment2 & segment);

		/** 
		* \name Intersections/Projections
		*/
		//@{

		/** 
		* \name get the center of Segment
		*/
		Vector2 getCenter() const;

		bool isVertical();

		bool isHorizontal();

		/**
		* \brief Returns true if the segment is isDegenerated() (i.e. if p1 ~= p2)
		*/
		bool isDegenerated(float precision = GLM_NUMERICAL_PRECISION) const;

		/** 
		* \brief Computes and returns the vector equal to p2 - p1.
		*/
		Vector2 getVector() const;

		/** 
		* \brief Computes and returns the normalized vector colinear to p2 - p1.
		*/
		Vector2 getNormalizedVector() const;

		/** 
		* \brief Compute and returns the length of the segment.
		*/
		float getLength() const;

		/**
		* \brief Check if a point is contained in the segment. 
		*/
		bool contains(const Vector2 & point, float precision=GLM_NUMERICAL_PRECISION) const;

		/** 
		* \brief Return the segment point closest to the given point.
		*/
		Vector2 closestPoint(const Vector2 & point) const ;

		/** 
		* \brief Check if the point projects on the segment.
		* \param p The point to test.
		* \return True if the point project on the segment.
		*/
		bool projectsOn(const Vector2 & point) const;

		/**
		* \brief Returns the side of a given point relative to the segment.
		*/
		Side side(const Vector2 & point) const;

		/** 
		* \brief Construct and returns the line whom the segment is a part of.
		* \return The line.
		*/
		Line2 getLine() const;

		/** 
		* \name GeometricTransformations
		*/
		//@{
        /**
         * \brief Translation
         *
         * \param translation Translation vector.
         * \return Le segment translaté
         */
        Segment2 operator+ (const Vector2 & translation) const;
		//@}

		// -------------------------------------------------------------------------------------------------------------------------
		/** 
		* \name Comparisons 
		*/
		//@{

		/** 
		* \brief Equality test between two segments.
		*
		* \param segment2 the other segment.
		* \return True if the two segments are equal.
		*/
		bool operator==(const Segment2 & segment2) const;

		/** 
		* \brief Approximate equality test between two segments.
		*
		* \param segment2 the other segment.
		* \param precision the equality precision.
		* \return True if the two segments are approximately equal.
		*/
		bool approxEqual(const Segment2 & segment2, float precision = GLM_NUMERICAL_PRECISION) const;

		/** 
		* \brief Inferiority operator used for STL containers.
		*
		* \param segment2 The given segment.
		* \return True if current segment is lexicographilly inferior to the given segment.
		*/
		bool operator<(const Segment2 & segment2) const;
		//@}

		/**
		* \brief Computes the bounding box of the segment
		* \param[out] min The first point of the bounding box (the lowest)
		* \param[out] max The second point of the bounding box (the highest)
		*/
		void getBoundingBox(Vector2& min, Vector2& max) const;

	};

	//----------------------------------------------------------------------------
	// dgetCenter
	//----------------------------------------------------------------------------
	inline Vector2 Segment2::getCenter() const
	{
		return (_p1+_p2)*0.5f;
	}

	//----------------------------------------------------------------------------
	// isVertical
	//----------------------------------------------------------------------------
	inline bool Segment2::isVertical()
	{
		return glm::approxEqual(_p1.x,_p2.x,GLM_NUMERICAL_PRECISION);
	}

	//----------------------------------------------------------------------------
	// isHorizontal
	//----------------------------------------------------------------------------
	inline bool Segment2::isHorizontal()
	{
		return glm::approxEqual(_p1.y,_p2.y,GLM_NUMERICAL_PRECISION);
	}

	//----------------------------------------------------------------------------
	// isDegenerated
	//----------------------------------------------------------------------------
	inline bool Segment2::isDegenerated(float precision) const
	{
		return glm::approxEqual(_p1, _p2, precision) ;
	}

	//----------------------------------------------------------------------------
	// getVector
	//----------------------------------------------------------------------------
	inline Vector2 Segment2::getVector() const
	{
		return _p2-_p1 ;
	}

	//----------------------------------------------------------------------------
	// getNormalizedVector
	//----------------------------------------------------------------------------
	inline Vector2 Segment2::getNormalizedVector() const
	{
		Vector2 temp = _p2-_p1;
		temp.normalizeSelf();
		return temp ;
	}

	//----------------------------------------------------------------------------
	// getLength
	//----------------------------------------------------------------------------
	inline float Segment2::getLength() const
	{
		return (_p1-_p2).norm() ;
	}

	//----------------------------------------------------------------------------
	// contains
	//----------------------------------------------------------------------------
	inline bool Segment2::contains(const Vector2 & point, float precision) const
	{
		return (_p1 - point).norm() + (_p2 - point).norm() - (_p1-_p2).norm() <=  precision;
	}


	//----------------------------------------------------------------------------
	// side
	//----------------------------------------------------------------------------
	inline Segment2::Side Segment2::side(const Vector2 & point) const
	{
		Vector2 direction = _p2-_p1;
		direction.rotateSelf90();
		float value = ( point - _p1 ) * direction ;
		if(value>0.f)
		{
			return positiveSide ;
		}
		else if(value<0.f)
		{
			return negativeSide ;
		}
		return onLine ;
	}

	//----------------------------------------------------------------------------
	// getLine
	//----------------------------------------------------------------------------
	inline Line2 Segment2::getLine() const
	{
		return Line2(_p1, _p2) ;
	}

	//----------------------------------------------------------------------------
	// operator+
	//----------------------------------------------------------------------------
	inline Segment2 Segment2::operator+ (const Vector2 & translation) const
	{
		return Segment2(this->_p1+translation, this->_p2+translation) ;
	}

	//----------------------------------------------------------------------------
	// operator==
	//----------------------------------------------------------------------------
	inline bool Segment2::operator==(const Segment2 & segment2) const
	{
		return (_p1==segment2._p1 && _p2==segment2._p2) ;
	}

	//----------------------------------------------------------------------------
	// approxEqual
	//----------------------------------------------------------------------------
	inline bool Segment2::approxEqual(const Segment2 & segment2, float precision) const
	{
		return (glm::approxEqual(_p1, segment2._p1, precision) && glm::approxEqual(_p2, segment2._p2, precision))
			|| (glm::approxEqual(_p1, segment2._p2, precision) && glm::approxEqual(_p2, segment2._p1, precision));
	}

	//----------------------------------------------------------------------------
	// operator<
	//----------------------------------------------------------------------------
	inline bool Segment2::operator<(const Segment2 & segment2) const
	{
		return ((*this)._p1<segment2._p1) || ((*this)._p1==segment2._p1 && (*this)._p2<segment2._p2) ;
	}
}

