package client.renderer;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import org.lwjgl.opengl.GL11;

import common.util.BoundingBox;
import common.util.ExtMath;

public class Frustum {
	private static class ClippingHelper {
		private final FloatBuffer projection = ByteBuffer.allocateDirect(16 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
		private final FloatBuffer modelview = ByteBuffer.allocateDirect(16 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
		private final float[][] frustum = new float[6][4];
		private final float[] projectionMat = new float[16];
		private final float[] modelviewMat = new float[16];
		private final float[] clipMat = new float[16];

		private static double dot(float[] data, double x, double y, double z) {
			return (double)data[0] * x + (double)data[1] * y + (double)data[2] * z + (double)data[3];
		}

		private static void normalize(float[] data) {
			float f = ExtMath.sqrtf(data[0] * data[0] + data[1] * data[1] + data[2] * data[2]);
			data[0] /= f;
			data[1] /= f;
			data[2] /= f;
			data[3] /= f;
		}

		public boolean isInFrustum(double x1, double y1, double z1, double x2, double y2, double z2) {
			for(int i = 0; i < 6; ++i) {
				float[] data = this.frustum[i];
				if(dot(data, x1, y1, z1) <= 0.0D && dot(data, x2, y1, z1) <= 0.0D && dot(data, x1, y2, z1) <= 0.0D
						&& dot(data, x2, y2, z1) <= 0.0D && dot(data, x1, y1, z2) <= 0.0D && dot(data, x2, y1, z2) <= 0.0D
						&& dot(data, x1, y2, z2) <= 0.0D && dot(data, x2, y2, z2) <= 0.0D)
					return false;
			}
			return true;
		}

		public void init() {
			this.projection.clear();
			this.modelview.clear();
			GL11.glGetFloatv(2983, this.projection);
			GL11.glGetFloatv(2982, this.modelview);
			float[] proj = this.projectionMat;
			float[] view = this.modelviewMat;
			this.projection.flip().limit(16);
			this.projection.get(proj);
			this.modelview.flip().limit(16);
			this.modelview.get(view);
			this.clipMat[0] = view[0] * proj[0] + view[1] * proj[4] + view[2] * proj[8] + view[3] * proj[12];
			this.clipMat[1] = view[0] * proj[1] + view[1] * proj[5] + view[2] * proj[9] + view[3] * proj[13];
			this.clipMat[2] = view[0] * proj[2] + view[1] * proj[6] + view[2] * proj[10] + view[3] * proj[14];
			this.clipMat[3] = view[0] * proj[3] + view[1] * proj[7] + view[2] * proj[11] + view[3] * proj[15];
			this.clipMat[4] = view[4] * proj[0] + view[5] * proj[4] + view[6] * proj[8] + view[7] * proj[12];
			this.clipMat[5] = view[4] * proj[1] + view[5] * proj[5] + view[6] * proj[9] + view[7] * proj[13];
			this.clipMat[6] = view[4] * proj[2] + view[5] * proj[6] + view[6] * proj[10] + view[7] * proj[14];
			this.clipMat[7] = view[4] * proj[3] + view[5] * proj[7] + view[6] * proj[11] + view[7] * proj[15];
			this.clipMat[8] = view[8] * proj[0] + view[9] * proj[4] + view[10] * proj[8] + view[11] * proj[12];
			this.clipMat[9] = view[8] * proj[1] + view[9] * proj[5] + view[10] * proj[9] + view[11] * proj[13];
			this.clipMat[10] = view[8] * proj[2] + view[9] * proj[6] + view[10] * proj[10] + view[11] * proj[14];
			this.clipMat[11] = view[8] * proj[3] + view[9] * proj[7] + view[10] * proj[11] + view[11] * proj[15];
			this.clipMat[12] = view[12] * proj[0] + view[13] * proj[4] + view[14] * proj[8] + view[15] * proj[12];
			this.clipMat[13] = view[12] * proj[1] + view[13] * proj[5] + view[14] * proj[9] + view[15] * proj[13];
			this.clipMat[14] = view[12] * proj[2] + view[13] * proj[6] + view[14] * proj[10] + view[15] * proj[14];
			this.clipMat[15] = view[12] * proj[3] + view[13] * proj[7] + view[14] * proj[11] + view[15] * proj[15];
			float[] f0 = this.frustum[0];
			f0[0] = this.clipMat[3] - this.clipMat[0];
			f0[1] = this.clipMat[7] - this.clipMat[4];
			f0[2] = this.clipMat[11] - this.clipMat[8];
			f0[3] = this.clipMat[15] - this.clipMat[12];
			normalize(f0);
			float[] f1 = this.frustum[1];
			f1[0] = this.clipMat[3] + this.clipMat[0];
			f1[1] = this.clipMat[7] + this.clipMat[4];
			f1[2] = this.clipMat[11] + this.clipMat[8];
			f1[3] = this.clipMat[15] + this.clipMat[12];
			normalize(f1);
			float[] f2 = this.frustum[2];
			f2[0] = this.clipMat[3] + this.clipMat[1];
			f2[1] = this.clipMat[7] + this.clipMat[5];
			f2[2] = this.clipMat[11] + this.clipMat[9];
			f2[3] = this.clipMat[15] + this.clipMat[13];
			normalize(f2);
			float[] f3 = this.frustum[3];
			f3[0] = this.clipMat[3] - this.clipMat[1];
			f3[1] = this.clipMat[7] - this.clipMat[5];
			f3[2] = this.clipMat[11] - this.clipMat[9];
			f3[3] = this.clipMat[15] - this.clipMat[13];
			normalize(f3);
			float[] f4 = this.frustum[4];
			f4[0] = this.clipMat[3] - this.clipMat[2];
			f4[1] = this.clipMat[7] - this.clipMat[6];
			f4[2] = this.clipMat[11] - this.clipMat[10];
			f4[3] = this.clipMat[15] - this.clipMat[14];
			normalize(f4);
			float[] f5 = this.frustum[5];
			f5[0] = this.clipMat[3] + this.clipMat[2];
			f5[1] = this.clipMat[7] + this.clipMat[6];
			f5[2] = this.clipMat[11] + this.clipMat[10];
			f5[3] = this.clipMat[15] + this.clipMat[14];
			normalize(f5);
		}
	}
	
	private static final ClippingHelper CLIP = new ClippingHelper();

	private static double xPos;
	private static double yPos;
	private static double zPos;

	public static void setPosition(double x, double y, double z) {
		CLIP.init();
		xPos = x;
		yPos = y;
		zPos = z;
	}

	public static boolean isInFrustum(BoundingBox bb) {
		return CLIP.isInFrustum(bb.minX - xPos, bb.minY - yPos, bb.minZ - zPos, bb.maxX - xPos, bb.maxY - yPos, bb.maxZ - zPos);
	}
}
