package speiger.src.coreengine.rendering.input.camera;

import speiger.src.coreengine.math.vector.floats.Vec2f;
import speiger.src.coreengine.math.vector.floats.Vec3f;
import speiger.src.coreengine.math.vector.floats.Vec4f;
import speiger.src.coreengine.math.vector.matrix.Matrix4f;
import speiger.src.coreengine.rendering.input.window.IWindowListener;
import speiger.src.coreengine.rendering.input.window.Window;
import speiger.src.coreengine.utils.helpers.InternalThreadPools;

public class Frustrum implements IWindowListener
{
	public static boolean FREEZE_FRUSTUM = false;
	Camera cam;
	Matrix4f projectMatrix = new Matrix4f();
	Vec4f[] frustrumPlanes = new Vec4f[6];
	
	public Frustrum(Camera camera)
	{
		cam = camera;
		projectMatrix = new Matrix4f();
		for(int i = 0;i<6;i++)
		{
			frustrumPlanes[i] = Vec4f.newMutable();
		}
	}
	
	@Override
	public void onWindowChanged(Window window)
	{
		projectMatrix.load(window.getProjectionMatrix());
		update();
	}
	
	public void update()
	{
		if(FREEZE_FRUSTUM)
		{
			return;
		}
		Matrix4f checkMatrix = InternalThreadPools.MATRIX.get().load(projectMatrix).multiply(cam.getViewMatrix());
		for(int i = 0;i<6;i++)
		{
			checkMatrix.storeFrustrumPlane(i, frustrumPlanes[i]);
		}
		InternalThreadPools.MATRIX.accept(checkMatrix);
	}
	
	public boolean isPointInFrustrum(Vec3f vec)
	{
		return isPointInFrustrum(vec.getX(), vec.getY(), vec.getZ());
	}
	
	public boolean isPointInFrustrum(float x, float y, float z)
	{
		return isSphereInFrustrum(x, y, z, 0);
	}
	
	public boolean isSphereInFrustrum(Vec3f vec, float radius)
	{
		return isSphereInFrustrum(vec.getX(), vec.getY(), vec.getZ(), radius);
	}
	
	public boolean isSphereInFrustrum(float x, float y, float z, float radius)
	{
		for(int i = 0;i<6;i++)
		{
			if(getDistance(i, x, y, z) < -radius)
			{
				return false;
			}
		}
		return true;
	}
	
	public boolean isCubeInFrustrum(Vec3f min, Vec3f max)
	{
		return isCubeInFrustrum(min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ());
	}
	
	public boolean isCubeInFrustrum(float minX, float minY, float minZ, float maxX, float maxY, float maxZ)
	{
		for(int i = 0;i<6;i++)
		{
			if(isOutsideOfPlane(i, minX, minY, minZ, maxX, maxY, maxZ))
			{
				return false;
			}
		}
		return true;
	}
	
	public boolean isPlaneInFrustrum(Vec2f min, Vec2f max)
	{
		return isPlaneInFrustrum(min.getX(), min.getY(), max.getX(), min.getY());
	}
	
	public boolean isPlaneInFrustrum(float minX, float minZ, float maxX, float maxZ)
	{
		for(int i = 0;i<6;i++)
		{
			if(isOutsideOfPlane(i, minX, minZ, maxX, maxZ))
			{
				return false;
			}
		}
		return true;
	}

	private float getDistance(int index, float x, float y, float z)
	{
		Vec4f vec = frustrumPlanes[index];
		return vec.getX() * x + vec.getY() * y + vec.getZ() * z + vec.getW();
	}
	
	private boolean isOutsideOfPlane(int index, float minX, float minZ, float maxX, float maxZ)
	{
		Vec4f vec = frustrumPlanes[index];
		return vec.getX() * (vec.getX() < 0 ? minX : maxX) + vec.getZ() * (vec.getZ() < 0 ? minZ : maxZ) >= -vec.getW();
	}
	
	private boolean isOutsideOfPlane(int index, float minX, float minY, float minZ, float maxX, float maxY, float maxZ)
	{
		Vec4f vec = frustrumPlanes[index];
		return vec.getX() * (vec.getX() < 0 ? minX : maxX) + vec.getY() * (vec.getY() < 0 ? minY : maxY) + vec.getZ() * (vec.getZ() < 0 ? minZ : maxZ) < -vec.getW();
	}
}
