#ifndef _PIUMA_LIB_GEOMETRY_H_
#define _PIUMA_LIB_GEOMETRY_H_

#include "types.h"
#include "math.h"

// Rect
union Rect
{
	struct
	{
		f32 x, y;
		f32 w, h;
	};
	struct
	{
		v2 position; // Usually, the top-left corner of the rectangle
		v2 size;
	};
};

inline bool is_inside(Rect rect, v2 p)
{
	bool in_range_x = rect.x <= p.x && p.x <= (rect.x + rect.w);
	bool in_range_y = rect.y <= p.y && p.y <= (rect.y + rect.h);
	return in_range_x && in_range_y;
}

// @Feature: add Cube/Parallelepiped? Unlike Box, it has rotations
// Box
struct Box
{
	v3 min;
	v3 max;
};

inline bool is_inside(Box b, v3 p)
{
	return
		(p.x < b.min.x || p.x > b.max.x) ||
		(p.y < b.min.y || p.y > b.max.y) ||
		(p.z < b.min.z || p.z > b.max.z);
}

inline bool overlaps(Box a, Box b)
{
	if(a.min.x > b.max.x) // no overlap
		return false;
	if(a.max.x < b.min.x) // no overlap
		return false;
	if(a.min.y > b.max.y) // no overlap
		return false;
	if(a.max.y < b.min.y) // no overlap
		return false;
	if(a.min.z > b.max.z) // no overlap
		return false;
	if(a.max.z < b.min.z) // no overlap
		return false;
	return true;
}

inline Box box_from_point_cloud(v3 *points, u32 count)
{
	if(count == 0)
		return Box{.min = {0,0,0}, .max = {0,0,0}};

	Box box;
	box.min = points[0];
	box.max = points[0];
	for(u32 i = 0; i < count; i++)
	{
		v3 p = points[i];
		box.min.x = minimum(box.min.x, p.x);
		box.min.y = minimum(box.min.y, p.y);
		box.min.z = minimum(box.min.z, p.z);
		box.max.x = maximum(box.max.x, p.x);
		box.max.y = maximum(box.max.y, p.y);
		box.max.z = maximum(box.max.z, p.z);
	}
	return box;
}

inline v3 box_closest_point(Box b, v3 point);
inline f32 box_SDF(Box b, v3 point, v3* res_closest = NULL)
{
	v3 closest = box_closest_point(b, point);
	f32 sign = -1 * is_inside(b, point);

	if(res_closest)
		*res_closest = closest;
	return sign * distance(closest, point);
}


// Ray
struct Ray
{
	v3 position;
	v3 direction;
};


// Circle
// @Cleanup: Should Circle be merged with Sphere?
struct Circle
{
	v2 center;
	f32 radius;
};

inline bool is_inside(Circle c, v2 p)
{
	v2 v = p - c.center;
	return dot(v, v) <= square(c.radius);
}

// Sphere
struct Sphere
{
	v3 center;
	f32 radius;
};

inline bool is_inside(Sphere s, v3 p)
{
	v3 v = p - s.center;
	return dot(v, v) <= square(s.radius); // distance² <= radius²
}

inline f32 sphere_SDF(Sphere s, v3 point)
{
	return distance(s.center, point) - s.radius;
}


// Segment
struct Segment
{
	v3 a;
	v3 b;
};


// Plane
struct Plane
{
	v3 normal;
	f32 offset;
};

inline f32 plane_SDF(Plane plane, v3 point)
{
	f32 projected = dot(point, plane.normal);
	return plane.offset - projected;
}



// Projections
inline f32 project_point_on_vector(v3 vector, v3 point)
{
	return dot(vector, point);
}

inline v3 project_point_on_plane(Plane plane, v3 point)
{
	f32 distance = plane_SDF(plane, point);
	return point - plane.normal * distance;
}


// Closest point
inline v3 segment_closest_point(Segment seg, v3 point)
{
	v3 ab = seg.b - seg.a;
	v3 ap = point - seg.a;
	f32 u = dot(ab, ap);
	return seg.a + ab * clamp(0.0, 1.0, u);
}

inline v3 box_closest_point(Box b, v3 point)
{
	v3 closest;
	// Closest point on the box is the one with coords closer to the ones of the point,
	// so for each axis we can clamp to the nearest point of the border.

	f32 dx1 = abs(b.min.x - point.x);
	f32 dx2 = abs(b.max.x - point.x);
	closest.x = (dx1 < dx2) ? b.min.x : b.max.x;
	f32 dy1 = abs(b.min.y - point.y);
	f32 dy2 = abs(b.max.y - point.y);
	closest.y = (dy1 < dy2) ? b.min.y : b.max.y;
	f32 dz1 = abs(b.min.z - point.z);
	f32 dz2 = abs(b.max.z - point.z);
	closest.z = (dz1 < dz2) ? b.min.z : b.max.z;

	return closest;
}



// Triangles functions
inline v3 triangle_normal(v3 a, v3 b, v3 c)
{
	v3 ba = b - a;
	v3 ca = c - a;
	v3 orthogonal = cross(ba, ca);
	return normalize(orthogonal);
}


// Transformations (all angles in radians)
inline m4 rotation_x(f32 angle)
{
	f32 c = cos(angle);
	f32 s = sin(angle);

	m4 result = m4_identity;
	result.E[1][1] =  c;
	result.E[1][2] = -s;
	result.E[2][1] =  s;
	result.E[2][2] =  c;

	return result;
}

inline m4 rotation_y(f32 angle)
{
	f32 c = cos(angle);
	f32 s = sin(angle);

	m4 result = m4_identity;
	result.E[0][0] =  c;
	result.E[0][2] =  s;
	result.E[2][0] = -s;
	result.E[2][2] =  c;

	return result;
}

inline m4 rotation_z(f32 angle)
{
	f32 c = cos(angle);
	f32 s = sin(angle);

	m4 result = m4_identity;
	result.E[0][0] =  c;
	result.E[0][1] = -s;
	result.E[1][0] =  s;
	result.E[1][1] =  c;

	return result;
}

inline m4 rotation(f32 x, f32 y, f32 z)
{
	return rotation_z(z) * rotation_y(y) * rotation_x(x);
}

inline m4 rotation_v3(v3 angle)
{
	return rotation(angle.x, angle.y, angle.z);
}


inline m4 translation(f32 x, f32 y, f32 z)
{
	m4 result = m4_identity;

	result.E[0][3] = x;
	result.E[1][3] = y;
	result.E[2][3] = z;

	return result;
}

inline m4 translation_v3(v3 t)
{
	return translation(t.x, t.y, t.z);
}

inline m4 scale(f32 x, f32 y, f32 z)
{
	m4 result = m4_zero;

	result.E[0][0] = x;
	result.E[1][1] = y;
	result.E[2][2] = z;
	result.E[3][3] = 1.0;

	return result;
}

inline m4 scale_v3(v3 factor)
{
	return scale(factor.x, factor.y, factor.z);
}

inline m4 scale(f32 factor)
{
	return scale(factor, factor, factor);
}




// Primitives
// Pass array of 8 elements to fill with coordinates
inline void build_cube_vertices(v3 *vertices)
{
	for(int x = 0; x < 2; x++)
	for(int y = 0; y < 2; y++)
	for(int z = 0; z < 2; z++)
		vertices[x*2*2 + y*2 + z] = v3{.x = x - 0.5f, .y = y - 0.5f, .z = z - 0.5f};
}



#endif
