#include "math.h"
#include "raylib/include/raylib.h"

#define FPS 60
#define FRAME_SPEED 8
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define SPRITE_SIZE 64
#define BOTTOM_POSITION ((float)SCREEN_HEIGHT - (float)SPRITE_SIZE)

/* Player */

typedef struct {
	Texture2D texture;
	Rectangle frame;
	int counter;
	int speed;
	int current;
	int total_frames;
} Graphics;

void updateGraphics(Graphics* graphics) {
	// update animation
	graphics->counter++;

	if (graphics->counter >= (FPS/graphics->speed)) {
		graphics->counter = 0;
		graphics->current++;

		if (graphics->current >= graphics->total_frames) graphics->current = 0;

		graphics->frame.y = (float)graphics->current * (float)graphics->texture.height/graphics->total_frames;
	}
}

typedef struct {
	int up;
	int left;
	int right;
} Keys;

typedef struct {
	Vector2 position;
	Vector2 acceleration;
	Graphics graphics;
	Keys keys;
} Player;

Player newPlayer(const char* path, Keys keys, float position) {
	Texture2D texture = LoadTexture(path);
	Graphics graphics = {
		.texture = texture,
		.frame = { 0.0f, 0.0f, (float)SPRITE_SIZE, (float)SPRITE_SIZE },
		.counter = 0,
		.speed = 8,
		.current = 0,
		.total_frames = 20,
	};


	Player player = {
		.position = { position - (float)SPRITE_SIZE / 2, BOTTOM_POSITION },
		.acceleration = { (float)0, (float)0 },
		.graphics = graphics,
		.keys = keys
	};
	return player;
}

void updatePlayer(Player* player, Sound* jump) {
	// update animation
	updateGraphics(&(player->graphics));

	// handle inputs X
	if (IsKeyDown(player->keys.right)) { player->acceleration.x += 0.25f; }
	if (IsKeyDown(player->keys.left)) { player->acceleration.x -= 0.25f; }
	if (!IsKeyDown(player->keys.left) && !IsKeyDown(player->keys.right)) {
		if (player->acceleration.x > 0) {
			player->acceleration.x -= 0.2f;
		}
		else if (player->acceleration.x < 0) {
			player->acceleration.x += 0.2f;
		}
	}
	// handle inputs Y
	if (IsKeyDown(player->keys.up) && player->position.y == BOTTOM_POSITION) {
		player->acceleration.y -= 7.0f;
		PlaySound(*jump);
	}
	if (player->position.y < BOTTOM_POSITION) {
		player->acceleration.y += 0.28f;
	}

	// fix acceleration
	if (player->acceleration.x > 6.0f) {
		player->acceleration.x = 6.0f;
	}
	else if (player->acceleration.x < -6.0f) {
		player->acceleration.x = -6.0f;
	}
	else if (-0.15f < player->acceleration.x && player->acceleration.x < 0.15f)  {
		player->acceleration.x = 0.0f;
	}

	// update position
	player->position.y += player->acceleration.y;
	if (player->position.y > BOTTOM_POSITION) {
		player->position.y = BOTTOM_POSITION;
		player->acceleration.y = 0.0f;
	}

	player->position.x += player->acceleration.x;
	if (player->position.x > SCREEN_WIDTH - SPRITE_SIZE) {
		player->position.x = SCREEN_WIDTH - SPRITE_SIZE;
		player->acceleration.x = 0.0f;
	}
	else if (player->position.x < 0) {
		player->position.x = 0;
		player->acceleration.x = 0.0f;
	}
}

Rectangle getPlayerBox(const Player* player) {
	Rectangle rectangle = {
		player->position.x,
		player->position.y,
		SPRITE_SIZE,
		SPRITE_SIZE,
	};
	return rectangle;
}

/* Ball */

typedef struct {
	Vector2 position;
	Vector2 acceleration;
	Vector2 origin;
	float rotation;
	Texture2D texture;
	Rectangle frame;
} Ball;

Ball newBall(const char* path) {
	Texture2D texture = LoadTexture(path);

	Ball ball = {
		.position = { (float)SCREEN_WIDTH/2 - (float)SPRITE_SIZE / 2, SCREEN_HEIGHT/2 },
		.acceleration = { (float)-3.0f, (float)-8.0f },
		.origin = { (float)SPRITE_SIZE / 2, (float)SPRITE_SIZE / 2 },
		.rotation = (float)0.0f,
		.texture = texture,
		.frame = { 0.0f, 0.0f, (float)SPRITE_SIZE, (float)SPRITE_SIZE },
	};
	return ball;
}

Rectangle getBallBox(const Ball* ball) {
	Rectangle rectangle = {
		ball->position.x,
		ball->position.y,
		SPRITE_SIZE,
		SPRITE_SIZE,
	};
	return rectangle;
}

float min(float a, float b) {
	return a < b ? a : b;
}

typedef enum {
	Air,
	Dropped,
	Saved,
} BallStatus;

BallStatus updateBall(Ball* ball, const Player* player1, const Player* player2) {
	BallStatus status = Air;

	// collision
	bool collision1 = CheckCollisionRecs(getBallBox(ball), getPlayerBox(player1));
	bool collision2 = CheckCollisionRecs(getBallBox(ball), getPlayerBox(player2));

	if (collision1 && collision2) {
		status = Saved;
		ball->acceleration.y = -8.0f + min(min(0, player1->acceleration.y), player2->acceleration.y);
		ball->acceleration.x = 1.3f + player1->acceleration.x;
		if (fabsf(player1->acceleration.x) > fabsf(player1->acceleration.x)) {
			ball->acceleration.x = 1.3f + player1->acceleration.x;
		} else {
			ball->acceleration.x = 1.2f + player2->acceleration.x;
		}
	}
	else if (collision1) {
		status = Saved;
		ball->acceleration.y = -8.0f + min(0, player1->acceleration.y);
		ball->acceleration.x += 1.0f * player1->acceleration.x;
	}
	else if (collision2) {
		status = Saved;
		ball->acceleration.y = -7.0f + min(0, player2->acceleration.y);
		ball->acceleration.x += 1.0f * player2->acceleration.x;
	}
	// physics
	// y
	else if (ball->position.y >= BOTTOM_POSITION) {
		status = Dropped;
		ball->position.y = BOTTOM_POSITION;

		if (-0.5f < ball->acceleration.y && ball->acceleration.y < 0.5f) {
			ball->acceleration.y = 0;
		}
		else {
			ball->acceleration.y *= -0.9f;
		}
	}
	if (ball->position.y < BOTTOM_POSITION) {
		ball->acceleration.y += 0.28f;
	}

	ball->position.y += ball->acceleration.y;

	// x
	if (ball->position.x > SCREEN_WIDTH - SPRITE_SIZE) {
		ball->position.x = SCREEN_WIDTH - SPRITE_SIZE;
		ball->acceleration.x *= -0.9f;
	}
	else if (ball->position.x < 0) {
		ball->position.x = 0;
		ball->acceleration.x *= -0.9f;
	}
	if (ball->acceleration.x < -10.0f) {
		ball->acceleration.x = -10.0f;
	}
	if (ball->acceleration.x > 10.0f) {
		ball->acceleration.x = 10.0f;
	}
	ball->position.x += ball->acceleration.x;

	ball->rotation += ball->acceleration.x;

	return status;
}

typedef struct {
	Graphics graphics;
	Vector2 position;
} Background;

Background newBackground(const char* path) {
	Texture2D texture = LoadTexture(path);
	Graphics graphics = {
		.texture = texture,
		.frame = { 0.0f, 0.0f, (float)SCREEN_WIDTH, (float)SCREEN_HEIGHT },
		.counter = 0,
		.speed = 4,
		.current = 0,
		.total_frames = 24,
	};


	Background background = {
		.graphics = graphics,
		.position = { 0, 0 },
	};
	return background;
}

void updateBackground(Background* background) {
	updateGraphics(&background->graphics);
}

/* Main */
int main(void) {
	InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Volleyball");
	InitAudioDevice();

	// sounds
	Music music = LoadMusicStream("assets/blobbybeach.mp3");
	Sound jump = LoadSound("assets/jump.wav");
	Sound floor = LoadSound("assets/floor.wav");
	Sound bop = LoadSound("assets/bop.wav");

	Background background = newBackground("assets/background.png");

	Keys pink_keys = { .up = KEY_W, .left = KEY_A, .right = KEY_D };
	Player pink_player = newPlayer("assets/pink_player.png", pink_keys, (float)SCREEN_WIDTH/4);
	Keys blue_keys = { .up = KEY_UP, .left = KEY_LEFT, .right = KEY_RIGHT };
	Player blue_player = newPlayer("assets/blue_player.png", blue_keys, (float)SCREEN_WIDTH/4 * 3);
	Ball ball = newBall("assets/ball.png");

	unsigned current_score = 0;
	unsigned best_record = 0;
	unsigned cooloff = 0;
	bool pause_music = false;

    PlayMusicStream(music);

	SetTargetFPS(FPS);

	// Game loop
	while (!WindowShouldClose())
	{
		UpdateMusicStream(music);

        // Restart music playing (stop and play)
        if (IsKeyPressed(KEY_M))
        {
			if (pause_music) {
				PlayMusicStream(music);
				pause_music = false;
			}
			else {
				StopMusicStream(music);
				pause_music = true;
			}
        }

		// Update
		if (cooloff > 0) { cooloff--; }
		updateBackground(&background);
		updatePlayer(&pink_player, &jump);
		updatePlayer(&blue_player, &jump);
		BallStatus ball_status = updateBall(&ball, &pink_player, &blue_player);

		switch (ball_status) {
		case Saved:
			if (cooloff == 0) {
				cooloff = 10;
				current_score++;
				if (current_score > best_record) {
					best_record = current_score;
				}
				PlaySound(bop);
			}
			break;
		case Dropped:
			current_score = 0;
			PlaySound(floor);
			break;
		default:
			break;
		}

		// Draw
		BeginDrawing();

		ClearBackground(RAYWHITE);

		DrawTextureRec(background.graphics.texture, background.graphics.frame, background.position, WHITE);

		DrawTextureRec(pink_player.graphics.texture, pink_player.graphics.frame, pink_player.position, WHITE);
		DrawTextureRec(blue_player.graphics.texture, blue_player.graphics.frame, blue_player.position, WHITE);
		//DrawTextureRec(ball.texture, ball.frame, ball.position, WHITE);
		Rectangle dest = { ball.position.x + SPRITE_SIZE/2, ball.position.y + SPRITE_SIZE/2, ball.origin.x * 2, ball.origin.y * 2 };
		DrawTexturePro(ball.texture, ball.frame, dest, ball.origin, ball.rotation, WHITE);

		DrawRectangle(0, 0, 240, 90, (Color){ 0, 10, 60, 120 });

		if (current_score == best_record) {
			DrawText(TextFormat("Best Record: %i", best_record), 10, 10, 24 + cooloff / 2, PINK);
			DrawText(TextFormat("Score: %i", current_score), 10, 50, 24 + cooloff / 2, PINK);
		} else {
			DrawText(TextFormat("Best Record: %i", best_record), 10, 10, 24, BLACK);
			DrawText(TextFormat("Score: %i", current_score), 10, 50, 24 + cooloff / 2, WHITE);
		}

		EndDrawing();
	}

	// De-Initialization
	UnloadTexture(pink_player.graphics.texture);
	UnloadTexture(blue_player.graphics.texture);
	UnloadTexture(ball.texture);
	CloseWindow();

	return 0;
}
