#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <raylib.h>
#include <math.h>

struct ball
{
	Vector2 position;
	Vector2 speed;
};


struct ball *balls;
int n_balls;

float ball_diameter = 2.0;
float distance_limit;
float px_per_sec;

Rectangle window; 



float distance(struct ball a, struct ball b)
{
	float x_dist = a.position.x - b.position.x;
	float y_dist = a.position.y - b.position.y;
	return sqrt(x_dist * x_dist + y_dist * y_dist);
}

int not_near_any_ball(int n, struct ball arr[n], struct ball b)
{
	for(int i = 0; i < n; i += 1)
	{
		if (distance(arr[i], b) <= ball_diameter)
		{
			return 0;
		}
	}
	return 1;
}

int at_border(struct ball b)
{
	Vector2 pos = b.position;
	return ((pos.x - ball_diameter <= 0) ||
		(pos.x + ball_diameter >= window.width) ||
		(pos.y - ball_diameter <= 0) ||
		(pos.y + ball_diameter >= window.height));
}

float rand_float()
{
	return (float)rand()/(float)(RAND_MAX);	
}

Vector2 random_speed(float px_speed)
{
	float t = rand_float() * PI * 2;
	return (Vector2) {
		px_speed * cos(t),
		px_speed * sin(t),
	};
}

void maybe_bounce(struct ball *b)
{
	if (b->position.x >= window.width || b->position.x <= 0)
	{
		b->speed.x *= -1;
	}

	if (b->position.y >= window.height || b->position.y <= 0)
	{
		b->speed.y *= -1;
	}
}

float line_thickness_from_distance(float d)
{
	static float max_thickness = 5.0f;
	if (d < 1) { return 1.0; }
	return 1.0f + (1 - d / distance_limit) * (max_thickness - 1);
}

Color color_from_distance(float d)
{
	static float min_distance = 5.0f;
	float t = (d - min_distance) / (distance_limit - min_distance);
	unsigned char val = 255 - (unsigned char) 255 * t;
	return (Color) { val, val, val, 255 };
}



int main()
{
	window = (Rectangle) { 0, 0, 1280, 720 };

	SetTraceLogLevel(LOG_NONE);
	InitWindow(window.width, window.height, "Bouncing balls");
	SetTargetFPS(60);

	float min_dim = window.width < window.height ? window.width : window.height;
	
	distance_limit = min_dim / 7.0f;
	px_per_sec     = min_dim / 10.0f;
	n_balls = window.width * window.height / 15000.0f;
	balls = malloc(n_balls * sizeof(struct ball));

	srand(time(0));
	// create balls
	int created = 0;
	while (created < n_balls)
	{
		struct ball b = {};
		b.position = (Vector2) {
			rand_float() * window.width,
			rand_float() * window.height
		};
		b.speed = random_speed(px_per_sec);

		if (not_near_any_ball(created, balls, b) && !at_border(b))
		{
			balls[created] = b;
			created += 1;
		}
	}	
	
	while (!WindowShouldClose())
	{
		float frame_time = GetFrameTime();

		// update balls
		{
			for(int i = 0; i < n_balls; i += 1)
			{
				struct ball *b = balls + i;
				b->position.x += b->speed.x * frame_time;
				b->position.y += b->speed.y * frame_time;
				maybe_bounce(b);
			}
		}

		
		BeginDrawing();
		ClearBackground(BLACK);

		// draw edges
		for (int i = 0; i < n_balls; i += 1)
		{
			for (int j = 0; j < n_balls; j += 1)
			{
				if (i == j) { continue; }
				float dist = distance(balls[i], balls[j]);
				if (dist < distance_limit)
				{
					float thickness = line_thickness_from_distance(dist);
					Color color     = color_from_distance(dist);
					DrawLineEx(balls[i].position, balls[j].position, thickness, color);
						   
				}
			}
		}

		// draw balls
		for (int i = 0; i < n_balls; i += 1)
		{
			DrawCircleV(balls[i].position, ball_diameter, WHITE);
		}
							
		EndDrawing();
		
	}

	CloseWindow();
	
	return 0;
}
