#include "deBoundingVolumes.hpp"

#pragma warning (disable : 4512)

class deBoundingVolume : virtual public IdeBoundingVolume, public deRefCountBase
{
public:
	deBoundingVolume(const BVType_t bvtype) : m_Position(deZeroVec), m_BVType(bvtype) {}
	~deBoundingVolume() {}

	BVType_t GetBVType() { return m_BVType; }
	void* GetBVInterface(BVType_t iface)
	{
		if (iface == BVType_Unknown)
			return (IdeBoundingVolume*)this;
		return NULL;
	}
	deBoolean TestIntersection(IdeBoundingVolume* other);
	void GetPosition(deVec3d& pos) { pos = m_Position; }
	void SetPosition(const deVec3d& pos) { m_Position = pos; }

protected:
	const BVType_t m_BVType;
	deVec3d m_Position;
};

class deBV_Sphere : public IdeBV_Sphere, public deBoundingVolume
{
public:
	deBV_Sphere() : deBoundingVolume(BVType_Sphere), m_Radius(0) {}
	deBV_Sphere(const deBoundSphere& sphere) : deBoundingVolume(BVType_Sphere), m_Radius(sphere.Radius)
	{
		m_Position = sphere.Center;
	}
	~deBV_Sphere() {}

	void* GetBVInterface(BVType_t iface)
	{
		if (iface == m_BVType)
			return (IdeBV_Sphere*)this;
		return deBoundingVolume::GetBVInterface(iface);
	}
	void SetRadius(deDouble Radius) { m_Radius = Radius; }
	deDouble GetRadius() { return m_Radius; }

	deBoolean TestLineIntersect(const deVec3d& begin, const deVec3d& end);
	deBoolean TestSphereIntersect(const deVec3d& center, deDouble radius);
	deBoolean TestAABBIntersect(const deVec3d& pos, const deAABB& box);

private:
	deDouble m_Radius;
};

class deBV_AABB : public IdeBV_AABB, public deBoundingVolume
{
public:
	deBV_AABB() : deBoundingVolume(BVType_AABB)
	{
		m_BBox.Min = deZeroVec;
		m_BBox.Max = deZeroVec;
	}
	deBV_AABB(const deAABB& bbox) : deBoundingVolume(BVType_AABB)
	{
		m_BBox.Min = bbox.Min;
		m_BBox.Max = bbox.Max;
	}
	~deBV_AABB() {}
	
	void* GetBVInterface(BVType_t iface)
	{
		if (iface == m_BVType)
			return (IdeBV_AABB*)this;
		return deBoundingVolume::GetBVInterface(iface);
	}
	void SetBBox(const deAABB& BBox) { m_BBox = BBox; }
	void GetBBox(deAABB& BBox) { BBox = m_BBox; }
	
	deBoolean TestLineIntersect(const deVec3d& begin, const deVec3d& end);
	deBoolean TestSphereIntersect(const deVec3d& center, deDouble radius);
	deBoolean TestAABBIntersect(const deVec3d& pos, const deAABB& box);

private:
	deAABB m_BBox;
};

#pragma todo (add an 'enclosed' parameter)
deBoolean deBoundingVolume::TestIntersection(IdeBoundingVolume* other)
{
	// take min/max so we can avoid redundant cases
	BVType_t temp1 = other->GetBVType();
	if (temp1 == BVType_Sphere)
	{
		IdeBV_Sphere* sphere = (IdeBV_Sphere*)other->GetBVInterface(BVType_Sphere);
		if (!sphere)
			DEBUGRETURN(deFALSE, "deBoundingVolume::TestIntersection - mismatched BV Types");
		deVec3d pos;
		other->GetPosition(pos);
		return TestSphereIntersect(pos, sphere->GetRadius());
	}
	else if (temp1 == BVType_AABB)
	{
		IdeBV_AABB* otherbox = (IdeBV_AABB*)other->GetBVInterface(BVType_AABB);
		deVec3d pos;
		deAABB box;
		otherbox->GetPosition(pos);
		otherbox->GetBBox(box);
		return TestAABBIntersect(pos, box);
	}
	BVType_t temp2 = max(m_BVType, temp1);
	temp1 = min(m_BVType, temp1);
	switch (temp1)
	{
	default:
		break;
	case BVType_Sphere:
		switch (temp2)
		{
		default:
			break;
		case BVType_Sphere:
			// sphere-sphere intesect
			break;
		case BVType_AABB:
			// sphere-AABB intersect
			break;
		}
		break;
	case BVType_AABB:
		switch (temp2)
		{
		default:
			break;
		case BVType_AABB:
			// AABB-AABB intersect
			break;
		}
		break;
	}
	DEBUGRETURN(deFALSE, "deBoundingVolume::TestIntersection - unhandled BV type");
}

DEBV_API IdeBoundingVolume * IdeBoundingVolume_Create(IdeBoundingVolume::BVType_t bvtype)
{
	switch(bvtype)
	{
	default:
		return NULL;
	case IdeBoundingVolume::BVType_Sphere:
		return new deBV_Sphere;
	case IdeBoundingVolume::BVType_AABB:
		return new deBV_AABB;
	}
}

// Sphere
deBoolean deBV_Sphere::TestLineIntersect(const deVec3d& begin, const deVec3d& end)
{
	deVec3d result;
	deDouble percent;
	return deGeometry::SphereLineIntersect(m_Position, m_Radius, begin, end, result, percent);
}

deBoolean deBV_Sphere::TestSphereIntersect(const deVec3d& center, deDouble radius)
{
	deDouble CombinedRadius = m_Radius + radius;
	deVec3d Distance = m_Position;
	Distance -= center;
	if (Vec3d_Length(Distance) <= CombinedRadius)
		return deTRUE;
	return deFALSE;
}

deBoolean deBV_Sphere::TestAABBIntersect(const deVec3d& pos, const deAABB& box)
{
	deBoundSphere sphere;
	deGeometry::MakeBoundSphereFromAABB(box, sphere);
	sphere.Center += pos;
	if (!TestSphereIntersect(sphere.Center, sphere.Radius))
		return deFALSE;
	deAABB offsetbox = box;
	offsetbox.Min += pos;
	offsetbox.Max += pos;
	deDouble dist;
	dist = deGeometry::PlaneDist(m_Position, deVecX, offsetbox.Min.x);
	if (dist < -m_Radius) return deFALSE;
	else if (dist < m_Radius) return deTRUE;
	dist = deGeometry::PlaneDist(m_Position, deVecX, offsetbox.Max.x);
	if (dist > m_Radius) return deFALSE;
	else if (dist > -m_Radius) return deTRUE;
	dist = deGeometry::PlaneDist(m_Position, deVecY, offsetbox.Min.y);
	if (dist < -m_Radius) return deFALSE;
	else if (dist < m_Radius) return deTRUE;
	dist = deGeometry::PlaneDist(m_Position, deVecY, offsetbox.Max.y);
	if (dist > m_Radius) return deFALSE;
	else if (dist > -m_Radius) return deTRUE;
	dist = deGeometry::PlaneDist(m_Position, deVecZ, offsetbox.Min.z);
	if (dist < -m_Radius) return deFALSE;
	else if (dist < m_Radius) return deTRUE;
	dist = deGeometry::PlaneDist(m_Position, deVecZ, offsetbox.Max.z);
	if (dist > m_Radius) return deFALSE;
	else if (dist > -m_Radius) return deTRUE;
	// inside
	return deTRUE;
}

// AABB
deBoolean deBV_AABB::TestLineIntersect(const deVec3d& /*begin*/, const deVec3d& /*end*/)
{
#pragma todo(deBV_AABB::TestLineIntersect unimplemented)
	DEBUGRETURN(deFALSE, "deBV_AABB::TestLineIntersect unimplemented");
}

deBoolean deBV_AABB::TestSphereIntersect(const deVec3d& center, deDouble radius)
{
	deAABB box = {{-radius,-radius,-radius},{radius,radius,radius}};
	if (!deGeometry::AABBIntersect(m_BBox, box))
		return deFALSE;
	deVec3d points[8];
	deGeometry::GetPointsOfAABB(m_BBox, points);
	int i;
	for (i = 0; i < 8; ++i)
	{
		points[i] += m_Position;
		points[i] -= center;
		if (Vec3d_Length(points[i]) <= radius)
			return deTRUE;
	}
	return deFALSE;
}

deBoolean deBV_AABB::TestAABBIntersect(const deVec3d& pos, const deAABB& box)
{
	deAABB temp = box;
	temp.Min += pos;
	temp.Min -= m_Position;
	temp.Max += pos;
	temp.Max -= m_Position;
	return deGeometry::AABBIntersect(m_BBox, temp);
}

