/* Triangle.cpp
 *
 * Definition of the Triangle class.
 */

#include "Triangle.h"
#include <assert.h>
#include <GL/glut.h>

using namespace std;

const Tuple Triangle::DEF_COLOR = Tuple(1,1,1); // set default color to white
const float Triangle::SHININESS = 10.0;

const double Triangle::MAX_SHRINKFACTOR = 1000.0;
const double Triangle::MIN_SHRINKFACTOR = 0.0;
const double Triangle::DEF_SHRINKFACTOR = 1000.0;
double Triangle::shrinkFactor_ = DEF_SHRINKFACTOR;
bool Triangle::crazyTris_ = false;


Triangle::Triangle(Vertex* v1, Vertex* v2, Vertex* v3)
: Obstruction(),
  v1_(v1),
  v2_(v2),
  v3_(v3),
  colors_(),
  isHole_(false)
{
	// Make sure pointers are non-null
	assert(v1_ && v2_ && v3_);

#ifdef vertConv
	Vertex* tmp = v2;
	v2 = v3;
	v3 = tmp;
#endif

	// Relative vectors for each of the vertices (i.e., distances from v1)
	Tuple rv1 = Tuple();
	Tuple rv2 = Tuple(v2->v() - v1->v());
	Tuple rv3 = Tuple(v3->v() - v1->v());

	// Unit vectors about the coordinate system defined by the triangle
	//   xNormal : direction along v2
	//   yNormal : the normal of the triangle's face
	//   zNormal : the other direction
	// Note, these calculations check that the triangle is not colinear
	Tuple xNormal = (rv2).norm();
	Tuple yNormal = (rv3).cross(rv2).norm();
	Tuple zNormal = xNormal.cross(yNormal);

	// Find the standardized vertices
	sv1_ = Tuple();
	sv2_ = Tuple(
		(rv2).length(),
		0.0,
		0.0);
	sv3_ = Tuple(
		(xNormal * xNormal.dot(rv3)).length(),
		0.0,
		(zNormal * zNormal.dot(rv3)).length());

	// Record the face normal for later use
	normal_ = yNormal;

	/* We must now calculate the rotation matrix used to transform paths when
	 * testing for intersection.  We will do this by composing three rotations
	 * about the axes (or two in the case that rv2 is straight up).  After
	 * computing each rotation, we'll transform the triangle's vertices to make
	 * it easier to compute the next rotation.
	 */

	// Check if rv2 is straight up; if so, we shouldn't rotate on the y-axis
	Tuplex yRotation;
	Tuple yv1;
	Tuple yv2;
	Tuple yv3;
	if (rv2.x() != 0.0 || rv2.z() != 0.0)
	{
		// Compute a and b, where a + bi is a unit complex factor of rotation
		//   for rotating rv2 such that it's in the xy-plain
		double a1 =
			Tuple(1.0, 0.0, 0.0).dot(Tuple(rv2.x(), 0.0, rv2.z()).norm());
		double b1 =
			Tuple(0.0, 0.0, 1.0).dot(Tuple(rv2.x(), 0.0, rv2.z()).norm());

		// Define a matrix to compute complex multiplcation (a + bi) * (z + xi)
		yRotation = Tuplex(
			Tuple(a1, 0.0, b1),
			Tuple(0.0, 1.0, 0.0),
			Tuple(-b1, 0.0, a1));

		// Rotate the triangle about the y-axis
		yv1 = yRotation * rv1;
		yv2 = yRotation * rv2;
		yv3 = yRotation * rv3;
	}
	else
	{
		// No rotation necessary
		yRotation = Tuplex(
			Tuple(1.0, 0.0, 0.0),
			Tuple(0.0, 1.0, 0.0),
			Tuple(0.0, 0.0, 1.0));

		yv1 = rv1;
		yv2 = rv2;
		yv3 = rv3;
	}

	// Compute a and b, where a + bi is a unit complex constant of rotation
	//   for rotating rv2 such that it's along the x-axis
	// Note, this rotaion should be negative, so negate b
	double a2 =
		Tuple(1.0, 0.0, 0.0).dot(Tuple(yv2.x(), yv2.y(), 0.0).norm());
	double b2 =
		-Tuple(0.0, 1.0, 0.0).dot(Tuple(yv2.x(), yv2.y(), 0.0).norm());

	// Define a matrix to compute complex multiplcation (a + bi) * (x + yi)
	Tuplex zRotation(
		Tuple(a2, -b2, 0.0),
		Tuple(b2, a2, 0.0),
		Tuple(0.0, 0.0, 1.0));

	// Rotate the triangle about the z-axis
	Tuple zv1 = zRotation * yv1;
	Tuple zv2 = zRotation * yv2;
	Tuple zv3 = zRotation * yv3;

	// Compute a and b, where a + bi is a unit complex constant of rotation
	//   for rotating rv3 such that it's in the xz-plain
	// Note, this rotaion should be negative, so negate b
	double a3 =
		Tuple(0.0, 0.0, 1.0).dot(Tuple(0.0, zv3.y(), zv3.z()).norm());
	double b3 =
		-Tuple(0.0, 1.0, 0.0).dot(Tuple(0.0, zv3.y(), zv3.z()).norm());

	// Define a matrix to compute complex multiplcation (a + bi) * (z + yi)
	Tuplex xRotation(
		Tuple(1.0, 0.0, 0.0),
		Tuple(0.0, a3, b3),
		Tuple(0.0, -b3, a3));

	// Compose the rotations
	rotation_ = xRotation * zRotation * yRotation;

	// Set up colors
	colors_.resize(NUM_OF_COLORS);
	for (int i = 0; i < (int)colors_.size(); ++i)
	{
		colors_[i] = DEF_COLOR;
	}

}

Triangle::~Triangle()
{
	// Nothing to do
}

void Triangle::setColors(const vector<Tuple>& colors)
{
	assert(colors.size() == NUM_OF_COLORS);
	colors_ = colors;
}

/* draw
 *
 * Draw the triangle at the given coordinates.
 */
void Triangle::draw(bool light) const
{
	// Get the tuples from the vertices for easier access
	Tuple t1 = v1_->v();
	Tuple t2 = v2_->v();
	Tuple t3 = v3_->v();

	Tuple t1n = v1_->litNormal();
	Tuple t2n = v2_->litNormal();
	Tuple t3n = v3_->litNormal();

	if (light)
	{
		glPushMatrix();
			if (crazyTris_)
			{
				Tuple pos = avgPos();
				glTranslatef(pos.x(), pos.y(), pos.z());
			}
			GLfloat matShininess[] = {SHININESS};
			Tuple color = ambient();
			GLfloat ambNDiff[] = {color.x(), color.y(), color.z(), 1.0};
			glMaterialfv(GL_FRONT, GL_SHININESS, matShininess);
			glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, ambNDiff);

			glBegin(GL_TRIANGLES);
				glNormal3f(t1n.x(),t1n.y(),t1n.z());
				glVertex3f(t1.x(), t1.y(), t1.z());
				glNormal3f(t2n.x(),t2n.y(),t2n.z());
				glVertex3f(t2.x(), t2.y(), t2.z());
				glNormal3f(t3n.x(),t3n.y(),t3n.z());
				glVertex3f(t3.x(), t3.y(), t3.z());
			glEnd();
		glPopMatrix();
/*
		GLfloat v1Color[] = {v1_->r(), v1_->g(), v1_->b(), 1.0};
		GLfloat v2Color[] = {v2_->r(), v2_->g(), v2_->b(), 1.0};
		GLfloat v3Color[] = {v3_->r(), v3_->g(), v3_->b(), 1.0};
		glBegin(GL_TRIANGLES);
			glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,v1Color);
			glNormal3f(t1n.x(),t1n.y(),t1n.z());
			glVertex3f(t1.x(), t1.y(), t1.z());
			glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,v2Color);
			glNormal3f(t2n.x(),t2n.y(),t2n.z());
			glVertex3f(t2.x(), t2.y(), t2.z());
			glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,v3Color);
			glNormal3f(t3n.x(),t3n.y(),t3n.z());
			glVertex3f(t3.x(), t3.y(), t3.z());
		glEnd();
*/
	}
	else
	{
		// Draw a triangle with position an colors based on the
		// triangle's three constituent vertices
		glPushMatrix();
		glBegin(GL_TRIANGLES);

			glNormal3f(t1n.x(),t1n.y(),t1n.z());
			glColor3f(v1_->r(), v1_->g(), v1_->b());
			glVertex3f(t1.x(), t1.y(), t1.z());
			
			glNormal3f(t2n.x(),t2n.y(),t2n.z());		
			glColor3f(v2_->r(), v2_->g(), v2_->b());
			glVertex3f(t2.x(), t2.y(), t2.z());

			glNormal3f(t3n.x(),t3n.y(),t3n.z());
			glColor3f(v3_->r(), v3_->g(), v3_->b());
			glVertex3f(t3.x(), t3.y(), t3.z());

		glEnd();
		glPopMatrix();
	}
}

/* pathIntersect
 *
 * Determines if this triangle is on the given path and returns the Beta value,
 * (the percent of path completed before collision).
 */
const double Triangle::pathIntersect(
	const Path& path,
	double radius) const
{
	// Standardize input path to match standardized triangle vertices
	Path sPath = path.translate(sv1_ - v1_->v()).rotate(rotation_);

	// Define a few helpful variables to clarify the following code
	double y1 = sPath.endPos().y();
	double y0 = sPath.begPos().y();

	// Check if the path moves downwards through the buffer on the xz-plain;
	//   offset by the ball's radius so that we consider the edge of the ball
	//   rather than it's center
	if (y1 < y0
		 && y0 >= 0.0 + radius
		 && y1 < 0.0 + radius + BUFFER)
	{
		// We'll fill this with the distance along the path where the ball
		//   touches the buffer on the xz-plane
		double beta;

		// If the ball starts in the buffer, collide instantly; otherwise
		//   just collide when the ball reaches the buffer
		if (y0 < 0.0 + radius + BUFFER)
			beta = 0.0;
		else
			beta = (radius + BUFFER - y0) / (y1 - y0);

		// Make sure beta is in range
		if (beta >= 0.0 && beta < 1.0)
		{
			// Find where the ball touches the xz-plain
			Tuple touchPos =
				sPath.begPos() + beta * (sPath.endPos() - sPath.begPos());

			// Define a few helpful variables to clarify the following code
			double xt = touchPos.x();
			double zt = touchPos.z();

			// Check if the path moves through the triangle
			if (zt > 0.0
				 && xt > zt * (sv3_.x() / sv3_.z())
				 && xt < zt * ((sv3_.x() - sv2_.x()) / sv3_.z()) + sv2_.x())
			{
				return beta;
			}
		}
	}

	// Add EPSILON to err on the safe side (no collision)
	return 1.0 + EPSILON;
}

/* calcNormal
 *
 * Returns the normal of the triangle.
 */
const Tuple Triangle::calcNormal(const Tuple& point) const
{
	(void)point;

	return normal_;
}

const Tuple Triangle::avgPos() const
{
	Tuple avg = v1_->v() + v2_->v() + v3_->v();
	return avg /= shrinkFactor_;
}

void Triangle::toggleCrazyTris() 
{
	crazyTris_ = !crazyTris_;
	if (!crazyTris_)
	{
		shrinkFactor_ = DEF_SHRINKFACTOR;
		cout << "Crazy Triangles: Deactivated" << endl;
	}
	else
	{
		cout << "Crazy Triangles: Activated" << endl;
	}
}

void Triangle::setCrazyTris() 
{
	crazyTris_ = true;
	cout << "Crazy Triangles: Activated" << endl;
}

void Triangle::unsetCrazyTris() 
{
	shrinkFactor_ = DEF_SHRINKFACTOR;
	crazyTris_ = false;
	cout << "Crazy Triangles: Deactivated" << endl;
}

void Triangle::setShrinkFactor(double shrinkFactor) 
{ 
	shrinkFactor_ = shrinkFactor; 

	if (shrinkFactor_ > MAX_SHRINKFACTOR) 
		shrinkFactor_ = MAX_SHRINKFACTOR;
	if (shrinkFactor_ < MIN_SHRINKFACTOR) 
		shrinkFactor_ = MIN_SHRINKFACTOR;
}

void Triangle::shrink() 
{ 
	if (!crazyTris_)
		return;

	shrinkFactor_ *= 1.2; 
	if (shrinkFactor_ > MAX_SHRINKFACTOR) 
		shrinkFactor_ = MAX_SHRINKFACTOR;
	cout << "Conjoining Triangles\n";

}

void Triangle::enlarge() 
{ 
	if (!crazyTris_)
		return;

	shrinkFactor_ /= 1.2; 
	if (shrinkFactor_ < MIN_SHRINKFACTOR) 
		shrinkFactor_ = MIN_SHRINKFACTOR;
	cout << "Separating Triangles\n";

}
