<?php
/**
 * getFinalScore
 *
 * Fetches the judging mode (1, 2, or 3) from the `settings` table by `run_id`,
 * retrieves all scores for that run from the `scores` table, and then calculates
 * the final score based on:
 *   - Mode 1 => Average of all scores
 *   - Mode 2 => Median of all scores
 *   - Mode 3 => Remove highest & lowest, then average the remaining
 *
 * @param int   $runId  The ID of the run
 * @param mysqli $conn  The MySQLi connection object
 *
 * @return float        The computed final score
 * @throws Exception    If no settings or no scores are found
 */
function getFinalScore(int $runId, mysqli $conn, $competitionCode): float
{
	// 1) Retrieve the judging mode from the settings table
	$sqlJudging = "SELECT judging FROM settings WHERE competition_code = ?";
	$stmt = $conn->prepare($sqlJudging);
	if (!$stmt) {
		throw new Exception("Failed to prepare statement: " . $conn->error);
	}

	// Bind the run_id parameter as integer (i)
	$stmt->bind_param("i", $competitionCode);
	$stmt->execute();
	$result = $stmt->get_result();
	$stmt->close();

	if ($result->num_rows === 0) {
		throw new Exception("No settings found for competition_code = $competitionCode");
	}

	$settingsRow = $result->fetch_assoc();
	$judgingMode = (int) $settingsRow['judging']; // e.g., 1, 2, or 3

	// 2) Retrieve all scores for this run
	$sqlScores = "SELECT score FROM scores WHERE run = ?";
	$stmt = $conn->prepare($sqlScores);
	if (!$stmt) {
		throw new Exception("Failed to prepare statement: " . $conn->error);
	}

	$stmt->bind_param("i", $runId);
	$stmt->execute();
	$result = $stmt->get_result();
	$stmt->close();

	if ($result->num_rows === 0) {
		return 0.0;
	}

	// Convert each row's 'score' into a float and store in $allScores
	$allScores = [];
	while ($row = $result->fetch_assoc()) {
		$allScores[] = (float) $row['score'];
	}

	// 3) Compute final score based on judging mode
	switch ($judgingMode) {
		case 1:
			// Mode 1 => Average of all scores
			return average($allScores);

		case 2:
			// Mode 2 => Median of all scores
			return median($allScores);

		case 3:
			// Mode 3 => Remove highest & lowest, then average the rest
			sort($allScores);  // ascending order
			$count = count($allScores);

			if ($count <= 2) {
				// Edge case: If 2 or fewer scores, just average them
				return average($allScores);
			}

			// Slice out the first (lowest) and last (highest)
			$trimmedScores = array_slice($allScores, 1, $count - 2);
			return average($trimmedScores);

		default:
			throw new Exception("Unknown judging mode: $judgingMode");
	}
}

/**
 * average
 *
 * @param float[] $scores
 * @return float
 */
function average(array $scores): float
{
	$count = count($scores);
	if ($count === 0) {
		return 0.0;
	}
	$sum = array_sum($scores);
	return $sum / $count;
}

/**
 * median
 *
 * @param float[] $scores
 * @return float
 */
function median(array $scores): float
{
	if (empty($scores)) {
		return 0.0;
	}

	sort($scores);
	$count = count($scores);
	$mid = (int) floor($count / 2);

	if ($count % 2 === 0) {
		// Even number of elements => average of two middle values
		return ($scores[$mid - 1] + $scores[$mid]) / 2;
	} else {
		// Odd number => just the middle element
		return $scores[$mid];
	}
}