Wanikani Burn Reviews

Adds a space on the main page that reviews random burned items. This is a maintained fork of the original script by Samuel Harbord

// ==UserScript==
// @name        Wanikani Burn Reviews
// @namespace   wkburnreviewnew
// @description Adds a space on the main page that reviews random burned items. This is a maintained fork of the original script by Samuel Harbord
// @version     2.2.6
// @author      Jonny Dark
// @license     Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0); http://creativecommons.org/licenses/by-nc/4.0/
// @include     http://www.wanikani.com/
// @include     https://www.wanikani.com/
// @include     http://www.wanikani.com/dashboard
// @include     https://www.wanikani.com/dashboard
// @require     https://greasyfork.org/scripts/19781-wanakana/code/WanaKana.js?version=126349
// @grant       none
// @require 		http://code.jquery.com/jquery-1.12.4.min.js

// Use site jquery and other variables
// Todo fix this properly to work with Greasmonkey
try {
  localStorage = unsafeWindow.localStorage;
  window = unsafeWindow;
}
catch {}

// CONSTANTS
var RADICAL   = 0;
var KANJI     = 1;
var VOCAB     = 2;

var UNDEFINED = -1;
var MEANING   = 0;
var READING   = 1;

var DEBUG   = 7;
var WARNING = 8;
var ERROR   = 9;

var LITEBLUE = "#00a0f1";
var PINK     = "#f100a0";
var PURPLE   = "#a000f1";


// Globals....ewww
var BRLoggingEnabled = (localStorage.getItem("BRLoggingEnabled") == "true");

BRData = { Radicals: [], Kanji: [], Vocab: [] };
BRConfig = { RadicalsEnabled: true, KanjiEnabled: true, VocabEnabled: true };

BRQuestion = {
    Item      : {},
    askingFor : UNDEFINED,
    itemType  : UNDEFINED,
    progress  : 0,
    answered  : false,

    IsRadical : function() { return this.itemType === RADICAL; },
    IsKanji   : function() { return this.itemType === KANJI; },
    IsVocab   : function() { return this.itemType === VOCAB; },

    IsAskingForMeaning: function() { return this.askingFor === MEANING || this.IsRadical(); },
    IsAskingForReading: function() { return this.askingFor === READING; },

    IsAnswered : function() { return this.answered; },
    SetAnswered: function(answered) { this.answered = answered; },

    GetAnswers : function() {
        if (this.IsAskingForMeaning()) {
            return this.Item.meaning;
        }
        else {
            if (BRQuestion.IsKanji()) {
                var importantReading = BRQuestion.Item.important_reading;
                return BRQuestion.Item[importantReading];
            }
            else {
                return BRQuestion.Item.kana;
            }
        }
    },

    Started    : function() { return this.progress > 0; },
    IsComplete : function() { return (this.IsRadical() && this.Started()) || this.progress >= 2; },
    Restart    : function() { this.progress = 0; },
    Skip       : function() { this.progress = 2; },
    NextPart   : function() {
        this.progress++;
        this.askingFor = this.IsAskingForMeaning() ? READING : MEANING;
    },

    DependingOnTypeUse : function (ifRadical, ifKanji, ifVocab) {
        return this.IsRadical() ? ifRadical : this.IsKanji() ? ifKanji : ifVocab;
    },

    Reset : function() {
        this.askingFor = UNDEFINED;
        this.itemType  = UNDEFINED;
        this.Item      = {};
        this.progress  = 0;
        this.answered  = false;
    }
};

function BRLog(logdata, level) {
    level = (typeof level == "undefined") ? DEBUG : level;
    if (!BRLoggingEnabled && level < WARNING) return;
    if (!console) return;

    var logmethod = console.log.bind(console);
    if (typeof level !== "undefined" && level !== DEBUG) {
        logmethod = (level == WARNING ? console.warn.bind(console) :
                     level == ERROR ? console.error.bind(console) :
                     logmethod);
    }

    logmethod("WKBurnReview: " + logdata);
    if (typeof logdata != "string") {
        logmethod(logdata);
    }
}

window.BREnableLogging = function() {
    BRLoggingEnabled = true;
    localStorage.setItem("BRLoggingEnabled", true);
};

window.BRDisableLogging = function() {
    BRLoggingEnabled = false;
    localStorage.removeItem("BRLoggingEnabled");
};

function getApiKeyThen(callback) {
    // First check if the API key is in local storage.
    var api_key = localStorage.getItem('apiKey');
    if (typeof api_key === 'string' && api_key.length === 32) {
        return callback(api_key);
    }
    else {
        // We don't have the API key.  Fetch it from the /settings/personal_access_tokens page.
      	$.get('https://www.wanikani.com/settings/personal_access_tokens')
            .done(function(page) {
                if (typeof page !== 'string') return callback(null);

                // Extract the API key.
                api_key = $(page).find('#user_api_key').val();
                if (typeof api_key == 'string' && api_key.length == 32) {
                    	// Store the updated user info.
                    	localStorage.setItem('apiKey', api_key);
                }
                else {
                    BRLog("Failed to get API key :( instead got: '" + api_key + "'", ERROR);
                }
                return callback(api_key);
            });

    }
}

function addBurnReviewStylesThen(callback) {

    if (stylesAlreadyAdded()) {
        BRLog("Styles already added, don't need to add again");
        return callback();
    }

    BRLog("Getting the review page stylesheet...");
    $.ajax({url:"https://www.wanikani.com/review", dataType:"html"}).done(
        function(data) {
            BRLog("Got the review page document. Extracting styles");
            var parser = new DOMParser();
            var reviewsdoc = parser.parseFromString(data, "text/html");
            var links = reviewsdoc.head.getElementsByTagName("link");
            for (var i = 0; i < links.length; i++)
            {
                var link = links[i];
                if (link.href.endsWith(".css"))
                {
                    BRLog("Adding " + link.outerHTML + " to document head");
                    $("head").append(link);
                }
            }
            appendExternalBurnReviewStylesheetThen(callback);
        });
}

function stylesAlreadyAdded() {
    return $('#burnReviewStyles').length > 0;
}

function appendExternalBurnReviewStylesheetThen(callback) {
    BRLog("Adding additional CSS");
    // TODO - tie query string to release version
    var cssFile = "https://cdn.rawgit.com/jonnydark/wanikani-burn/master/BurnReviews.css?v=2.2";

    $.get(cssFile, function(content) {

        $('head').append('<style id="burnReviewStyles"></style>');
        $('#burnReviewStyles').text(content);

    callback();
    });
}

// This is for dumping CSS that must be present before loading main stylesheet
function appendPriorityCSS() {
    var priorityStyles =
    '<style type="text/css">'                                                                                             +
        '.burn-review-container { float:left;}'                                                                           +
        '#loadingBR { position: relative; background-color: #d4d4d4; margin-top: 0px; padding-top: 42px; height: 99px; }' +
        '#dim-overlay { position: fixed; background-color: black; opacity: 0.75; width: 100%; height: 100%; z-index: 1; margin-top: -122px; padding-bottom: 122px; display: none; }' +
    '</style>';
    $(priorityStyles).appendTo($("head"));
}

function injectWidgetHtmlWrapper() {
        $(".low-percentage.kotoba-table-list.dashboard-sub-section").parent().wrap('<div class="col burn-review-container"></div>');
        $("<br />" + wrapperHTML() + "<!-- span4 -->").insertAfter($(".low-percentage.kotoba-table-list.dashboard-sub-section"));
        setLanguage();
}

function wrapperHTML() {
    var html =
        '<div class="">'                                                                                                                     +
            '<section class="burn-reviews kotoba-table-list dashboard-sub-section one-second-transition" style="z-index: 2; position: relative">' +
                '<h3 class="small-caps">'                                                                                                         +
                    '<span class="br-en">BURN REVIEWS</span>'                                                                                     +
                    '<span class="br-jp">焦げた復習</span>'                                                                                       +
                '</h3>'                                                                                                                           +
                '<div id="loadingBR" align="center" style=""></div>'                                                                              +
                '<div class="see-more" style="margin-top: -1px">'                                                                                 +
                    '<a href="javascript:void(0)" id="new-item" class="small-caps">'                                                              +
                        '<span class="br-en">NEW ITEM</span>'                                                                                     +
                        '<span class="br-jp">新しい項目</span>'                                                                                   +
                    '</a>'                                                                                                                        +
                '</div>'                                                                                                                          +
            '</section>'                                                                                                                          +
        '</div>';
    return html;
}

function constructBurnReviewHtml() {

    BRLog("Constructing Burn Review HTML");
    $("#user-response").attr("disabled", false).val("").focus();

    $("body").prepend('<div id="dim-overlay"></div>');
    BRLog("Overlay applied");

    var strReview =
       '<div class="answer-exception-form" id="answer-exception" align="center">'                                                                                                       +
           '<span>Answer goes here</span>'                                                                                                                                              +
        '</div>'                                                                                                                                                                        +
        '<div id="question" class="br-question">'                                                                                                                                       +
            '<div class="item-toggle-buttons">'                                                                                                                                         +
                '<div class="radicals-toggle' + ((BRConfig.RadicalsEnabled) ? ' on' : '') +'">'                                                                                         +
                    '<span lang="ja">部</span>'                                                                                                                                         +
                '</div>'                                                                                                                                                                +
                '<div class="kanji-toggle' + ((BRConfig.KanjiEnabled) ? ' on' : '') +'" style="padding-top: 1px">'                                                                      +
                    '<span lang="ja">漢</span>'                                                                                                                                         +
                '</div>'                                                                                                                                                                +
                '<div class="vocab-toggle' + ((BRConfig.VocabEnabled) ? ' on' : '') +'">'                                                                                               +
                    '<span lang="ja">語</span>'                                                                                                                                         +
                '</div>'                                                                                                                                                                +
            '</div>'                                                                                                                                                                    +
            '<div class="left-side-action-buttons">'                                                                                                                                    +
                '<div class="load-button">'                                                                                                                                             +
                    '<span class="br-en" lang="ja">Load</span>'                                                                                                                         +
                    '<span class="br-jp" lang="ja">ロード</span>'                                                                                                                       +
                '</div>'                                                                                                                                                                +
                '<div class="start-button-toggle' + ((localStorage.getItem('BRStartButton') !== null) ? ' on' : '') +'">'                                                               +
                    '<span lang="ja" class="br-en">Start Button</span>'                                                                                                                 +
                    '<span lang="ja" class="br-jp">開始<br />ボタン</span>'                                                                                                             +
                '</div>'                                                                                                                                                                +
            '</div>'                                                                                                                                                                    +
            '<div class="right-side-toggle-buttons">'                                                                                                                                   +
                '<div class="toggle-language-button">'                                                                                                                                  +
                    '<span lang="ja" class="br-en">日本語</span>'                                                                                                                       +
                    '<span lang="ja" class="br-jp">English</span>'                                                                                                                      +
                '</div>'                                                                                                                                                                +
                '<div class="resize-button">'                                                                                                                                           +
                    '<span lang="ja" class="br-en">Resize</span>'                                                                                                                       +
                    '<span lang="ja" class="br-jp">拡大する</span>'                                                                                                                     +
                '</div>'                                                                                                                                                                +
                '</div>'                                                                                                                                                                +
                '<div class="review-item-container">'                                                                                                                                   +
                    '<span class="review-item" lang="ja">' + BRQuestion.Item.character +'</span>'                                                                                       +
                '</div>'                                                                                                                                                                +
                '<div id="question-type"><h1 id="question-type-text" align="center">' + getReviewTypeText() +'</h1></div>'                                                              +
                '<div id="answer-form" tabindex="10">'                                                                                                                                  +
                    '<form onSubmit="return false">'                                                                                                                                    +
                        '<fieldset>'                                                                                                                                                    +
                            '<input autocapitalize="off" autocomplete="off" autocorrect="off" id="user-response" name="user-response" placeholder="Your Response" type="text"></input>' +
                            '<button id="answer-button" type="button"><i class="icon-chevron-right"></i></button>'                                                                      +
                        '</fieldset>'                                                                                                                                                   +
                    '</form>'                                                                                                                                                           +
                '</div>'                                                                                                                                                                +
            '</div>'                                                                                                                                                                    +
        '</div>';

    BRLog(strReview);
    $(strReview).insertAfter($(".burn-reviews.kotoba-table-list.dashboard-sub-section h3"));
    setLanguage();
}

function rand(low, high) {
    return Math.floor(Math.random()*(high + 1)) + low;
}

function enableKanaInput() {
    wanakana.bind(document.getElementById('user-response'));
}

function disableKanaInput() {
    wanakana.unbind(document.getElementById('user-response'));
}

function nextQuestion() {

    BRLog("Getting burn review");

    BRQuestion.NextPart();
    BRQuestion.SetAnswered(false);

    $("#user-response").attr("disabled", false).val("").focus();
    $(".answer-exception-form").css("display", "none");

    if (BRQuestion.IsComplete()) {
        newBRItem();
        updateBRItem(true);
    }

    configureInputForEnglishOrJapanese();
    $("#question-type-text").html(getReviewTypeText());
    setLanguage(); //TODO See if there's a way to bind this to jquery.html();

    document.getElementById('user-response').value = "";
    $("#answer-form fieldset").removeClass("correct").removeClass("incorrect");

}

function getReviewTypeText() {
    var reviewTypeTextEng;
    var reviewTypeTextJp;

    if (BRQuestion.IsAskingForMeaning()) {
        reviewTypeTextEng = "Meaning";
        reviewTypeTextJp  = "意味";
    }
    else {
        if (BRQuestion.Item.important_reading == "onyomi") {
            reviewTypeTextEng = "onyomi";
            reviewTypeTextJp  = "音";
        }
        else {
            reviewTypeTextEng = "kunyomi";
            reviewTypeTextJp  = "訓";
        }
        reviewTypeTextEng += " Reading";
        reviewTypeTextJp  += "読み";
    }
    return '<span class="br-en">' + reviewTypeTextEng + '</span><span class="br-jp">' + reviewTypeTextJp + '</span>';
}

function setLanguage() {
    var langToHide = BRLangJP ? ".br-en" : ".br-jp";
    $(langToHide).addClass("br-hide");
}

function newBRItem() {
    BRLog("Getting new burn item");

    // Need to get a weighted Random
    var itemTypeArray = [];
    if (BRConfig.RadicalsEnabled) {
        itemTypeArray = itemTypeArray.concat(new Array(BRData.Radicals.length).fill(RADICAL));
    }
    if (BRConfig.KanjiEnabled) {
        itemTypeArray = itemTypeArray.concat(new Array(BRData.Kanji.length).fill(KANJI));
    }
    if (BRConfig.VocabEnabled) {
        itemTypeArray = itemTypeArray.concat(new Array(BRData.Vocab.length).fill(VOCAB));
    }
    BRQuestion.itemType = itemTypeArray[rand(0, itemTypeArray.length)];

    var dataBank = [BRData.Radicals, BRData.Kanji, BRData.Vocab][BRQuestion.itemType];
    BRQuestion.ItemIndex = rand(0, dataBank.length - 1);

    BRQuestion.Item = dataBank[BRQuestion.ItemIndex];

    BRQuestion.askingFor = BRQuestion.IsRadical() ? MEANING : rand(MEANING, READING);

    BRQuestion.Restart();

    BRLog("Burn item type: " + BRQuestion.itemType);
    BRLog("Burn item: " + BRQuestion.Item);

}

function updateBRItem(updateText) {

    BRLog("Updating Burn review item");
    if (updateText) {
        $(".review-item").html(BRQuestion.Item.character);
        setItemFontSize();
    }

    var bg = BRQuestion.DependingOnTypeUse(LITEBLUE, PINK, PURPLE);
    var bgi = "linear-gradient(to bottom, ";

    bgi += BRQuestion.DependingOnTypeUse("#0af, #0093dd", "#f0a, #dd0093", "#a0f, #9300dd");
    $(".review-item-container").css({"background-color": bg, "background-image": bgi });
}

function setItemFontSize() {
    var itemLength = BRQuestion.Item.character.length;
    var fontSize = 48;
    switch(itemLength) {
        case 1:
        case 2:
        case 3:
            fontSize = 48;
            break;
        case 4:
            fontSize = 38;
            break;
        case 5:
            fontSize = 28;
            break;
        default:
            fontSize = 24;
            break;
    }
    $(".review-item").css("font-size", fontSize + "px");
}

function skipItem() {
   	BRQuestion.Skip();
    nextQuestion();
    return false;
}

function displayStartMessage() {
    var text = BRLangJP ? "開始" : "Start";
    $("#loadingBR").html('<a lang="ja" href="javascript:void(0)" style="font-size: 52px; color: #434343; text-decoration: none">' + text + '</a>');
}

function bindStartButtonClickEvent() {
    $("#loadingBR a").click(function() {
        startWaniKaniBurnReviews();
    });
}

function displayLoadingMessage(color, english, japanese) {
    $("#loadingBR").html('<h3 style="color:' + color + '">' + (BRLangJP ? japanese : english)  + '</h3>');
}

function displayRadicalLoadingMessage() {
    displayLoadingMessage(LITEBLUE, "Retrieving radical data...", "部首データを検索中…");
}

function displayKanjiLoadingMessage() {
    displayLoadingMessage(PINK,"Retrieving kanji data...", "漢字データを検索中…");
}

function displayVocabLoadingMessage() {
    displayLoadingMessage(PURPLE,"Retrieving vocabulary data...", "単語データを検索中…");
}


function getRadicalCharacter(radical) {
    return radical.character ? radical.character :
            "<img class=\"radical-question\" src=\"" + radical.image + "\" />";
}

function itemIsBurned(item) {
    return item.user_specific ? item.user_specific.burned : false;
}

function getMeaning(item) {
    var usyn = item.user_specific ? item.user_specific.user_synonyms : null;
    var meaning = item.meaning.split(', ');
    return usyn !== null ? meaning.concat(usyn) : meaning;
}

function fetchAndCacheBurnedRadicalsThen(callback) {
    fetchAndCacheBurnedItemsThen(callback, "radicals", "Radicals", "burnedRadicals",
        function(radical) {
            return { character : getRadicalCharacter(radical),
                     meaning   : getMeaning(radical)
            };
        });
}

function fetchAndCacheBurnedKanjiThen(callback) {
    fetchAndCacheBurnedItemsThen(callback, "kanji", "Kanji", "burnedKanji",
        function(kanji) {
            return { character         : kanji.character,
                     meaning           : getMeaning(kanji),
                     onyomi            : kanji.onyomi ? kanji.onyomi.split(", ") : null,
                     kunyomi           : kanji.kunyomi ? kanji.kunyomi.split(", ") : null,
                     important_reading : kanji.important_reading,
            };
        });
}

function fetchAndCacheBurnedVocabThen(callback) {
    fetchAndCacheBurnedItemsThen(callback, "vocabulary", "Vocab", "burnedVocab",
        function(vocab) {
            return { character : vocab.character,
                     meaning   : getMeaning(vocab),
                     kana      : vocab.kana.split(", "),
            };
        });
}

function fetchAndCacheBurnedItemsThen(callback, requestedResource, type, storageKey, mapFunction) {
    $.ajax({url:"https://www.wanikani.com/api/user/" + apiKey + "/" + requestedResource, dataType:"json"})
        .done(function(response) {
            // vocabulary for some reason has everything in a child called general, kanji and radicals do not
            var requestData = response.requested_information.general ?
                                response.requested_information.general : response.requested_information;
            var burnedItems = requestData.filter(itemIsBurned);
            BRData[type] = burnedItems.map(mapFunction);

            localStorage.setItem(storageKey, JSON.stringify(BRData[type]));
            callback();
        })
        .fail(function() {
            BRLog("Request to WaniKani API failed. Catastrophic failure ermagerd D:", ERROR);
        });
}

function maybeGetBurnedItemsThen(callback, storageKey, type, fetchFunction) {
    var RawBRData = localStorage.getItem(storageKey);
    if (RawBRData !== null) {
        try {
            BRData[type] = JSON.parse(RawBRData);
            if (BRData[type].length > 0) {
                return callback();
            }
            BRLog("No burned " + type + " in cache. Refectching...", WARNING);
        }
        catch(e) {
            BRLog("Could not parse cached radical data. Refetching...", WARNING);
        }
    }
    return fetchFunction(callback);
}


function maybeGetBurnedRadicalsThen(callback) {
    displayRadicalLoadingMessage();
    maybeGetBurnedItemsThen(callback, "burnedRadicals", "Radicals", fetchAndCacheBurnedRadicalsThen);
}

function maybeGetBurnedKanjiThen(callback) {
    displayKanjiLoadingMessage();
    maybeGetBurnedItemsThen(callback, "burnedKanji", "Kanji", fetchAndCacheBurnedKanjiThen);
}

function maybeGetBurnedVocabThen(callback) {
    displayVocabLoadingMessage();
    maybeGetBurnedItemsThen(callback, "burnedVocab", "Vocab", fetchAndCacheBurnedVocabThen);
}

function getBurnReviewDataThen(callback) {
    BRLog("Getting WaniKana data");

    maybeGetBurnedRadicalsThen(function() {
        maybeGetBurnedKanjiThen(function() {
            maybeGetBurnedVocabThen(function() {

                BRLog("Data items { RadicalData: " + BRData.Radicals.length +
                                 "; KanjiData: " + BRData.Kanji.length +
                                 "; VocabData: " + BRData.Vocab.length + "}");
                callback();
            });
        });
    });

}

function clearBurnedItemData() {
    localStorage.removeItem("burnedRadicals");
    localStorage.removeItem("burnedKanji");
    localStorage.removeItem("burnedVocab");
    BRData.Radicals = [];
    BRData.Kanji    = [];
    BRData.Vocab    = [];
}

function confirmResurrection() {
    $(".answer-exception-form").css({"display": "block", "opacity": "0", "-webkit-transform": "translateY(20px)", "-moz-transform": "translateY(20px)"}).removeClass("animated fadeInUp");
    $(".answer-exception-form").addClass("animated fadeInUp");

    var itemTypeForUrl = BRQuestion.DependingOnTypeUse("radicals/", "kanji/", "vocabulary/");
    var resurrectionUrl = "https://www.wanikani.com/retired/" + itemTypeForUrl + BRQuestion.Item.character + "?resurrect=true";
    var resurrectionLink = '<a href="' + resurrectionUrl + '" target="_blank" class="btn btn-mini resurrect-btn" data-method="put" rel="nofollow">';

    var resurrectEng = '<div class="br-en">Are you sure you want to ' + resurrectionLink + 'Resurrect</a> the ' +
                            BRQuestion.DependingOnTypeUse("radical", "kanji item", "vocabulary item") + ' "' + BRQuestion.Item.character + '"?</div>';

    var resurrectJp  = '<div class="br-jp">' + BRQuestion.DependingOnTypeUse("部首", "漢字", "単語") + "「" + BRQuestion.Item.character  + "」を" +
                            resurrectionLink + '復活</a>する<br />本当によろしいですか?</div>';

    $(".answer-exception-form span").html(resurrectEng + resurrectJp);
    setLanguage();

    document.getElementById("answer-exception").onclick = "return false";
    return false;
}

function initBurnReviews() {

    BRLog("Initialising the Burn Review widget");

    var loadStylesAndConstructWidget = function() {
        useCache = false;
        $("#loadingBR").remove();
        addBurnReviewStylesThen(constructBurnReviewWidget);
    };

    getBurnReviewDataThen(loadStylesAndConstructWidget);

}

function constructBurnReviewWidget() {

    BRLog("Getting new burn review item");
    newBRItem();

    BRLog("Adding burn review section");
    constructBurnReviewHtml();

    $("#answer-button").click(function() {
        submitBRAnswer();
    });
    updateBRItem(false);
    configureInputForEnglishOrJapanese();

    bindMouseClickEvents();

}

function configureInputForEnglishOrJapanese() {
    if (BRQuestion.IsAskingForMeaning()) {
        disableKanaInput();
        $("#user-response").removeAttr("lang").attr("placeholder","Your Response");
        $("#question-type").addClass("meaning");
        $("#question-type").removeClass("reading");
    } else {
        enableKanaInput();
        $("#user-response").attr({lang:"ja",placeholder:"答え"});
        $("#question-type").addClass("reading");
        $("#question-type").removeClass("meaning");
    }
}

function bindMouseClickEvents() {

    bindQuestionTypeToggleButtonClickEvents();

    bindLoadButtonClickEvent();

    bindStartButtonToggleButtonClickEvent();

    bindLanguageToggleButtonClickEvent();

    bindResizeButtonClickEvent();

    bindDimOverlayClickEvent();

    bindNewItemButtonClickEvent();
}

function bindNewItemButtonClickEvent() {
    document.getElementById("new-item").onclick = skipItem;
}

function bindLanguageToggleButtonClickEvent() {
    $('.toggle-language-button').click(function() {
        switchBRLang();
    });
}

function resizeWidget() {
    if (!resizeWidget.complete) return;

    resizeWidget.complete = false;
    $('.resize-button').toggleClass("on");
    $(".burn-reviews.kotoba-table-list.dashboard-sub-section").toggleClass("scale-up");
    $("#dim-overlay").fadeToggle({
        duration: 1000,
        complete: function() { resizeWidget.complete = true; }
    });
}
resizeWidget.complete = true;

function bindResizeButtonClickEvent() {
    $('.resize-button').click(function() {
        resizeWidget();
    });
}

function bindDimOverlayClickEvent() {
    $("#dim-overlay").click(function () {
        $('.resize-button').trigger("click");
    });
}


function bindLoadButtonClickEvent() {
    $(".load-button").click(function() {
        $("#dim-overlay").remove();
        $(".burn-reviews").parent().remove();
        BRQuestion.Reset();
        queueBRAnim = false;
        allowQueueBRAnim = true;
        $(wrapperHTML()).insertAfter($(".low-percentage.kotoba-table-list.dashboard-sub-section").parent().next());
        displayStartMessage();
        clearBurnedItemData();
        initBurnReviews();
    });
}

function bindStartButtonToggleButtonClickEvent() {
    $(".start-button-toggle").click(function() {
        $(this).toggleClass("on");
        if ($(this).hasClass("on")) {
            localStorage.setItem("BRStartButton", true);
        }
        else {
            localStorage.removeItem("BRStartButton");
        }
    });
}

function bindQuestionTypeToggleButtonClickEvents() {
    bindRadicalsToggleButtonClickEvent();
    bindKanjiToggleButtonClickEvent();
    bindVocabToggleButtonClickEvent();
}

function bindVocabToggleButtonClickEvent() {
    bindItemToggleButtonClickEvent('.vocab-toggle', "BRVocabEnabled", "VocabEnabled", BRQuestion.IsVocab.bind(BRQuestion));
}

function bindKanjiToggleButtonClickEvent() {
    bindItemToggleButtonClickEvent('.kanji-toggle', "BRKanjiEnabled", "KanjiEnabled", BRQuestion.IsKanji.bind(BRQuestion));
}

function bindRadicalsToggleButtonClickEvent() {
    bindItemToggleButtonClickEvent('.radicals-toggle', "BRRadicalsEnabled", "RadicalsEnabled", BRQuestion.IsRadical.bind(BRQuestion));
}

function bindItemToggleButtonClickEvent(cssClass, storageKey, configKey, currentQuestionIsType) {
    $('.item-toggle-buttons ' + cssClass).click(function() {
        if ($(this).hasClass("on")) {
            // Don't let last button be disabled
            if ($('.item-toggle-buttons .on').length == 1) return;

            localStorage.setItem(storageKey, false);
            BRConfig[configKey] = false;
            if (currentQuestionIsType()) {
                skipItem();
            }
        }
        else {
            localStorage.removeItem(storageKey);
            BRConfig[configKey] = true;
        }
        $(this).toggleClass("on");
    });
}

function switchBRLang() {
    if (!BRLangJP) {
        localStorage.setItem("BRLangJP", true);
    }
    else {
        localStorage.removeItem("BRLangJP");
    }
    BRLangJP = !BRLangJP;

    $('.br-en,.br-jp').toggleClass('br-hide');
}

function checkBurnReviewAnswer() {
    BRLog("Checking answer");
    var response = $("#user-response").val().toLowerCase().trim();
    response = BRQuestion.IsAskingForReading() ? addTerminalN(response) : response;
    var answers = BRQuestion.GetAnswers();
    var answerIsCorrect = isAnswerCorrect(response, answers);

    $("#answer-form").focus(); // fix for FF to stop focus being trapped
    $("#user-response").attr("disabled", true);

    if (responseIsValid(response)) {
        if (isIncorrectReading(response, answerIsCorrect)) {
            shakeAnswerForm();
            displayIncorrectReadingMessage();
            $("#user-response").attr("disabled", false).focus();
        }
        else {
            if (answerIsCorrect) {
                onCorrectAnswer(response);
            } else {
                onIncorrectAnswer(response);
            }
            BRQuestion.SetAnswered(true);
    	}
    } else {
        shakeAnswerForm();
        $("#user-response").attr("disabled", false).focus();
    }
}

function addTerminalN(str = '') {
  return /n/i.test(str.slice(-1)) ? `${str.slice(0, -1)}ん` : str;
}

function isAnswerCorrect(response, answers) {
    for (var a = 0; a < answers.length; a++) {
        if (response == answers[a]) {
            return true;
        }
    }
    return false;
}

function shakeAnswerForm() {
    $('#answer-form').addClass('shake')
                     .delay(1000)
                     .queue(function(next) {
                        $(this).removeClass('shake');
                        next();
                     });
}

function setInputValue(text) {
    $("#user-response").val(text);
}

function onCorrectAnswer(answer) {
    $("#answer-form fieldset").removeClass("incorrect").addClass("correct");
    setInputValue(answer);
}

function onIncorrectAnswer(answer) {
    $("#answer-form fieldset").removeClass("correct").addClass("incorrect");
    setInputValue(answer);
    displayIncorrectAnswerMessage();
}

function responseIsValid(response) {
    return ((BRQuestion.IsAskingForMeaning() && isAsciiPresent(response)) ||
            (!isAsciiPresent(response) && BRQuestion.IsAskingForReading())) && response !== "";
}

function displayIncorrectAnswerMessage() {
    // concat in to string of comma-separated answers
    var answerList = BRQuestion.GetAnswers().join(", ");
    var resurrectButton = '<a href="#" class="btn btn-mini resurrect-btn">';

    var answerTextEng = '<div class="br-en">The answer was:<br />"' + answerList + '"<br />' + resurrectButton + 'Resurrect</a> this item?</div>';
    var answerTextJp  = '<div class="br-jp">解答は<br />「' + answerList + '」であった。<br />この項目を' + resurrectButton + '復活</a>したいか?</div>';
    $('.answer-exception-form span').html(answerTextEng + answerTextJp);
    setLanguage();

    $(".answer-exception-form").css({"display": "block"}).addClass("animated fadeInUp");
    $('.resurrect-btn').on('click', confirmResurrection);
}

function isIncorrectReading(response, answerIsCorrect) {
    return (!answerIsCorrect && BRQuestion.IsKanji() && BRQuestion.IsAskingForReading() && ((BRQuestion.Item.important_reading == "onyomi" &&
       		compareKunyomiReading(response, BRQuestion.Item.kunyomi)) || (BRQuestion.Item.important_reading == "kunyomi" && response == BRQuestion.Item.onyomi)));
}

function displayIncorrectReadingMessage() {
    var incorrectReadingText = '<div class="br-en">Oops! You entered the wrong reading.</div>' +
                               '<div class="br-jp">おっと、異なる読みを入力してしまった。</div>';
    $(".answer-exception-form span").html(incorrectReadingText);
    setLanguage();

    $(".answer-exception-form").css({"display": "block"}).addClass("animated fadeInUp").delay(5000).queue(function(){
        $(this).addClass("fadeOut").dequeue().delay(800).queue(function(){
            $(this).removeClass("fadeOut").css("display", "none").dequeue();
        });
    });
}

function compareKunyomiReading(input, reading) {
    return (input == reading || input == reading.toString().substring(0, reading.indexOf(".")) || input == reading.toString().replace("*", input.substring(reading.indexOf(".") + 1)).replace(".", ""));
}

function submitBRAnswer() {
    if (!BRQuestion.IsAnswered()) {
        checkBurnReviewAnswer();
    }
    else {
        nextQuestion();
    }
}

function isAsciiPresent(e){
    return (BRQuestion.IsAskingForMeaning()) ? !/[^a-z \-0-9]/i.test(e) : /[^ぁ-ー0-9 ]/.test(e);
}

function startWaniKaniBurnReviews() {
    if (!useCache) {
        clearBurnedItemData();
    }
    BRLog("Loading...");

    var checkReady = setInterval(function() {
        BRLog("Checking for wanakana...");
        if (wanakana !== undefined) {
            clearInterval(checkReady);
            initBurnReviews();
        }
    }, 250);
}

function pageIsDashboard() {
    return $('title').text().search("Dashboard") > 0;
}

function main() {

    if (!pageIsDashboard()) {
        BRLog("Script not running on dashboard, exiting...");
        return;
    }


    getApiKeyThen(function(key) {

        if (key === null) {
            BRLog("Couldn't fetch API key. It's all gone Pete Tong. Cannot continue ;__;", ERROR);
            return;
        }

        apiKey = key; //global
        BRLog("Running!");

        useCache            =  !(localStorage.getItem("burnedRadicals") === null || localStorage.getItem("burnedKanji") === null || localStorage.getItem("burnedVocab") === null);
        BRIsChrome          =  (navigator.userAgent.toLowerCase().indexOf('chrome') > -1);
        BRQuestion.Reset();
        queueBRAnim              =  false;
        allowQueueBRAnim         =  true;
        BRLangJP                 =  (localStorage.getItem("BRLangJP") == "true");
        BRConfig.RadicalsEnabled =  (localStorage.getItem("BRRadicalsEnabled") != "false");
        BRConfig.KanjiEnabled    =  (localStorage.getItem("BRKanjiEnabled") != "false");
        BRConfig.VocabEnabled    =  (localStorage.getItem("BRVocabEnabled") != "false");


        String.prototype.trim = function() {
            return(this.replace(/^ +/,'').replace(/ +$/,''));
        };

        appendPriorityCSS();
        injectWidgetHtmlWrapper();

        displayStartMessage();
        bindStartButtonClickEvent();

        function submitOnEnterPress(event) {
            const isEnterKey = event.keyCode == 13;
            const isNotWkSearchInput = !event.target.matches('#query.search-query');
            if(isNotWkSearchInput && isEnterKey) {
                BRLog("User pressed Enter");
                submitBRAnswer();
            }
         }

        $(document).unbind('keypress', submitOnEnterPress).bind('keypress', submitOnEnterPress);

        if (localStorage.getItem("BRStartButton") === null)
        {
            startWaniKaniBurnReviews();
        }
    });
}

if (document.readyState === 'complete')
    main();
else
    window.addEventListener("load", main, false);

// ==/UserScript==