#include "BGCPString.h"
#include "MechShell.h"
#include "intersections.h"
#include "BoundingBox.h"

#include <assert.h>
#include <iostream>

#define FILE_NUMBER FILE_NO_TESTS

void testBGCPString1()
{
	BGCPString str1 = "Hello ";
	BGCPString str2 = "world";
	BGCPString cat = str1+str2;
	BGCPString str3 (L"Hello world", CP_UTF16_LE);
	bool result = (cat == str3);
	assert(result);
}

void testBGCPString2()
{
	{
		BGCPString str = "  The  quick    brown fox jumped over the lazy old dog";
		auto words = str.split(" ", false);

		assert(words.size() == 10);
		assert(words[0] == "The");
		assert(words[3] == "fox");
		assert(words[9] == "dog");
	}

	{
		BGCPString str = "The  fox jumped    ";
		auto words = str.split(" ", false);
		assert(words.size() == 3);
	}
}

void testBGCPString3()
{
	BGCPString str = "Hello world";

	BGCPString word1 = str.copyPart(0, 5);
	assert(word1 == "Hello");

	BGCPString word2 = str.copyPart(6, 5);
	assert(word2 == "world");
}

void testBGCPString2w()
{
	BGCPString str (L"The quick brown fox jumped over the lazy old dog", CP_UTF16_LE);

	auto words = str.split(" ", false);
	assert(words[0] == "The");
	assert(words[3] == "fox");
	assert(words[9] == "dog");
}

void testBGCPStringSplit()
{
	BGCPString selectionSet = ",,SMK\\MRAID.SMK,,,,,,,,,,,,,,SMK\\MEND.SMK,,,,,";
	auto words = selectionSet.split(",", true);
	assert(words.size() == 22);

	for (size_t i = 0; i < 22; ++i) {
		const BGCPString& str = words[i];
		if (i == 2) {
			assert(str == "SMK\\MRAID.SMK");
		} else if (i == 16) {
			assert(str == "SMK\\MEND.SMK");
		} else {
			assert(str.empty());
		}
	}
}

void test_BGCPString_equalsNoCase()
{
	BGCPString str1 (L"fox", CP_UTF16_LE);
	BGCPString str2 (L"dog", CP_UTF16_LE);
	BGCPString str3 ("fox", CP_ASCII);
	BGCPString str4 ("dog", CP_ASCII);

	assert(str1.equalsNoCase(str1));
	assert(str2.equalsNoCase(str2));
	assert(str3.equalsNoCase(str3));
	assert(str4.equalsNoCase(str4));
	assert(str1.equalsNoCase(str3));
	assert(str2.equalsNoCase(str4));
	assert(!str1.equalsNoCase(str2));
	assert(!str1.equalsNoCase(str4));
	assert(!str2.equalsNoCase(str3));
}

void testMechShellVars()
{
	MechShell shell;
	shell.setVar(BGCPString("chassis"), BGCPString("0"));
	shell.setVar(BGCPString("MEKnames"), BGCPString("ELE,STM,TBR"));

	{
		BGCPString result;
		shell.getVar(BGCPString("chassis"), result);
		assert(result == "0");
	}

	{
		BGCPString val = "%chassis%";
		shell.evaluate(val, true);
		assert(val == "0");
	}

	{
		BGCPString sel = "chassis,%MEKnames%";
		BGCPString result;
		shell.selectStr(sel, result);
		assert(result == "ELE");
	}
}

void testMechShellCondition()
{
	MechShell shell;
	shell.setVar(BGCPString("mission"), BGCPString("0"));
	bool cndRes1 = shell.checkCondition("%mission%=0");
	assert(cndRes1);

	bool cndRes2 = shell.checkCondition("%mission%=1");
	assert(!cndRes2);
}

void testIntersections()
{
	int errors = 0;
	{
		// The y-plane and the y axis should intersect in the origin
		Plane p(Point3D(0.0, 0.0, 0.0), Point3D(1.0, 0.0, 0.0), Point3D(0.0, 0.0, 1.0));
		StraightLine sl(Point3D(0.0, 0.0, 0.0), Point3D(0.0, 1.0, 0.0));
		Point3D intersection;
		if (p.intersects(sl, intersection) != ObjectsIntersect
		||  intersection.length() > 0.001) 
		{
			++errors;
		}
	}

	{
		// Similar to before, but the plane is shifted by 1 along the y-axis
		Plane p(Point3D(0.0, 1.0, 0.0), Point3D(1.0, 1.0, 0.0), Point3D(0.0, 1.0, 1.0));
		StraightLine sl(Point3D(0.0, 0.0, 0.0), Point3D(0.0, 1.0, 0.0));
		Point3D intersection;
		if (p.intersects(sl, intersection) != ObjectsIntersect
		||  (intersection - Point3D(0.0, 1.0, 0.0)).length() > 0.001) 
		{
			++errors;
		}
	}

	{
		// The y=0 plane and the x axis should be detected as parallel
		Plane p(Point3D(0.0, 0.0, 0.0), Point3D(1.0, 0.0, 0.0), Point3D(0.0, 0.0, 1.0));
		StraightLine sl(Point3D(0.0, 0.0, 0.0), Point3D(1.0, 0.0, 0.0));
		Point3D intersection;
		if (p.intersects(sl, intersection) != ObjectsParallel) 
		{
			++errors;
		}
	}

	{
		// test intersection between a line segment and a plane and greater distances
		Plane p(Point3D(100.0, 0.0, 0.0), Point3D(100.0, 1.0, 0.0), Point3D(100.0, 0.0, 1.0));
		StraightLine sl(Point3D(0.0, 5.0, 0.0), Point3D(1.0, 5.0, 0.0));
		Point3D intersection;
		Point3D expected(100.0, 5.0, 0.0);
		if (p.intersects(sl, intersection) != ObjectsIntersect
		||  (intersection - expected).length() > 0.001) 
		{
			++errors;
		}
	}


	// Test line-line intersection: parallel line segments
	{
		LineSegment ls1(Point3D(0, 0, 0), Point3D(1, 0, 0));
		LineSegment ls2(Point3D(0, 0.5, 0), Point3D(1, 0.5, 0));
		Point3D p;
		if (ls1.findPointBetween(ls2, p) != ObjectsParallel)
		{
			++errors;
		}
	}

	// Test line-line intersection: line segments do not intersect, but are not parallel. Then there is a point exactly in between them
	{
		LineSegment ls1(Point3D(0, 0, 0), Point3D(1, 0, 1));
		LineSegment ls2(Point3D(1, 1, 0), Point3D(0, 1, 1));
		Point3D p;
		Point3D expected(0.5, 0.5, 0.5);
		auto findPointResult = ls1.findPointBetween(ls2, p);
		if (findPointResult == ObjectsParallel || (p-expected).length() > 0.001)
		{
			++errors;
		}
	}

	// Test line-line intersection: line segments that do intersect, but are not parallel. Check if the algorithm finds the precise location.
	{
		LineSegment ls1(Point3D(0, 0, 0), Point3D(1, 0, 1));
		LineSegment ls2(Point3D(1, 0, 0), Point3D(0, 0, 1));
		Point3D p;
		Point3D expected(0.5, 0, 0.5);
		auto findResult = ls1.findPointBetween(ls2, p);
		//if (findResult != ObjectsParallel // forget this: cannot reliably determine this with floating point prediction?
		if ((p-expected).length() > 0.001)
		{
			++errors;
		}
	}

	// Test plane-plane intersections: parallel planes
	{
		Plane p1(Point3D(0, 0, 0), Point3D(1, 0, 0));
		Plane p2(Point3D(1, 0, 0), Point3D(1, 0, 0));
		StraightLine sl;
		if (p1.intersects(p2, sl) != ObjectsParallel) {
			++errors;
		}
	}

	// Test plane-plane intersections: identical planes
	{
		Plane p1(Point3D(0, 0, 0), Point3D(1, 0, 0));
		Plane p2(Point3D(0, 0, 0), Point3D(1, 0, 0));
		StraightLine sl;
		if (p1.intersects(p2, sl) != ObjectsParallel) {
			++errors;
		}
	}

	// Test plane-plane intersections: intersecting planes
	{
		Plane p1(Point3D(0, 0, 0), Point3D(1, 0, 0));
		Plane p2(Point3D(0, 0, 0), Point3D(0, 1, 0));
		StraightLine sl;
		auto intersectionTestResult = p1.intersects(p2, sl);
		if (intersectionTestResult == ObjectsParallel || sl.dir.x != 0 || sl.dir.y != 0 || sl.dir.z == 0) {
			++errors;
		}
	}

	if (errors > 0) {
		COUT << errors << " errors while testing intersection code" << std::endl;
	}
}

void testPolygonIntersections()
{
	int errors = 0;
	Mesh mesh;

	// Create a quad in the x-z plane
	auto i0 = mesh.addPoint(0.0, 0.0, 0.0);
	auto i1 = mesh.addPoint(1.0, 0.0, 0.0);
	auto i2 = mesh.addPoint(1.0, 0.0, 1.0);
	auto i3 = mesh.addPoint(0.0, 0.0, 1.0);

	MeshPolygon* poly1 = mesh.addQuad();
	poly1->setVertexIndex(0, i0);
	poly1->setVertexIndex(1, i1);
	poly1->setVertexIndex(2, i2);
	poly1->setVertexIndex(3, i3);

	// Check for collision of a perpendicular polygon
	{
		auto j0 = mesh.addPoint(0.0,  1.0, 0.5);
		auto j1 = mesh.addPoint(1.0,  1.0, 0.5);
		auto j2 = mesh.addPoint(0.5, -1.0, 0.5);

		MeshPolygon* poly2 = mesh.addTriangle();
		poly2->setVertexIndex(0, j0);
		poly2->setVertexIndex(1, j1);
		poly2->setVertexIndex(2, j2);
		mesh.cacheTransformedVertices(Matrix());
		bool test1ok = poly1->collides(poly2);
		bool test2ok = poly2->collides(poly1);
		if (!test1ok || !test2ok) {
			COUT << "Polygon collision test 1 fails" << std::endl;
		}
	}

	// Check that a parallel polygon does not collide
	{
		auto j0 = mesh.addPoint(0.0, 0.3, 0.0);
		auto j1 = mesh.addPoint(1.0, 0.3, 0.0);
		auto j2 = mesh.addPoint(1.0, 0.3, 1.0);
		auto j3 = mesh.addPoint(0.0, 0.3, 1.0);

		MeshPolygon* poly3 = mesh.addQuad();
		poly3->setVertexIndex(0, j0);
		poly3->setVertexIndex(1, j1);
		poly3->setVertexIndex(2, j2);
		poly3->setVertexIndex(3, j3);
		mesh.cacheTransformedVertices(Matrix());
		bool test1ok = !poly1->collides(poly3);
		bool test2ok = !poly3->collides(poly1);
		if (!test1ok || !test2ok) {
			COUT << "Polygon collision test 2 fails" << std::endl;
		}
	}

	// Test case where polygons are not parallel but do not intersect
	{
		auto j0 = mesh.addPoint(0.0, 0.5, 0.5);
		auto j1 = mesh.addPoint(1.0, 0.5, 0.5);
		auto j2 = mesh.addPoint(1.0, 1.3, 1.0);
		auto j3 = mesh.addPoint(0.0, 1.3, 1.0);

		MeshPolygon* poly3 = mesh.addQuad();
		poly3->setVertexIndex(0, j0);
		poly3->setVertexIndex(1, j1);
		poly3->setVertexIndex(2, j2);
		poly3->setVertexIndex(3, j3);
		mesh.cacheTransformedVertices(Matrix());
		bool test1ok = !poly1->collides(poly3);
		bool test2ok = !poly3->collides(poly1);
		if (!test1ok || !test2ok) {
			COUT << "Polygon collision test 3 fails" << std::endl;
		}
	}

	{
		// Check distance between a point a polygon: point is inside of polygon, then distance should be zero
		auto j0 = mesh.addPoint(0.0, 0.0, 0.0);
		auto j1 = mesh.addPoint(1.0, 0.0, 0.0);
		auto j2 = mesh.addPoint(1.0, 0.0, 1.0);
		auto j3 = mesh.addPoint(0.0, 0.0, 1.0);

		MeshPolygon* poly4 = mesh.addQuad();
		poly4->setVertexIndex(0, j0);
		poly4->setVertexIndex(1, j1);
		poly4->setVertexIndex(2, j2);
		poly4->setVertexIndex(3, j3);
		mesh.cacheTransformedVertices(Matrix());

		double dist = poly4->distanceTo(Point3D(0.5, 0.0, 0.5));
		if (dist > 0.0) {
			COUT << "Polygon to point distance test 1 failed" << std::endl;
		}

		double dist2 = poly4->distanceTo(Point3D(1.5, 0.0, 0.5));
		if (dist2 != 0.5) {
			COUT << "Polygon to point distance test 2 failed" << std::endl;
		}

	}

	//mesh.saveToObj(L"c:\\temp\\testMesh.obj");
}

void testBoundingBoxes()
{
	{
		// A newly constructed BB isn't valid
		BoundingBox bb;
		assert(!bb.valid());
	}

	{
		// A bb constructed with at least one point is valid
		Point3D a, b;
		BoundingBox bb(a, b);
		assert(bb.valid());
	}

	{
		// A bb with that was fed at least one point is valid
		BoundingBox bb;
		bb.update(Point3D());
		assert(bb.valid());
	}

	{
		// test with two points
		BoundingBox bb;
		bb.update(Point3D(1, 1, 1));
		bb.update(Point3D(2, 2, 2));
		assert(bb.valid());

		Point3D expectedMin = Point3D(1, 1, 1);
		double distMin = (expectedMin - bb.min()).length();
		assert(distMin < 0.001);

		Point3D expectedMax = Point3D(2, 2, 2);
		double distMax = (expectedMax - bb.max()).length();
		assert(distMax < 0.001);
	}

	{
		// Two boxes that intersect
		BoundingBox bb1(Point3D(0, 0, 0), Point3D(2, 2, 2));
		BoundingBox bb2(Point3D(1, 1, 1), Point3D(3, 3, 3));
		assert(bb1.intersects(bb2));
	}

	{
		// Two boxes that do not intersect
		BoundingBox bb1(Point3D(0, 0, 0), Point3D(1, 1, 1));
		BoundingBox bb2(Point3D(2, 2, 2), Point3D(3, 3, 3));
		assert(!bb1.intersects(bb2));
	}
}

void testFindMeshEdges()
{
	SET_SCOPE;
	BGCPString filePath(getDataDir());
	filePath.addPathSep("campaigns");
	filePath.addPathSep("mw2-31stcc");
	filePath.addPathSep("box.obj");

	Mesh mesh;
	if (!mesh.loadFromObj(filePath.getAsPath())) {
		COUT << "Cannot run testFixHoles because the required file is missing" << std::endl;
		RETURN;
	}

	mesh.findMeshEdges();

	// After calling findMeshEdges for a cube, the mesh's water tight flag must be set ...
	assert((mesh.getFlags() & RenderableObject::Flag_waterTight) != 0);

	RETURN;
}

void testFixHoles()
{
	SET_SCOPE;
	BGCPString filePath(getDataDir());
	filePath.addPathSep("weapons");
	filePath.addPathSep("test-fix.obj");

	Mesh mesh;
	if (!mesh.loadFromObj(filePath.getAsPath())) {
		COUT << "Cannot run testFixHoles because the required file is missing" << std::endl;
		RETURN;
	}

	mesh.findMeshEdges();
	bool result = mesh.fillHoles();
	if (!result) {
		COUT << "testFixHoles failed" << std::endl;
	}

	BGCPString savePath(getDataDir());
	savePath.addPathSep("temp");
	savePath.addPathSep("test-fixed.obj");
	mesh.saveToObj(savePath.getAsPath());

	RETURN;
}

void runTests()
{
	// Automated tests
	testBGCPString1();
	testBGCPString2();
	testBGCPString3();
	testBGCPString2w();
	testBGCPStringSplit();
	testMechShellVars();
	testMechShellCondition();
	testIntersections();
	testPolygonIntersections();
	testBoundingBoxes();

	testFindMeshEdges();

	// Tests where the result may have to be inspected by the user
	testFixHoles();
}
