#include "Plane.h"
#include "Vector.h"
#include "Line.h"
#include <assert.h>
#include "BoundingBox.h"

#include <d3dUtility.h>

namespace cbk
{
	Plane::Plane(void)
	{
		pos[0] = 0.0f;
		pos[1] = 0.0f;
		pos[2] = 0.0f;
		pos[3] = 0.0f;
	}

	Plane::Plane(const Vector& abc, const float& d)
	{
		CreatePlaneFromABCD(abc, d);
	}

	Plane::Plane(const Vector& n, const Vector& p)
	{
		CreatePlaneFromPointNormal(n, p);
	}

	Plane::Plane(const Vector& p1, const Vector& p2, const Vector& p3)
	{
		CreatePlaneFromPoints(p1, p2, p3);
	}

	Plane& Plane::CreatePlaneFromABCD(const Vector& abc, const float& d)
	{
		pos[0] = abc.X;
		pos[1] = abc.Y;
		pos[2] = abc.Z;
		pos[3] = d;
		normalize();

		return *this;
	}

	Plane& Plane::CreatePlaneFromABCD(const float& a, const float& b, const float& c, const float& d)
	{
		pos[0] = a;
		pos[1] = b;
		pos[2] = c;
		pos[3] = d;
		normalize();

		return *this;
	}

	Plane& Plane::CreatePlaneFromPointNormal(const Vector& n, const Vector& p)
	{
		pos[0] = n.X;
		pos[1] = n.Y;
		pos[2] = n.Z;
		pos[3] = -(n.dot(p));
		normalize();

		return *this;
	}

	Plane& Plane::CreatePlaneFromPoints(const Vector& p1, const Vector& p2, const Vector& p3)
	{
		Vector n = ((p2 - p1).cross(p3 - p1));
		pos[0] = n.X;
		pos[1] = n.Y;
		pos[2] = n.Z;
		pos[3] = -(n.dot(p1));
		normalize();

		return *this;
	}

	const Vector Plane::getNormal(void) const
	{
		return Vector(pos[0], pos[1], pos[2]);
	}

	Plane& Plane::setNormal(const Vector& normal)
	{
		pos[0] = normal.X;
		pos[1] = normal.Y;
		pos[2] = normal.Z;
		normalize();
		return *this;
	}

	const float& Plane::getD(void) const
	{
		return pos[3];
	}

	Plane& Plane::setD(const float& d)
	{
		pos[3] = d;
		return *this;
	}

	const Plane::Direction Plane::pointInFront(const Vector& p) const
	{
		const float& dis = (pos[0] * p.X) + (pos[1] * p.Y) + (pos[2] * p.Z) + pos[3];

		if (dis > 0.0f)
			return DIR_Front;
		else if (dis < 0.0f)
			return DIR_Behind;
		else
			return DIR_Equal;
	}

	const Plane::Direction Plane::boundingBoxInFront(const BoundingBox& p) const
	{
		const Vector& center = p.getCenter();
		const Vector& minPoint = p.getLesser();
		const Vector& maxPoint = p.getGreater();

		const Vector& normal = getNormal();
		
		const float mi = (minPoint.X * abs(normal.X)) + (minPoint.Y * abs(normal.Y)) + (minPoint.Z * abs(normal.Z));
		const float ma = (maxPoint.X * abs(normal.X)) + (maxPoint.Y * abs(normal.Y)) + (maxPoint.Z * abs(normal.Z));
		const float s = normal.dot(center) - pos[3];

		if ((s >= mi) && (s <= ma))
			return DIR_Equal;
		else if (s < mi)
			return DIR_Behind;
		else
			return DIR_Equal;
	}

	void Plane::normalize(void)
	{
		const float mag = sqrt((pos[0] * pos[0]) + (pos[1] * pos[1]) + (pos[2] * pos[2]));
		pos[0] /= mag;
		pos[1] /= mag;
		pos[2] /= mag;
		pos[3] /= mag;
	}

	const Vector Plane::getPoint(void) const
	{
		const Vector normal(pos[0], pos[1], pos[2]);
		return normal * -pos[3];
	}

	const Plane::Direction Plane::Intersection(const Plane& p) const
	{
		const Vector t(pos[0], pos[1], pos[2]);
		const Vector o(p.pos[0], p.pos[1], p.pos[2]);
		const float& dot = o.dot(t);

		if (dot > 0.0f)
			return DIR_Front;
		else if (dot < 0.0f)
			return DIR_Behind;
		else
			return DIR_Equal;
	}

	const Plane::Direction Plane::Intersection(const Line& l) const
	{
		const Vector& p1 = l.GetPoint(0);
		const Vector& p2 = l.GetPoint(1);
		const Vector& normal = getNormal();

		const float& den = normal.dot(p2 - p1);
		const Vector& t1 = normal * p1;
		const float div = pos[3] - t1.X - t1.Y - t1.Z;
		if (den == 0)
		{
			if (div == 0)
				return DIR_Equal;
			else
			{
				const Vector& pp = getPoint();
				const Vector& ray = pp - p1;
				const float& dot = normal.dot(ray);
				if (dot == 0.0f)
					return DIR_Equal;
				else if (dot > 0.0f)
					return DIR_Front;
				else
					return DIR_Behind;
			}
		}

		const float r = div / den;
		if ((r >= 0.0f) && (r <= 1.0f))
			return DIR_Equal;
		else if (r > 1.0f)
			return DIR_Front;
		else
			return DIR_Behind;
	}

	void Plane::draw(IDirect3DDevice9* device) const
	{
		const Vector& point = getPoint();
		const Vector& normal = getNormal();

		const BoundingBox bb(Vector(-0.1f, -50.0f, -50.0f), Vector(0.1f, 50.0f, 50.0f));

		D3DXMATRIX w, t;

		const Vector x(1.0f, 0.0f, 0.0f); 
		const D3DXVECTOR3& axis = normal.cross(x);
		const float& xDot = normal.dot(x);

		D3DXMatrixRotationAxis(&w, &axis, acos(xDot));
		D3DXMatrixTranslation(&t, point.X, point.Y + 78.0f, point.Z);
		w = w * t;

		device->SetTransform(D3DTS_WORLD, &w);
		bb.draw(device);
	}
}