<?php
requirePHPLib('judger');
requirePHPLib('data');

if (!authenticateJudger()) {
	UOJResponse::page404();
}

function submissionJudged() {
	UOJSubmission::onJudged(UOJRequest::post('id'), UOJRequest::post('result'), UOJRequest::post('judge_time'));
}

function customTestSubmissionJudged() {
	$submission = DB::selectFirst([
		"select submitter, status, content, result, problem_id from custom_test_submissions",
		"where", ['id' => $_POST['id']]
	]);
	if ($submission == null) {
		return;
	}
	if ($submission['status'] != 'Judging') {
		return;
	}
	$result = json_decode($_POST['result'], true);
	$result['details'] = uojTextEncode($result['details']);
	DB::update([
		"update custom_test_submissions",
		"set", [
			'status' => $result['status'],
			'status_details' => '',
			'result' => json_encode($result, JSON_UNESCAPED_UNICODE)
		], "where", ['id' => $_POST['id']]
	]);
}

function hackJudged() {
	$result = json_decode($_POST['result'], true);

	UOJHack::init($_POST['id']);
	UOJHack::cur()->setProblem();
	UOJHack::cur()->setSubmission();

	if ($result['score']) {
		$status = 'Judged, Waiting';
	} else {
		$status = 'Judged';
	}

	$ok = DB::update([
		"update hacks",
		"set", [
			'success' => $result['score'],
			'status' => $status,
			'details' => uojTextEncode($result['details'])
		], "where", ['id' => $_POST['id']]
	]);

	if (!$result['score']) {
		return;
	}
	if (!$ok) {
		return;
	}

	if (!(validateUploadedFile('hack_input') && validateUploadedFile('std_output'))) {
		UOJLog::error("hack successfully but received no data. id: {$_POST['id']}");
		return;
	}

	$input = UOJContext::storagePath() . UOJHack::info('input');
	$up_in = $_FILES["hack_input"]['tmp_name'];
	$up_out = $_FILES["std_output"]['tmp_name'];

	if (!UOJHack::cur()->problem->needToReviewHack()) {
		$err = dataAddHackPoint(UOJHack::cur()->problem->info, $up_in, $up_out);
		if ($err === '') {
			unlink($input);
			DB::update([
				"update hacks",
				"set", [
					'status' => 'Judged'
				], "where", ['id' => $_POST['id']]
			]);
			return;
		} else {
			UOJLog::error("hack successfully but failed to add an extra test: {$err}");
		}
	}
	move_uploaded_file($up_in, "{$input}_in");
	move_uploaded_file($up_out, "{$input}_out");
	DB::update([
		"update hacks",
		"set", [
			'status' => 'Judged, WaitingM'
		], "where", ['id' => $_POST['id']]
	]);
}

if (isset($_POST['submit'])) {
	if (!validateUInt($_POST['id'])) {
		die("Wow! hacker! T_T....");
	}
	if (isset($_POST['is_hack'])) {
		hackJudged();
	} elseif (isset($_POST['is_custom_test'])) {
		customTestSubmissionJudged();
	} else {
		submissionJudged();
	}
}
if (isset($_POST['update-status'])) {
	if (!validateUInt($_POST['id'])) {
		die("Wow! hacker! T_T....");
	}

	$status_details = $_POST['status'];
	if (isset($_POST['is_custom_test'])) {
		DB::update([
			"update custom_test_submissions",
			"set", ["status_details" => $status_details],
			"where", ["id" => $_POST['id']]
		]);
	} else {
		DB::update([
			"update submissions",
			"set", ["status_details" => $status_details],
			"where", ["id" => $_POST['id']]
		]);
	}
	die();
}

$assignCond = [];

$problem_ban_list = array_map(fn ($x) => $x['id'], DB::selectAll([
	"select id from problems",
	"where", [
		["assigned_to_judger", "!=", "any"],
		["assigned_to_judger", "!=", $_POST['judger_name']]
	]
]));

if ($problem_ban_list) {
	$assignCond[] = ["problem_id", "not in", DB::rawtuple($problem_ban_list)];
}

if ($_POST['judger_name'] == "remote_judger") {
	$problem_ban_list = array_map(fn ($x) => $x['id'], DB::selectAll([
		"select id from problems",
		"where", [
			["type", "!=", "remote"],
		],
	]));
} else {
	$problem_ban_list = array_map(fn ($x) => $x['id'], DB::selectAll([
		"select id from problems",
		"where", [
			["type", "!=", "local"],
		],
	]));
}

if ($problem_ban_list) {
	$assignCond[] = ["problem_id", "not in", DB::rawtuple($problem_ban_list)];
}

$submission = null;
$hack = null;
function querySubmissionToJudge($status, $set_q) {
	global $assignCond;

	for ($times = 0; $times < 10; $times++) {
		$submission = DB::selectFirst([
			"select id from submissions",
			"where", array_merge(["status" => $status], $assignCond),
			"order by id limit 1"
		]);
		if (!$submission) {
			return null;
		}

		$ok = DB::transaction(function () use (&$submission, $set_q, $status) {
			DB::update([
				"update submissions",
				"set", $set_q,
				"where", [
					"id" => $submission['id'],
					"status" => $status
				]
			]);
			if (DB::affected_rows() == 1) {
				$submission = DB::selectFirst([
					"select id, problem_id, content, status, judge_time from submissions",
					"where", ["id" => $submission['id']]
				]);
				return true;
			} else {
				return false;
			}
		});
		if ($ok) {
			return $submission;
		}
	}
}
function queryMinorSubmissionToJudge($status, $set_q) {
	global $assignCond;

	for ($times = 0; $times < 10; $times++) {
		$submission = null;
		$his = DB::selectFirst([
			"select id, submission_id from submissions_history",
			"where", ["status" => $status, "major" => 0], // $assignCond is removed!!! fix this bug in the future!
			"order by id limit 1"
		]);
		if (!$his) {
			return null;
		}

		$ok = DB::transaction(function () use (&$submission, &$his, $set_q, $status) {
			$submission = DB::selectFirst([
				"select id, problem_id, content from submissions",
				"where", ["id" => $his['submission_id']], DB::for_share()
			]);
			if (!$submission) {
				return false;
			}
			DB::update([
				"update submissions_history",
				"set", $set_q,
				"where", [
					"id" => $his['id'],
					"status" => $status
				]
			]);
			if (DB::affected_rows() == 1) {
				$ret = DB::selectFirst([
					"select status, judge_time from submissions_history",
					"where", ["id" => $his['id']]
				]);
				if ($ret === false) {
					return false;
				}
				$submission += $ret;
				return true;
			} else {
				return false;
			}
		});
		if ($ok) {
			return $submission;
		}
	}
}
function queryCustomTestSubmissionToJudge() {
	global $assignCond;

	while (true) {
		$submission = DB::selectFirst([
			"select id, problem_id, content from custom_test_submissions",
			"where", array_merge(["judge_time" => null], $assignCond),
			"order by id limit 1"
		]);
		if (!$submission) {
			return null;
		}
		$submission['is_custom_test'] = '';

		DB::update([
			"update custom_test_submissions",
			"set", [
				"judge_time" => DB::now(),
				"status" => 'Judging'
			], "where", [
				"id" => $submission['id'],
				"judge_time" => null
			]
		]);
		if (DB::affected_rows() == 1) {
			$submission['status'] = 'Judging';
			return $submission;
		}
	}
}
function queryHackToJudge() {
	global $assignCond;

	while (true) {
		if (DB::selectFirst([
			"select 1 from hacks",
			"where", [
				["status", "!=", "Waiting"],
				["status", "!=", "Judged"],
			], "order by id limit 1"
		])) {
			return null;
		}

		$hack = DB::selectFirst([
			"select id, submission_id, input, input_type from hacks",
			"where", array_merge(["judge_time" => null], $assignCond),
			"order by id limit 1"
		]);
		if (!$hack) {
			return null;
		}

		DB::update([
			"update hacks",
			"set", [
				"judge_time" => DB::now(),
				"status" => 'Judging'
			],
			"where", [
				"id" => $hack['id'],
				"judge_time" => null
			]
		]);
		if (DB::affected_rows() == 1) {
			$hack['status'] = 'Judging';
			return $hack;
		}
	}
}
function findSubmissionToJudge() {
	global $submission, $hack;
	$submission = querySubmissionToJudge('Waiting', [
		"judge_time" => DB::now(),
		"judger" => $_POST['judger_name'],
		"status" => 'Judging'
	]);
	if ($submission) {
		return true;
	}

	$submission = queryCustomTestSubmissionToJudge();
	if ($submission) {
		return true;
	}

	$submission = querySubmissionToJudge('Waiting Rejudge', [
		"judge_time" => DB::now(),
		"judger" => $_POST['judger_name'],
		"status" => 'Judging'
	]);
	if ($submission) {
		return true;
	}

	$submission = querySubmissionToJudge('Judged, Waiting', [
		"status" => 'Judged, Judging'
	]);
	if ($submission) {
		return true;
	}

	$submission = queryMinorSubmissionToJudge('Waiting Rejudge', [
		"judge_time" => DB::now(),
		"judger" => $_POST['judger_name'],
		"status" => 'Judging'
	]);
	if ($submission) {
		return true;
	}

	$submission = queryMinorSubmissionToJudge('Judged, Waiting', [
		"status" => 'Judged, Judging'
	]);
	if ($submission) {
		return true;
	}

	$hack = queryHackToJudge();
	if ($hack) {
		$submission = DB::selectFirst([
			"select id, problem_id, content from submissions",
			"where", [
				"id" => $hack['submission_id'],
				"score" => 100
			]
		]);
		if (!$submission) {
			$details = "<error>the score gained by the hacked submission is not 100.</error>";
			DB::update([
				"update hacks",
				"set", [
					'success' => 0,
					'status' => 'Judged',
					'details' => uojTextEncode($details)
				], "where", ["id" => $hack['id']]
			]);
			return false;
		}
		return true;
	}
	return false;
}

if (isset($_POST['fetch_new']) && !$_POST['fetch_new']) {
	die("Nothing to judge");
}
if (!findSubmissionToJudge()) {
	die("Nothing to judge");
}

$submission['id'] = (int)$submission['id'];
$submission['problem_id'] = (int)$submission['problem_id'];
$submission['problem_mtime'] = filemtime("/var/uoj_data/{$submission['problem_id']}");
$submission['content'] = json_decode($submission['content'], true);
if (isset($submission['status']) && $submission['status'] == 'Judged, Judging' && isset($submission['content']['final_test_config'])) {
	$submission['content']['config'] = $submission['content']['final_test_config'];
	unset($submission['content']['final_test_config']);
}

if ($hack) {
	$submission['is_hack'] = "";
	$submission['hack']['id'] = (int)$hack['id'];
	$submission['hack']['input'] = $hack['input'];
	$submission['hack']['input_type'] = $hack['input_type'];
}

echo json_encode($submission);