package de.rico.adventure.entity.hotspot;

import javax.media.opengl.GL;

import com.sun.opengl.util.GLUT;

import de.rico.engine.camera.Camera;
import de.rico.engine.camera.culling.FrustumPlane;
import de.rico.engine.camera.culling.ViewingFrustum;
import de.rico.engine.enums.BlendDestFactor;
import de.rico.engine.enums.BlendSourceFactor;
import de.rico.engine.geometry.Line3D;
import de.rico.engine.geometry.Point3D;
import de.rico.engine.geometry.collision.Ray;
import de.rico.engine.util.Tools;

/**
 * This class represents a spherial interactive hotspot.
 * @author Frank Bruns
 */
public class SphericalHotspot extends AbstractInteractiveHotspot
{

	private float radius = 0f;

	private GLUT glut = null;

	/**
	 * Creates a new spherical hot spot with the specified radius at the
	 * specified position. You can specify, if the hotspot needs an
	 * item to be usable.
	 * @param pos position of the hotspot
	 * @param radius radius of the hotspots sphere
	 * @param needsItem needs item to use? --> true=yes, false=no
	 */
	public SphericalHotspot(Point3D pos, float radius, boolean needsItem)
	{
		super(pos,needsItem);
		this.radius = radius;
		this.glut = new GLUT();
	}

	/**
	 * Returns the radius of the spherical hotspot.
	 * @return radius of the hotspot
	 */
	public float getRadius()
	{
		return this.radius;
	}

	/**
	 * Sets the radius of the hotspot
	 * @param radius radius of the hotspot
	 */
	public void setRadius(float radius)
	{
		this.radius = radius;
	}

	/*
	 * (non-Javadoc)
	 * @see de.rico.adventure.entity.hotspot.AbstractInteractiveHotspot#isMouseOverHotspot(float,
	 *      float)
	 */
	@Override
	public boolean isMouseOverHotspot(int screenX, int screenY)
	{
		// if the hotspot isn't enabled do nothing
		if(this.isEnabled() == false) return false;

		Line3D line = Tools.createLineFromScreenCoords(screenX, screenY);

		// create ray from the specified line
		Ray ray = new Ray(line.getStartPoint(), Point3D.subtract(line
				.getEndPoint(), line.getStartPoint()));

		// for explanations see "3D Math Primer for Graphics and
		// Game Development" by Fletcher Dunn and Ian Parberry
		// at page 287.
		//
		// t = a-f <-- t is the requested value
		// a = e * rayDirection
		// e = sphereCenter - rayOrigin
		// f = sqrt(radius*radius - e*e + a*a)

		// calculate the value for t:
		Point3D e = Point3D.subtract(this.getPosition(), ray.getOrigin());

		ray.getDirection().normalize(); // we need the normalized direction
		float a = Point3D.getDotProduct(e, ray.getDirection());

		float term = this.radius * this.radius
				- Point3D.getDotProduct(e, e) + a * a;

		if (term < 0) return false; // i.e. there is no intersection
		else return true;
	}

	/* (non-Javadoc)
	 * @see de.rico.adventure.entity.hotspot.AbstractInteractiveHotspot#getMouseOverHotspot(int, int)
	 */
	@Override
	public Point3D getMouseOverHotspot(int screenX, int screenY)
	{
		// if the hotspot isn't enabled do nothing
		if(this.isEnabled() == false) return null;

		Line3D line = Tools.createLineFromScreenCoords(screenX, screenY);

		// create ray from the specified line
		Ray ray = new Ray(line.getStartPoint(), Point3D.subtract(line
				.getEndPoint(), line.getStartPoint()));

		// for explanations see "3D Math Primer for Graphics and
		// Game Development" by Fletcher Dunn and Ian Parberry
		// at page 287.
		//
		// t = a-f <-- t is the requested value
		// a = e * rayDirection
		// e = sphereCenter - rayOrigin
		// f = sqrt(radius*radius - e*e + a*a)

		// calculate the value for t:
		Point3D e = Point3D.subtract(this.getPosition(), ray.getOrigin());

		ray.getDirection().normalize(); // we need the normalized direction
		float a = Point3D.getDotProduct(e, ray.getDirection());

		float term = this.radius * this.radius
				- Point3D.getDotProduct(e, e) + a * a;

		if (term < 0) return null; // i.e. there is no intersection

		float f = (float) Math.sqrt(term);

		float t = a - f;

		// return point of intersection
		Point3D intersect = ray.getPointAtDistance(t);
		if(intersect.isWithinFrustum(Camera.getInstance().getFrustum()))
			return intersect;
		else return null;
	}

	/* (non-Javadoc)
	 * @see de.rico.adventure.entity.hotspot.AbstractInteractiveHotspot#isWithinFrustum(de.rico.engine.camera.culling.ViewingFrustum)
	 */
	@Override
	public boolean isWithinFrustum(ViewingFrustum frustum)
	{
		// if the hotspot isn't enabled do nothing
		if(this.isEnabled() == false) return false;

		// organize the planes in an array to be able to use
		// a simple loop for treating them
		FrustumPlane[] planes = new FrustumPlane[6];
		planes[0] = frustum.getFrontPlane();
		planes[1] = frustum.getBackPlane();
		planes[2] = frustum.getLeftPlane();
		planes[3] = frustum.getRightPlane();
		planes[4] = frustum.getTopPlane();
		planes[5] = frustum.getBottomPlane();

		// check sphere with the different planes
		for(int i=0; i<planes.length; i++)
		{
			if (planes[i].getA() * this.getPosition().x
					+ planes[i].getB() * this.getPosition().y
					+ planes[i].getC() * this.getPosition().z
					+ planes[i].getD() <= -this.getRadius()) return false;
		}

		// return true if none of the planes caused a return of false
		return true;
	}

	/*
	 * (non-Javadoc)
	 * @see de.rico.adventure.entity.hotspot.AbstractInteractiveHotspot#draw(javax.media.opengl.GL)
	 */
	@Override
	public void draw(GL gl)
	{
		// if the hotspot isn't enabled do nothing
		if(this.isEnabled() == false) return;

		gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT_AND_DIFFUSE,
				new float[] { 1f, 1f, 1f, 0.5f }, 0);
		gl.glEnable(GL.GL_BLEND);
		gl.glDepthMask(false);
		Tools.setUpBlendFunction(gl, BlendSourceFactor.SOURCE_ALPHA,
				BlendDestFactor.ONE_MINUS_SOURCE_ALPHA);

		gl.glPushMatrix();
		gl.glTranslatef(this.getPosition().x, this.getPosition().y, this
				.getPosition().z);
		gl.glRotatef(-90f, 1f, 0f, 0f);
		this.glut.glutSolidSphere(this.radius, 10, 10);
		gl.glPopMatrix();

		gl.glDepthMask(true);
		gl.glDisable(GL.GL_BLEND);
	}
}
