Domjudge-UI-for-Codeforces

For ICPC competitors practicing for the DOMjudge UI in codeforces.

// ==UserScript==
// @name         Domjudge-UI-for-Codeforces
// @namespace    http://tampermonkey.net/
// @version      0.1.0
// @license      MIT
// @description  For ICPC competitors practicing for the DOMjudge UI in codeforces.
// @author       jakao
// @match        https://codeforces.com/gym/*/submit*
// @match        https://codeforces.com/contest/*/submit*
// @icon         https://i.imgur.com/RwGYTmF.png
// @grant        GM_addStyle
// ==/UserScript==

const css = `
.summary-table {
    text-align: center;
}
table {
    width: 100%;
    border-collapse: collapse;
}
.score-penalty-table {
    display: flex;
    align-items: center;
}
.score-penalty-table > div {
    flex: 1;
    padding: 5px;
    border-right: 1px solid #ccc; /* Optional border for visual separation */
}
.score-penalty-table > div:first-child {
    border-right: 1px solid transparent; /* Transparent border between left and right */
}
.badge {
    display: inline-block;
    padding: .25em .4em;
    font-size: 75%;
    font-weight: 700;
    line-height: 1;
    text-align: center;
    white-space: nowrap;
    vertical-align: baseline;
    border-radius: .25rem;
    transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
.problem-badge {
    font-size: 100%;
}
td.score_cell {
    min-width: 4.2em;
    padding: 2px 1px 2px 1px;
    border-right: none;
}
.summary-table td.score_cell {
    min-width: 4.2em;
    border-right: none;
}
thead {
    display: table-header-group;
    vertical-align: middle;
    unicode-bidi: isolate;
    border-color: inherit;
}
.sortorderswitch {
    border-top: 2px solid black;
}
.summary-table {
    margin-top: 2.5em;
}
.summary-table a {
    display: block;
    padding: 2px 1px 2px 1px;
    text-decoration: none;
    color: black;
}
.scorenc, .scorett, .scorepl {
    text-align: center;
    width: 2ex;
}
.scorenc {
    font-weight: bold;
}
.summary-table .scoreaf { white-space: nowrap; border: 0; text-align: center; }
.summary-table tr {
    border-bottom: 1px solid black;
    border-bottom-width: 1px;
    border-bottom-style: solid;
    border-bottom-color: black;
    height: 42px;
}
.summary-table .scoretn {
    padding: 0px 5px 0px;
    text-align: right;
    font-weight: bold;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.summary-table td, .summary-table th {
    border-right: 1px solid silver;
    padding: 0px;
}
.summary-table-header {
    font-variant: small-caps;
    border-bottom: 2px solid black;
    white-space: nowrap;
}
.summary-table-header th{
    text-align: center;
    box-shadow: -1px 0px 0px 0px silver inset, 0px 2px 0px 0px black;
    border: none;
    background: var(--background-color);
    position: sticky;
    top: 0px;
    z-index: 1;
}
col {
    display: table-column;
    unicode-bidi: isolate;
}
colgroup {
    display: table-column-group;
    unicode-bidi: isolate;
}
.summary-table td {
    font-size: small;
    vertical-align: middle;
    text-align: center;
}
.summary-table td div {
    width: 4em;
    font-size: 120%;
    display: inline-block;
}
.summary-table td div span {
    font-weight: normal;
    font-size: 70%;
    display: block;
}
.summary-table th {
    text-align: center;
    box-shadow: -1px 0px 0px 0px silver inset, 0px 2px 0px 0px black;
    border: none;
    background: var(--background-color);
    position: sticky;
    top: 0px;
    z-index: 1;
}
td.scorenc {
    border-color: silver;
    border-right: 0;
}
*, ::after, ::before {
    box-sizing: border-box;
}
.row {
    display: -ms-flexbox;
    display: flex;
    -ms-flex-wrap: wrap;
    flex-wrap: wrap;
    margin-right: -15px;
    margin-left: -15px;
}
.col {
    -ms-flex-preferred-size: 0;
    flex-basis: 0;
    -ms-flex-positive: 1;
    flex-grow: 1;
    max-width: 100%;
}
.teamoverview {
    border-top: solid 1px darkgray;
    border-bottom: solid 1px darkgray;
    background-color: #c4d8ff;
    margin-top: 2ex;
    padding-left: 1ex;
    font-size: 1.17em;
    text-align: center;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
    margin-bottom: .5rem;
    font-weight: 500 !important;
    line-height: 1.2;
}
h1 {
    display: block !important;
    font-size: 2em;
    margin-block-start: 0.67em !important;
    margin-block-end: 0.67em !important;
    margin-inline-start: 0px !important;
    margin-inline-end: 0px !important;
    font-weight: bold;
    unicode-bidi: isolate !important;
}
.data-table tr {
    border-bottom: 1px solid silver;
}
.table td, .table th {
    padding: .75rem;
    vertical-align: top;
    border-top: 1px solid #dee2e6;
}
.table-sm td, .table-sm th {
    padding: .3rem;
}
.table thead th {
    vertical-align: bottom;
    border-bottom: 2px solid #dee2e6;
}
.table .thead-light th {
    color: #495057;
    background-color: #e9ecef;
    border-color: #dee2e6;
}
table {
    border-collapse: collapse;
}
.table {
    width: 100%;
    margin-bottom: 1rem;
    color: #212529;
}
th {
    display: table-cell;
    vertical-align: inherit;
    font-weight: bold !important;
    text-align: -internal-center;
    unicode-bidi: isolate;
}
.sol {
    font-weight: bold;
    font-variant: small-caps;
}
.sol_queued {
    color: gray;
}
.sol_incorrect, .compile-unsuccessful {
    color: red;
}
.sol_correct, .compile-successful {
    color: green;
}
.probid, .langid {
    font-variant: small-caps;
}
.data-table td a, .data-table td a:hover {
    display: block;
    text-decoration: none;
    color: inherit;
    padding: 3px 5px;
}
.table-striped tbody tr:nth-of-type(odd) {
    background-color: rgba(0, 0, 0, .05);
}
body {
    color: var(--text-color);
    background-color: var(--background-color);
    font-family: Roboto, sans-serif !important;
    padding-bottom: 4em;
    padding-top: 4.5rem;
}

div {
    display: block;
    unicode-bidi: isolate;
}
.navbar-collapse {
    -ms-flex-preferred-size: 100%;
    flex-basis: 100%;
    -ms-flex-positive: 1;
    flex-grow: 1;
    -ms-flex-align: center;
    align-items: center;
}
ul {
    display: block;
    list-style-type: disc;
    margin-block-start: 1em;
    margin-block-end: 1em;
    margin-inline-start: 0px;
    margin-inline-end: 0px;
    padding-inline-start: 40px;
    unicode-bidi: isolate;
}
dl, ol, ul {
    margin-top: 0;
    margin-bottom: 1rem;
}
.mr-auto, .mx-auto {
    margin-right: auto !important;
}
.navbar-nav {
    display: -ms-flexbox;
    display: flex;
    -ms-flex-direction: column;
    flex-direction: row;
    padding-left: 0;
    margin-bottom: 0;
    list-style: none;
}
li {
    display: list-item;
    text-align: -webkit-match-parent;
    unicode-bidi: isolate;
}
#menuDefault ul li.nav-item {
    white-space: nowrap;
    white-space-collapse: collapse;
    text-wrap: nowrap;
}
#menuDefault {
    display: flex;
}
.nav-link {
    display: block;
    padding: .5rem 1rem;
}
.navbar-dark .navbar-nav .active>.nav-link, .navbar-dark .navbar-nav .nav-link.active, .navbar-dark .navbar-nav .nav-link.show, .navbar-dark .navbar-nav .show>.nav-link {
    color: #fff !important;
}
.navbar-dark .navbar-nav .nav-link {
    color: #FFFFFF80 !important;
    font-size: 16px;
}
.fa, .fa-brands, .fa-classic, .fa-regular, .fa-sharp, .fa-solid, .fab, .far, .fas {
    -moz-osx-font-smoothing: grayscale;
    -webkit-font-smoothing: antialiased;
    display: var(--fa-display, inline-block);
    font-style: normal;
    font-variant: normal;
    line-height: 1;
    text-rendering: auto;
}
.fa-classic, .fa-regular, .fa-solid, .far, .fas {
    font-family: "Font Awesome 6 Free";
}
.fa-solid, .fas {
    font-weight: 900;
}
a {
    color: #007bff;
    text-decoration: none;
    background-color: transparent;
}
.navbar-brand {
    display: inline-block;
    padding-top: .3125rem;
    padding-bottom: .3125rem;
    margin-right: 1rem;
    /* font-size: 1.25rem; */
    font-size: 20px !important;
    line-height: inherit;
    white-space: nowrap;
}
.navbar-dark .navbar-brand {
    color: #fff;
}
.navbar-dark .navbar-text {
    color: rgba(255, 255, 255, .5);
    font-size: 16px;
}
.navbar-text {
    display: inline-block;
    padding-top: .5rem;
    padding-bottom: .5rem;
}
nav {
    display: block;
    unicode-bidi: isolate;
}
.navbar {
    position: relative;
    justify-content: space-between;
    -ms-flex-pack: justify;
    -ms-flex-wrap: wrap;
    -ms-flex-align: center;
    align-items: center;
    flex-direction: row;
    display: flex;
    display: -ms-flexbox;
    padding-top: 8px;
    padding-right: 16px;
    padding-bottom: 8px;
    padding-left: 16px;
}
.bg-dark {
    background-color: #343a40 !important;
}
.fixed-top {
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
    z-index: 1030;
}

/* submit button */
.justify-content-center {
    -ms-flex-pack: center !important;
    justify-content: center !important;
}
.btn {
    display: inline-block;
    font-weight: 400;
    color: #212529;
    text-align: center;
    vertical-align: middle;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    background-color: transparent;
    border: 1px solid transparent;
    padding: 6px 12px;
    font-size: 16px;
    line-height: 1.5;
    border-radius: .25rem;
    transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
.btn:not(:disabled):not(.disabled) {
    cursor: pointer;
}
.btn-success {
    color: #fff !important;
    background: #0a8927 !important;
}
.btn-info {
    color: #fff !important;
    background-color: #17a2b8  !important;
}
.score_pending {
    background: #6666FF !important;
}
.btn-secondary {
    color: #fff;
    background-color: #6c757d;
    border-color: #6c757d;
}
.modal-footer>* {
    margin: .25rem;
}
.btn-group-sm>.btn, .btn-sm {
    padding: .25rem .5rem;
    font-size: 14px;
    line-height: 1.5;
    border-radius: .2rem;
}
.modal-header {
    display: -ms-flexbox;
    display: flex;
    -ms-flex-align: start;
    align-items: flex-start;
    -ms-flex-pack: justify;
    justify-content: space-between;
    padding: 1rem 1rem;
    border-bottom: 1px solid #dee2e6;
    border-top-left-radius: calc(.3rem - 1px);
    border-top-right-radius: calc(.3rem - 1px);
}
.modal-title {
    margin-bottom: 0;
    line-height: 1.5;
}
.modal-header .close {
    padding: 1rem 1rem;
    margin: -1rem -1rem -1rem auto;
}
button.close {
    padding: 0;
    background-color: transparent;
    border: 0;
}
.close {
    float: right;
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1;
    color: #000;
    text-shadow: 0 1px 0 #fff;
    opacity: .5;
}
[type=button], [type=reset], [type=submit], button {
    -webkit-appearance: button;
}
button, select {
    text-transform: none;
}

button, input {
    overflow: visible;
}
button, input, optgroup, select, textarea {
    margin: 0;
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
}
button {
    border-radius: 0;
}

.close {
    color: #aaa;
    float: right;
    font-size: 28px;
    font-weight: bold;
}
.close:hover,
.close:focus {
    color: black;
    text-decoration: none;
    cursor: pointer;
}
.modal-open .modal {
    overflow-x: hidden;
    overflow-y: auto;
}
.modal {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1050;
    display: none;
    width: 100%;
    height: 100%;
    overflow: hidden;
    outline: 0;
}
.fade {
    transition: opacity .15s linear;
}
.modal-header {
    display: -ms-flexbox;
    display: flex;
    -ms-flex-align: start;
    align-items: flex-start;
    -ms-flex-pack: justify;
    justify-content: space-between;
    padding: 16px 16px;
    border-bottom: 1px solid #dee2e6;
    border-top-left-radius: calc(.3rem - 1px);
    border-top-right-radius: calc(.3rem - 1px);
}
.modal-body {
    position: relative;
    -ms-flex: 1 1 auto;
    flex: 1 1 auto;
    padding: 1rem;
}
form {
    display: block;
    margin-top: 0em;
    unicode-bidi: isolate;
}
.modal-content {
    position: relative;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-direction: column;
    flex-direction: column;
    width: 100%;
    pointer-events: auto;
    background-color: #fff;
    background-clip: padding-box;
    border: 1px solid rgba(0, 0, 0, .2);
    border-radius: 4.8px;
    outline: 0;
}
.modal-footer {
    display: -ms-flexbox;
    display: flex;
    -ms-flex-wrap: wrap;
    flex-wrap: wrap;
    -ms-flex-align: center;
    align-items: center;
    -ms-flex-pack: end;
    justify-content: flex-end;
    padding: .75rem;
    border-top: 1px solid #dee2e6;
    border-bottom-right-radius: calc(.3rem - 1px);
    border-bottom-left-radius: calc(.3rem - 1px);
}
h5 {
    font-size: 20px !important;
}
.modal.fade .modal-dialog {
    transition: -webkit-transform .3s ease-out;
    transition: transform .3s ease-out;
    transition: transform .3s ease-out, -webkit-transform .3s ease-out;
    -webkit-transform: translate(0, -50px);
    transform: translate(0, -50px);
}
.modal.show .modal-dialog {
    -webkit-transform: none;
    transform: none;
}
.modal-dialog {
    position: relative;
    width: auto;
    margin: .5rem;
    pointer-events: none;
}
@media (min-width: 576px) {
    .modal-dialog {
        max-width: 500px;
        margin: 1.75rem auto;
    }
}
@media (min-width: 992px) {
    .modal-lg, .modal-xl {
        max-width: 800px;
    }
}
label {
    display: inline-block;
    margin-bottom: .5rem;
}
label {
    cursor: default;
}
.form-group {
    margin-bottom: 1rem;
}
.custom-file {
    position: relative;
    display: inline-block;
    width: 100%;
    height: calc(1.5em + .75rem + 2px);
    margin-bottom: 0;
}
input:not([type="image" i], [type="range" i], [type="checkbox" i], [type="radio" i]) {
    overflow-clip-margin: 0px !important;
    overflow: clip !important;
}
input[type="file" i] {
    appearance: none;
    background-color: initial;
    cursor: default;
    align-items: baseline;
    color: inherit;
    text-overflow: ellipsis;
    text-align: start !important;
    padding: initial;
    border: initial;
    white-space: pre;
}
input {
    font-style: ;
    font-variant-ligatures: ;
    font-variant-caps: ;
    font-variant-numeric: ;
    font-variant-east-asian: ;
    font-variant-alternates: ;
    font-variant-position: ;
    font-weight: ;
    font-stretch: ;
    font-size: ;
    font-family: ;
    font-optical-sizing: ;
    font-size-adjust: ;
    font-kerning: ;
    font-feature-settings: ;
    font-variation-settings: ;
    text-rendering: auto;
    color: fieldtext;
    letter-spacing: normal;
    word-spacing: normal;
    line-height: normal;
    text-transform: none;
    text-indent: 0px;
    text-shadow: none;
    display: inline-block;
    text-align: start;
    appearance: auto;
    -webkit-rtl-ordering: logical;
    cursor: text;
    background-color: field;
    margin: 0em;
    padding: 1px 0px;
    border-width: 2px;
    border-style: inset;
    border-color: light-dark(rgb(118, 118, 118), rgb(133, 133, 133));
    border-image: initial;
    padding-block: 1px;
    padding-inline: 2px;
}
.text-muted {
    color: #6c757d !important;
}
.text-truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
file-label, .custom-select {
    transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
.custom-select {
    display: inline-block;
    width: 100%;
    height: calc(1.5em + .75rem + 2px);
    padding: .375rem 1.75rem .375rem .75rem;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.5;
    color: #495057;
    vertical-align: middle;
    border: 1px solid #ced4da;
    border-radius: .25rem;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
}
.form-control {
    display: block;
    width: 100%;
    height: calc(1.5em + .75rem + 2px);
    padding: .375rem .75rem;
    font-size: 14px;
    font-family: Roboto, sans-serif;
    font-weight: 400;
    line-height: 1.5;
    color: #495057;
    background-color: #fff;
    background-clip: padding-box;
    border: 1px solid #ced4da;
    border-radius: .25rem;
    transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
select {
    word-wrap: normal;
}
.custom-file-label {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    z-index: 1;
    height: calc(1.5em + .75rem + 2px);
    padding: .375rem .75rem;
    overflow: hidden;
    font-weight: 400;
    line-height: 1.5;
    color: #495057;
    background-color: #fff;
    border: 1px solid #ced4da;
    border-radius: .25rem;
}
.custom-file-label::after {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 3;
    display: block;
    height: calc(1.5em + .75rem);
    padding: .375rem .75rem;
    line-height: 1.5;
    color: #495057;
    content: "Browse";
    background-color: #e9ecef;
    border-left: inherit;
    border-radius: 0 .25rem .25rem 0;
}
.alert-warning {
    color: #856404;
    background-color: #fff3cd;
    border-color: #ffeeba;
}
.alert {
    position: relative;
    padding: .75rem 1.25rem;
    margin-bottom: 1rem;
    border: 1px solid transparent;
    border-radius: .25rem;
}
option {
    font-weight: normal;
    display: block;
    padding-block-start: 0px;
    padding-block-end: 1px;
    min-block-size: 1.2em;
    padding-inline: 2px;
    white-space: nowrap;
}
.modal-backdrop.fade {
    opacity: 0;
}
.modal-backdrop.show {
    opacity: .5;
}
.modal-backdrop {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1040;
    width: 100vw;
    height: 100vh;
    background-color: #000;
}
`;

GM_addStyle(css);

let contestStartTime = -1;
// let contestStartTime = 1726755078;
let blueLine;
let submissionResult;
let contestProblems, contestProblemName, gymRound;
let teamname, rank, penalty, score;
let problemStatus = {};
let timerInterval;
let submitForm;
let contestLength = 18000;

function getCurrentURL () {
    return window.location.href;
}

function parseLanguage(language){
    if(language.startsWith('C++')){
        return "CPP";
    }
    if(language.startsWith('Py')){
        return "PY3";
    }
    return language;
}

function parseVerdict(verdict){
    if(verdict == "OK") return "CORRECT";
    if(verdict == "WRONG_ANSWER")   return "WRONG-ANSWER";
    if(verdict == "TIME_LIMIT_EXCEEDED") return "TIMELIMIT";
    if(verdict == "RUNTIME_ERROR") return "RUN-ERROR";
    if(verdict == "MEMORY_LIMIT_EXCEEDED") return "RUN-ERROR";
    if(verdict == "COMPILATION_ERROR")  return "COMPILER-ERROR";
    if(verdict == "TESTING")  return "PENDING";
    return "PENDING";
}

function parseSubmission(result){
    submissionResult = [];
    for(let submission of result){
        if(contestStartTime <= submission.creationTimeSeconds && submission.creationTimeSeconds - contestStartTime < contestLength){
            const date = new Date(submission.creationTimeSeconds * 1000);
            submissionResult.push({
                index: submission.problem.index,
                verdict: parseVerdict(submission.verdict),
                time: date.getHours().toString().padStart(2, '0')+":"+date.getMinutes().toString().padStart(2, '0'),
                submitMinute: Math.floor(submission.relativeTimeSeconds/60),
                language: parseLanguage(submission.programmingLanguage)
            });
        }
    }
    const reversedSubmissionResult = submissionResult.slice().reverse();
    for(let submission of reversedSubmissionResult){
        if(submission.index in problemStatus){
            if(submission.verdict == "CORRECT"){
                if(problemStatus[submission.index].passtime == -1)
                    problemStatus[submission.index].passtime = submission.submitMinute;
            }
            else if(submission.verdict == "PENDING"){
                problemStatus[submission.index].pendingNumber = problemStatus[submission.index].pendingNumber+1;
                if(problemStatus[submission.index].pending == -1)
                    problemStatus[submission.index].pending = submission.submitMinute;
            }
            else{
                problemStatus[submission.index].rejected = problemStatus[submission.index].rejected + (submission.verdict == "COMPILER-ERROR" ? 0 : 1);
            }
        }
        else{
            let newStatus;
            if(submission.verdict == "PENDING"){
                newStatus = {
                    rejected: 0,
                    passtime: -1,
                    pending: submission.submitMinute,
                    pendingNumber: 1
                }
            }
            else if(submission.verdict != "CORRECT"){
                newStatus = {
                    rejected: submission.verdict == "COMPILER-ERROR" ? 0 : 1,
                    passtime: -1,
                    pending: -1,
                    pendingNumber: 0
                }
            }
            else{
                newStatus = {
                    rejected: 0,
                    passtime: submission.submitMinute,
                    pending: -1,
                    pendingNumber: 0
                }
            }
            problemStatus[submission.index] = newStatus;
        }
    }
}

async function getApiData () {
    domjudgeView();
    gymRound = getCurrentURL().split('/')[4];
    // console.log(gymRound);

    let links = document.querySelectorAll('a'), username;
    // 取得 username
    for (let link of links) {
        if (link.href.includes('/profile/')) {
            username = link.href.split('/')[4];
            break;
        }
    }

    const SubmissionApiURL = 'https://codeforces.com/api/contest.status?contestId=' + gymRound + '&handle=' + username;
    // console.log(SubmissionApiURL);

    await fetch(SubmissionApiURL)
        .then(response => response.json())  // 將回應轉為 JSON 格式
        .then(data => {
        if (data.result && data.result.length > 0) {
            if("startTimeSeconds" in data.result[0].author)
                contestStartTime = data.result[0].author.startTimeSeconds;
            parseSubmission(data.result);
            // return data.result[0].creationTimeSeconds;
        } else {
            console.log("No result found in Submission API response");
        }
    })
        .then(() => drawHeader())
        .catch(error => {
        console.error("Error fetching API:", error);
    });

    const ContestApiURL = 'https://codeforces.com/api/contest.standings?contestId=' + gymRound + '&from=1&showUnofficial=true';
    // console.log(ContestApiURL);

    await fetch(ContestApiURL)
        .then(response => response.json())  // 將回應轉為 JSON 格式
        .then(data => {
        if (data.result) {
            contestLength = data.result.contest.durationSeconds;

            contestProblems = [];
            for(let problem of data.result.problems){
                contestProblems.push(problem.index);
            }

            contestProblemName = [];
            for(let problem of data.result.problems){
                contestProblemName.push(problem.index + " - " + problem.name);
            }

            for(let team of data.result.rows){
                if(team.party.members.some(item => item.handle === username)){
                    penalty = team.penalty;
                    rank = team.rank;
                    score = team.points;
                    if("teamName" in team.party)
                        teamname = team.party.teamName;
                    else
                        teamname = username;
                    break;
                }
            }
        } else {
            console.log("No result found in Contest API response");
        }
    })
        .then(() => drawTimeLine())
        .then(() => updateTimeLine())
        .then(() => drawTeamsSummary())
        .then(() => drawSubmission())
        .then(() => addSubmitPage())
        .then(() => submitButtomJquery());
    // .catch(error => {
    //     console.error("Error fetching API:", error);
    // });

    timerInterval = setInterval(updateTimer, 1000);
}

function updateTimeLine(){
    if(contestStartTime == -1)  return;
    // console.log((Date.now() / 1000 - contestStartTime), contestLength);
    let contestDuringPrecentage = (Date.now() / 1000 - contestStartTime) / contestLength;
    // contestDuringPrecentage = Math.random();
    if(contestDuringPrecentage > 1) {
        return;
    }
    let pageWidth = window.innerWidth;
    let blueLineWidth = contestDuringPrecentage * pageWidth;
    blueLine.style.width = blueLineWidth + 'px'; // 設定寬度
}

function addFontAwesome(){
    // 獲取頁面中的 <header> 元素
    let header = document.querySelector('head');

    // 創建一個新的 <script> 元素
    let scriptElement = document.createElement('script');

    // 設置 <script> 標籤的屬性
    scriptElement.src = "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"
    scriptElement.type = "text/javascript";

    let linkElement = document.createElement('link');

    // 設置 <link> 標籤的屬性
    linkElement.href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css";
    linkElement.rel = "stylesheet";
    linkElement.type = "text/css";
    //linkElement.type = "text/javascript";

    // 將 <link> 標籤添加到 <head> 中


    // 將 <script> 元素添加到 <header> 中
    if (header) {
        header.appendChild(scriptElement);
        header.appendChild(linkElement);
    }
}
function drawTimeLine(){
    if(contestStartTime == -1)  return;
    let contestDuringPrecentage = (Date.now() / 1000 - contestStartTime) / contestLength;
    // contestDuringPrecentage = 0.5;
    if(contestDuringPrecentage > 1) {
        return;
    }
    let pageWidth = window.innerWidth;
    let blueLineWidth = contestDuringPrecentage * pageWidth;

    blueLine = document.createElement('div');
    blueLine.style.position = 'absolute';
    blueLine.style.bottom = '0';
    blueLine.style.left = '0';
    blueLine.style.height = '5.5px'; // 可以調整高度
    blueLine.style.backgroundColor = '#0079ff';
    blueLine.style.zIndex = '9999'; // 保證藍線在最上層
    blueLine.style.width = blueLineWidth + 'px'; // 設定寬度
    document.querySelector('nav').appendChild(blueLine);

}

function drawHeader(){
    document.getElementById('body').style.margin = "0";

    // 選擇所有的 <a> 元素
    let links = document.querySelectorAll('a');
    let LogoutLink;

    // 遍歷每個 <a>,檢查其 innerHTML 是否包含 "Logout"
    links.forEach(link => {
        if (link.innerHTML.includes('Logout')) {
            // console.log(link); // 找到的 <a> 元素會被輸出到控制台
            LogoutLink = link;
        }
    });

    document.getElementById('header').remove();
    document.querySelector('.menu-list-container').remove();
    document.querySelector('.menu-box').remove();
    document.querySelector('.alert-success').remove();

    let baseUrl = getCurrentURL().split('/').slice(0, 5).join('/');

    let navElement = document.createElement('nav');
    navElement.classList.add('navbar','navbar-expand-md','navbar-dark','bg-dark','fixed-top');

    let navHTML = "";

    navHTML = `<a class="navbar-brand hidden-sm-down" href="${baseUrl}/submit">DOMjudge</a>`;

    navHTML += '<div class="collapse navbar-collapse" id="menuDefault">';
    navHTML += '<ul class="navbar-nav mr-auto">';
    navHTML += `<li class="nav-item active"><a class="nav-link" href="${baseUrl}/submit"><i class="fas fa-home"></i> Home </a></li>`;
    navHTML += `<li class="nav-item"><a class="nav-link" href="${baseUrl}"><i class="fas fa-book-open"></i> Problemset </a></li>`;
    navHTML += `<li class="nav-item"><a class="nav-link" href="${baseUrl}/standings"><i class="fas fa-list-ol"></i> Scoreboard </a></li>`;

    navHTML += '</ul>';
    navHTML += '<div id="submitbut"><a id="submitLink" class="nav-link justify-content-center" data-ajax-modal="" data-ajax-modal-after="initSubmitModal" href="#"><span class="btn btn-success btn-sm"><i class="fas fa-cloud-upload-alt"></i> Submit</span></a></div>';
    navHTML += `<a class="btn btn-info btn-sm justify-content-center" href="${LogoutLink}" onclick="return confirmLogout();"><i class="fas fa-sign-out-alt"></i> Logout</a>`;
    navHTML += `<div class="navbar-text" style="white-space:nowrap;"><span style="padding-left: 10px;"><i class="fas fa-clock loading-indicator"></i></span><span id="timeleft"> contest over</span></div>`;
    navHTML += '</div>';

    navElement.innerHTML = navHTML;

    let bodyDiv = document.getElementById('body');
    bodyDiv.insertBefore(navElement, bodyDiv.firstChild);
}

function updateTimer(){
    if(Math.floor(Date.now()/1000) - contestStartTime > contestLength){
        document.getElementById("timeleft").innerHTML = " contest over";
        clearInterval(timerInterval); // 倒數結束時停止計時
    }
    else{
        let leftSecond = contestLength - (Math.floor(Date.now()/1000) - contestStartTime);
        let minutes = (Math.floor((leftSecond%3600)/60));
        let seconds = leftSecond%60;
        let displayMinutes = minutes < 10 ? '0' + minutes : minutes;
        let displaySeconds = seconds < 10 ? '0' + seconds : seconds;
        if(leftSecond > 3600)
            document.getElementById("timeleft").innerHTML = " " + Math.floor(leftSecond/3600).toString() + ":" + displayMinutes + ":" + displaySeconds;
        else
            document.getElementById("timeleft").innerHTML = " " + displayMinutes + ":" + displaySeconds;
    }
}

function drawTeamsSummary(){
    // console.log(contestProblems);

    let tableHTML = '<table class="summary-table center">';

    // Table Header
    tableHTML += '<colgroup><col id="scorerank"><col id="scoreteamname"></colgroup>';
    tableHTML += '<colgroup><col id="scoresolv"><col id="scoretotal"></colgroup>';
    tableHTML += '<colgroup>';
    for(let i = 0; i < contestProblems.length; i++){
        tableHTML += '<col class="scoreprob"';
    }
    tableHTML += '</colgroup>';
    tableHTML += '<thead><tr class="summary-table-header">';
    tableHTML += '<th title="rank" scope="col">rank</th>';
    tableHTML += '<th title="team name" scope="col" colspan="3">team</th>';
    tableHTML += '<th title="# solved / penalty time" colspan="2" scope="col">score</th>';
    // tableHTML += '<th style="text-align: center;">score</th>';
    contestProblems.forEach(problemIndex => {
        const linkURL = `https://codeforces.com/gym/${gymRound}/problem/${problemIndex.toString()}`;
        tableHTML += `<th title="" scope="col"><a href="${linkURL}" target="_blank"><span class="badge problem-badge" style="min-width: 28px; border: 1px solid"><span style="color: #000000;">${problemIndex}</span></span></a></th>`;
    });
    tableHTML += '</tr></thead><tbody>';

    tableHTML += '<tr class="sortorderswitch">';
    tableHTML += `<td class="scorepl">${rank}</td>`;
    tableHTML += `<td class="scoreaf"> </td>`;
    tableHTML += `<td class="scoreaf"> </td>`;
    tableHTML += `<td class="scoretn">${teamname}</td>`;
    // tableHTML += `<td class="score-penalty-table"><div>${score}</div><div>${penalty}</div></td>`;
    tableHTML += `<td class="scorenc">${score}</td>`;
    tableHTML += `<td class="scorett">${penalty}</td>`;

    contestProblems.forEach(problemIndex => {
        if(!(problemIndex in problemStatus))
            tableHTML += `<td class="score_cell"></td>`;
        else if(problemStatus[problemIndex].passtime != -1){
            // console.log(problemIndex, problemStatus[problemIndex].passtime, problemStatus[problemIndex].pending, problemStatus[problemIndex].pendingNumber);
            if(problemStatus[problemIndex].passtime > problemStatus[problemIndex].pending && problemStatus[problemIndex].pendingNumber != 0){
                const tryTime = problemStatus[problemIndex].rejected;
                const tryString = tryTime.toString() + " + " + (problemStatus[problemIndex].pendingNumber).toString() + " tries";
                tableHTML += `<td class="score_cell"><a><div style="background:#6666FF">&nbsp;<span>${tryString}</span></div></a></td>`;
            }
            else{
                const tryTime = problemStatus[problemIndex].rejected+1;
                const tryString = tryTime.toString() + (tryTime > 1 ? " tries" : " try");
                tableHTML += `<td class="score_cell"><a><div style="background:#60e760">${problemStatus[problemIndex].passtime}<span>${tryString}</span></div></a></td>`;
            }
        }
        else if(problemStatus[problemIndex].pendingNumber != 0){
            const tryTime = problemStatus[problemIndex].rejected;
            const tryString = tryTime.toString() + " + " + (problemStatus[problemIndex].pendingNumber).toString() + " tries";
            tableHTML += `<td class="score_cell"><a><div style="background:#6666FF">&nbsp;<span>${tryString}</span></div></a></td>`;
        }
        else{
            const tryTime = problemStatus[problemIndex].rejected;
            const tryString = tryTime.toString() + (tryTime > 1 ? " tries" : " try");
            tableHTML += `<td class="score_cell"><a><div style="background:#e87272">&nbsp;<span>${tryString}</span></div></a></td>`;
        }

    });
    tableHTML += '</tr>';

    tableHTML += '</tbody></table>';

    submitForm = document.getElementById('pageContent').getElementsByTagName('form')[0];

    document.getElementById('pageContent').innerHTML = tableHTML;

}
function drawSubmission(){
    let tableHTML = '<div class="row><div class="col">';

    tableHTML += '<h1 class="teamoverview">Submissions</h1>';
    tableHTML += '<table class="data-table table table-hover table-striped table-sm submissions-table">'

    tableHTML += '<thead class="thead-light"><tr><th scope="col">time</th><th scope="col">problem</th><th scope="col">lang</th><th scope="col">result</th></tr></thead>';

    tableHTML += '<tbody>';

    submissionResult.forEach(Submission =>{
        tableHTML += '<tr class>';
        tableHTML += `<td>${Submission.time}</td>`;
        tableHTML += `<td class="probid"><a><span class="badge problem-badge" style="min-width: 28px;border: 1px solid #7293a8";><span>${Submission.index}</span></span><a></td>`;
        tableHTML += `<td class="langid"><a>${Submission.language}</a></td>`;
        if(Submission.verdict == "CORRECT")
            tableHTML += `<td class="sol sol_correct"><a>${Submission.verdict}</a></td>`;
        else if(Submission.verdict == "PENDING")
            tableHTML += `<td class="sol sol_queued"><a>${Submission.verdict}</a></td>`;
        else
            tableHTML += `<td class="sol sol_incorrect"><a>${Submission.verdict}</a></td>`;
        tableHTML += '</tr>';
    });

    tableHTML += '</tbody></table>';
    tableHTML += '</div></div>';

    document.getElementById('pageContent').innerHTML += tableHTML;
    document.getElementById('sidebar').innerHTML = "";
}

addFontAwesome();
getApiData();

function domjudgeView() {
    // 遍歷所有的文本節點
    let walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
    let node, nextNode;

    while (node = walker.nextNode()) {
        // 檢查節點是否包含 'on test'
        let textContent = node.nodeValue;
        let index = textContent.indexOf(' ON TEST');

        if (index !== -1) {
            // 刪除 'on test' 及其後面的所有內容
            node.nodeValue = textContent.substring(0, index);
            nextNode = walker.nextNode();
            nextNode.nodeValue = "";
        }
        else{
            let pretestIndex = textContent.indexOf(' ON PRETEST');
            if (pretestIndex !== -1) {
                // 刪除 'on pretest' 及其後面的所有內容
                node.nodeValue = textContent.substring(0, pretestIndex);
                nextNode = walker.nextNode();
                nextNode.nodeValue = "";
            }
        }
    }
}

domjudgeView();

// 監聽 DOM 的變化以處理動態更新的內容
const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
        // 針對新增的節點,重新執行移除操作
        if (mutation.addedNodes.length) {
            // chrome.storage.sync.get(['featureEnabled'], function (result) {
            //     if (result.featureEnabled) {
            //         domjudgeView();
            //         updateTimeLine();
            //     }
            // });
            domjudgeView();
            updateTimeLine();
        }
    });
});

// 配置監聽器參數
const observerConfig = {
    childList: true,  // 監聽子節點變動
    subtree: true,    // 監聽整個子樹
    characterData: true  // 監聽文字內容變動
};

// 啟動監聽器
observer.observe(document.body, observerConfig);

function humanReadableTimeDiff(seconds) {
    var intervals = [
        ['years', 365 * 24 * 60 * 60],
        ['months', 30 * 24 * 60 * 60],
        ['days', 24 * 60 * 60],
        ['hours', 60 * 60],
        ['minutes', 60],
    ];
    for (let [name, length] of intervals) {
        if (seconds / length >= 2) {
            return Math.floor(seconds/length) + ' ' + name;
        }
    }
    return Math.floor(seconds) + ' seconds';
}

function humanReadableBytes(bytes) {
    var sizes = [
        ['GB', 1024*1024*1024],
        ['MB', 1024*1024],
        ['KB', 1024],
    ];
    for (let [name, length] of sizes) {
        if (bytes / length >= 2) {
            return Math.floor(bytes/length) + name;
        }
    }
    return Math.floor(bytes) + 'B';
}

function addSubmitPage(){
    // 創建模態框的 HTML

    let submitTable = submitForm.querySelector('tbody');
    let rows = submitTable.querySelectorAll('tr');

    submitTable.insertBefore(rows[4], rows[0]);
    submitTable.removeChild(rows[3]);

    submitForm.querySelector('.field-name').innerHTML = "Source files";
    submitForm.querySelector('.programTypeNotice').remove();
    submitForm.querySelector('.outputOnlyProgramTypeIdNotice').remove();
    submitForm.querySelector(".error__submittedProblemIndex").remove();

    let submitButInput = submitTable.querySelector(".submit");
    let buttonElement = document.createElement("button");
    buttonElement.id = submitButInput.id;
    buttonElement.type = submitButInput.type;
    buttonElement.className = submitButInput.className;
    buttonElement.innerHTML = `<i class="fas fa-cloud-upload-alt"></i> Submit `;
    // submitTable.replaceChild(buttonElement, submitButInput);

    buttonElement.classList.add('btn');
    buttonElement.classList.add('btn-success');


    const allowedLanguage = ["43", "89", "87", "31"];
    const languageName = ["C", "CPP", "JAVA", "PYTHON3"];

    const selectElement = submitForm.querySelector('select[name="programTypeId"]');
    Array.from(selectElement.options).forEach(option => {
        if (!allowedLanguage.includes(option.value)) {
            option.remove();
        }
        else{
            for(let i = 0; i < allowedLanguage.length; i++){
                if(allowedLanguage[i] == option.value){
                    option.innerHTML = languageName[i];
                    option.removeAttribute('selected');
                    break;
                }
            }
        }
    });

    const noLanguageOption = document.createElement('option');
    noLanguageOption.value = "0";
    noLanguageOption.text = "Select a language";
    noLanguageOption
    selectElement.insertBefore(noLanguageOption, selectElement.firstChild);

    // console.log(submitForm.outerHTML);

    // <span class="close">&times;</span>

    submitForm.querySelector('select[name="submittedProblemIndex"]').querySelector('option[value=""]').remove();
    let submit_problem_option = submitForm.querySelector('select[name="submittedProblemIndex"]').innerHTML;
    let csrf_token_input = submitForm.querySelector('input[name="csrf_token"]').outerHTML;
    // console.log(submit_problem_option);

    const modalHTML = `<div id="myModal" class="modal fade show" tabindex="-1" role="dialog" aria-modal="true" style="display: none;">
    <div class="modal-dialog modal-lg" role="document"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">Submit</h5><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button></div>
<form class="submit-form" name="csrf_token" method="post" action="/team/submit" enctype="multipart/form-data">`
    + csrf_token_input +
          `<input type="hidden" name="ftaa" value="">
    <input type="hidden" name="bfaa" value="">
    <input type="hidden" name="action" value="submitSolutionFormSubmitted">
    <div class="modal-body">
    <div class="form-group"><label for="submit_problem_code" class="required">Source files</label><div class="custom-file"><input type="file" id="submit_problem_code" name="sourceFile" required="required" class="custom-file-input custom-file-input"><label class="custom-file-label text-truncate text-muted" for="submit_problem_code">No file selected</label></div></div>
    <div class="alert alert-warning" id="files_not_modified" style="display:none;"></div>
    <div class="form-group"><label class="required" for="submit_problem_problem">Problem</label><select id="submit_problem_problem" name="submittedProblemIndex" required="required" class="form-control custom-select form-control"><option value="" selected="selected">Select a problem</option>` + submit_problem_option + `</select></div>
    <div class="form-group"><label class="required" for="submit_problem_language">Language</label><select id="submit_problem_language" name="programTypeId" required="required" class="form-control custom-select form-control"><option value="" selected="selected">Select a language</option><option value="43">C</option><option value="89">C++</option><option value="87">Java</option><option value="31">Python 3</option></select></div></div>
    <div class="modal-footer"><button id="cancelBtn" type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button><button type="submit" class="btn-success btn"><i class="fas fa-cloud-upload-alt"></i> Submit </button></div><input type="hidden" name="_tta" value="396"></form></div></div>
    </div>`;

    // 插入模態框到頁面中
    document.body.insertAdjacentHTML('beforeend', modalHTML);

    $(function () {
        $('body').on('change', '.custom-file-input', function () {
            var files = this.files;
            var fileNames = [];
            for (var i = 0; i < files.length; i++) {
                fileNames.push(files.item(i).name);
            }
            $(this).next('.custom-file-label').html(fileNames.join(", "));
            $(this).next('.custom-file-label').removeClass('text-muted');
        });
    });

    const fileInput = document.getElementById('submit_problem_code');
    fileInput.addEventListener('change', (event) => {

        const five_minutes_in_ms = 5 * 60 * 1000;
        const now = Date.now();
        const filesNotModified = document.getElementById('files_not_modified');
        filesNotModified.style.display = 'none';

        var atLeastOneFileRecent = false;
        var fileInfoHtml = '';
        const files = event.target.files;
        for (let file of files) {
            const date = new Date(file.lastModified);
            const ago = humanReadableTimeDiff((now - date)/1000) + ' ago';
            if (date > now - five_minutes_in_ms) {
                atLeastOneFileRecent = true;
            }
            let size = humanReadableBytes(file.size);
            fileInfoHtml += `<li><span class="filename">${file.name}</span>, ${size}, last modified ${ago}</li>`;
        }
        if (!atLeastOneFileRecent) {
            filesNotModified.style.display = 'block';
            filesNotModified.innerHTML =
                'None of the selected files has been recently modified:' +
                '<ul>' + fileInfoHtml + '</ul>';
        }
    });

    const modal_backdrop_html = `<div class="modal-backdrop fade show" style="display:none;"></div>`;
    document.body.insertAdjacentHTML('beforeend', modal_backdrop_html);

    // 把 input 按鈕換成 button
    // submitButInput = document.getElementById("singlePageSubmitButton");
    // submitButInput.parentNode.insertBefore(buttonElement, submitButInput);
    // submitButInput.remove();

    // 獲取模態框和觸發連結
    const modal = document.getElementById('myModal');
    const openModal = document.getElementById('submitbut');
    const closeModal = document.querySelector('.close');
    const cancelModal = document.getElementById('cancelBtn');
    const modal_backdrop = document.querySelector('.modal-backdrop');
    const filesNotModified = document.getElementById('files_not_modified');

    // 當用戶點擊連結時,顯示模態框
    openModal.addEventListener('click', function(event) {
        event.preventDefault(); // 防止連結跳轉
        modal.style.display = 'block';
        modal_backdrop.style.display = 'block';
    });

    // 當用戶點擊關閉按鈕時,隱藏模態框
    closeModal.addEventListener('click', function() {
        modal.style.display = 'none';
        modal_backdrop.style.display = 'none';
        filesNotModified.style.display = 'none';
    });

    // 當用戶點擊模態框外部時,隱藏模態框
    window.addEventListener('click', function(event) {
        if (event.target === modal) {
            modal.style.display = 'none';
            modal_backdrop.style.display = 'none';
            filesNotModified.style.display = 'none';
        }
    });

    cancelModal.addEventListener('click', function() {
        modal.style.display = 'none';
        modal_backdrop.style.display = 'none';
        filesNotModified.style.display = 'none';
    });
}


function getMainExtension(ext) {
    switch (ext) {
        case 'c':
            return '43';
        case 'cpp':
            return '89';
        case 'cc':
            return '89';
        case 'cxx':
            return '89';
        case 'c++':
            return '89';
        case 'java':
            return '87';
        case 'py':
            return '31';
        default:
            return '';
    }
}


function submitButtomJquery(){

    $(document).ready(function () {
        {
        }
        var processFile = function () {
            var filename = $('#submit_problem_code').val();
            if (filename !== '' && filename !== undefined) {
                filename = filename.replace(/^.*[\\\/]/, '');
                var parts = filename.split('.').reverse();
                if (parts.length < 2) return;
                var lcParts = [parts[0].toLowerCase(), parts[1].toLowerCase()];

                // language ID

                var language = document.getElementById('submit_problem_language');
                // the "autodetect" option has empty value
                if (language.value !== '') return;

                var langid = getMainExtension(lcParts[0]);
                for (i = 0; i < language.length; i++) {
                    if (language.options[i].value === langid) {
                        language.selectedIndex = i;
                    }
                }

                // Problem ID

                var problem = document.getElementById('submit_problem_problem');
                // the "autodetect" option has empty value
                if (problem.value !== '') {
                    return;
                }

                for (var i = 0; i < problem.length; i++) {
                    if (problem.options[i].text.split(/ - /)[0].toLowerCase() === lcParts[1]) {
                        problem.selectedIndex = i;
                    }
                }
            }
        };
        var $body = $('body');
        $body.on('change', '#submit_problem_code', processFile);
    });

    const form = document.querySelector('.submit-form');

    form.addEventListener('submit', function(event) {
        event.preventDefault();

        const formData = new FormData(document.querySelector('.submit-form'));

        // for (const [key, value] of formData.entries()) {
        //     console.log(key, value);
        // }

        var langelt = document.getElementById("submit_problem_language");
        var language = langelt.options[langelt.selectedIndex].value;
        var languagetxt = langelt.options[langelt.selectedIndex].text;
        var fileelt = document.getElementById("submit_problem_code");
        var filenames = fileelt.files;
        var filename = filenames[0].name;
        var probelt = document.getElementById("submit_problem_problem");
        var problem = probelt.options[probelt.selectedIndex].value;
        var problemtxt = probelt.options[probelt.selectedIndex].text;

        var error = false;
        if (language === "") {
            langelt.focus();
            langelt.className = langelt.className + " errorfield";
            error = true;
        }
        if (problem === "") {
            probelt.focus();
            probelt.className = probelt.className + " errorfield";
            error = true;
        }
        if (filename === "") {
            error = true;
        }
        if (error) return false;

        var auxfileno = 0;
        // start at one; skip maincode file field
        for (var i = 1; i < filenames.length; i++) {
            if (filenames[i].value !== "") {
                auxfileno++;
            }
        }
        var extrafiles = '';
        if (auxfileno > 0) {
            extrafiles = "Additional source files: " + auxfileno + '\n';
        }
        var question =
            'Main source file: ' + filename + '\n' +
            extrafiles + '\n' +
            'Problem: ' + problemtxt + '\n' +
            'Language: ' + languagetxt + '\n' +
            '\nMake submission?';
        if(confirm(question)){
            fetch(getCurrentURL().split('/').slice(0, 5).join('/')+'/submit?csrf_token='+formData.get('csrf_token'), {
                method: form.method,
                body: formData,
            })
                .then(response => {
                if (response.ok) {
                    location.reload();

                } else {
                    console.error('submit fail');
                }
            });
        }
        // .catch(error => {
        //     console.error('error: ', error);
        // });
    });
}