CS UBB Timetable Fixer

A userscript that enhances the tabelar timetables for CS UBB students.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         CS UBB Timetable Fixer
// @namespace    http://tampermonkey.net/
// @version      2.3.6
// @description  A userscript that enhances the tabelar timetables for CS UBB students.
// @author       myklosbotond
// @match        http*://www.cs.ubbcluj.ro/files/orar/*/tabelar/*.html
// @exclude      http*://www.cs.ubbcluj.ro/files/orar/*/tabelar/index.html
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// @require      https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
// @resource     bootstrap_CSS https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css
// @resource     jqUI_CSS https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_getResourceText
// ==/UserScript==

/*
 * jshint esversion: 6
 * jshint esnext: true
*/

const DEVELOPEMENT = false;
const URGENT_TODO = ['Custom add own entry'];
const TODO = ['List grouped by discipline'];

var defaultHTML;
var options;
var disciplines = [];
var idList = new Set();
var customError = false;

var customListScroll = 0;

const days = ["Luni", "Marti", "Miercuri", "Joi", "Vineri"];

const disciplineColumn = "disciplina";

(function () {
    'use strict';

    loadOptions();
    setupCss();
    setupFunctions();

    structureHTML();
    injectSettingHTML();

    getDisciplinesList();

    setEventHandlers();
    updateSettingsUi();

    runFixer();
})();

//color in just the curs / laboratory column on unselected

function setupCss() {
    'use strict';

    let jqUI_CssSrc = GM_getResourceText("jqUI_CSS");
    jqUI_CssSrc = jqUI_CssSrc.replace(/url\(['"]images\/ui\-bg_.*00\.png['"]\)/g, "");
    jqUI_CssSrc = jqUI_CssSrc.replace(/url\(['"]images\/ui\-icons_.*\.png['"]\)/g, "");

    GM_addStyle(jqUI_CssSrc);

    let bootstrap_Css = GM_getResourceText("bootstrap_CSS");
    GM_addStyle(bootstrap_Css);

    //Insert the styles from the css:
    GM_addStyle(`
        #urgent-todo {
            background: #e29797;
        }
        
        #todo {
            background: #fde6ce;
        }
        
        .todo-wrapper {
            margin: 10px 0 -10px
        }
        
        .todo-wrapper ul {
            text-align: left;
            display: inline-block;
            margin: 0;
        }
        
        body {
            margin: 0;
        }
        
        #settings-wrapper {
            background: #f5f9fb;
            padding-bottom: 10px;
        }
        
        #settings-container {
            position: relative;
        }
        
        #toggle-settings {
            position: absolute;
            right: 1%;
            top: 12px;
        }
        
        #tables-wrapper {
            margin-bottom: 20px;
        }
        
        .curs-tr {
            background: #fffedd;
        }
        
        .curs-tr td a {
            color: #c16b00;
        }
        
        .seminar-tr {
            background: #ddf2ff;
        }
        
        .seminar-tr td a {
            color: #0e62a5;
        }
        
        .labor-tr {
            background: #e4ffdd;
        }
        
        .labor-tr td a {
            color: #009688;
        }
        
        .error {
            color: red;
        }
        
        .last-of-day-tr {
            border-bottom: 2px solid
        }
        
        a:hover,
        a:hover font {
            background-color: transparent;
            text-decoration: underline
        }
        
        .other-week-tr {
            filter: grayscale(100%);
        }
        
        .other-week-tr td {
            background: transparent
        }
        
        .other-week-tr:hover {
            filter: grayscale(0%);
        }
        
        .meh-tr {
            opacity: 0.3;
            filter: grayscale(100%);
        }
        
        .meh-tr td {
            background: transparent
        }
        
        .meh-tr:hover {
            opacity: 0.7
        }
        
        .nope-tr {
            background: transparent
        }
        
        .nope-tr td {
            background: transparent
        }
        
        .nope-tr a {
            color: #003399
        }
        
        .hide-tr {
            display: none;
        }
        
        #offset-label {
            margin-top: 0px;
            margin-bottom: 0;
            padding-bottom: 7px;
            padding-top: 8px;
        }
        
        .ui-selectmenu-button.ui-button {
            width: auto
        }
        
        .ui-front:not(.ui-widget-overlay):not(.ui-selectmenu-open) {
            z-index: 100 !important;
        }
        
        .ui-selectmenu-open {
            z-index: 9999 !important;
        }
        
        .ui-selectmenu-icon.ui-icon {
            float: right;
            margin-top: 8px;
            border-style: solid;
            border-width: 3px 3px 0 3px;
            border-color: #0f0f0d transparent transparent transparent;
            width: 0;
            height: 0;
        }
        
        span.ui-button-icon.ui-icon.ui-icon-closethick {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg\" width="24" height="24" viewBox="0 0 24 24"><path d="M24 20.188l-8.315-8.209 8.2-8.282-3.697-3.697-8.212 8.318-8.31-8.203-3.666 3.666 8.321 8.24-8.206 8.313 3.666 3.666 8.237-8.318 8.285 8.203z"/></svg>');
            background-position: center;
            background-size: 50%;
        }
        
        #hider-list-wrapper {
            max-height: 300px;
            overflow: auto;
            margin-bottom: 9px;
        }
        
        #renamer-list-wrapper {
            max-height: 330px;
            overflow: auto;
            margin-bottom: 9px;
        }
        
        #visibility-dialog .row {
            margin-bottom: 2px;
        }
        
        #visibility-dialog label {
            line-height: 30px;
        }
        
        #visibility-dialog .ui-selectmenu-button {
            width: 100%;
        }
        
        #hider-dialog table {
            width: 100%;
        }
        
        #hider-dialog .check-column {
            width: 1px;
        }
        
        .option-wrapper {
            display: inline-block;
            margin-top: 7px;
        }
        
        .finder-options-wrapper {
            margin-left: 91px;
        }
        
        #finder-result,
        #finder-time-result {
            margin: 10px auto 0 auto;
        }
        
        #import-status {
            color: red;
            font-weight: bold;
        }
        
        .beta-button::after {
            content: "BETA";
            font-size: 10px;
            vertical-align: super;
            color: white;
            background: darkred;
            font-weight: bold;
            padding: 0 3px;
            border-radius: 3px;
            margin-left: 3px;
        }
        
        /* ================ Custom ============== */
        
        #customize-dialog {
            overflow: hidden;
        }
        
        #customize-dialog h3 {
            text-align: center;
        }
        
        #custom-empty-view {
            text-align: center;
        }
        
        #custom-selected-wrapper,
        #custom-list-wrapper {
            vertical-align: top;
            display: inline-block;
            max-width: 355px;
        }
        
        #custom-selected-wrapper .scroller,
        #custom-list-wrapper .scroller {
            max-height: 500px;
            overflow: auto;
        }
        
        #custom-controls {
            display: inline-block;
            vertical-align: top;
            margin: 100px 10px 0;
        }
        
        #custom-work-view .column-disciplina {
            max-width: 80px;
            overflow: hidden;
            white-space: nowrap;
        }
        
        #custom-work-view .column-cadrul-didactic {
            white-space: nowrap;
        }
        
        #custom-work-view .ui-selectee:not(.ui-selected) {
            background: none;
        }
        
        #custom-work-view .ui-selectee:not(.ui-selected) td {
            opacity: .8;
        }
        
        #custom-work-view .ui-selectee:not(.ui-selected) a {
            color: black;
        }
        
        #custom-work-view .ui-selected {
            font-weight: bold;
        }
        
        #custom-work-view .ui-selected tf {
            color: #313131;
        }
        
        #custom-work-view .matching:not(.ui-selected) td {
            text-decoration: underline;
        }
        
        #custom-work-view .matching .column-ziua {
            font-weight: bold;
        }
        
        #custom-work-view .curs-tr.ui-selectee td:not(:first-child):not(:nth-child(2)):not(:last-child) {
            background: #fffedd;
        }
        
        #custom-work-view .seminar-tr.ui-selectee td:not(:first-child):not(:nth-child(2)):not(:last-child) {
            background: #ddf2ff;
        }
        
        #custom-work-view .labor-tr.ui-selectee td:not(:first-child):not(:nth-child(2)):not(:last-child) {
            background: #e4ffdd;
        }
        
        .matched-table tr:not(.ui-selected):not(.matching) {
            filter: grayscale(100%);
        }
        
        /* ================ Icons =============== */
        
        .ui-icon-gear {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M24 13.616v-3.232c-1.651-.587-2.694-.752-3.219-2.019v-.001c-.527-1.271.1-2.134.847-3.707l-2.285-2.285c-1.561.742-2.433 1.375-3.707.847h-.001c-1.269-.526-1.435-1.576-2.019-3.219h-3.232c-.582 1.635-.749 2.692-2.019 3.219h-.001c-1.271.528-2.132-.098-3.707-.847l-2.285 2.285c.745 1.568 1.375 2.434.847 3.707-.527 1.271-1.584 1.438-3.219 2.02v3.232c1.632.58 2.692.749 3.219 2.019.53 1.282-.114 2.166-.847 3.707l2.285 2.286c1.562-.743 2.434-1.375 3.707-.847h.001c1.27.526 1.436 1.579 2.019 3.219h3.232c.582-1.636.75-2.69 2.027-3.222h.001c1.262-.524 2.12.101 3.698.851l2.285-2.286c-.744-1.563-1.375-2.433-.848-3.706.527-1.271 1.588-1.44 3.221-2.021zm-12 2.384c-2.209 0-4-1.791-4-4s1.791-4 4-4 4 1.791 4 4-1.791 4-4 4z"/></svg>');
            background-position: center;
            background-size: 100%;
        }
        
        .ui-icon-eye {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12.015 7c4.751 0 8.063 3.012 9.504 4.636-1.401 1.837-4.713 5.364-9.504 5.364-4.42 0-7.93-3.536-9.478-5.407 1.493-1.647 4.817-4.593 9.478-4.593zm0-2c-7.569 0-12.015 6.551-12.015 6.551s4.835 7.449 12.015 7.449c7.733 0 11.985-7.449 11.985-7.449s-4.291-6.551-11.985-6.551zm-.015 3c-2.209 0-4 1.792-4 4 0 2.209 1.791 4 4 4s4-1.791 4-4c0-2.208-1.791-4-4-4z"/></svg>');
            background-position: center;
            background-size: 100%;
        }
        
        .ui-icon-hide {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19.604 2.562l-3.346 3.137c-1.27-.428-2.686-.699-4.243-.699-7.569 0-12.015 6.551-12.015 6.551s1.928 2.951 5.146 5.138l-2.911 2.909 1.414 1.414 17.37-17.035-1.415-1.415zm-6.016 5.779c-3.288-1.453-6.681 1.908-5.265 5.206l-1.726 1.707c-1.814-1.16-3.225-2.65-4.06-3.66 1.493-1.648 4.817-4.594 9.478-4.594.927 0 1.796.119 2.61.315l-1.037 1.026zm-2.883 7.431l5.09-4.993c1.017 3.111-2.003 6.067-5.09 4.993zm13.295-4.221s-4.252 7.449-11.985 7.449c-1.379 0-2.662-.291-3.851-.737l1.614-1.583c.715.193 1.458.32 2.237.32 4.791 0 8.104-3.527 9.504-5.364-.729-.822-1.956-1.99-3.587-2.952l1.489-1.46c2.982 1.9 4.579 4.327 4.579 4.327z"/></svg>');
            background-position: center;
            background-size: 100%;
        }
        
        .ui-icon-rename {
            background-image: url('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8"?><svg viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="m18.483 14.133h-1.462v-9h1.462v9z"/><path d="m12.525 6.6228h-10.542v6h10.542v1.51h-12.042v-9h12.042v1.49z"/><path d="m15.521 2.8728v13.5c0 0.414 0.335 0.75 0.75 0.75h1.5v1.5h-6v-1.5h1.504c0.415 0 0.75-0.336 0.75-0.75v-13.5c0-0.414-0.335-0.75-0.75-0.75h-1.504v-1.499h6v1.499h-1.5c-0.415 0-0.75 0.336-0.75 0.75"/></svg>');
            background-position: center;
            background-size: 100%;
        }
        
        .ui-icon-find {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M21.172 24l-7.387-7.387c-1.388.874-3.024 1.387-4.785 1.387-4.971 0-9-4.029-9-9s4.029-9 9-9 9 4.029 9 9c0 1.761-.514 3.398-1.387 4.785l7.387 7.387-2.828 2.828zm-12.172-8c3.859 0 7-3.14 7-7s-3.141-7-7-7-7 3.14-7 7 3.141 7 7 7z"/></svg>');
            background-position: center;
            background-size: 100%;
        }
        
        .ui-icon-reset {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M17.026 22.957c10.957-11.421-2.326-20.865-10.384-13.309l2.464 2.352h-9.106v-8.947l2.232 2.229c14.794-13.203 31.51 7.051 14.794 17.675z"/></svg>');
            background-position: center;
            background-size: 100%;
        }
        
        .ui-icon-printer {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M14 20h-6v-1h6v1zm10-15v13h-4v6h-16v-6h-4v-13h4v-5h16v5h4zm-6 10h-12v7h12v-7zm0-13h-12v3h12v-3zm4 5.5c0-.276-.224-.5-.5-.5s-.5.224-.5.5.224.5.5.5.5-.224.5-.5zm-6 9.5h-8v1h8v-1z"/></svg>');
            background-position: center;
            background-size: 100%;
        }
        
        .ui-icon-import {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 12l9-8v6h15v4h-15v6z"/></svg>');
            background-position: center;
            background-size: 100%;
        }
        
        .ui-icon-export {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M24 12l-9-8v6h-15v4h15v6z"/></svg>');
            background-position: center;
            background-size: 100%;
        }
        
        .ui-icon-customize {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M14.078 4.232l-12.64 12.639-1.438 7.129 7.127-1.438 12.641-12.64-5.69-5.69zm-10.369 14.893l-.85-.85 11.141-11.125.849.849-11.14 11.126zm2.008 2.008l-.85-.85 11.141-11.125.85.85-11.141 11.125zm18.283-15.444l-2.816 2.818-5.691-5.691 2.816-2.816 5.691 5.689z"/></svg>');
            background-position: center;
            background-size: 100%;
        }
        
        /* =========== Printer styles =========== */
        
        @media print {
            #settings-wrapper,
            #toggle-settings {
                display: none;
            }
            #tables-wrapper table {
                page-break-after: always;
            }
        }
    `);

}


function initCustomTable() {
    const grp = $('#custom-group-select').val();
    const halfGrp = parseInt($('#custom-half-group-select').val(), 10);
    const otherHalf = 3 - halfGrp;
    const otherRegEx = new RegExp(`[0-9]+/${otherHalf}`);
    const table = $('.table-group-' + grp);

    table.find('tr:has(td)')
        .filter((_, row) => {
            const $row = $(row);
            const origDiscName = $row.find(`.column-${disciplineColumn}`).data("original");
            const formation = $row.find(`.column-formatia`).text();

            const halfOk = !otherRegEx.test(formation);

            return halfOk && !options.disciplinesToHide.includes(origDiscName)
        })
        .each(function () {
            let rowId = $(this).attr('data-id');
            if (rowId && rowId.length) {
                options.customTable.push(rowId);
            }
        });
}

function removeElement(array, element) {
    const dataIdx = array.indexOf(element);
    if (dataIdx >= 0) {
        array.splice(dataIdx, 1);
    }
}

function structureHTML() {
    'use strict';

    let html = $('center').html();
    $('center').html('<div id="tables-wrapper"></div>');
    let wrapper = $('#tables-wrapper');
    wrapper.html(html);

    wrapper
        .find('h1:first')
        .insertBefore(wrapper)
        .attr('id', 'title-heading');

    wrapper.find('table').each(function () {
        let columns = [];
        let grp = $(this).prev('h1').text().replace('Grupa ', '');

        $(this).addClass('table-group-' + grp);

        $(this).find('tr:first th').each(function () {
            columns.push($(this).text().replace(/\s/g, '-').toLowerCase());
        });

        for (let i = 0; i < columns.length; ++i) {
            $(this).find('td:nth-child(' + (i + 1) + ')').addClass('column-' + columns[i]);
        }
    });

    $('.column-' + disciplineColumn).each(function () {
        $(this).attr('data-original', $(this).text());
    });

    wrapper.find("tr:contains('Curs')").addClass("curs-tr");
    wrapper.find("tr:contains('Seminar')").addClass("seminar-tr");
    wrapper.find("tr:contains('Laborator')").addClass("labor-tr");

    wrapper.find('tr:has(td)').each(setRowId);

    defaultHTML = wrapper.html();
}

function setRowId() {
    let row = $(this);

    columns = row.getColumns();

    let day = columns.day.text();
    let hour = columns.hour.text();
    let freq = columns.frequency.text();
    let grp = columns.group.text();
    let disc = columns.discipline.attr('data-original');

    let id = '';
    let dayi = -1;

    switch (day.substr(0, 2)) {
        case 'Lu':
            dayi = 0;
            break;
        case 'Ma':
            dayi = 1;
            break;
        case 'Mi':
            dayi = 2;
            break;
        case 'Jo':
            dayi = 3;
            break;
        case 'Vi':
            dayi = 4;
            break;
    }

    id += dayi;

    let hr = hour.split('-')[0];
    if (hr.length < 2) {
        hr = '0' + hr;
    }
    id += hr;
    if (/sapt\.\s[0-9]/.test(freq)) {
        id += 's' + freq.replace('sapt. ', '');
    }
    else {
        id += 's0';
    }
    id += grp;
    id += disc.replace(/\s/g, '-');

    idList.add(id);
    row.attr("data-id", id);
}

/**
    * @returns {day, hour, frequency, room, group,
    * 
    *  type, discipline, teacher}
    */

function checkSwitch() {
    let sel = $('#custom-selected-table').find('.ui-selected');
    let rem = $('#custom-list-table').find('.ui-selected');

    let selColumns = sel.getColumns();
    let remColumns = rem.getColumns();

    if (sel.length === 1) {
        highlightMatching();
    }
    else {
        clearHighlights();
    }

    if (sel.length === 1 && rem.length === 1 &&
        selColumns.discipline.text() === remColumns.discipline.text() &&
        selColumns.type.text() === remColumns.type.text()) {
        $('#custom-switch').button('enable');
    }
    else {
        $('#custom-switch').button('disable');
    }
}

function highlightMatching() {
    let sel = $('#custom-selected-table')
        .find('.ui-selected').eq(0).getColumns();

    $('#custom-list-table').addClass('matched-table');

    $('#custom-list-table tr').each(function () {
        let cols = $(this).getColumns();
        if (sel.discipline.text() === cols.discipline.text() &&
            sel.type.text() === cols.type.text()) {
            $(this).addClass('matching');
        }
    });
}

function clearHighlights() {
    $('#custom-list-table').find('.matching').removeClass('matching');
    $('#custom-list-table').removeClass('matched-table');
}

function listifiy(arr) {
    html = '<ul>';
    for (let i = 0; i < arr.length; ++i) {
        html += '<li>' + arr[i] + '</li>';
    }
    return html + '</ul>';
}

function getDisciplinesList() {
    let discSet = new Set();
    $('.column-' + disciplineColumn).each(function () {
        discSet.add($(this).text());
    });

    disciplines = [...discSet];
}

function rememberScroll() {
    customListScroll = $('#custom-list-wrapper .scroller').scrollTop();
}

function findDiscipline() {
    let disc = $('#finder-select').val();
    let type = $('#finder-type').val();

    let result = $('#finder-result');
    result.html('');

    $('#tables-wrapper tr').each(function () {
        if ($(this).find('.column-tipul').text().toLowerCase() === type &&
            ($(this).find('.column-disciplina').text() === disc ||
                $(this).find('.column-disciplina').attr('data-original') === disc)) {

            cloneStripped($(this)).appendTo(result);
        }
    });

    let rows = $('#finder-result tr').get();
    rows.sort(function (a, b) {
        a = $(a);
        b = $(b);

        let aDay = a.find('.column-ziua').text();
        let bDay = b.find('.column-ziua').text();
        if (aDay === bDay) {
            let aHr = a.find('.column-orele').text().split('-')[0];
            aHr = parseInt(aHr, 10);
            let bHr = b.find('.column-orele').text().split('-')[0];
            bHr = parseInt(bHr, 10);

            return aHr - bHr;
        }
        else {
            aDay = days.indexOf(aDay);
            bDay = days.indexOf(bDay);
            return aDay - bDay;
        }
    });


    rows = rows.filter(function (value, index, self) {
        if (index + 1 < self.length) {
            let tr = $(value);
            let nextTr = $(self[index + 1]);
            return !equalTrs(tr, nextTr);
        }
        return true;
    });
    $('#finder-result').html(rows);

    addDayLines($('#finder-result'));
}

function equalTrs(a, b) {
    let aDay = a.find('.column-ziua').text();
    let aHr = a.find('.column-orele').text();
    let aFreq = a.find('.column-frecventa').text();
    let aSala = a.find('.column-sala').text();
    let aForm = a.find('.column-formatia').text();
    let aType = a.find('.column-tipul').text();
    let aDisc = a.find('.column-disciplina').text();
    let aTeacher = a.find('.column-cadrul-didactic').text();

    let bDay = b.find('.column-ziua').text();
    let bHr = b.find('.column-orele').text();
    let bFreq = b.find('.column-frecventa').text();
    let bSala = b.find('.column-sala').text();
    let bForm = b.find('.column-formatia').text();
    let bType = b.find('.column-tipul').text();
    let bDisc = b.find('.column-disciplina').text();
    let bTeacher = b.find('.column-cadrul-didactic').text();

    return aDay === bDay && aHr === bHr && aFreq === bFreq && aSala === bSala &&
        aForm === bForm && aType === bType && aDisc === bDisc && aTeacher === bTeacher;
}

function updateFinderHourSelect() {
    let hours = new Set();
    $('tr:contains(' + $('#finder-day').val() + ')').each(function () {
        hours.add($(this).find('.column-orele').text());
    });
    hours = [...hours].sort(function (a, b) {
        a = parseInt(a.split('-')[0], 10);
        b = parseInt(b.split('-')[0], 10);
        return a - b;
    });

    $('#finder-hour').html('');
    for (let i = 0; i < hours.length; ++i) {
        $('#finder-hour').append('<option value="' + hours[i] + '">' + hours[i] + '</option>');
    }
    $('#finder-hour').selectmenu('refresh');

    findDisciplineByTime();
}

function findDisciplineByTime() {
    let day = $('#finder-day').val();
    let hour = $('#finder-hour').val();
    let week = $('#finder-sapt').val();

    let result = $('#finder-time-result');
    result.html('');

    $('#tables-wrapper tr').each(function () {
        let weekString = $(this).find('.column-frecventa').text();
        if ($(this).find('.column-ziua').text().toLowerCase() === day.toLowerCase() &&
            $(this).find('.column-orele').text() === hour &&
            (week == "0" || weekString.replace(/\s/g, '') === '' || weekString === 'sapt. ' + week)) {
            $(this)
                .clone()
                .removeClass('meh-tr')
                .removeClass('other-week-tr')
                .removeClass('nope-tr')
                .removeClass('last-of-day-tr')
                .appendTo(result);
        }
    });

    let rows = $('#finder-time-result tr').get();
    rows.sort(function (a, b) {
        a = $(a);
        b = $(b);

        let aGroup = a.find('.column-formatia').text();
        let bGroup = b.find('.column-formatia').text();
        if (aGroup === bGroup) {
            let aDisc = a.find('.column-disciplina').text();
            let bDisc = b.find('.column-disciplina').text();

            return compareStrings(aDisc, bDisc);
        }
        else {
            return compareStrings(aGroup, bGroup);
        }
    });
    rows = rows.filter(function (value, index, self) {
        if (index + 1 < self.length) {
            let tr = $(value);
            let nextTr = $(self[index + 1]);
            return !equalTrs(tr, nextTr);
        }
        return true;
    });
    $('#finder-time-result').html(rows);
}

function compareStrings(a, b) {
    if (a < b) {
        return -1;
    }
    else if (a > b) {
        return 1;
    }
    return 0;
}

function performChange() {
    saveOptions();
    updateSettingsUi();
    runFixer();
}

function updateSettingsUi() {
    let week = getWeekNo();
    if (week === 0) {
        week = 2;
    }
    $('#week-span').text("Week " + week + ": ");

    $('#offset-week')
        .prop('checked', options.weekOffset === 1)
        .checkboxradio('refresh');

    if ($('#tables-wrapper .column-frecventa:contains(sapt)').length === 0) {
        $('#week-wrapper').hide();
    }
    else {
        $('#week-wrapper').show();
    }



    if ($('#group-select:contains(' + options.group + ')').length) {
        $('#group-select')
            .val(options.group)
            .selectmenu('refresh');
    }

    if ($('#tables-wrapper table').length === 1) {
        $('#group-wrapper').hide();
    }
    else {
        $('#group-wrapper').show();
    }

    $('#half-group-select')
        .val(options.halfGroup)
        .selectmenu('refresh');

    $('#hidden-fade-select')
        .val(options.fadeHidden ? 'true' : 'false')
        .selectmenu('refresh');

    $('#other-halfgr-fade-select')
        .val(options.fadeHalfGroup ? 'true' : 'false')
        .selectmenu('refresh');

    $('#other-week-fade-select')
        .val(options.fadeWeek ? 'true' : 'false')
        .selectmenu('refresh');

    if ($('#tables-wrapper .column-formatia:contains(/1)').length === 0 &&
        $('#tables-wrapper .column-formatia:contains(/2)').length === 0) {
        $('#half-group-wrapper').hide();
    }
    else {
        $('#half-group-wrapper').show();
    }

    let hiderList = '<table>';
    let renamerList = '<table>';
    let finderOptions = '';

    let lDisciplines = disciplines.map(discipline => {
        let discName = discipline;
        if (options.disciplinesToReplace.hasOwnProperty(discipline) &&
            options.disciplinesToReplace[discipline]) {

            discName = options.disciplinesToReplace[discipline];
        }

        return {
            discipline,
            discName,
            hidden: isHidden(discipline)
        }
    });

    hiderList += lDisciplines.map(d =>
        `<tr>
            <td class="discipline-name" data-discipline="${d.discipline}">${d.discName}</td>
            <td class="check-column">
                <input class="hide-check" type="checkbox" ${(d.hidden ? 'checked' : '')}/>
            </td>
        </tr>`
    ).join('');

    sortedDisciplines = lDisciplines.sort((a, b) => {
        if (a.hidden == b.hidden) {
            a.discipline.localeCompare(b.discipline);
        } else {
            if (a.hidden) {
                return 1;
            }
            return -1;
        }
    });

    renamerList += sortedDisciplines.map(d => `
        <tr>
            <td class="discipline-name" >${d.discipline}</td>
            <td class="input-column">
                <input class="rename-to" type="text" value="${d.discipline == d.discName ? '' : d.discName}"/>
            </td>
        </tr>
    `).join('');

    finderOptions += sortedDisciplines.map(d => `
        <option value="${d.discipline}">${d.discName}</option>
    `).join('');


    hiderList += '</table>';
    renamerList += '</table>';

    $('#hider-list-wrapper').html(hiderList);
    $('#renamer-list-wrapper').html(renamerList);
    $('#finder-select').html(finderOptions);
    $('#finder-select').selectmenu('refresh');

    if (options.customTable.length == 0) {
        $('#custom-empty-view').show();
        $('#custom-work-view').hide();
    } else {
        $('#custom-empty-view').hide();
        $('#custom-work-view').show();
        updateCustomWorkView();
    }
}

function updateCustomWorkView() {
    let chosen = $('#custom-selected-table');
    chosen.html('');

    let remList = $('#custom-list-table');
    remList.html('');


    let remaining = new Set(idList);

    for (let i = 0; i < options.customTable.length; ++i) {
        let id = options.customTable[i];

        remaining.delete(id);

        let row = cloneStripped($('tr[data-id="' + id + '"]').eq(0));
        shortenInfo(row);
        chosen.append(row);
    }
    chosen.selectable('refresh');
    addDayLinesCustom(chosen);

    remaining = Array.from(remaining);
    sortIds(remaining);

    for (let i = 0; i < remaining.length; ++i) {
        let id = remaining[i];

        let row = cloneStripped($('tr[data-id="' + id + '"]').eq(0));
        shortenInfo(row);
        remList.append(row);
    }
    remList.selectable('refresh');
    addDayLinesCustom(remList);

    $('#custom-add, #custom-remove, #custom-switch').button('disable');

    $('#custom-list-wrapper .scroller').scrollTop(customListScroll);
}

function sortIds(array) {
    array.sort();
}

function cloneStripped(element) {
    return element
        .clone()
        .removeClass('meh-tr')
        .removeClass('hide-tr')
        .removeClass('other-week-tr')
        .removeClass('nope-tr')
        .removeClass('last-of-day-tr');
}

function shortenInfo(row) {
    if (row.length == 0) {
        return;
    }
    let columns = row.getColumns();
    /*
     * @returns {day, 
     * hour, 
     * frequency, room, group, type, discipline, teacher}
     */

    let day = columns.day.text();
    columns.day.text(day.substr(0, 2)).attr('title', day);

    let hour = columns.hour.text();
    columns.hour.text(hour.split('-')[0]).attr('title', hour);

    let sapt = columns.frequency.text();
    columns.frequency.text(sapt.replace('sapt. ', 's')).attr('title', sapt);

    let room = columns.room.text();
    columns.room.text(room.substr(0, 4) + ((room.length > 4) ? '.' : '')).attr('title', room);

    let type = columns.type.text();
    columns.type.text(type.substr(0, 1)).attr('title', type);

    let disc = columns.discipline.text();
    columns.discipline.attr('title', disc);

    let teacher = columns.teacher.text();
    let teach = teacher.split(' ');
    let tStr = teach[1];

    let tt = tStr.split('-');
    tStr = tt[0].substr(0, 1) + tt[0].substr(1).toLowerCase();

    for (let i = 1; i < tt.length; ++i) {
        tStr += '-' + tt[i].substr(0, 1) + '.';
    }

    for (let i = 2; i < teach.length; ++i) {
        tStr += ' ' + teach[i].substr(0, 1) + '.';
    }
    columns.teacher.text(tStr).attr('title', teacher);
}

function runFixer() {
    'use strict';

    $('#tables-wrapper').html(defaultHTML);

    let myGroupHeading = $("h1:contains('" + options.group + "')");
    if ($('#tables-wrapper table').length === 1) {
        myGroupHeading = $('#tables-wrapper h1');
    }
    let myGroupTable = myGroupHeading.next("table");

    myGroupHeading.prependTo("#tables-wrapper").css("margin", "30px");
    myGroupTable.insertAfter(myGroupHeading).css("margin-bottom", "147px");

    let hasCustom = options.customTable.length > 0;

    if (hasCustom) {
        if (!insertCustomTable()) {
            options.customTable = [];
            customError = true;
            performChange();
            return;
        }
        else {
            customError = false;
        }
    }
    if (customError) {
        $('#tables-wrapper').prepend('<hgroup><h1 class="error">Some custom items were not found</h1><h2 class="error">The timetable might have changed. The custom table has been cleared.</h2></hgroup>');
    }
    let customTable = $('.table-custom');

    for (let discipline in options.disciplinesToReplace) {
        if (options.disciplinesToReplace.hasOwnProperty(discipline) && options.disciplinesToReplace[discipline]) {
            let a = $('.column-' + disciplineColumn + ' a:contains(' + discipline + ')');
            a.each(function () {
                let text = $(this).text();
                $(this)
                    .text(text.replace(discipline, options.disciplinesToReplace[discipline]));
            });
        }
    }

    fixTable(myGroupTable);
    if (hasCustom) {
        fixTable(customTable, true);
    }


    $("table").each(function () {
        addDayLines($(this));
    });
}

function fixTable(table, custom = false) {

    for (let i = 0; i < options.disciplinesToHide.length; ++i) {
        let discipline = options.disciplinesToHide[i];
        let row = table
            .find('.column-' + disciplineColumn + '[data-original="' + discipline + '"]')
            .closest('tr');

        if (options.fadeHidden) {
            row.addClass("meh-tr")
                .addClass("nope-tr");
        }
        else {
            row.addClass("hide-tr");
        }
    }

    if (!custom) {
        let otherHgTrs = table.find("tr:contains('" + options.group + "/" + (3 - options.halfGroup) + "')");

        if (options.fadeHalfGroup) {
            otherHgTrs.addClass("meh-tr");
        }
        else {
            otherHgTrs.addClass("hide-tr");
        }
    }

    let weekNo = getWeekNo();
    let otherWeekNo = weekNo + 1;

    let saptString = "sapt. " + otherWeekNo;

    let otherWeekTrs = table.find("tr:contains(" + saptString + ")");

    if (options.fadeWeek) {
        otherWeekTrs.addClass("other-week-tr");
    }
    else {
        otherWeekTrs.addClass("hide-tr");
    }
}

function insertCustomTable() {
    let table = $('<table class="table-custom">');
    table.append($('#tables-wrapper table').eq(0).find('tr:has(th)').clone());

    for (let i = 0; i < options.customTable.length; ++i) {
        let id = options.customTable[i];

        let row = $('tr[data-id="' + id + '"]');
        if (row.length > 0) {
            row = row.eq(0);
            table.append(row.clone());
        }
        else {
            console.error(id + ' NOT FOUND');
            return false;
        }
    }

    let customHeading = $('<h1>Custom</h1>');

    customHeading.prependTo("#tables-wrapper").css("margin", "30px");
    table.insertAfter(customHeading);

    return true;
}

function addDayLines(table) {
    let lastVisible = table.find('tr:visible:last');

    for (let i in days) {
        let dayString = days[i];
        let last = table.find("tr:visible:contains(" + dayString + ")");

        last = $(last[last.length - 1]);
        if (!last.is(lastVisible)) {
            last.addClass("last-of-day-tr");
        }
    }
}

function addDayLinesCustom(table) {
    for (let i in days) {
        let dayString = days[i];
        let last = table.find("td[title='" + dayString + "']").closest('tr');

        last = $(last[last.length - 1]);
        if (!last.is(':last-child')) {
            last.addClass("last-of-day-tr");
        }
    }
}

function getWeekNo() {
    let weekInYear = new Date().getWeek();
    let firstWeek = new Date(new Date().getYear(), 0, 1).getWeek();

    return (weekInYear - firstWeek + 1 + options.weekOffset) % 2;
}

function setupFunctions() {
    'use strict';

    Date.prototype.getWeek = function () {
        var onejan = new Date(this.getFullYear(), 0, 1);
        return Math.ceil((((this - onejan) / 86400000) + onejan.getDay() + 1) / 7);
    };

    /**
     * @returns {day, hour, frequency, room, group, type, discipline, teacher}
     */
    jQuery.fn.getColumns = function () {
        if (this.is('tr')) {
            return {
                day: this.find('.column-ziua'),
                hour: this.find('.column-orele'),
                frequency: this.find('.column-frecventa'),
                room: this.find('.column-sala'),
                group: this.find('.column-formatia'),
                type: this.find('.column-tipul'),
                discipline: this.find('.column-disciplina'),
                teacher: this.find('.column-cadrul-didactic')
            };
        }
        else {
            return {};
        }
    };

    setDialogsPositionFixed();
    setFavicon();
}

function setDialogsPositionFixed() {
    $.ui.dialog.prototype._oldinit = $.ui.dialog.prototype._init;
    $.ui.dialog.prototype._init = function () {

        $(this.element).parent().css("position", "fixed");
        $(this.element).parent().addClass("no-select");
        $(this.element).parent().css("margin", "5px"); //limit 5px to the top
        $(this.element).dialog("option", {
            resizeStart: function (event, ui) {
                $(event.target).parent().css("position", "fixed");
                return true;
            },
            resizeStop: function (event, ui) {
                $(event.target).parent().css("position", "fixed");
                return true;
            }
        });
        this._oldinit();
    };
}

function setFavicon() {
    const href = 'https://i.imgur.com/s6FbuBK.png';
    $('head').append(`<link rel="shortcut icon" type="image/png" href="${href}"/>`);
}

function injectSettingHTML() {
    'use strict';

    let groupOptions = '';
    $('#tables-wrapper h1').each(function () {
        try {
            let group = parseInt($(this).text().replace('Grupa ', ''), 10);
            let sel = '';
            if (group == options.group) {
                sel = 'selected';
            }
            groupOptions += `<option value="${group}" ${sel}>${group}</option>`;
        }
        catch (e) {
            console.log(e);
        }
    });

    const halfGroupOptions = [[1, 1], [2, 2], [0, "Don't hide"]].map(([val, txt]) =>
        `<option value="${val}" ${val === options.halfGroup ? "selected" : ""}>${txt}</option>`
    );

    let html =
        `<div id="settings-container">
            <button id="toggle-settings">Settings</button>
            <div id="settings-wrapper">
            <div id="week-wrapper" class="option-wrapper">
                <span id="week-span"></span>
                <label id="offset-label" for="offset-week">Offset week by 1</label>
                <input type="checkbox" id="offset-week" />
            </div>
            <div id="group-wrapper" class="option-wrapper">
                <label for="group-select">Group: </label>
                <select id="group-select">
                    <option value="0"></option>
                    ${groupOptions}
                </select>
            </div>
            <div id="half-group-wrapper" class="option-wrapper">
                <label for="half-group-select">Halfgroup: </label>
                <select id="half-group-select">
                    ${halfGroupOptions}
                </select>
            </div>
            <div class="option-wrapper">
                <button id="visibility-button">Edit Visibility</button>
            </div>
            <br>
            <div id="buttons-wrapper" class="option-wrapper">
                <button id="hider-button">Hidden disciplines</button>
                <button id="renamer-button">Rename disciplines</button>
                <button id="finder-button">Find disciplines</button>
                <button id="reset-button">Reset Settings</button>
                <button id="print-button">Print this page</button>
            </div>
            <br>
            <div id="buttons-wrapper-2" class="option-wrapper">
                <button id="import-button">Import settings</button>
                <button id="export-button">Export settings</button>
                <a id="download-helper" class="ui-helper-hidden"></a>
                <button id="customize-button" class="beta-button">Edit custom</button>
            </div><br>`;
    if (DEVELOPEMENT) {
        html += '<div id="urgent-todo" class="todo-wrapper">' + listifiy(URGENT_TODO) + '</div>';
        html += '<div id="todo" class="todo-wrapper">' + listifiy(TODO) + '</div>';
    }

    html += `
        <div id="visibility-dialog">
            <div id="hidden-fade-wrapper" class="row no-gutters">
                <label for="hidden-fade-select" class="col-7">Hidden items: </label>
                <div class="col-5">
                    <select id="hidden-fade-select">
                        <option value="true">Faded</option>
                        <option value="false">Hidden</option>
                    </select>
                </div>
            </div>
            <div id="other-halfgr-fade-wrapper" class="row no-gutters">
                <label for="other-halfgr-fade-select" class="col-7">Other halfgroup's items: </label>
                <div class="col-5">
                    <select id="other-halfgr-fade-select">
                        <option value="true">Faded</option>
                        <option value="false">Hidden</option>
                    </select>   
                </div>
            </div>
            <div class="row no-gutters">
                <label for="other-week-fade-select" class="col-7">Other week items: </label>
                <div class="col-5">
                    <select id="other-week-fade-select">
                        <option value="true">Faded</option>
                        <option value="false">Hidden</option>
                    </select>   
                </div>
            </div>
        </div>`;

    html += `
        <div id="hider-dialog">
            <span>Select the disciplines you <b>don\'t</b> want highlighted:</span>
            <div id="hider-list-wrapper"></div>
            <div style="float: right;">
                <button id="hider-save">Save</button>
                <button id="hider-close">Cancel</button>
            </div>
        </div>`;

    html += `
        <div id="renamer-dialog">
            <span>Enter the text you want each discipline to be renamed to. <br> Leave the field blank if you don\'t want a discipline\'s name replaced.</span>
            <div id="renamer-list-wrapper"></div>
            <div style="float: right;">
                <button id="renamer-save">Save</button>
                <button id="renamer-close">Cancel</button>
            </div>
        </div>`;

    html += `
        <div id="finder-dialog">
            <div id="tabs">
                <ul>
                    <li><a href="#tabs-1">By Discipline</a></li>
                    <li><a href="#tabs-2">By Time</a></li>
                </ul>
                <div id="tabs-1">
                    <div class="finder-options-wrapper">
                        <label for="finder-select">Discipline to search for: </label>
                        <select id="finder-select"></select>
                        
                        <label for="finder-type"> Type: </label>
                        <select id="finder-type">
                            <option value="curs">Course</option>
                            <option value="seminar">Seminar</option>
                            <option value="laborator">Laboratory</option>
                        </select>
                    </div>
                    <table id="finder-result"></table>
                </div>
                <div id="tabs-2">
                    <div class="finder-options-wrapper">
                        <label for="finder-day">Day: </label><select id="finder-day"></select>
                        <label for="finder-hour"> Hour: </label>
                        <select id="finder-hour"></select>
                        <label for="finder-sapt"> Week: </label>
                        <select id="finder-sapt">
                            <option value="0">Both</option>
                            <option value="1">1</option>
                            <option value="2">2</option>
                        </select>
                    </div>
                    <table id="finder-time-result"></table>
                </div>
            </div>
        </div>`;

    html += `
        <div id="import-dialog">
            <span id="import-status"></span>
            <label for="import-file">Choose settings file:</label>
            <input type="file" id="import-file" accept=".json,text/json"/>
            <div style="float: right; margin-top: 20px;">
                <button id="do-import">Import</button>
            </div>
        </div>`;

    html += `
        <div id="customize-dialog">
            <div id="custom-empty-view">
                <label for="custom-group-select">List empty. Start with group: </label>
                <select id="custom-group-select">
                    ${groupOptions}
                </select>
                <label for="custom-half-group-select">Halfgroup: </label>
                <select id="custom-half-group-select">
                    ${halfGroupOptions}
                </select>
                <hr>
                <button id="custom-start">Go</button>
            </div>
            <div id="custom-work-view">
                <div id="custom-selected-wrapper">
                    <h3>Your items</h3>
                    <div class="scroller">
                        <table id="custom-selected-table"></table>
                    </div>
                </div>
                <div id="custom-controls">
                    <button id="custom-add" title="Add">&lt;&lt;</button>
                    <hr>
                    <button id="custom-remove" title="Remove">&gt;&gt;</button>
                    <hr>
                    <button id="custom-switch" title="Switch">&lt;&gt;</button>
                </div>
                <div id="custom-list-wrapper">
                    <h3>Other items</h3>
                    <div class="scroller">
                        <table id="custom-list-table"></table>
                    </div>
                </div>
                <hr>
                <button id="custom-clear" style="margin-left: 42%;">Clear All</button>
            </div>
        </div>`;
    html += '</div></div>';
    $(html).insertAfter('#title-heading');

    if (options.settingsHidden) {
        $('#settings-wrapper').hide();
    }

    for (let i = 0; i < days.length; ++i) {
        $('#finder-day').append('<option value="' + days[i] + '">' + days[i] + '</option>');
    }

    $('#offset-week').checkboxradio({
        icon: false
    });
    $('button').button();
    $('select').selectmenu();

    $('#toggle-settings').button({
        icon: 'ui-icon-gear',
        text: false
    });
    $('#visibility-button').button({
        icon: 'ui-icon-eye'
    });
    $('#hider-button').button({
        icon: 'ui-icon-hide'
    });
    $('#renamer-button').button({
        icon: 'ui-icon-rename'
    });
    $('#finder-button').button({
        icon: 'ui-icon-find'
    });
    $('#reset-button').button({
        icon: 'ui-icon-reset'
    });
    $('#print-button').button({
        icon: 'ui-icon-printer'
    });
    $('#import-button').button({
        icon: 'ui-icon-import'
    });
    $('#export-button').button({
        icon: 'ui-icon-export'
    });
    $('#customize-button').button({
        icon: 'ui-icon-customize'
    });

    $('#tabs').tabs();

    $('#visibility-dialog').dialog({
        modal: true,
        width: 320,
        title: 'Edit visibility options',
        autoOpen: false
    });

    $('#hider-dialog').dialog({
        modal: true,
        width: 320,
        title: 'Hide disciplines',
        autoOpen: false
    });

    $('#renamer-dialog').dialog({
        modal: true,
        width: 420,
        title: 'Rename disciplines',
        autoOpen: false
    });

    $('#finder-dialog').dialog({
        modal: true,
        width: 620,
        height: 'auto',
        title: 'Find disciplines',
        autoOpen: false
    });
    updateFinderHourSelect();

    $('#import-dialog').dialog({
        modal: true,
        width: 320,
        height: 'auto',
        title: 'Import settings',
        autoOpen: false
    });
    $('#do-import').button("option", "disabled", true);

    $('#customize-dialog').dialog({
        modal: true,
        minWidth: 820,
        height: 'auto',
        title: 'Edit your custom timetable',
        autoOpen: false
    });

    $('#custom-selected-table').selectable({
        filter: 'tr',
        selected: function () {
            $('#custom-remove').button('enable');

            checkSwitch();
        },
        unselected: function () {
            if ($(this).find('.ui-selected').length == 0) {
                $('#custom-remove').button('disable');
            }
            checkSwitch();
        }
    });
    $('#custom-list-table').selectable({
        filter: 'tr',
        selected: function () {
            $('#custom-add').button('enable');

            checkSwitch();
        },
        unselected: function () {
            if ($(this).find('.ui-selected').length == 0) {
                $('#custom-add').button('disable');
            }
            checkSwitch();
        }
    });
}


function setEventHandlers() {
    $('body')
        .on('click', '#toggle-settings', function () {
            $('#settings-wrapper').slideToggle();
            options.settingsHidden = !options.settingsHidden;
            saveOptions();
        })
        .on('change', '#offset-week', function () {
            options.weekOffset = ($(this).prop('checked') ? 1 : 0);
            performChange();
        })
        .on('selectmenuchange', '#group-select', function () {
            options.group = parseInt($(this).val(), 10);
            performChange();
        })
        .on('selectmenuchange', '#half-group-select', function () {
            options.halfGroup = parseInt($(this).val(), 10);
            performChange();
        })
        .on('selectmenuchange', '#hidden-fade-select', function () {
            let value = $(this).val() === 'true';
            options.fadeHidden = value;
            performChange();
        })
        .on('selectmenuchange', '#other-halfgr-fade-select', function () {
            let value = $(this).val() === 'true';
            options.fadeHalfGroup = value;
            performChange();
        })
        .on('selectmenuchange', '#other-week-fade-select', function () {
            let value = $(this).val() === 'true';
            options.fadeWeek = value;
            performChange();
        })
        .on('click', '#visibility-button', function () {
            $('#visibility-dialog')
                .dialog('option', 'position', {
                    my: 'center top',
                    at: 'center top+50',
                    of: window
                })
                .dialog('open');
        })
        .on('click', '#hider-button', function () {
            $('#hider-dialog')
                .dialog('option', 'position', {
                    my: 'center top',
                    at: 'center top+50',
                    of: window
                })
                .dialog('open');
        })
        .on('click', '#renamer-button', function () {
            $('#renamer-dialog')
                .dialog('option', 'position', {
                    my: 'center top',
                    at: 'center top+50',
                    of: window
                })
                .dialog('open');
        })
        .on('click', '#reset-button', function () {
            if (confirm('Are you sure you want to reset?')) {
                options = getDefaultOptions();
                performChange();
            }
        })
        .on('click', '#import-button', function () {
            $('#import-dialog')
                .dialog('option', 'position', {
                    my: 'center top',
                    at: 'center top+50',
                    of: window
                })
                .dialog('open');
        })
        .on('click', '#export-button', exportOptions)
        .on('click', '#print-button', window.print)
        .on('click', '#hider-save', function () {
            let hides = [];
            $('#hider-list-wrapper tr').each(function () {
                if ($(this).find('.hide-check').is(':checked')) {
                    hides.push($(this).find('.discipline-name').attr('data-discipline'));
                }
            });

            $('#hider-dialog').dialog('close');
            options.disciplinesToHide = hides;
            performChange();
        })
        .on('click', '#hider-close', function () {
            $('#hider-dialog').dialog('close');
        })
        .on('click', '#renamer-save', function () {
            let renames = {};
            $('#renamer-list-wrapper tr').each(function () {
                let renameTo = $(this).find('.rename-to').val();
                if (renameTo.length > 0) {
                    renames[$(this).find('.discipline-name').text()] = renameTo;
                }
            });

            options.disciplinesToReplace = renames;

            $('#renamer-dialog').dialog('close');
            performChange();
        })
        .on('click', '#renamer-close', function () {
            $('#renamer-dialog').dialog('close');
        })
        .on('click', '#finder-button', function () {
            $('#finder-dialog')
                .dialog('option', 'position', {
                    my: 'center top',
                    at: 'center top+50',
                    of: window
                })
                .dialog('open');
            findDiscipline();
        })
        .on('selectmenuchange', '#finder-dialog select', findDiscipline)
        .on('selectmenuchange', '#finder-day', updateFinderHourSelect)
        .on('selectmenuchange', '#finder-hour, #finder-sapt', findDisciplineByTime)

        .on('change', '#import-file', function () {
            let files = $(this)[0].files;
            $('#do-import').button("option", "disabled", files.length < 1);
        })
        .on('click', '#do-import', importOptions)
        .on('click', '#customize-button', function () {
            $('#customize-dialog')
                .dialog('option', 'position', {
                    my: 'center top',
                    at: 'center top+20',
                    of: window
                })
                .dialog('open');
        })
        .on('click', '#custom-start', function () {
            initCustomTable();
            performChange();
        })
        .on('click', '#custom-clear', function () {
            if (confirm("Are you sure you want to clear everything?")) {
                options.customTable = [];
                performChange();
            }
        })
        .on('click', '#custom-add', function () {
            $('#custom-list-table .ui-selected').each(function () {
                options.customTable.push($(this).attr('data-id'));
            });
            sortIds(options.customTable);
            rememberScroll();
            performChange();
        })
        .on('click', '#custom-remove', function () {
            $('#custom-selected-table .ui-selected').each(function () {
                removeElement(options.customTable, $(this).attr('data-id'));
            });
            rememberScroll();
            performChange();
        })
        .on('click', '#custom-switch', function () {
            $('#custom-list-table .ui-selected').each(function () {
                options.customTable.push($(this).attr('data-id'));
            });
            $('#custom-selected-table .ui-selected').each(function () {
                removeElement(options.customTable, $(this).attr('data-id'));
            });
            sortIds(options.customTable);
            rememberScroll();
            performChange();
            clearHighlights();
        });
}

function getDefaultOptions() {
    'use strict';

    return {
        weekOffset: 0,
        group: 0,
        halfGroup: 0,
        disciplinesToReplace: {},
        disciplinesToHide: [],
        customTable: [],
        fadeHidden: true,
        fadeHalfGroup: true,
        fadeWeek: true,
        settingsHidden: false
    };
}

function getBaseOptionKey() {
    return "options";
}

function getOptionKey() {
    parts = location.pathname.match('/([0-9]+-[0-9]+)/.*/([^.]+)');
    try {
        return getBaseOptionKey() + "_" + parts[1] + "_" + parts[2];
    }
    catch (err) {
        return getBaseOptionKey();
    }
}

function loadOptions() {
    'use strict';

    let defOptions = getDefaultOptions();
    let savedOptions = GM_getValue(getOptionKey(), GM_getValue(getBaseOptionKey(), "{}"));
    try {
        savedOptions = JSON.parse(savedOptions);
    }
    catch (err) {
        savedOptions = {};
    }

    options = $.extend(defOptions, savedOptions);
}


function exportOptions() {
    let dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(options));
    let downloadHelper = $('#download-helper')

    downloadHelper.attr('href', dataStr);
    downloadHelper.attr('download', 'fixer-options.json');
    downloadHelper[0].click();
}

function importOptions() {
    let files = $('#import-file')[0].files;
    if (files.length > 0) {
        let file = files[0];
        let reader = new FileReader();

        reader.onload = function (e) {
            parseOptionFile(reader.result);
        }

        reader.readAsText(file);
    }
}

function parseOptionFile(text) {
    try {
        options = JSON.parse(text);
        performChange();
        $('#import-dialog').dialog('close');
        $('#import-status').text("");
    }
    catch (err) {
        $('#import-status').text("Import failed. Invalid JSON.");
        console.error(err);
    }
}

function saveOptions() {
    GM_setValue(getOptionKey(), JSON.stringify(options));
}


function isHidden(disc) {
    return options.disciplinesToHide.includes(disc);
}