#pragma once

#include "volumeshop.h"

#include <iostream>
#include <limits>

#include "Vector.h"
#include "Matrix.h"

#include "Attribute.h"
#include "Element.h"


class Box
{
public:
	Box() : m_vecMinimum(std::numeric_limits<float>::max(),std::numeric_limits<float>::max(),std::numeric_limits<float>::max()), m_vecMaximum(-std::numeric_limits<float>::max(),-std::numeric_limits<float>::max(),-std::numeric_limits<float>::max())
	{
	};

	Box(const Vector & vecVector) : m_vecMinimum(vecVector), m_vecMaximum(vecVector)
	{
	};

	Box(const Vector & vecMinimum, const Vector & vecMaximum) : m_vecMinimum(vecMinimum) , m_vecMaximum(vecMaximum)
	{
	};

	~Box()
	{
	};

	void Set(const Vector & vecMinimum, const Vector & vecMaximum)
	{
		m_vecMinimum = vecMinimum;
		m_vecMaximum = vecMaximum;
	};

	void SetMinimum(const Vector & vecMinimum)
	{
		m_vecMinimum = vecMinimum;
	};

	void SetMaximum(const Vector & vecMaximum)
	{
		m_vecMaximum = vecMaximum;
	};

	const Vector & GetMinimum() const
	{
		return m_vecMinimum;
	};

	const Vector & GetMaximum() const
	{
		return m_vecMaximum;
	};

	const Vector GetCenter() const
	{
		return (m_vecMinimum + m_vecMaximum) * 0.5f;
	};

	const Vector GetExtent() const
	{
		return (m_vecMaximum - m_vecMinimum);
	};

	const float GetRadius() const
	{
		return (m_vecMaximum - m_vecMinimum).GetMagnitude() * 0.5f;
	};

	const Vector GetCorner(const unsigned int uX, const unsigned int uY, const unsigned int uZ) const
	{		
		const Vector vecExtent = GetExtent();
		return Vector(GetMinimum().GetX()+vecExtent.GetX()*float(uX),GetMinimum().GetY()+vecExtent.GetY()*float(uY),GetMinimum().GetZ()+vecExtent.GetZ()*float(uZ));
	};

	const Vector GetCorner(const unsigned int uIndex) const
	{
		const unsigned int uX = (uIndex & 0x1);
		const unsigned int uY = (uIndex & 0x2) >> 1;
		const unsigned int uZ = (uIndex & 0x4) >> 2;
		return GetCorner(uX,uY,uZ);
	};

	const Vector GetMinimum(const Matrix & matTransformation) const
	{
		Vector vecMinimum = matTransformation * GetCorner(0);

		for (unsigned int i=1;i<8;i++)
		{
			const Vector vecCorner = matTransformation * GetCorner(i);
			vecMinimum.SetX(std::min(vecMinimum.GetX(),vecCorner.GetX()));
			vecMinimum.SetY(std::min(vecMinimum.GetY(),vecCorner.GetY()));
			vecMinimum.SetZ(std::min(vecMinimum.GetZ(),vecCorner.GetZ()));
		}

		return vecMinimum;
	};

	const Vector GetMaximum(const Matrix & matTransformation) const
	{
		Vector vecMaximum = matTransformation * GetCorner(0);

		for (unsigned int i=1;i<8;i++)
		{
			const Vector vecCorner = matTransformation * GetCorner(i);
			vecMaximum.SetX(std::max(vecMaximum.GetX(),vecCorner.GetX()));
			vecMaximum.SetY(std::max(vecMaximum.GetY(),vecCorner.GetY()));
			vecMaximum.SetZ(std::max(vecMaximum.GetZ(),vecCorner.GetZ()));
		}

		return vecMaximum;
	};

	const bool IsInside(const Vector & vecVector) const
	{
		return (vecVector.GetX() >= m_vecMinimum.GetX() && vecVector.GetX() <= m_vecMaximum.GetX() && vecVector.GetY() >= m_vecMinimum.GetY() && vecVector.GetY() <= m_vecMaximum.GetY() && vecVector.GetZ() >= m_vecMinimum.GetZ() && vecVector.GetZ() <= m_vecMaximum.GetZ());
	};

	const bool IsEmpty() const
	{
		return (m_vecMinimum.GetX() > m_vecMaximum .GetX() || m_vecMinimum.GetY() > m_vecMaximum .GetY() || m_vecMinimum.GetZ() > m_vecMaximum .GetZ());
	};

	const Box GetTransformed(const Matrix & matMatrix) const
	{
		if (!IsEmpty())
		{
			Vector vecMinimum(matMatrix * GetCorner(0));
			Vector vecMaximum(vecMinimum);

			for (int i=1;i<8;i++)
			{
				const Vector vecCorner = matMatrix * GetCorner(i);

				vecMinimum.SetX(std::min(vecMinimum.GetX(),vecCorner.GetX()));
				vecMinimum.SetY(std::min(vecMinimum.GetY(),vecCorner.GetY()));
				vecMinimum.SetZ(std::min(vecMinimum.GetZ(),vecCorner.GetZ()));

				vecMaximum.SetX(std::max(vecMaximum.GetX(),vecCorner.GetX()));
				vecMaximum.SetY(std::max(vecMaximum.GetY(),vecCorner.GetY()));
				vecMaximum.SetZ(std::max(vecMaximum.GetZ(),vecCorner.GetZ()));
			}

			return Box(vecMinimum,vecMaximum);
		}

		return *this;
	}

	const Box GetTranslated(const Vector & vecVector) const
	{
		if (!IsEmpty())
			return Box(m_vecMinimum+vecVector,m_vecMaximum+vecVector);

		return *this;
	};

	void translate(const Vector & vecVector)
	{
		*this = GetTranslated(vecVector);
	};

	void transform(const Matrix & matMatrix)
	{
		*this = GetTransformed(matMatrix);
	}

	const bool operator==(const Box & boxOther) const
	{
		return (GetMinimum() == boxOther.GetMinimum()) && (GetMaximum() == boxOther.GetMaximum());
	};

	const bool operator!=(const Box & boxOther) const
	{
		return !(*this == boxOther);
	};

	const Box & operator+=(const Box & boxOther)
	{
		if (!(IsEmpty() && boxOther.IsEmpty()))
		{
			m_vecMinimum.SetX(std::min(m_vecMinimum.GetX(),boxOther.m_vecMinimum.GetX()));
			m_vecMinimum.SetY(std::min(m_vecMinimum.GetY(),boxOther.m_vecMinimum.GetY()));
			m_vecMinimum.SetZ(std::min(m_vecMinimum.GetZ(),boxOther.m_vecMinimum.GetZ()));

			m_vecMaximum.SetX(std::max(m_vecMaximum.GetX(),boxOther.m_vecMaximum.GetX()));
			m_vecMaximum.SetY(std::max(m_vecMaximum.GetY(),boxOther.m_vecMaximum.GetY()));
			m_vecMaximum.SetZ(std::max(m_vecMaximum.GetZ(),boxOther.m_vecMaximum.GetZ()));
		}
		return *this;
	};

	const Box operator+(const Box & boxOther) const
	{
		Box vecNew = *this;
		vecNew += boxOther;
		return vecNew;
	};

	operator Element() const
	{
		Element eleElement("box");
		eleElement[ Element("minimum") ][ Vector() ] = GetMinimum();
		eleElement[ Element("maximum") ][ Vector() ] = GetMaximum();
		return eleElement;		
	};

	Box & operator=(const Element & eleElement)
	{
		Vector vecMinimum, vecMaximum;
		vecMinimum = Element(eleElement[ Element("minimum") ][ m_vecMinimum ]);
		vecMaximum = Element(eleElement[ Element("maximum") ][ m_vecMaximum ]);	
		SetMinimum(vecMinimum);
		SetMaximum(vecMaximum);
		return *this;
	};

private:

	Vector m_vecMinimum;
	Vector m_vecMaximum;
};

inline std::ostream & operator<< (std::ostream & os, const Box & boxBox)
{
	os << "(" << boxBox.GetMinimum() << "," << boxBox.GetMaximum() << ")";
	return os;
}