package speiger.src.coreengine.math.vector.floats;

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

import speiger.src.coreengine.math.MathUtils;
import speiger.src.coreengine.math.vector.bytes.Vec4b;
import speiger.src.coreengine.math.vector.doubles.Vec4d;
import speiger.src.coreengine.math.vector.ints.Vec4i;
import speiger.src.coreengine.math.vector.longs.Vec4l;
import speiger.src.coreengine.math.vector.matrix.Matrix4f;
import speiger.src.coreengine.math.vector.shorts.Vec4s;

public interface Vec4f extends Vecf {
	public static final Vec4f ZERO = of();
	public static final Vec4f MINUS_ONE = of(-1F);
	public static final Vec4f ONE = of(1F);
	
	public static Vec4f mutable() { return new Vec4fMutable(); }
	public static Vec4f mutable(float value) { return new Vec4fMutable(value); }
	public static Vec4f mutable(float x, float y, float z, float w) { return new Vec4fMutable(x, y, z, w); }
	public static Vec4f mutable(Vec4f vec) { return mutable(vec.x(), vec.y(), vec.z(), vec.w()); }
	public static Vec4f of() { return new Vec4fImmutable(); }
	public static Vec4f of(float value) { return new Vec4fImmutable(value); }
	public static Vec4f of(float x, float y, float z, float w) { return new Vec4fImmutable(x, y, z, w); }
	public static Vec4f of(Vec4f vec) { return of(vec.x(), vec.y(), vec.z(), vec.w()); }
	
	public float x();
	public float y();
	public float z();
	public float w();
	public Vec4f x(float x);
	public Vec4f y(float y);
	public Vec4f z(float z);
	public Vec4f w(float w);
	
	@Override
	public default float[] asArray() { return new float[] {x(), y(), z(), w()}; }
	@Override
	public Vec4f copy();
	@Override
	public default Vec4f abs() { return set(Math.abs(x()), Math.abs(y()), Math.abs(z()), Math.abs(w())); }
	@Override
	public default Vec4f negate() { return set(0F, 0F, 0F, 0F); }
	@Override
	public default Vec4f invert() { return set(-x(), -y(), -z(), -w()); }
	public default Vec4f normalize() {
		float l = length();
		return l == 0F ? this : multiply(1.0F / l);
	}
	
	public default Vec4f normalize3D() {
		float value = (x() * x()) + (y() * y()) + (z() * z());
		return value == 0F ? this : multiply(1F / value);
	}
	
	@Override
	public default Vec4f add(float value) { return add(value, value, value, value); }
	public default Vec4f add(Vec4f value) { return add(value.x(), value.y(), value.z(), value.w()); }
	public default Vec4f add(float x, float y, float z, float w) { return set(x() + x, y() + y, z() + z, w() + w); }
	@Override
	public default Vec4f sub(float value) { return sub(value, value, value, value); }
	public default Vec4f sub(Vec4f value) { return sub(value.x(), value.y(), value.z(), value.w()); }
	public default Vec4f sub(float x, float y, float z, float w) { return set(x() - x, y() - y, z() - z, w() - w); }
	@Override
	public default Vec4f multiply(float value) { return multiply(value, value, value, value); }
	public default Vec4f multiply(Vec4f value) { return multiply(value.x(), value.y(), value.z(), value.w()); }
	public default Vec4f multiply(float x, float y, float z, float w) { return set(x() * x, y() * y, z() * z, w() * w); }
	@Override
	public default Vec4f devide(float value) { return devide(value, value, value, value); }
	public default Vec4f devide(Vec4f value) { return devide(value.x(), value.y(), value.z(), value.w()); }
	public default Vec4f devide(float x, float y, float z, float w) { return set(x() / x, y() / y, z() / z, w() / w); }
	@Override
	public default Vec4f set(float value) { return set(value, value, value, value); }
	public default Vec4f set(Vec4f value) { return set(value.x(), value.y(), value.z(), value.w()); }
	public Vec4f set(float x, float y, float z, float w);
	public default double distanceTo(Vec4f value) { return distanceTo(value.x(), value.y(), value.z(), value.w()); }
	public default double distanceTo(float x, float y, float z, float w) { return Math.sqrt(distanceTo(x, y, z, w)); }
	public default double distanceToSquared(Vec4f value) { return distanceToSquared(value.x(), value.y(), value.z(), value.w()); }
	public default double distanceToSquared(float x, float y, float z, float w) {
		double xPos = x() - x;
		double yPos = y() - y;
		double zPos = z() - z;
		double wPos = w() - w;
		return (xPos * xPos) + (yPos * yPos) + (zPos * zPos) + (wPos * wPos);
	}
	
	@Override
	public default float lengthSquared() { return (x() * x()) + (y() * y()) + (z() * z()) + (w() * w()); }
	public default double dotProduct(Vec4f value) { return dotProduct(value.x(), value.y(), value.z(), value.w()); }
	public default double dotProduct(float x, float y, float z, float w) { return (x() * x) + (y() * y) + (z() * z) + (w() * w); };
	public default Vec4f lerp(Vec4f value, float progress, Vec4f result) { return lerp(value.x(), value.y(), value.z(), value.w(), progress, result); }
	public default Vec4f lerp(float x, float y, float z, float w, float progress, Vec4f result) { return result.set(MathUtils.lerp(x(), x, progress), MathUtils.lerp(y(), y, progress), MathUtils.lerp(z(), z, progress), MathUtils.lerp(w(), w, progress)); }
	public default float angle(Vec4f value) { return angle(value.x(), value.y(), value.z(), value.w()); }
	public default float angle(float x, float y, float z, float w) { return (float)Math.acos(MathUtils.clamp(-1F, 1F, angleCos(x, y, z, w))); }
	public default float angleCos(Vec4f value) { return angleCos(value.x(), value.y(), value.z(), value.w()); }
	public default float angleCos(float x, float y, float z, float w) { return (float)(dotProduct(x, y, z, w) / Math.sqrt(lengthSquared() * (x * x) + (y * y) + (z * z) + (w * w))); }
	public default Vec4f rotate(float angle, Vec4f axis) { return rotate(angle, axis.x(), axis.y(), axis.z(), axis.w()); }
	public default Vec4f rotate(float angle, Vec4f axis, Vec4f result) { return rotate(angle, axis.x(), axis.y(), axis.z(), axis.w(), result); }
	public default Vec4f rotate(float angle, float x, float y, float z, float w) { return rotate(angle, x, y, z, w, this); }
	public default Vec4f rotate(float angle, float x, float y, float z, float w, Vec4f result) {
		double cos = MathUtils.cos(angle);
		double sin = MathUtils.sin(angle);
		double dot = dotProduct(x, y, z, w);
		double xPos = x * dot * (1D - cos) + (x() * cos) + (-z * y() + y * z()) * sin;
		double yPos = y * dot * (1D - cos) + (y() * cos) + (z * x() - x * z()) * sin;
		double zPos = z * dot * (1D - cos) + (z() * cos) + (-y * x() + x * y()) * sin;
		return result.set((float)xPos, (float)yPos, (float)zPos, w());
	}
	
	public default Vec4f rotateX(float angle) { return rotateX(angle, this); }
	public default Vec4f rotateX(float angle, Vec4f result) {
		double cos = MathUtils.cos(angle);
		double sin = MathUtils.sin(angle);
		double y = y() * cos + z() * sin;
		double z = z() * cos - y() * sin;
		return result.set(x(), (float)y, (float)z, w());
	}
	
	public default Vec4f rotateY(float angle) { return rotateY(angle, this); }
	public default Vec4f rotateY(float angle, Vec4f result) {
		double cos = MathUtils.cos(angle);
		double sin = MathUtils.sin(angle);
		double x = x() * cos + z() * sin;
		double z = z() * cos - x() * sin;
		return result.set((float)x, y(), (float)z, w());
	}
	
	public default Vec4f rotateZ(float angle) { return rotateZ(angle, this); }
	public default Vec4f rotateZ(float angle, Vec4f result) {
		double cos = MathUtils.cos(angle);
		double sin = MathUtils.sin(angle);
		double x = cos * x() - sin * y();
		double y = sin * x() + cos * y();
		return result.set((float)x, (float)y, z(), w());
	}
	
	public default Vec4f transform(Matrix4f matrix) { return transform(matrix, this); }
	public default Vec4f transform(Matrix4f matrix, Vec4f result) {
		if((matrix.properties() & Matrix4f.PROPERTY_IDENTITY) != 0) {
			return result.set(this);
		}
		if((matrix.properties() & Matrix4f.PROPERTY_ROTATION) != 0) {
			return result.set(
				matrix.fma(Matrix4f.M00, Matrix4f.M10, Matrix4f.M20, Matrix4f.M30, x(), y(), z(), w()), 
				matrix.fma(Matrix4f.M01, Matrix4f.M11, Matrix4f.M21, Matrix4f.M31, x(), y(), z(), w()), 
				matrix.fma(Matrix4f.M02, Matrix4f.M12, Matrix4f.M22, Matrix4f.M32, x(), y(), z(), w()), 
				matrix.fma(Matrix4f.M03, Matrix4f.M13, Matrix4f.M23, Matrix4f.M33, x(), y(), z(), w()));
		}
		if((matrix.properties() & Matrix4f.PROPERTY_SCALE) != 0) {
			return set(
				Math.fma(matrix.get(Matrix4f.M00), x(), matrix.get(Matrix4f.M30)), 
				Math.fma(matrix.get(Matrix4f.M01), y(), matrix.get(Matrix4f.M31)), 
				Math.fma(matrix.get(Matrix4f.M02), z(), matrix.get(Matrix4f.M32)), 
				Math.fma(matrix.get(Matrix4f.M03), w(), matrix.get(Matrix4f.M33)));
		}
		return set(x() + matrix.get(Matrix4f.M30), y() + matrix.get(Matrix4f.M31), z() + matrix.get(Matrix4f.M32), w() + matrix.get(Matrix4f.M33));
	}
	
	public default Vec4f smoothStep(Vec4f value, float progress, Vec4f result) { return smoothStep(value.x(), value.y(), value.z(), value.w(), progress, result); }
	public default Vec4f smoothStep(float x, float y, float z, float w, float progress, Vec4f result) {
		float t2 = progress * progress;
		float t3 = t2 * progress;
		float xPos = ((x() + x() - x - x) * t3 + (3.0F * x - 3.0F * x()) * t2 + x() * progress + x());
		float yPos = ((y() + y() - y - y) * t3 + (3.0F * y - 3.0F * y()) * t2 + y() * progress + y());
		float zPos = ((z() + z() - z - z) * t3 + (3.0F * z - 3.0F * z()) * t2 + z() * progress + z());
		float wPos = ((w() + w() - w - w) * t3 + (3.0F * w - 3.0F * w()) * t2 + w() * progress + w());
		return result.set(xPos, yPos, zPos, wPos);
	}
	
	public default Vec4f min(Vec4f other) { return min(other, this); }
	public default Vec4f min(Vec4f other, Vec4f result) { return min(other.x(), other.y(), other.z(), other.w(), result); }
	public default Vec4f min(float x, float y, float z, float w) { return min(x, y, z, w, this); }
	public default Vec4f min(float x, float y, float z, float w, Vec4f result) { return result.set(Math.min(x(), x), Math.min(y(), y), Math.min(z(), z), Math.min(w(), w)); }
	public default Vec4f max(Vec4f other) { return max(other, this); }
	public default Vec4f max(Vec4f other, Vec4f result) { return max(other.x(), other.y(), other.z(), other.w(), result); }
	public default Vec4f max(float x, float y, float z, float w) { return max(x, y, z, w, this); }
	public default Vec4f max(float x, float y, float z, float w, Vec4f result) { return result.set(Math.max(x(), x), Math.max(y(), y), Math.max(z(), z), Math.max(z(), z)); }
	public default Vec4f difference(Vec4f other) { return difference(other, this); }
	public default Vec4f difference(Vec4f other, Vec4f result) { return difference(other.x(), other.y(), other.z(), other.w(), result); }
	public default Vec4f difference(float x, float y, float z, float w) { return difference(x, y, z, w, this); }
	public default Vec4f difference(float x, float y, float z, float w, Vec4f result) { return result.set(x() - x, y() - y, z() - z, w() - w); }
	@Override
	public default Vec4f clamp(float min, float max) { return clamp(min, max, ALL); }
	public default Vec4f clamp(float min, float max, Vec4f result) { return clamp(min, max, result, ALL); }
	@Override
	public default Vec4f clamp(float min, float max, int filter) { return clamp(min, max, this, filter); }
	public default Vec4f clamp(float min, float max, Vec4f result, int filter) { return result.set((filter & X) == 0 ? x() : MathUtils.clamp(min, max, x()), (filter & Y) == 0 ? y() : MathUtils.clamp(min, max, y()), (filter & Z) == 0 ? z() : MathUtils.clamp(min, max, z()), (filter & W) == 0 ? w() : MathUtils.clamp(min, max, w())); }
	
	@Override
	public default Vec4f store(ByteBuffer buffer) {
		buffer.putFloat(x()).putFloat(y()).putFloat(z()).putFloat(w());
		return this;
	}
	
	@Override
	public default Vec4f load(ByteBuffer buffer) { return set(buffer.getFloat(), buffer.getFloat(), buffer.getFloat(), buffer.getFloat()); }
	
	@Override
	public default Vec4f store(FloatBuffer buffer) {
		buffer.put(x()).put(y()).put(z()).put(w());
		return this;
	}
	
	@Override
	public default Vec4f load(FloatBuffer buffer) { return set(buffer.get(), buffer.get(), buffer.get(), buffer.get()); }
	
	@Override
	public default Vec4b asByte() { return isMutable() ? Vec4b.mutable((byte)MathUtils.floor(x()), (byte)MathUtils.floor(y()), (byte)MathUtils.floor(z()), (byte)MathUtils.floor(w())) : Vec4b.of((byte)MathUtils.floor(x()), (byte)MathUtils.floor(y()), (byte)MathUtils.floor(z()), (byte)MathUtils.floor(w())); }
	@Override
	public default Vec4s asShort() { return isMutable() ? Vec4s.mutable((short)MathUtils.floor(x()), (short)MathUtils.floor(y()), (short)MathUtils.floor(z()), (short)MathUtils.floor(w())) : Vec4s.of((short)MathUtils.floor(x()), (short)MathUtils.floor(y()), (short)MathUtils.floor(z()), (short)MathUtils.floor(w())); }
	@Override
	public default Vec4i asInt() { return isMutable() ? Vec4i.mutable(MathUtils.floor(x()), MathUtils.floor(y()), MathUtils.floor(z()), MathUtils.floor(w())) : Vec4i.of(MathUtils.floor(x()), MathUtils.floor(y()), MathUtils.floor(z()), MathUtils.floor(w())); }
	@Override
	public default Vec4l asLong() { return isMutable() ? Vec4l.mutable(MathUtils.floor(x()), MathUtils.floor(y()), MathUtils.floor(z()), MathUtils.floor(w())) : Vec4l.of(MathUtils.floor(x()), MathUtils.floor(y()), MathUtils.floor(z()), MathUtils.floor(w())); }
	@Override
	public default Vec4d asDouble() { return isMutable() ? Vec4d.mutable(x(), y(), z(), w()) : Vec4d.of(x(), y(), z(), w()); }
	@Override
	public default Vec4f asMutable() { return isMutable() ? this : mutable(this); }
	@Override
	public default Vec4f asImmutable() { return isMutable() ? of(this) : this; }
	@Override
	public default Vec4f copyAsMutable() { return mutable(this); }
	@Override
	public default Vec4f copyAsImmutable() { return of(this); }
}
