#pragma once

#include "dllinterface.h"
#include "d3dx9.h"
#include "camera.h"
#include "dxvector2.h"
#include "dxvector3.h"
#include "dxvector4.h"
#include "dxplane.h"

#include <vector>

//
enum CONTAINMENTTESTRESULT 
{
	CTR_DISJOINT = 0,
	CTR_CONTAINS,
	CTR_INTERSECT
};

class BoundingMesh;
class AABB;

class DXFUNCTIONS_API IBoundingVolume
{
public:
	//if completely on negative side of plane
	virtual bool IsCulled(const DXPlane& p) const = 0;

	virtual AABB BuildAABB(void) const = 0;
	//TODO:
	//virtual BoundingMesh* CollapseToMesh(void) = 0;
};

class DXFUNCTIONS_API AABB : public IBoundingVolume
{
	DXVector3 m_Min;
	DXVector3 m_Max;
	DXVector3 m_E;
	DXVector3 m_Center;
public:

	AABB(void)
	{
		ZeroMemory(&m_Min, sizeof(DXVector3)*4);
	}

	AABB(const DXVector3& vmin, const DXVector3& vmax)
	{
		SetVolume(vmin, vmax);
	}

	~AABB(void){}

	void AddPoint(DXVector3 p)
	{
		if(p.x < m_Min.x)
			m_Min.x = p.x;
		else if(p.x > m_Max.x)
			m_Max.x = p.x;

		if(p.y < m_Min.y)
			m_Min.y = p.y;
		else if(p.y > m_Max.y)
			m_Max.y = p.y;

		if(p.z < m_Min.z)
			m_Min.z = p.z;
		else if(p.z > m_Max.z)
			m_Max.z = p.z;

		m_Center = (m_Min + m_Max) / 2;
		m_E = (m_Max - m_Min) / 2; 
	}

	void SetVolume(DXVector3 min, DXVector3 max)
	{
		m_Min = min;
		m_Max = max;
		m_Center = (m_Min + m_Max) * 0.5f;
		m_E = (m_Max - m_Min) * 0.5f;
	}

	DXVector3 GetMin(void) const {return m_Min;}
 	DXVector3 GetMax(void) const {return m_Max;}
	DXVector3 GetCenter(void) const {return m_Center;}
	DXVector3 GetE(void) const {return m_E;}	

	void Unify(const AABB& box)
	{
		char change = 0;

		if(box.GetMin().x < m_Min.x)
		{
			m_Min.x = box.GetMin().x;
			change++;
		}
		if(box.GetMax().x > m_Max.x)
		{
			m_Max.x = box.GetMax().x;
			change++;
		}

		if(box.GetMin().y < m_Min.y)
		{
			m_Min.y = box.GetMin().y;
			change++;
		}
		if(box.GetMax().y > m_Max.y)
		{
			m_Max.y = box.GetMax().y;
			change++;
		}

		if(box.GetMin().z < m_Min.z)
		{
			m_Min.z = box.GetMin().z;
			change++;
		}
		if(box.GetMax().z > m_Max.z)
		{
			m_Max.z = box.GetMax().z;
			change++;
		}

		if(change)
		{
			m_Center = (m_Min + m_Max) * 0.5f;
			m_E = (m_Max - m_Min) * 0.5f;
		}
	}

	void AddOffset(const DXVector3& offset)
	{
		m_Min += offset;
		m_Max += offset;
		m_Center += offset;
	}

	//if completely on negative side of plane
	bool IsCulled(const DXPlane& plane) const
	{
		const float x = m_E.x * plane.a;
		const float y = m_E.y * plane.b;
		const float z = m_E.z * plane.c;

		const float rad = fabs(x) + fabs(y) + fabs(z); 
		const float signeddist = plane.DotNormal(m_Center) + plane.d;
		
		return signeddist <= -rad; 
	}

	AABB BuildAABB(void) const
	{
		return *this;
	}
};


class DXFUNCTIONS_API OBB : public IBoundingVolume
{
	DXVector3 m_Center; //in worldspace
	DXVector3 m_E; //axis extends
	DXVector3 m_Min;
	DXVector3 m_Max;
	DXMatrix  m_Orientation;

public:
	OBB(void)
	{
		ZeroMemory(&m_Center, sizeof(DXVector3)*4 + sizeof(DXMatrix));
	}
	OBB(DXVector3 min, DXVector3 max)
	{
		SetVolume(min, max);
	}
	~OBB(void){}

	void SetVolume(DXVector3 min, DXVector3 max)
	{
		m_Min = min;
		m_Max = max;
		//m_Center = (min + max) * 0.5f;
		m_E = (max - min) * 0.5f;
		D3DXVec3TransformCoord((D3DXVECTOR3*)&m_Center, (D3DXVECTOR3*)&((min + max) * 0.5f), (D3DXMATRIX*)&m_Orientation);
	}

	void SetOrientation(const DXMatrix& orientation)
	{
		m_Orientation = orientation;
		D3DXVec3TransformCoord((D3DXVECTOR3*)&m_Center, (D3DXVECTOR3*)&((m_Min + m_Max) * 0.5f), (D3DXMATRIX*)&m_Orientation);
	}

	AABB BuildAABB(void) const
	{
		DXVector3 worldE;
		D3DXVec3TransformNormal((D3DXVECTOR3*)&worldE, (D3DXVECTOR3*)&m_E, (D3DXMATRIX*)&m_Orientation);

		const float x = D3DXVec3Dot((D3DXVECTOR3*)&worldE, (D3DXVECTOR3*)&DXVector3(1,0,0));
		const float y = D3DXVec3Dot((D3DXVECTOR3*)&worldE, (D3DXVECTOR3*)&DXVector3(0,1,0));
		const float z = D3DXVec3Dot((D3DXVECTOR3*)&worldE, (D3DXVECTOR3*)&DXVector3(0,0,1));

		const DXVector3 aabbE(fabs(x), fabs(y), fabs(z));
		//const DXVector3 c(m_Orientation._41, m_Orientation._42, m_Orientation._43);

		//return AABB(c + m_Center - aabbE, c + m_Center + aabbE);
		return AABB(m_Center - aabbE, m_Center + aabbE);
	}

	DXVector3 GetMin(void) const {return m_Min;}
 	DXVector3 GetMax(void) const {return m_Max;}
	DXVector3 GetCenter(void) const {return m_Center;}
	DXVector3 GetE(void) const {return m_E;}
	DXMatrix GetOrientation(void) const {return m_Orientation;}

	//if completely on negative side of plane
	bool IsCulled(const DXPlane& plane) const
	{
		const DXVector3 planenormal(plane.a, plane.b, plane.c);
	
		const float x = m_E.x * planenormal.Dot(m_Orientation.GetXAxis());
		const float y = m_E.y * planenormal.Dot(m_Orientation.GetYAxis());
		const float z = m_E.z * planenormal.Dot(m_Orientation.GetZAxis());

		const float rad = fabs(x) + fabs(y) + fabs(z);
		const float signeddist = plane.DotNormal(m_Center) + plane.d;  
			
		return signeddist <= -rad;  
	}
};

struct DXFUNCTIONS_API OBS : public IBoundingVolume
{
	DXVector3 Center;
	float Radius;

	OBS(void)
	{
		ZeroMemory(&Center, sizeof(float)*4);
	}
	OBS(DXVector3 center,float r)
	{
		Center = center;
		Radius = r;
	}
	~OBS(void){}

	AABB BuildAABB(void) const
	{
		return AABB(Center - DXVector3(Radius, Radius, Radius), Center + DXVector3(Radius, Radius, Radius));
	}

	void AddOffset(const DXVector3& offset)
	{
		Center += offset;
	}

	//if completely on negative side of plane
	bool IsCulled(const DXPlane& plane) const
	{
		if(plane.DotNormal(Center) + plane.d < -Radius)
			return true;
		return false;
	}

	void Unify(const OBS& obs)
	{
		DXVector3 vd = obs.Center - Center;
		const float d = vd.Length();
		const float gap = d + obs.Radius - Radius;
		if(gap > 0)
		{
			vd.Normalize();
			Center += vd * gap * 0.5;
			Radius += gap * 0.5f;
		}
	}

	DXVector3 GetCenter(void) const { return Center; }
	float GetRadius(void) const { return Radius; }
};

struct DXFUNCTIONS_API Frustum
{
	std::vector<DXPlane> planes;

	bool IsVolumeInside(const IBoundingVolume& b) const 
	{
		for (std::vector<DXPlane>::const_iterator it = planes.begin(); it != planes.end(); it++)
		{
			if(b.IsCulled(*it))
				return false;
		}

		return true;
	}
};

struct DXFUNCTIONS_API ViewFrustum
{
	DXVector3 Orig;
	DXVector3 Dir;
	DXVector3 Up;
	DXVector3 Right; 
	float		Far,Near; //distance of near and farplane
	float		UBound,RBound; //extends in Up and Right;

	//constants calculated during initiation;
	float	DRatio;
	float	MTwoUF;
	float	MTwoRF;

	//points of frustum
	DXVector3 FrustumPoints[9];
	DXPlane ClipPlanes[6];//normals of planes point to center of frustum

	ViewFrustum(void){}
	ViewFrustum(const Camera &cam) { Update(cam); }
	~ViewFrustum(void){}

	void Update(const Camera &cam);
};

//a simple mesh which has a list of points an each 3 points build a triangle, the face orientation is determined by ccw/cw culling
class DXFUNCTIONS_API BoundingMesh
{
	DXVector3* m_Points;
	int			m_TriangleCount;

public:
	BoundingMesh(void)
	{
		m_Points = NULL;
		m_TriangleCount = 0;
	}

	BoundingMesh(const AABB& aabb);
	BoundingMesh(const OBB& obb);
	BoundingMesh(const ViewFrustum& frustum);
	
	BoundingMesh(DXVector3* points, int trianglecount)
	{
		if(trianglecount)
		{
			m_Points = new DXVector3[trianglecount * 3];
			memcpy(m_Points, points, sizeof(DXVector3) * trianglecount * 3);
		}
		else
		{
			m_Points = NULL;
		}
		m_TriangleCount = trianglecount;
	}

	BoundingMesh(const BoundingMesh& b)
	{
		m_Points = new DXVector3[b.m_TriangleCount*3];
		memcpy(m_Points, b.m_Points, b.m_TriangleCount*3 * sizeof(DXVector3));
		m_TriangleCount = b.m_TriangleCount;
	}

	~BoundingMesh(void)
	{
		delete [] m_Points;
	}

	void operator =(const BoundingMesh& b)
	{
		if(m_TriangleCount < b.m_TriangleCount)
		{
			delete [] m_Points;
			m_Points = new DXVector3[b.m_TriangleCount*3];
		}
		memcpy(m_Points, b.m_Points, b.m_TriangleCount*3 * sizeof(DXVector3));
		m_TriangleCount = b.m_TriangleCount;
	}

	int GetTriangleCount(void) const {return m_TriangleCount;}
	DXVector3* GetPoints(void) const {return m_Points;}
	void TransformPoints(const DXMatrix& matTrans) {D3DXVec3TransformCoordArray((D3DXVECTOR3*)m_Points, sizeof(DXVector3), (D3DXVECTOR3*)m_Points, sizeof(DXVector3), (D3DXMATRIX*)&matTrans, m_TriangleCount*3);}
	
	void ClipByAABB(const AABB& aabb);
	void ClipByPlane(const D3DXPLANE &plane);
	AABB BuildAABB(void) const;
};

DXFUNCTIONS_API float Dist_Point_Plane(const DXVector3 &point,const D3DXPLANE &plane);

DXFUNCTIONS_API bool Intr_AABB_Point(const DXVector3 &point,const OBB &box);
DXFUNCTIONS_API bool Intr_AABB_Ray(const AABB&box,const DXVector3&orig,const DXVector3& dir);
DXFUNCTIONS_API bool Intr_AABB_Line(const AABB&box,const DXVector3&start,const DXVector3& end);
DXFUNCTIONS_API bool Intr_AABB_Plane(const AABB &box, const DXPlane& plane);
DXFUNCTIONS_API bool Intr_AABB_OBB(const AABB& aabb, const OBB& obb);
DXFUNCTIONS_API bool Intr_AABB_OBS(const AABB& aabb, const OBS& obs);
DXFUNCTIONS_API bool Intr_OBS_ViewFrustum(const OBS &sphere,const ViewFrustum &frustum);
DXFUNCTIONS_API bool Intr_OBB_ViewFrustum(const OBB &box,const ViewFrustum &frustum);
DXFUNCTIONS_API bool Intr_OBB_Plane(const OBB& box, const DXPlane& plane);
DXFUNCTIONS_API bool Intr_OBB_OBB(const OBB& box1, const OBB& box2);
DXFUNCTIONS_API bool Intr_OBB_OBS(const OBB& box, const OBS& obs);
DXFUNCTIONS_API bool Intr_Point_ViewFrustum(const DXVector3 &point,const ViewFrustum &frustum);
DXFUNCTIONS_API bool Intr_Point_Frustum(const DXVector3 &point,const Frustum &frustum);
DXFUNCTIONS_API DXVector3 Intr_Ray_Plane(const DXPlane& plane,const DXVector3& orig,const DXVector3& dir);
DXFUNCTIONS_API DXVector3 Intr_Ray_Plane(const DXPlane& plane,const DXVector3& orig,const DXVector3& dir, float& distout);

DXFUNCTIONS_API CONTAINMENTTESTRESULT Cont_AABB_In_Frustum(const AABB& aabb, const Frustum& f);
DXFUNCTIONS_API CONTAINMENTTESTRESULT Cont_AABB_In_AABB(const AABB& aabb1, const AABB& aabb2);
DXFUNCTIONS_API CONTAINMENTTESTRESULT Cont_OBS_In_Frustum(const OBS& sphere, const Frustum& f);

#include "Bound.inl"