package org.iatoki.judgels.uriel.controllers;
import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.iatoki.judgels.play.IdentityUtils;
import org.iatoki.judgels.play.InternalLink;
import org.iatoki.judgels.play.LazyHtml;
import org.iatoki.judgels.play.controllers.AbstractJudgelsController;
import org.iatoki.judgels.play.views.html.layouts.heading3WithActionsLayout;
import org.iatoki.judgels.sandalphon.Submission;
import org.iatoki.judgels.sandalphon.services.SubmissionService;
import org.iatoki.judgels.uriel.Contest;
import org.iatoki.judgels.uriel.ContestConfiguration;
import org.iatoki.judgels.uriel.ContestNotFoundException;
import org.iatoki.judgels.uriel.ContestScoreboard;
import org.iatoki.judgels.uriel.ContestScoreboardType;
import org.iatoki.judgels.uriel.ICPCScoreboardContent;
import org.iatoki.judgels.uriel.ICPCScoreboardEntry;
import org.iatoki.judgels.uriel.services.ContestProblemService;
import org.iatoki.judgels.uriel.services.ContestScoreboardService;
import org.iatoki.judgels.uriel.services.ContestService;
import org.iatoki.judgels.uriel.ContestTeam;
import org.iatoki.judgels.uriel.ContestTeamCoach;
import org.iatoki.judgels.uriel.ContestTeamMember;
import org.iatoki.judgels.uriel.StandardContestTypeConfig;
import org.iatoki.judgels.uriel.services.ContestTeamService;
import org.iatoki.judgels.uriel.services.impls.JidCacheServiceImpl;
import org.iatoki.judgels.uriel.adapters.ScoreboardAdapter;
import org.iatoki.judgels.uriel.adapters.impls.ScoreboardAdapters;
import org.iatoki.judgels.uriel.ScoreboardState;
import org.iatoki.judgels.uriel.IOIScoreboardContent;
import org.iatoki.judgels.uriel.IOIScoreboardEntry;
import org.iatoki.judgels.uriel.Scoreboard;
import org.iatoki.judgels.uriel.ScoreboardContent;
import org.iatoki.judgels.uriel.controllers.securities.Authenticated;
import org.iatoki.judgels.uriel.controllers.securities.HasRole;
import org.iatoki.judgels.uriel.controllers.securities.LoggedIn;
import org.iatoki.judgels.uriel.views.html.layouts.accessTypeByStatusLayout;
import play.db.jpa.Transactional;
import play.i18n.Messages;
import play.mvc.Http;
import play.mvc.Result;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Authenticated(value = {LoggedIn.class, HasRole.class})
@Singleton
@Named
public class ContestScoreboardController extends AbstractJudgelsController {
private final ContestService contestService;
private final ContestScoreboardService contestScoreboardService;
private final ContestProblemService contestProblemService;
private final ContestTeamService contestTeamService;
private final SubmissionService submissionService;
@Inject
public ContestScoreboardController(ContestService contestService, ContestScoreboardService contestScoreboardService, ContestProblemService contestProblemService, ContestTeamService contestTeamService, SubmissionService submissionService) {
this.contestService = contestService;
this.contestScoreboardService = contestScoreboardService;
this.contestProblemService = contestProblemService;
this.contestTeamService = contestTeamService;
this.submissionService = submissionService;
}
@Transactional(readOnly = true)
public Result viewScoreboard(long contestId) throws ContestNotFoundException {
Contest contest = contestService.findContestById(contestId);
if ((contest.isUsingScoreboard()) && (ContestControllerUtils.getInstance().isAllowedToEnterContest(contest))) {
ContestScoreboard contestScoreboard;
ContestConfiguration contestConfiguration = contestService.findContestConfigurationByContestJid(contest.getJid());
if ((contest.isStandard()) && ((new Gson().fromJson(contestConfiguration.getTypeConfig(), StandardContestTypeConfig.class)).getScoreboardFreezeTime() < System.currentTimeMillis()) && (!(new Gson().fromJson(contestConfiguration.getTypeConfig(), StandardContestTypeConfig.class)).isOfficialScoreboardAllowed())) {
if (contestScoreboardService.isContestScoreboardExistByContestJidAndScoreboardType(contest.getJid(), ContestScoreboardType.FROZEN)) {
contestScoreboard = contestScoreboardService.findContestScoreboardByContestJidAndScoreboardType(contest.getJid(), ContestScoreboardType.FROZEN);
} else {
contestScoreboard = null;
}
} else {
contestScoreboard = contestScoreboardService.findContestScoreboardByContestJidAndScoreboardType(contest.getJid(), ContestScoreboardType.OFFICIAL);
}
ScoreboardAdapter adapter = ScoreboardAdapters.fromContestStyle(contest.getStyle());
LazyHtml content;
if (contestScoreboard == null) {
content = new LazyHtml(adapter.renderScoreboard(null, null, JidCacheServiceImpl.getInstance(), IdentityUtils.getUserJid(), false, ImmutableSet.of()));
} else {
Scoreboard scoreboard = contestScoreboard.getScoreboard();
Set<String> openProblemJids = contestProblemService.findOpenedContestProblemByContestJid(contest.getJid())
.stream()
.map(c -> c.getProblemJid())
.collect(Collectors.toSet());
scoreboard = adapter.filterOpenProblems(scoreboard, openProblemJids);
if (contest.isIncognitoScoreboard()) {
if (ContestControllerUtils.getInstance().isCoach(contest)) {
List<ContestTeamMember> contestTeamMembers = contestTeamService.findContestTeamMembersByContestJidAndCoachJid(contest.getJid(), IdentityUtils.getUserJid());
content = new LazyHtml(adapter.renderScoreboard(scoreboard, contestScoreboard.getLastUpdateTime(), JidCacheServiceImpl.getInstance(), IdentityUtils.getUserJid(), true, contestTeamMembers.stream().map(ct -> ct.getMemberJid()).collect(Collectors.toSet())));
} else {
content = new LazyHtml(adapter.renderScoreboard(scoreboard, contestScoreboard.getLastUpdateTime(), JidCacheServiceImpl.getInstance(), IdentityUtils.getUserJid(), true, ImmutableSet.of(IdentityUtils.getUserJid())));
}
} else {
content = new LazyHtml(adapter.renderScoreboard(scoreboard, contestScoreboard.getLastUpdateTime(), JidCacheServiceImpl.getInstance(), IdentityUtils.getUserJid(), false, scoreboard.getState().getContestantJids().stream().collect(Collectors.toSet())));
}
}
if (isAllowedToSuperviseScoreboard(contest)) {
appendSubtabsLayout(content, contest);
}
ContestControllerUtils.getInstance().appendTabsLayout(content, contest);
ControllerUtils.getInstance().appendSidebarLayout(content);
appendBreadcrumbsLayout(content, contest,
new InternalLink(Messages.get("status.contestant"), routes.ContestScoreboardController.viewScoreboard(contest.getId()))
);
ControllerUtils.getInstance().appendTemplateLayout(content, "Contest - Public Scoreboard");
ControllerUtils.getInstance().addActivityLog("View public scoreboard in contest " + contest.getName() + " <a href=\"" + "http://" + Http.Context.current().request().host() + Http.Context.current().request().uri() + "\">link</a>.");
return ControllerUtils.getInstance().lazyOk(content);
} else {
return ContestControllerUtils.getInstance().tryEnteringContest(contest);
}
}
@Transactional(readOnly = true)
public Result viewOfficialScoreboard(long contestId) throws ContestNotFoundException {
Contest contest = contestService.findContestById(contestId);
if ((contest.isUsingScoreboard()) && (isAllowedToSuperviseScoreboard(contest))) {
ContestScoreboard contestScoreboard = contestScoreboardService.findContestScoreboardByContestJidAndScoreboardType(contest.getJid(), ContestScoreboardType.OFFICIAL);
ScoreboardAdapter adapter = ScoreboardAdapters.fromContestStyle(contest.getStyle());
Scoreboard scoreboard = contestScoreboard.getScoreboard();
LazyHtml content = new LazyHtml(adapter.renderScoreboard(scoreboard, contestScoreboard.getLastUpdateTime(), JidCacheServiceImpl.getInstance(), IdentityUtils.getUserJid(), false, scoreboard.getState().getContestantJids().stream().collect(Collectors.toSet())));
content.appendLayout(c -> heading3WithActionsLayout.render(Messages.get("scoreboard.scoreboard"), new InternalLink[]{new InternalLink(Messages.get("scoreboard.refresh"), routes.ContestScoreboardController.refreshAllScoreboard(contest.getId())), new InternalLink(Messages.get("data.download"), routes.ContestScoreboardController.downloadContestDataAsXLS(contest.getId()))}, c));
appendSubtabsLayout(content, contest);
ContestControllerUtils.getInstance().appendTabsLayout(content, contest);
ControllerUtils.getInstance().appendSidebarLayout(content);
appendBreadcrumbsLayout(content, contest,
new InternalLink(Messages.get("status.supervisor"), routes.ContestScoreboardController.viewOfficialScoreboard(contest.getId()))
);
ControllerUtils.getInstance().appendTemplateLayout(content, "Contest - Official Scoreboard");
ControllerUtils.getInstance().addActivityLog("View official scoreboard in contest " + contest.getName() + " <a href=\"" + "http://" + Http.Context.current().request().host() + Http.Context.current().request().uri() + "\">link</a>.");
return ControllerUtils.getInstance().lazyOk(content);
} else {
return ContestControllerUtils.getInstance().tryEnteringContest(contest);
}
}
@Transactional
public Result refreshAllScoreboard(long contestId) throws ContestNotFoundException {
Contest contest = contestService.findContestById(contestId);
if ((contest.isUsingScoreboard()) && (isAllowedToSuperviseScoreboard(contest))) {
ContestConfiguration contestConfiguration = contestService.findContestConfigurationByContestJid(contest.getJid());
ScoreboardAdapter adapter = ScoreboardAdapters.fromContestStyle(contest.getStyle());
ScoreboardState state = contestService.getContestStateByJid(contest.getJid());
List<Submission> submissions = submissionService.findAllSubmissionsByContestJid(contest.getJid());
ScoreboardContent content = adapter.computeScoreboardContent(contest, contestConfiguration.getStyleConfig(), state, submissions, contestScoreboardService.getMapContestantJidToImageUrlInContest(contest.getJid()));
Scoreboard scoreboard = adapter.createScoreboard(state, content);
contestScoreboardService.updateContestScoreboardByContestJidAndScoreboardType(contest.getJid(), ContestScoreboardType.OFFICIAL, scoreboard);
if (contest.isStandard()) {
refreshFrozenScoreboard(contest, adapter, state);
}
ControllerUtils.getInstance().addActivityLog("Refresh all scoreboard in contest " + contest.getName() + " <a href=\"" + "http://" + Http.Context.current().request().host() + Http.Context.current().request().uri() + "\">link</a>.");
return redirect(routes.ContestScoreboardController.viewOfficialScoreboard(contest.getId()));
} else {
return ContestControllerUtils.getInstance().tryEnteringContest(contest);
}
}
@Transactional(readOnly = true)
public Result downloadContestDataAsXLS(long contestId) throws ContestNotFoundException {
Contest contest = contestService.findContestById(contestId);
if ((contest.isUsingScoreboard()) && (isAllowedToSuperviseScoreboard(contest))) {
ContestConfiguration contestConfiguration = contestService.findContestConfigurationByContestJid(contest.getJid());
ContestScoreboard contestScoreboard = contestScoreboardService.findContestScoreboardByContestJidAndScoreboardType(contest.getJid(), ContestScoreboardType.OFFICIAL);
ScoreboardState scoreboardState = contestScoreboard.getScoreboard().getState();
Workbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet(Messages.get("problem.problems"));
int rowNum = 0;
int cellNum = 0;
Row row = sheet.createRow(rowNum++);
Cell cell = row.createCell(cellNum++);
cell.setCellValue(Messages.get("problem.alias"));
cell = row.createCell(cellNum++);
cell.setCellValue(Messages.get("problem.name"));
for (int i=0;i<scoreboardState.getProblemJids().size();++i) {
row = sheet.createRow(rowNum++);
cellNum = 0;
cell = row.createCell(cellNum++);
cell.setCellValue(scoreboardState.getProblemAliases().get(i));
cell = row.createCell(cellNum++);
cell.setCellValue(JidCacheServiceImpl.getInstance().getDisplayName(scoreboardState.getProblemJids().get(i)));
}
sheet = workbook.createSheet(Messages.get("team.teams"));
List<ContestTeam> contestTeams = contestTeamService.findAllContestTeams(contest.getJid());
rowNum = 0;
cellNum = 0;
row = sheet.createRow(rowNum++);
cell = row.createCell(cellNum++);
cell.setCellValue(Messages.get("team.name"));
cell = row.createCell(cellNum++);
cell.setCellValue(Messages.get("team.coach.name"));
cell = row.createCell(cellNum++);
cell.setCellValue(Messages.get("team.member.name"));
for (ContestTeam contestTeam : contestTeams) {
cellNum = 0;
row = sheet.createRow(rowNum++);
cell = row.createCell(cellNum++);
cell.setCellValue(contestTeam.getName());
List<ContestTeamCoach> contestTeamCoaches = contestTeamService.findContestTeamCoachesByTeamJid(contestTeam.getJid());
List<ContestTeamMember> contestTeamMembers = contestTeamService.findContestTeamMembersByTeamJid(contestTeam.getJid());
if (contestTeamCoaches.size() > 0) {
cell = row.createCell(1);
cell.setCellValue(JidCacheServiceImpl.getInstance().getDisplayName(contestTeamCoaches.get(0).getCoachJid()));
}
if (contestTeamMembers.size() > 0) {
cell = row.createCell(2);
cell.setCellValue(JidCacheServiceImpl.getInstance().getDisplayName(contestTeamMembers.get(0).getMemberJid()));
}
int max = Math.max(contestTeamCoaches.size(), contestTeamMembers.size());
for (int i=1;i<max;++i) {
row = sheet.createRow(rowNum++);
if (contestTeamCoaches.size() > i) {
cell = row.createCell(1);
cell.setCellValue(JidCacheServiceImpl.getInstance().getDisplayName(contestTeamCoaches.get(i).getCoachJid()));
}
if (contestTeamMembers.size() > i) {
cell = row.createCell(2);
cell.setCellValue(JidCacheServiceImpl.getInstance().getDisplayName(contestTeamMembers.get(i).getMemberJid()));
}
}
}
sheet = workbook.createSheet("Rank");
if (contest.isIOI()) {
IOIScoreboardContent ioiScoreboardContent = (IOIScoreboardContent) contestScoreboard.getScoreboard().getContent();
rowNum = 0;
row = sheet.createRow(rowNum++);
cellNum = 0;
cell = row.createCell(cellNum++);
cell.setCellValue("Rank");
cell = row.createCell(cellNum++);
cell.setCellValue("Contestant");
cell = row.createCell(cellNum++);
cell.setCellValue("Total");
for (String s : scoreboardState.getProblemAliases()) {
cell = row.createCell(cellNum++);
cell.setCellValue(s);
}
for (IOIScoreboardEntry entry : ioiScoreboardContent.getEntries()) {
row = sheet.createRow(rowNum++);
cellNum = 0;
cell = row.createCell(cellNum++);
cell.setCellValue(entry.rank);
cell = row.createCell(cellNum++);
cell.setCellValue(JidCacheServiceImpl.getInstance().getDisplayName(entry.contestantJid));
cell = row.createCell(cellNum++);
cell.setCellValue(entry.totalScores);
for (Integer score : entry.scores) {
cell = row.createCell(cellNum++);
if (score != null) {
cell.setCellValue(score);
}
}
}
} else {
ICPCScoreboardContent icpcScoreboardContent = (ICPCScoreboardContent) contestScoreboard.getScoreboard().getContent();
rowNum = 0;
row = sheet.createRow(rowNum++);
cellNum = 0;
cell = row.createCell(cellNum++);
cell.setCellValue("Rank");
cell = row.createCell(cellNum++);
cell.setCellValue("Contestant");
cell = row.createCell(cellNum++);
cell.setCellValue("Score");
cell = row.createCell(cellNum++);
cell.setCellValue("Penalty");
for (String s : scoreboardState.getProblemAliases()) {
cell = row.createCell(cellNum++);
cell.setCellValue(s);
cell = row.createCell(cellNum++);
sheet.addMergedRegion(new CellRangeAddress(rowNum-1, rowNum-1, cellNum-2, cellNum-1));
}
for (ICPCScoreboardEntry entry : icpcScoreboardContent.getEntries()) {
row = sheet.createRow(rowNum++);
cellNum = 0;
cell = row.createCell(cellNum++);
cell.setCellValue(entry.rank);
cell = row.createCell(cellNum++);
cell.setCellValue(JidCacheServiceImpl.getInstance().getDisplayName(entry.contestantJid));
cell = row.createCell(cellNum++);
cell.setCellValue(entry.totalAccepted);
cell = row.createCell(cellNum++);
cell.setCellValue(entry.totalPenalties);
for (int i = 0; i < entry.attemptsList.size(); i++) {
cell = row.createCell(cellNum++);
cell.setCellValue(entry.attemptsList.get(i));
cell = row.createCell(cellNum++);
if (entry.isAcceptedList.get(i)) {
cell.setCellValue(entry.penaltyList.get(i));
}
}
}
}
ControllerUtils.getInstance().addActivityLog("Download contest data in contest " + contest.getName() + " <a href=\"" + "http://" + Http.Context.current().request().host() + Http.Context.current().request().uri() + "\">link</a>.");
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
workbook.write(baos);
baos.close();
response().setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response().setHeader("Content-Disposition", "attachment; filename=\"" + contest.getName()+ ".xls\"");
return ok(baos.toByteArray());
} catch (IOException e) {
return internalServerError();
}
} else {
return ContestControllerUtils.getInstance().tryEnteringContest(contest);
}
}
private void refreshFrozenScoreboard(Contest contest, ScoreboardAdapter adapter, ScoreboardState state) {
ContestConfiguration contestConfiguration = contestService.findContestConfigurationByContestJid(contest.getJid());
List<Submission> submissions = submissionService.findAllSubmissionsByContestJidBeforeTime(contest.getJid(), new Gson().fromJson(contestConfiguration.getTypeConfig(), StandardContestTypeConfig.class).getScoreboardFreezeTime());
ScoreboardContent content = adapter.computeScoreboardContent(contest, contestConfiguration.getStyleConfig(), state, submissions, contestScoreboardService.getMapContestantJidToImageUrlInContest(contest.getJid()));
Scoreboard scoreboard = adapter.createScoreboard(state, content);
contestScoreboardService.updateContestScoreboardByContestJidAndScoreboardType(contest.getJid(), ContestScoreboardType.FROZEN, scoreboard);
}
private void appendSubtabsLayout(LazyHtml content, Contest contest) {
content.appendLayout(c -> accessTypeByStatusLayout.render(routes.ContestScoreboardController.viewScoreboard(contest.getId()), routes.ContestScoreboardController.viewOfficialScoreboard(contest.getId()), c));
}
private void appendBreadcrumbsLayout(LazyHtml content, Contest contest, InternalLink... lastLinks) {
ControllerUtils.getInstance().appendBreadcrumbsLayout(content,
ContestControllerUtils.getInstance().getContestBreadcrumbsBuilder(contest)
.add(new InternalLink(Messages.get("scoreboard.scoreboard"), routes.ContestController.jumpToScoreboard(contest.getId())))
.addAll(Arrays.asList(lastLinks))
.build()
);
}
private boolean isAllowedToSuperviseScoreboard(Contest contest) {
return ControllerUtils.getInstance().isAdmin() || ContestControllerUtils.getInstance().isManager(contest) || ContestControllerUtils.getInstance().isSupervisor(contest);
}
}