package ps5;

import RayTracer.Ray;
import RayTracer.Renderable;
import RayTracer.Surface;
import RayTracer.Vector3D;

import java.awt.Color;
import java.util.Vector;


public class Parallelpiped implements Renderable {
	Surface surface;
	Vector3D ptCentroid;
	float xExtent;
	float yExtent;
	float zExtent;
	float radius;
	float radSqr;
	
	float t;
	
	private static final int XPLANE = 0;
	private static final int YPLANE = 1;
	private static final int ZPLANE = 2;
	
	Vector3D xNormal;
	Vector3D yNormal;
	Vector3D zNormal;
	
	//TODO: make it generalized, using angles
	float xzAngle;
	
	public Parallelpiped (Surface s, Vector3D c, float x, float y, float z) {	//DONE: Appropriate parameters for parallelpiped
		// the centroid is the center of the parallelpiped, and the extents
		// extend equally on both sides for length (extent / 2)
		surface = s;
		ptCentroid = c;
		xExtent = x;
		yExtent = y;
		zExtent = z;
		radius = (float)Math.sqrt((x * 0.5) * (x * 0.5) +
								  (y * 0.5) * (y * 0.5) +
								  (z * 0.5) * (z * 0.5));
		radSqr = radius * radius;

	float xExtentTop;
	float xExtentBottom;
	float yExtentTop;
	float yExtentBottom;
	float zExtentTop;
	float zExtentBottom;

		xNormal = new Vector3D(1, 0, 0);
		yNormal = new Vector3D(0, 1, 0);
		zNormal = new Vector3D(0, 0, 1);		
	}
	
	public boolean intersect (Ray ray) {
		float dx = ptCentroid.x - ray.getRayOrigin().x;
		float dy = ptCentroid.y - ray.getRayOrigin().y;
		float dz = ptCentroid.z - ray.getRayOrigin().z;
		
		Vector3D rayDirection = ray.getRayDirection();
		
		float v = rayDirection.dot(dx, dy, dz);

        // Do the following quick check to see if there is even a chance
        // that an intersection here might be closer than a previous one
        if (v - radius > ray.getT())
            return (false);

		//HACK: quick check of intersecting sphere
        float t = radSqr + v*v - dx*dx - dy*dy - dz*dz;
        if (t < 0)
            return (false);
		
		//does it intersect?
		
		if (this.intersection(ray) == null)
			return (false);
		else {
	        ray.setT(t);
		    ray.setObject(this);
			return (true);
		}
	}
	
	public Color Shade (Ray ray, Vector lights, Vector objects, Color bgnd) {
        //   1. the point of intersection (p)
        //   2. a unit-length surface normal (n)
        //   3. a unit-length vector towards the ray's origin (v)
		//TODO: figure out p, v, and n vectors
		
		Vector3D p = new Vector3D();
        Vector3D v = new Vector3D(-(ray.getRayDirection()).x, -(ray.getRayDirection()).y, -(ray.getRayDirection()).z);
		Vector3D n = new Vector3D();
	
		return (surface.Shade(p, n, v, lights, objects, bgnd, ray.illuminosityIntensity));
	}
	
	private Vector3D intersection (Ray ray) {
		//return the point of intersection if it exists.
		//return null if the ray does not intersect this
		Vector3D rayDirection = ray.getRayDirection();
		Vector3D ptIntersection = null;
		//now figure out if it actually intersects the parallelpiped.
        float xCoord = ptCentroid.x;
        float yCoord = ptCentroid.y;
        float zCoord = ptCentroid.z;
		
		//since rayDirection is pointing away from the ray origin,
		//negative dot products mean that the face is potentially "visible"
		float faceX = rayDirection.dot(xNormal);
		float faceY = rayDirection.dot(yNormal);
		float faceZ = rayDirection.dot(zNormal);
		
		//xCoord, yCoord, zCoord denote the x, y, and z coordinates of the
		//three possible planes through them that can be hit (the boundaries of this).
		float xE = xExtent * 0.5f;
		float yE = yExtent * 0.5f;
		float zE = zExtent * 0.5f;
		
		xCoord += (faceX < 0) ? xE : -xE;
		yCoord += (faceY < 0) ? yE : -yE;
		zCoord += (faceZ < 0) ? zE : -zE;
		
		Vector3D face1 = solveRayAndPlaneIntersection(ray, xCoord, XPLANE);
		float face1t = t;	//TODO: are they different?
		Vector3D face2 = solveRayAndPlaneIntersection(ray, yCoord, YPLANE);
		float face2t = t;
		Vector3D face3 = solveRayAndPlaneIntersection(ray, zCoord, ZPLANE);
		float face3t = t;
		
		//now, AT MOST ONE of face1, face2, and face3 is inside the x, y, and z
		//bounds of this.  Find out!
		
		if ((face1.y > ptCentroid.y - yE) && (face1.y < ptCentroid.y + yE) &&
			(face1.z > ptCentroid.z - zE) && (face1.z < ptCentroid.z + zE)) {//it's on the xplane
			ptIntersection = face1;
			t = face1t;
		}
		if ((face2.x > ptCentroid.x - xE) && (face2.x < ptCentroid.x + xE) &&
			(face2.z > ptCentroid.z - zE) && (face2.z < ptCentroid.z + zE)) {//it's on the xplane
			ptIntersection = face2;
			t = face2t;
		}
		if ((face3.x > ptCentroid.x - xE) && (face3.x < ptCentroid.x + xE) &&
			(face3.y > ptCentroid.y - yE) && (face3.y < ptCentroid.y + yE)) {//it's on the xplane
			ptIntersection = face3;
			t = face3t;
		}
		
		return (ptIntersection);	//if it was not in any of the planes, it'll be null (no intersection)
	}
	
	private Vector3D solveRayAndPlaneIntersection (Ray ray, float pointGiven, int solveGiven) {
		//returns the point of intersection
		//ray.direction = (Xv, Yv, Zv)
		//ray.origin = (Xo, Yo, Zo)
		//either Xp, Yp, or Zp is in pointGiven, depending on solveGiven
		
		//Xp = Xo + Xvt -> (Xp - Xo) / Xv
		//Yp = Yo + Yvt -> (Yp - Yo) / Yv
		//Zp = Zo + Zvt -> (Zp - Zo) / Zv		
		
		Vector3D returnVector3D = null;
		//float t;	-- t is the distance
		float pt2;
		float pt3;
		
		Vector3D O = ray.getRayOrigin();
		Vector3D V = ray.getRayDirection();
		
		switch (solveGiven) {
			case XPLANE:
				//x = pointGiven
				t = (pointGiven - O.x) / V.x;
				pt2 = O.y + V.y * t;	//y
				pt3	= O.z + V.z * t;	//z
				
				returnVector3D = new Vector3D(pointGiven, pt2, pt3);
				break;
			case YPLANE:
				//y = pointGiven
				t = (pointGiven - O.y) / V.y;

				pt2 = O.x + V.x * t;	//x
				pt3	= O.z + V.z * t;	//z

				returnVector3D = new Vector3D(pt2, pointGiven, pt3);
				break;
			case ZPLANE:
				//z = pointGiven
				t = (pointGiven - O.z) / V.z;
				
				pt2 = O.x + V.x * t;	//x
				pt3	= O.y + V.y * t;	//y

				returnVector3D = new Vector3D(pt2, pt3, pointGiven);
				break;
		}		
		if (returnVector3D == null)
			System.exit(-1);	//should raise exception here
		
		return (returnVector3D);	
	}	
}
