#pragma once

#include "vector3.h"
#include "math_util.h"

#include <numeric>
#include <limits>


namespace lm
{


//! axis aligned box
template<typename T>
class range3
{
public:
	range3(void)
	{
		// 引数なしの場合は min > max となるように設定する
		float v_max = (std::numeric_limits<T>::max)();
		float v_min = -v_max;

		p0.set( v_max , v_max , v_max );
		p1.set( v_min , v_min , v_min );
	}

	range3( const T& x0 , const T& y0 , const T& z0 , const T& x1 , const T& y1 , const T& z1 )
		: p0( x0 , y0 , z0 ) , p1( x1 , y1 , z1 )
	{}
	range3( const vector3<T>& i_p0 , const vector3<T>& i_p1 )
		: p0(i_p0) , p1(i_p1)
	{}

	void clear(void);

	//! 有効な範囲が設定されているか(体積が0以上か)確かめる
	bool is_valid(void) const;

	//! 範囲を広げる
	void expand( const vector3<T>& v );
	void expand( const range3<T>& i_range );

	//! 指定した座標を含む場合はtrue
	bool contains( const vector3<T>& v ) const;
	//! 指定したrangeを交差する範囲があればtrue
	bool intersect( const range3<T>& i_range ) const;

	T length_x(void) const { return p1.x - p0.x; }
	T length_y(void) const { return p1.y - p0.y; }
	T length_z(void) const { return p1.z - p0.z; }

	T min_length(void) const { return (lm::min)( length_x() , length_y() , length_z() ); }
	T max_length(void) const { return (lm::max)( length_x() , length_y() , length_z() ); }

	T&       min_x(void)       { return p0.x; }
	T&       min_y(void)       { return p0.y; }
	T&       min_z(void)       { return p0.z; }
	T&       max_x(void)       { return p1.x; }
	T&       max_y(void)       { return p1.y; }
	T&       max_z(void)       { return p1.z; }
	const T& min_x(void) const { return p0.x; }
	const T& min_y(void) const { return p0.y; }
	const T& min_z(void) const { return p0.z; }
	const T& max_x(void) const { return p1.x; }
	const T& max_y(void) const { return p1.y; }
	const T& max_z(void) const { return p1.z; }

	T mid_x(void) const { return ( p1.x + p0.x ) / T(2); }
	T mid_y(void) const { return ( p1.y + p0.y ) / T(2); }
	T mid_z(void) const { return ( p1.z + p0.z ) / T(2); }
	vector3<T> center(void) const { return ( p1 + p0 ) / T(2); }

	vector3<T>&       min_point(void)       { return p0; }
	vector3<T>&       max_point(void)       { return p1; }
	const vector3<T>& min_point(void) const { return p0; }
	const vector3<T>& max_point(void) const { return p1; }

	vector3<T> mid_point(void) const { return T(0.5) * ( p0 + p1 ); }

protected:
	// p0 <= p1
	vector3<T> p0 , p1;
};


typedef range3<float>  range3f;
typedef range3<double> range3d;



// ----------------------------------------------------------------------------------------------------------------------------------------------------------------


template<typename T> inline
void range3<T>::clear(void)
{
	static const float v_max = (std::numeric_limits<T>::max)();
	static const float v_min = -v_max;
	
	p0.set( v_max , v_max , v_max );
	p1.set( v_min , v_min , v_min );
}

	//! 有効な範囲が設定されているか(体積が0以上か)確かめる
template<typename T> inline
bool range3<T>::is_valid(void) const
{
	if( p0.x > p1.x ) return false;
	if( p0.y > p1.y ) return false;
	if( p0.z > p1.z ) return false;
	return true;
}

template<typename T> inline
void range3<T>::expand( const vector3<T>& v )
{
	p0.x = (std::min)( v.x , p0.x );
	p0.y = (std::min)( v.y , p0.y );
	p0.z = (std::min)( v.z , p0.z );
	p1.x = (std::max)( v.x , p1.x );
	p1.y = (std::max)( v.y , p1.y );
	p1.z = (std::max)( v.z , p1.z );
}

template<typename T> inline
void range3<T>::expand( const range3<T>& i_range )
{
	if( !i_range.is_valid() )
		return;

	expand( i_range.min_point() );
	expand( i_range.max_point() );
}

template<typename T> inline
bool range3<T>::contains( const vector3<T>& v ) const
{
	if( v.x < p0.x || p1.x < v.x ) return false;
	if( v.y < p0.y || p1.y < v.y ) return false;
	if( v.z < p0.z || p1.z < v.z ) return false;

	return true;
}

template<typename T> inline
bool range3<T>::intersect( const range3<T>& i_range ) const
{
	if( this->max_x() < i_range.min_x() || this->min_x() > i_range.max_x() ) return false;
	if( this->max_y() < i_range.min_y() || this->min_y() > i_range.max_y() ) return false;
	if( this->max_z() < i_range.min_z() || this->min_z() > i_range.max_z() ) return false;

	return true;
}


}
