ac-predictor-cn

AtCoder 预测工具 (由GoodCoder666翻译为简体中文)

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name        ac-predictor-cn
// @namespace   https://github.com/GoodCoder666/ac-predictor-extension-CN
// @icon        https://atcoder.jp/favicon.ico
// @version     1.2.16
// @description AtCoder 预测工具 (由GoodCoder666翻译为简体中文)
// @author      GoodCoder666
// @license     MIT
// @supportURL  https://github.com/GoodCoder666/ac-predictor-extension-CN/issues
// @match       https://atcoder.jp/*
// @exclude     https://atcoder.jp/*/json
// ==/UserScript==

function __awaiter(thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
}

var dom = "<div id=\"predictor-alert\" class=\"row\"><h5 class=\"sidemenu-txt\">加载中…</h5></div>\n<div id=\"predictor-data\" class=\"row\">\n    <div class=\"input-group col-xs-12\">\n        <span class=\"input-group-addon\">名次\n            <style>\n                .predictor-tooltip-icon:hover+.tooltip{\n                    opacity: .9;\n                    filter: alpha(opacity=90);\n                }\n            </style>\n            <span class=\"predictor-tooltip-icon glyphicon glyphicon-question-sign\"></span>\n            <div class=\"tooltip fade bottom\" style=\"pointer-events:none\">\n                <div class=\"tooltip-arrow\" style=\"left: 18%;\"></div>\n                <div class=\"tooltip-inner\">Rated 范围内的排名,多人同名次时加上人数。</div>\n            </div>\n        </span>\n        <input class=\"form-control\" id=\"predictor-input-rank\">\n        <span class=\"input-group-addon\">位</span>\n    </div>\n        \n    <div class=\"input-group col-xs-12\">\n        <span class=\"input-group-addon\">Performance</span>\n        <input class=\"form-control\" id=\"predictor-input-perf\">\n    </div>\n\n    <div class=\"input-group col-xs-12\">\n        <span class=\"input-group-addon\">预计 Rating</span>\n        <input class=\"form-control\" id=\"predictor-input-rate\">\n    </div>\n</div>\n<div class=\"row\">\n    <div class=\"btn-group\">\n        <button class=\"btn btn-default\" id=\"predictor-current\">现在的名次</button>\n        <button type=\"button\" class=\"btn btn-primary\" id=\"predictor-reload\" data-loading-text=\"更新中…\">更新</button>\n        <!--<button class=\"btn btn-default\" id=\"predictor-solved\" disabled>当前问题AC后</button>-->\n    </div>\n</div>";

class Result {
    constructor(isRated, isSubmitted, userScreenName, place, ratedRank, oldRating, newRating, competitions, performance, innerPerformance) {
        this.IsRated = isRated;
        this.IsSubmitted = isSubmitted;
        this.UserScreenName = userScreenName;
        this.Place = place;
        this.RatedRank = ratedRank;
        this.OldRating = oldRating;
        this.NewRating = newRating;
        this.Competitions = competitions;
        this.Performance = performance;
        this.InnerPerformance = innerPerformance;
    }
}

function analyzeStandingsData(fixed, standingsData, aPerfs, defaultAPerf, ratedLimit, isHeuristic) {
    function analyze(isUserRated) {
        const contestantAPerf = [];
        const templateResults = {};
        let currentRatedRank = 1;
        let lastRank = 0;
        const tiedUsers = [];
        let ratedInTiedUsers = 0;
        function applyTiedUsers() {
            tiedUsers.forEach((data) => {
                if (isUserRated(data)) {
                    contestantAPerf.push(aPerfs[data.UserScreenName] || defaultAPerf);
                    ratedInTiedUsers++;
                }
            });
            const ratedRank = currentRatedRank + Math.max(0, ratedInTiedUsers - 1) / 2;
            tiedUsers.forEach((data) => {
                templateResults[data.UserScreenName] = new Result(!isHeuristic /* FIXME: Temporary disabled for the AHC rating system */ && isUserRated(data), !isHeuristic || data.TotalResult.Count !== 0, data.UserScreenName, data.Rank, ratedRank, fixed ? data.OldRating : data.Rating, null, data.Competitions, null, null);
            });
            currentRatedRank += ratedInTiedUsers;
            tiedUsers.length = 0;
            ratedInTiedUsers = 0;
        }
        standingsData.forEach((data) => {
            if (lastRank !== data.Rank)
                applyTiedUsers();
            lastRank = data.Rank;
            tiedUsers.push(data);
        });
        applyTiedUsers();
        return {
            contestantAPerf: contestantAPerf,
            templateResults: templateResults,
        };
    }
    let analyzedData = analyze((data) => data.IsRated && (!isHeuristic || data.TotalResult.Count !== 0));
    let isRated = true;
    if (analyzedData.contestantAPerf.length === 0) {
        analyzedData = analyze((data) => data.OldRating < ratedLimit && (!isHeuristic || data.TotalResult.Count !== 0));
        isRated = false;
    }
    const res = analyzedData;
    res.isRated = isRated;
    return res;
}
class Contest {
    constructor(contestScreenName, contestInformation, standings, aPerfs) {
        this.ratedLimit = contestInformation.RatedRange[1] + 1;
        this.perfLimit = this.ratedLimit + 400;
        this.standings = standings;
        this.aPerfs = aPerfs;
        this.rankMemo = {};
        const analyzedData = analyzeStandingsData(standings.Fixed, standings.StandingsData, aPerfs, contestInformation.isHeuristic ? 1000 : ({ 2000: 800, 2800: 1000, Infinity: 1200 }[this.ratedLimit] || 1200), this.ratedLimit, contestInformation.isHeuristic);
        this.contestantAPerf = analyzedData.contestantAPerf;
        this.templateResults = analyzedData.templateResults;
        this.IsRated = analyzedData.isRated;
    }
    getRatedRank(X) {
        if (this.rankMemo[X])
            return this.rankMemo[X];
        return (this.rankMemo[X] = this.contestantAPerf.reduce((val, APerf) => val + 1.0 / (1.0 + Math.pow(6.0, (X - APerf) / 400.0)), 0.5));
    }
    getPerf(ratedRank) {
        return Math.min(this.getInnerPerf(ratedRank), this.perfLimit);
    }
    getInnerPerf(ratedRank) {
        let upper = 6144;
        let lower = -2048;
        while (upper - lower > 0.5) {
            const mid = (upper + lower) / 2;
            if (ratedRank > this.getRatedRank(mid))
                upper = mid;
            else
                lower = mid;
        }
        return Math.round((upper + lower) / 2);
    }
}

class Results {
}

//Copyright © 2017 koba-e964.
//from : https://github.com/koba-e964/atcoder-rating-estimator
const finf = bigf(400);
function bigf(n) {
    let pow1 = 1;
    let pow2 = 1;
    let numerator = 0;
    let denominator = 0;
    for (let i = 0; i < n; ++i) {
        pow1 *= 0.81;
        pow2 *= 0.9;
        numerator += pow1;
        denominator += pow2;
    }
    return Math.sqrt(numerator) / denominator;
}
function f(n) {
    return ((bigf(n) - finf) / (bigf(1) - finf)) * 1200.0;
}
/**
 * calculate unpositivized rating from performance history
 * @param {Number[]} [history] performance history with ascending order
 * @returns {Number} unpositivized rating
 */
function calcRatingFromHistory(history) {
    const n = history.length;
    let pow = 1;
    let numerator = 0.0;
    let denominator = 0.0;
    for (let i = n - 1; i >= 0; i--) {
        pow *= 0.9;
        numerator += Math.pow(2, history[i] / 800.0) * pow;
        denominator += pow;
    }
    return Math.log2(numerator / denominator) * 800.0 - f(n);
}
/**
 * calculate unpositivized rating from last state
 * @param {Number} [last] last unpositivized rating
 * @param {Number} [perf] performance
 * @param {Number} [ratedMatches] count of participated rated contest
 * @returns {number} estimated unpositivized rating
 */
function calcRatingFromLast(last, perf, ratedMatches) {
    if (ratedMatches === 0)
        return perf - 1200;
    last += f(ratedMatches);
    const weight = 9 - 9 * Math.pow(0.9, ratedMatches);
    const numerator = weight * Math.pow(2, (last / 800.0)) + Math.pow(2, (perf / 800.0));
    const denominator = 1 + weight;
    return Math.log2(numerator / denominator) * 800.0 - f(ratedMatches + 1);
}
/**
 * (-inf, inf) -> (0, inf)
 * @param {Number} [rating] unpositivized rating
 * @returns {number} positivized rating
 */
function positivizeRating(rating) {
    if (rating >= 400.0) {
        return rating;
    }
    return 400.0 * Math.exp((rating - 400.0) / 400.0);
}
/**
 * (0, inf) -> (-inf, inf)
 * @param {Number} [rating] positivized rating
 * @returns {number} unpositivized rating
 */
function unpositivizeRating(rating) {
    if (rating >= 400.0) {
        return rating;
    }
    return 400.0 + 400.0 * Math.log(rating / 400.0);
}
/**
 * calculate the performance required to reach a target rate
 * @param {Number} [targetRating] targeted unpositivized rating
 * @param {Number[]} [history] performance history with ascending order
 * @returns {number} performance
 */
function calcRequiredPerformance(targetRating, history) {
    let valid = 10000.0;
    let invalid = -10000.0;
    for (let i = 0; i < 100; ++i) {
        const mid = (invalid + valid) / 2;
        const rating = Math.round(calcRatingFromHistory(history.concat([mid])));
        if (targetRating <= rating)
            valid = mid;
        else
            invalid = mid;
    }
    return valid;
}
const colorNames = ["unrated", "gray", "brown", "green", "cyan", "blue", "yellow", "orange", "red"];
function getColor(rating) {
    const colorIndex = rating > 0 ? Math.min(Math.floor(rating / 400) + 1, 8) : 0;
    return colorNames[colorIndex];
}

class OnDemandResults extends Results {
    constructor(contest, templateResults) {
        super();
        this.Contest = contest;
        this.TemplateResults = templateResults;
    }
    getUserResult(userScreenName) {
        if (!Object.prototype.hasOwnProperty.call(this.TemplateResults, userScreenName))
            return null;
        const baseResults = this.TemplateResults[userScreenName];
        if (!baseResults)
            return null;
        if (!baseResults.Performance) {
            baseResults.InnerPerformance = this.Contest.getInnerPerf(baseResults.RatedRank);
            baseResults.Performance = Math.min(baseResults.InnerPerformance, this.Contest.perfLimit);
            baseResults.NewRating = Math.round(positivizeRating(calcRatingFromLast(unpositivizeRating(baseResults.OldRating), baseResults.Performance, baseResults.Competitions)));
        }
        return baseResults;
    }
}

class FixedResults extends Results {
    constructor(results) {
        super();
        this.resultsDic = {};
        results.forEach((result) => {
            this.resultsDic[result.UserScreenName] = result;
        });
    }
    getUserResult(userScreenName) {
        return Object.prototype.hasOwnProperty.call(this.resultsDic, userScreenName)
            ? this.resultsDic[userScreenName]
            : null;
    }
}

class PredictorModel {
    constructor(model) {
        this.enabled = model.enabled;
        this.contest = model.contest;
        this.history = model.history;
        this.updateInformation(model.information);
        this.updateData(model.rankValue, model.perfValue, model.rateValue);
    }
    setEnable(state) {
        this.enabled = state;
    }
    updateInformation(information) {
        this.information = information;
    }
    updateData(rankValue, perfValue, rateValue) {
        this.rankValue = rankValue;
        this.perfValue = perfValue;
        this.rateValue = rateValue;
    }
}

class CalcFromRankModel extends PredictorModel {
    updateData(rankValue, perfValue, rateValue) {
        perfValue = this.contest.getPerf(rankValue);
        rateValue = positivizeRating(calcRatingFromHistory(this.history.concat([perfValue])));
        super.updateData(rankValue, perfValue, rateValue);
    }
}

class CalcFromPerfModel extends PredictorModel {
    updateData(rankValue, perfValue, rateValue) {
        rankValue = this.contest.getRatedRank(perfValue);
        rateValue = positivizeRating(calcRatingFromHistory(this.history.concat([perfValue])));
        super.updateData(rankValue, perfValue, rateValue);
    }
}

class CalcFromRateModel extends PredictorModel {
    updateData(rankValue, perfValue, rateValue) {
        perfValue = calcRequiredPerformance(unpositivizeRating(rateValue), this.history);
        rankValue = this.contest.getRatedRank(perfValue);
        super.updateData(rankValue, perfValue, rateValue);
    }
}

function roundValue(value, numDigits) {
    return Math.round(value * Math.pow(10, numDigits)) / Math.pow(10, numDigits);
}

class ContestInformation {
    constructor(canParticipateRange, ratedRange, penalty, isHeuristic) {
        this.CanParticipateRange = canParticipateRange;
        this.RatedRange = ratedRange;
        this.Penalty = penalty;
        this.isHeuristic = isHeuristic;
    }
}
function parseRangeString(s) {
    s = s.trim();
    if (s === "-")
        return [0, -1];
    if (s === "All")
        return [0, Infinity];
    if (!/[-~]/.test(s))
        return [0, -1];
    const res = s.split(/[-~]/).map((x) => parseInt(x.trim()));
    if (isNaN(res[0]))
        res[0] = 0;
    if (isNaN(res[1]))
        res[1] = Infinity;
    return res;
}
function parseDurationString(s) {
    if (s === "None" || s === "なし")
        return 0;
    if (!/(\d+[^\d]+)/.test(s))
        return NaN;
    const durationDic = {
        日: 24 * 60 * 60 * 1000,
        day: 24 * 60 * 60 * 1000,
        days: 24 * 60 * 60 * 1000,
        時間: 60 * 60 * 1000,
        hour: 60 * 60 * 1000,
        hours: 60 * 60 * 1000,
        分: 60 * 1000,
        minute: 60 * 1000,
        minutes: 60 * 1000,
        秒: 1000,
        second: 1000,
        seconds: 1000,
    };
    let res = 0;
    s.match(/(\d+[^\d]+)/g).forEach((x) => {
        var _a;
        const trimmed = x.trim();
        const num = parseInt(/\d+/.exec(trimmed)[0]);
        const unit = /[^\d]+/.exec(trimmed)[0];
        const duration = (_a = durationDic[unit]) !== null && _a !== void 0 ? _a : 0;
        res += num * duration;
    });
    return res;
}
function fetchJsonDataAsync(url) {
    return __awaiter(this, void 0, void 0, function* () {
        const response = yield fetch(url);
        if (response.ok)
            return (yield response.json());
        throw new Error(`request to ${url} returns ${response.status}`);
    });
}
function fetchTextDataAsync(url) {
    return __awaiter(this, void 0, void 0, function* () {
        const response = yield fetch(url);
        if (response.ok)
            return response.text();
        throw new Error(`request to ${url} returns ${response.status}`);
    });
}
function getStandingsDataAsync(contestScreenName) {
    return __awaiter(this, void 0, void 0, function* () {
        return yield fetchJsonDataAsync(`https://atcoder.jp/contests/${contestScreenName}/standings/json`);
    });
}

function getAPerfsDataAsync(contestScreenName) {
    return __awaiter(this, void 0, void 0, function* () {
        let url = `https://data.ac-predictor.com/aperfs/${contestScreenName}.json`;
        // if (contestScreenName === "arc119") url = `https://raw.githubusercontent.com/key-moon/ac-predictor-data/master/aperfs/${contestScreenName}.json`;
        return yield fetchJsonDataAsync(url);
    });
}
function getResultsDataAsync(contestScreenName) {
    return __awaiter(this, void 0, void 0, function* () {
        return yield fetchJsonDataAsync(`https://atcoder.jp/contests/${contestScreenName}/results/json`);
    });
}
function getHistoryDataAsync(userScreenName) {
    return __awaiter(this, void 0, void 0, function* () {
        return yield fetchJsonDataAsync(`https://atcoder.jp/users/${userScreenName}/history/json`);
    });
}
function getContestInformationAsync(contestScreenName) {
    return __awaiter(this, void 0, void 0, function* () {
        const html = yield fetchTextDataAsync(`https://atcoder.jp/contests/${contestScreenName}`);
        const topPageDom = new DOMParser().parseFromString(html, "text/html");
        const dataParagraph = topPageDom.getElementsByClassName("small")[0];
        const data = Array.from(dataParagraph.children).map((x) => x.innerHTML.split(":")[1].trim());
        const isAHC = /^ahc\d{3}$/.test(contestScreenName) || html.includes("This contest is rated for AHC rating");
        return new ContestInformation(parseRangeString(data[0]), parseRangeString(data[1]), parseDurationString(data[2]), isAHC);
    });
}
/**
 * ユーザーのPerformance履歴を時間昇順で取得
 */
function getPerformanceHistories(history) {
    const onlyRated = history.filter((x) => x.IsRated);
    onlyRated.sort((a, b) => {
        return new Date(a.EndTime).getTime() - new Date(b.EndTime).getTime();
    });
    return onlyRated.map((x) => x.Performance);
}

/**
* サイドメニューに追加される要素のクラス
*/
class SideMenuElement {
    shouldDisplayed(url) {
        return this.match.test(url);
    }
    /**
     * 要素のHTMLを取得
     */
    GetHTML() {
        return `<div class="menu-wrapper">
    <div class="menu-header">
        <h4 class="sidemenu-txt">${this.title}<span class="glyphicon glyphicon-menu-up" style="float: right"></span></h4>
    </div>
    <div class="menu-box"><div class="menu-content" id="${this.id}">${this.document}</div></div>
</div>`;
    }
}

function getGlobalVals() {
    const script = [...document.querySelectorAll("head script:not([src])")].map((x) => x.innerHTML).join("\n");
    const res = {};
    script.match(/var [^ ]+ = .+$/gm).forEach((statement) => {
        const match = /var ([^ ]+) = (.+)$/m.exec(statement);
        function safeEval(val) {
            function trim(val) {
                while (val.endsWith(";") || val.endsWith(" "))
                    val = val.substr(0, val.length - 1);
                while (val.startsWith(" "))
                    val = val.substr(1, val.length - 1);
                return val;
            }
            function isStringToken(val) {
                return 1 < val.length && val.startsWith('"') && val.endsWith('"');
            }
            function evalStringToken(val) {
                if (!isStringToken(val))
                    throw new Error();
                return val.substr(1, val.length - 2); // TODO: parse escape
            }
            val = trim(val);
            if (isStringToken(val))
                return evalStringToken(val);
            if (val.startsWith("moment("))
                return new Date(evalStringToken(trim(val.substr(7, val.length - (7 + 1)))));
            return val;
        }
        res[match[1]] = safeEval(match[2]);
    });
    return res;
}
const globalVals = getGlobalVals();
const userScreenName = globalVals["userScreenName"];
const contestScreenName = globalVals["contestScreenName"];
const startTime = globalVals["startTime"];

class AllRowUpdater {
    update(table) {
        Array.from(table.rows).forEach((row) => this.rowModifier.modifyRow(row));
    }
}

class StandingsRowModifier {
    isHeader(row) {
        return row.parentElement.tagName.toLowerCase() == "thead";
    }
    isFooter(row) {
        return row.firstElementChild.hasAttribute("colspan") && row.firstElementChild.getAttribute("colspan") == "3";
    }
    modifyRow(row) {
        if (this.isHeader(row))
            this.modifyHeader(row);
        else if (this.isFooter(row))
            this.modifyFooter(row);
        else
            this.modifyContent(row);
    }
}

class PerfAndRateChangeAppender extends StandingsRowModifier {
    modifyContent(content) {
        var _a;
        this.removeOldElem(content);
        if (content.firstElementChild.textContent === "-") {
            const longCell = content.getElementsByClassName("standings-result")[0];
            longCell.setAttribute("colspan", (parseInt(longCell.getAttribute("colspan")) + 2).toString());
            return;
        }
        const userScreenName = content.querySelector(".standings-username .username span").textContent;
        const result = (_a = this.results) === null || _a === void 0 ? void 0 : _a.getUserResult(userScreenName);
        const perfElem = (result === null || result === void 0 ? void 0 : result.IsSubmitted) ? this.getRatingSpan(Math.round(positivizeRating(result.Performance)))
            : "-";
        const ratingElem = result
            ? (result === null || result === void 0 ? void 0 : result.IsRated) && (this === null || this === void 0 ? void 0 : this.isRated)
                ? this.getChangedRatingElem(result.OldRating, result.NewRating)
                : this.getUnratedElem(result.OldRating)
            : "-";
        content.insertAdjacentHTML("beforeend", `<td class="standings-result standings-perf">${perfElem}</td>`);
        content.insertAdjacentHTML("beforeend", `<td class="standings-result standings-rate">${ratingElem}</td>`);
    }
    getChangedRatingElem(oldRate, newRate) {
        const oldRateSpan = this.getRatingSpan(oldRate);
        const newRateSpan = this.getRatingSpan(newRate);
        const diff = this.toSignedString(newRate - oldRate);
        return `<span class="bold">${oldRateSpan}</span> → <span class="bold">${newRateSpan}</span> <span class="grey">(${diff})</span>`;
    }
    toSignedString(n) {
        return `${n >= 0 ? "+" : ""}${n}`;
    }
    getUnratedElem(rate) {
        return `<span class="bold">${this.getRatingSpan(rate)}</span> <span class="grey">(unrated)</span>`;
    }
    getRatingSpan(rate) {
        return `<span class="user-${getColor(rate)}">${rate}</span>`;
    }
    modifyFooter(footer) {
        this.removeOldElem(footer);
        footer.insertAdjacentHTML("beforeend", '<td class="standings-result standings-perf standings-rate" colspan="2">-</td>');
    }
    modifyHeader(header) {
        this.removeOldElem(header);
        header.insertAdjacentHTML("beforeend", '<th class="standings-result-th standings-perf" style="width:84px;min-width:84px;">Performance</th><th class="standings-result-th standings-rate" style="width:168px;min-width:168px;">Rating 变化</th>');
    }
    removeOldElem(row) {
        row.querySelectorAll(".standings-perf, .standings-rate").forEach((elem) => elem.remove());
    }
}

class PredictorElement extends SideMenuElement {
    constructor() {
        super(...arguments);
        this.id = "predictor";
        this.title = "Predictor";
        this.match = /atcoder.jp\/contests\/.+/;
        this.document = dom;
        this.historyData = [];
        this.contestOnUpdated = [];
        this.resultsOnUpdated = [];
    }
    set contest(val) {
        this._contest = val;
        this.contestOnUpdated.forEach((func) => func(val));
    }
    get contest() {
        return this._contest;
    }
    set results(val) {
        this._results = val;
        this.resultsOnUpdated.forEach((func) => func(val));
    }
    get results() {
        return this._results;
    }
    isStandingsPage() {
        return /standings([^/]*)?$/.test(document.location.href);
    }
    afterAppend() {
        const loaded = () => !!document.getElementById("standings-tbody");
        if (!this.isStandingsPage() || loaded()) {
            void this.initialize();
            return;
        }
        const loadingElem = document.getElementById("vue-standings").getElementsByClassName("loading-show")[0];
        new MutationObserver(() => {
            if (loaded())
                void this.initialize();
        }).observe(loadingElem, { attributes: true });
    }
    initialize() {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            const firstContestDate = new Date(2016, 6, 16, 21);
            const predictorElements = [
                "predictor-input-rank",
                "predictor-input-perf",
                "predictor-input-rate",
                "predictor-current",
                "predictor-reload",
            ];
            const isStandingsPage = this.isStandingsPage();
            const contestInformation = yield getContestInformationAsync(contestScreenName);
            const rowUpdater = new PerfAndRateChangeAppender();
            this.resultsOnUpdated.push((val) => {
                rowUpdater.results = val;
            });
            this.contestOnUpdated.push((val) => {
                rowUpdater.isRated = val.IsRated;
            });
            const tableUpdater = new AllRowUpdater();
            tableUpdater.rowModifier = rowUpdater;
            const tableElement = (_a = document.getElementById("standings-tbody")) === null || _a === void 0 ? void 0 : _a.parentElement;
            let model = new PredictorModel({
                rankValue: 0,
                perfValue: 0,
                rateValue: 0,
                enabled: false,
                history: this.historyData,
            });
            const updateData = (aperfs, standings) => __awaiter(this, void 0, void 0, function* () {
                this.contest = new Contest(contestScreenName, contestInformation, standings, aperfs);
                model.contest = this.contest;
                if (this.contest.standings.Fixed && this.contest.IsRated) {
                    const rawResult = yield getResultsDataAsync(contestScreenName);
                    rawResult.sort((a, b) => (a.Place !== b.Place ? a.Place - b.Place : b.OldRating - a.OldRating));
                    const sortedStandingsData = Array.from(this.contest.standings.StandingsData);
                    if (contestInformation.isHeuristic) sortedStandingsData.filter((x) => x.TotalResult.Count !== 0);
                    sortedStandingsData.sort((a, b) => {
                        if (a.TotalResult.Count === 0 && b.TotalResult.Count === 0)
                            return 0;
                        if (a.TotalResult.Count === 0)
                            return 1;
                        if (b.TotalResult.Count === 0)
                            return -1;
                        if (a.Rank !== b.Rank)
                            return a.Rank - b.Rank;
                        if (b.OldRating !== a.OldRating)
                            return b.OldRating - a.OldRating;
                        if (a.UserIsDeleted)
                            return -1;
                        if (b.UserIsDeleted)
                            return 1;
                        return 0;
                    });
                    let lastPerformance = this.contest.perfLimit;
                    let deletedCount = 0;
                    this.results = new FixedResults(sortedStandingsData.map((data, index) => {
                        let result = rawResult[index - deletedCount];
                        if (!result || data.OldRating !== result.OldRating) {
                            deletedCount++;
                            result = null;
                        }
                        return new Result(result ? result.IsRated : false, !contestInformation.isHeuristic || data.TotalResult.Count !== 0, data.UserScreenName, data.Rank, -1, data.OldRating, result ? result.NewRating : 0, 0, result && result.IsRated ? (lastPerformance = result.Performance) : lastPerformance, result ? result.InnerPerformance : 0);
                    }));
                }
                else {
                    this.results = new OnDemandResults(this.contest, this.contest.templateResults);
                }
            });
            if (!shouldEnabledPredictor().verdict) {
                model.updateInformation(shouldEnabledPredictor().message);
                updateView();
                return;
            }
            try {
                let aPerfs;
                let standings;
                try {
                    standings = yield getStandingsDataAsync(contestScreenName);
                }
                catch (e) {
                    throw new Error("Standings读取失败。");
                }
                try {
                    aPerfs = yield getAPerfsDataAsync(contestScreenName);
                }
                catch (e) {
                    throw new Error("APerf获取失败。");
                }
                yield updateData(aPerfs, standings);
                model.setEnable(true);
                model.updateInformation(`最后更新时间: ${new Date().toTimeString().split(" ")[0]}`);
                if (isStandingsPage) {
                    new MutationObserver(() => {
                        tableUpdater.update(tableElement);
                    }).observe(tableElement.tBodies[0], {
                        childList: true,
                    });
                    const refreshElem = document.getElementById("refresh");
                    if (refreshElem)
                        new MutationObserver((mutationRecord) => {
                            const disabled = mutationRecord[0].target.classList.contains("disabled");
                            if (disabled) {
                                void (() => __awaiter(this, void 0, void 0, function* () {
                                    yield updateStandingsFromAPI();
                                    updateView();
                                }))();
                            }
                        }).observe(refreshElem, {
                            attributes: true,
                            attributeFilter: ["class"],
                        });
                }
            }
            catch (e) {
                model.updateInformation(e.message);
                model.setEnable(false);
            }
            updateView();
            {
                const reloadButton = document.getElementById("predictor-reload");
                reloadButton.addEventListener("click", () => {
                    void (() => __awaiter(this, void 0, void 0, function* () {
                        model.updateInformation("");
                        reloadButton.disabled = true;
                        updateView();
                        yield updateStandingsFromAPI();
                        reloadButton.disabled = false;
                        updateView();
                    }))();
                });
                document.getElementById("predictor-current").addEventListener("click", () => {
                    const myResult = this.contest.templateResults[userScreenName];
                    if (!myResult)
                        return;
                    model = new CalcFromRankModel(model);
                    model.updateData(myResult.RatedRank, model.perfValue, model.rateValue);
                    updateView();
                });
                document.getElementById("predictor-input-rank").addEventListener("keyup", () => {
                    const inputString = document.getElementById("predictor-input-rank").value;
                    const inputNumber = parseInt(inputString);
                    if (!isFinite(inputNumber))
                        return;
                    model = new CalcFromRankModel(model);
                    model.updateData(inputNumber, 0, 0);
                    updateView();
                });
                document.getElementById("predictor-input-perf").addEventListener("keyup", () => {
                    const inputString = document.getElementById("predictor-input-perf").value;
                    const inputNumber = parseInt(inputString);
                    if (!isFinite(inputNumber))
                        return;
                    model = new CalcFromPerfModel(model);
                    model.updateData(0, inputNumber, 0);
                    updateView();
                });
                document.getElementById("predictor-input-rate").addEventListener("keyup", () => {
                    const inputString = document.getElementById("predictor-input-rate").value;
                    const inputNumber = parseInt(inputString);
                    if (!isFinite(inputNumber))
                        return;
                    model = new CalcFromRateModel(model);
                    model.updateData(0, 0, inputNumber);
                    updateView();
                });
            }
            function updateStandingsFromAPI() {
                return __awaiter(this, void 0, void 0, function* () {
                    try {
                        const shouldEnabled = shouldEnabledPredictor();
                        if (!shouldEnabled.verdict) {
                            model.updateInformation(shouldEnabled.message);
                            model.setEnable(false);
                            return;
                        }
                        const standings = yield getStandingsDataAsync(contestScreenName);
                        const aperfs = yield getAPerfsDataAsync(contestScreenName);
                        yield updateData(aperfs, standings);
                        model.updateInformation(`最后更新时间: ${new Date().toTimeString().split(" ")[0]}`);
                        model.setEnable(true);
                    }
                    catch (e) {
                        model.updateInformation(e.message);
                        model.setEnable(false);
                    }
                });
            }
            function shouldEnabledPredictor() {
                if (new Date() < startTime)
                    return { verdict: false, message: "比赛暂未开始" };
                if (startTime < firstContestDate)
                    return {
                        verdict: false,
                        message: "这场比赛是在使用现行 Rating 制度之前举行的,无法准确计算 Rating 数据。",
                    };
                if (contestInformation.RatedRange[0] > contestInformation.RatedRange[1])
                    return {
                        verdict: false,
                        message: "This contest is unrated.",
                    };
                return { verdict: true, message: "" };
            }
            function updateView() {
                const roundedRankValue = isFinite(model.rankValue) ? roundValue(model.rankValue, 2).toString() : "";
                const roundedPerfValue = isFinite(model.perfValue) ? roundValue(model.perfValue, 2).toString() : "";
                const roundedRateValue = isFinite(model.rateValue) ? roundValue(model.rateValue, 2).toString() : "";
                document.getElementById("predictor-input-rank").value = roundedRankValue;
                document.getElementById("predictor-input-perf").value = roundedPerfValue;
                document.getElementById("predictor-input-rate").value = roundedRateValue;
                document.getElementById("predictor-alert").innerHTML = `<h5 class='sidemenu-txt'>${model.information}</h5>`;
                if (model.enabled)
                    enabled();
                else
                    disabled();
                if (isStandingsPage && shouldEnabledPredictor().verdict) {
                    tableUpdater.update(tableElement);
                }
                function enabled() {
                    predictorElements.forEach((element) => {
                        document.getElementById(element).disabled = false;
                    });
                }
                function disabled() {
                    predictorElements.forEach((element) => {
                        document.getElementById(element).disabled = false;
                    });
                }
            }
        });
    }
    afterOpen() {
        return __awaiter(this, void 0, void 0, function* () {
            getPerformanceHistories(yield getHistoryDataAsync(userScreenName)).forEach((elem) => this.historyData.push(elem));
        });
    }
}
const predictor = new PredictorElement();

var dom$1 = "<div id=\"estimator-alert\"></div>\n<div class=\"row\">\n\t<div class=\"input-group\">\n\t\t<span class=\"input-group-addon\" id=\"estimator-input-desc\"></span>\n\t\t<input type=\"number\" class=\"form-control\" id=\"estimator-input\">\n\t</div>\n</div>\n<div class=\"row\">\n\t<div class=\"input-group\">\n\t\t<span class=\"input-group-addon\" id=\"estimator-res-desc\"></span>\n\t\t<input class=\"form-control\" id=\"estimator-res\" disabled=\"disabled\">\n\t\t<span class=\"input-group-btn\">\n\t\t\t<button class=\"btn btn-default\" id=\"estimator-toggle\">交换</button>\n\t\t</span>\n\t</div>\n</div>\n<div class=\"row\" style=\"margin: 10px 0px;\">\n\t<a class=\"btn btn-default col-xs-offset-8 col-xs-4\" rel=\"nofollow\" onclick=\"window.open(encodeURI(decodeURI(this.href)),'twwindow','width=550, height=450, personalbar=0, toolbar=0, scrollbars=1'); return false;\" id=\"estimator-tweet\">Tweet</a>\n</div>";

class EstimatorModel {
    constructor(inputValue, perfHistory) {
        this.inputDesc = "";
        this.resultDesc = "";
        this.perfHistory = perfHistory;
        this.updateInput(inputValue);
    }
    updateInput(value) {
        this.inputValue = value;
        this.resultValue = this.calcResult(value);
    }
    toggle() {
        return null;
    }
    calcResult(input) {
        return input;
    }
}

class CalcRatingModel extends EstimatorModel {
    constructor(inputValue, perfHistory) {
        super(inputValue, perfHistory);
        this.inputDesc = "Performance";
        this.resultDesc = "预计 Rating";
    }
    toggle() {
        return new CalcPerfModel(this.resultValue, this.perfHistory);
    }
    calcResult(input) {
        return positivizeRating(calcRatingFromHistory(this.perfHistory.concat([input])));
    }
}

class CalcPerfModel extends EstimatorModel {
    constructor(inputValue, perfHistory) {
        super(inputValue, perfHistory);
        this.inputDesc = "目标 Rating";
        this.resultDesc = "所需 Performance";
    }
    toggle() {
        return new CalcRatingModel(this.resultValue, this.perfHistory);
    }
    calcResult(input) {
        return calcRequiredPerformance(unpositivizeRating(input), this.perfHistory);
    }
}

function GetEmbedTweetLink(content, url) {
    return `https://twitter.com/share?text=${encodeURI(content)}&url=${encodeURI(url)}`;
}

function getLS(key) {
    const val = localStorage.getItem(key);
    return (val ? JSON.parse(val) : val);
}
function setLS(key, val) {
    try {
        localStorage.setItem(key, JSON.stringify(val));
    }
    catch (error) {
        console.log(error);
    }
}
const models = [CalcPerfModel, CalcRatingModel];
function GetModelFromStateCode(state, value, history) {
    let model = models.find((model) => model.name === state);
    if (!model)
        model = CalcPerfModel;
    return new model(value, history);
}
class EstimatorElement extends SideMenuElement {
    constructor() {
        super(...arguments);
        this.id = "estimator";
        this.title = "Estimator";
        this.document = dom$1;
        this.match = /atcoder.jp/;
    }
    afterAppend() {
        //nothing to do
    }
    // nothing to do
    afterOpen() {
        return __awaiter(this, void 0, void 0, function* () {
            const estimatorInputSelector = document.getElementById("estimator-input");
            const estimatorResultSelector = document.getElementById("estimator-res");
            let model = GetModelFromStateCode(getLS("sidemenu_estimator_state"), getLS("sidemenu_estimator_value"), getPerformanceHistories(yield getHistoryDataAsync(userScreenName)));
            updateView();
            document.getElementById("estimator-toggle").addEventListener("click", () => {
                model = model.toggle();
                updateLocalStorage();
                updateView();
            });
            estimatorInputSelector.addEventListener("keyup", () => {
                updateModel();
                updateLocalStorage();
                updateView();
            });
            /** modelをinputの値に応じて更新 */
            function updateModel() {
                const inputNumber = estimatorInputSelector.valueAsNumber;
                if (!isFinite(inputNumber))
                    return;
                model.updateInput(inputNumber);
            }
            /** modelの状態をLSに保存 */
            function updateLocalStorage() {
                setLS("sidemenu_estimator_value", model.inputValue);
                setLS("sidemenu_estimator_state", model.constructor.name);
            }
            /** modelを元にviewを更新 */
            function updateView() {
                const roundedInput = roundValue(model.inputValue, 2);
                const roundedResult = roundValue(model.resultValue, 2);
                document.getElementById("estimator-input-desc").innerText = model.inputDesc;
                document.getElementById("estimator-res-desc").innerText = model.resultDesc;
                estimatorInputSelector.value = String(roundedInput);
                estimatorResultSelector.value = String(roundedResult);
                const tweetStr = `AtCoderのハンドルネーム: ${userScreenName}\n${model.inputDesc}: ${roundedInput}\n${model.resultDesc}: ${roundedResult}\n`;
                document.getElementById("estimator-tweet").href = GetEmbedTweetLink(tweetStr, "https://greasyfork.org/ja/scripts/369954-ac-predictor");
            }
        });
    }
}
const estimator = new EstimatorElement();

var sidemenuHtml = "<style>\n    #menu-wrap {\n        display: block;\n        position: fixed;\n        top: 0;\n        z-index: 20;\n        width: 400px;\n        right: -350px;\n        transition: all 150ms 0ms ease;\n        margin-top: 50px;\n    }\n\n    #sidemenu {\n        background: #000;\n        opacity: 0.85;\n    }\n    #sidemenu-key {\n        border-radius: 5px 0px 0px 5px;\n        background: #000;\n        opacity: 0.85;\n        color: #FFF;\n        padding: 30px 0;\n        cursor: pointer;\n        margin-top: 100px;\n        text-align: center;\n    }\n\n    #sidemenu {\n        display: inline-block;\n        width: 350px;\n        float: right;\n    }\n\n    #sidemenu-key {\n        display: inline-block;\n        width: 50px;\n        float: right;\n    }\n\n    .sidemenu-active {\n        transform: translateX(-350px);\n    }\n\n    .sidemenu-txt {\n        color: #DDD;\n    }\n\n    .menu-wrapper {\n        border-bottom: 1px solid #FFF;\n    }\n\n    .menu-header {\n        margin: 10px 20px 10px 20px;\n        user-select: none;\n    }\n\n    .menu-box {\n        overflow: hidden;\n        transition: all 300ms 0s ease;\n    }\n    .menu-box-collapse {\n        height: 0px !important;\n    }\n    .menu-box-collapse .menu-content {\n        transform: translateY(-100%);\n    }\n    .menu-content {\n        padding: 10px 20px 10px 20px;\n        transition: all 300ms 0s ease;\n    }\n    .cnvtb-fixed {\n        z-index: 19;\n    }\n</style>\n<div id=\"menu-wrap\">\n    <div id=\"sidemenu\" class=\"container\"></div>\n    <div id=\"sidemenu-key\" class=\"glyphicon glyphicon-menu-left\"></div>\n</div>";

//import "./sidemenu.scss";
class SideMenu {
    constructor() {
        this.pendingElements = [];
        this.Generate();
    }
    Generate() {
        document.getElementById("main-div").insertAdjacentHTML("afterbegin", sidemenuHtml);
        resizeSidemenuHeight();
        const key = document.getElementById("sidemenu-key");
        const wrap = document.getElementById("menu-wrap");
        key.addEventListener("click", () => {
            this.pendingElements.forEach((elem) => {
                elem.afterOpen();
            });
            this.pendingElements.length = 0;
            key.classList.toggle("glyphicon-menu-left");
            key.classList.toggle("glyphicon-menu-right");
            wrap.classList.toggle("sidemenu-active");
        });
        window.addEventListener("onresize", resizeSidemenuHeight);
        document.getElementById("sidemenu").addEventListener("click", (event) => {
            const target = event.target;
            const header = target.closest(".menu-header");
            if (!header)
                return;
            const box = target.closest(".menu-wrapper").querySelector(".menu-box");
            box.classList.toggle("menu-box-collapse");
            const arrow = target.querySelector(".glyphicon");
            arrow.classList.toggle("glyphicon-menu-down");
            arrow.classList.toggle("glyphicon-menu-up");
        });
        function resizeSidemenuHeight() {
            document.getElementById("sidemenu").style.height = `${window.innerHeight}px`;
        }
    }
    addElement(element) {
        if (!element.shouldDisplayed(document.location.href))
            return;
        const sidemenu = document.getElementById("sidemenu");
        sidemenu.insertAdjacentHTML("afterbegin", element.GetHTML());
        const content = sidemenu.querySelector(".menu-content");
        content.parentElement.style.height = `${content.offsetHeight}px`;
        element.afterAppend();
        this.pendingElements.push(element);
    }
}

const sidemenu = new SideMenu();
const elements = [predictor, estimator];
for (let i = elements.length - 1; i >= 0; i--) {
    sidemenu.addElement(elements[i]);
}