// ==UserScript==
// @name         SUSTech tis cheater
// @namespace    https://blog.vollate.top/
// @version      1.3.3
// @description  SUSTech 可能会变质,但绝对不会倒闭
// @author       Vollate
// @match        https://tis.sustech.edu.cn/*
// @icon         https://www.sustech.edu.cn/static/images/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @require      https://s4.zstatic.net/ajax/libs/jquery/3.7.1/jquery.min.js
// @run-at       document-end
// ==/UserScript==

(function ($) {
    $(document).ready(function () {
        'use strict';
        const DEFAULT_INTERVAL = 1600;
        const language = navigator.language || navigator.userLanguage;
        const platform = navigator.platform;
        const ua = navigator.userAgent;

        let secChUa = "";
        let secChUaMobile = "";
        let secChUaPlatform = "";
        if (navigator.userAgentData) {
            secChUa = navigator.userAgentData.brands.map(b => `"${b.brand}";v="${b.version}"`).join(", ");
            secChUaMobile = navigator.userAgentData.mobile ? "?1" : "?0";
            secChUaPlatform = navigator.userAgentData.platform;
        } else {
            secChUa = `"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"`;
            secChUaMobile = "?0";
            secChUaPlatform = platform;
        }

        const FetchHeaders = {
            "Accept": "*/*",
            "Accept-Encoding": "gzip, deflate, br",
            "Accept-Language": language,
            "Connection": "keep-alive",
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
            "Host": "tis.sustech.edu.cn",
            "Origin": "https://tis.sustech.edu.cn",
            "Referer": "https://tis.sustech.edu.cn/Xsxk/query/1",
            "RoleCode": "01",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-origin",
            "X-Requested-With": "XMLHttpRequest",
            "sec-ch-ua": secChUa,
            "sec-ch-ua-mobile": secChUaMobile,
            "sec-ch-ua-platform": secChUaPlatform
        };

        const CSSManager = {
            modalOverlay: {
                position: 'fixed',
                top: '0',
                left: '0',
                width: '100%',
                height: '100%',
                backgroundColor: 'rgba(0, 0, 0, 0.6)',
                zIndex: '1000'
            }, modal: {
                position: 'fixed',
                top: '50%',
                left: '50%',
                transform: 'translate(-50%, -50%)',
                backgroundColor: '#f9f9f9',
                padding: '20px',
                borderRadius: '10px',
                boxShadow: '0 5px 15px rgba(0, 0, 0, 0.3)',
                zIndex: '9999',
                maxWidth: '400px',
                width: '100%'
            }, modalTitle: {
                margin: '0 0 20px 0',
                fontSize: '20px',
                textAlign: 'center',
                color: '#333',
                borderBottom: '2px solid #ddd',
                paddingBottom: '10px'
            }, courseLine: {
                display: 'flex',
                justifyContent: 'space-between',
                alignItems: 'center',
                marginBottom: '12px',
                padding: '10px',
                backgroundColor: '#fff',
                borderRadius: '5px',
                boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
                fontSize: '16px',
                color: '#555'
            }, deleteButton: {
                padding: '5px 10px',
                backgroundColor: '#e74c3c',
                color: '#fff',
                border: 'none',
                borderRadius: '5px',
                cursor: 'pointer',
                fontSize: '14px'
            }, closeButton: {
                display: 'block',
                margin: '20px auto 0',
                padding: '10px 20px',
                backgroundColor: '#3498db',
                color: '#fff',
                border: 'none',
                borderRadius: '5px',
                cursor: 'pointer',
                fontSize: '16px'
            }, confirmButton: {
                display: 'block',
                margin: '20px auto 0',
                padding: '10px 20px',
                backgroundColor: '#0cdf24',
                color: '#fff',
                border: 'none',
                borderRadius: '5px',
                cursor: 'pointer',
                fontSize: '16px'
            }, popupNotification: {
                position: 'fixed',
                top: '20px',
                right: '20px',
                backgroundColor: '#01af15',
                color: '#fff',
                padding: '10px 20px',
                borderRadius: '5px',
                boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
                zIndex: '1001',
                fontSize: '16px',
                opacity: '0.9',
                display: 'flex',
                alignItems: 'center'
            }, popupErrorNotification: {
                position: 'fixed',
                top: '20px',
                right: '20px',
                backgroundColor: '#af012a',
                color: '#fff',
                padding: '10px 20px',
                borderRadius: '5px',
                boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
                zIndex: '1001',
                fontSize: '16px',
                opacity: '0.9',
                display: 'flex',
                alignItems: 'center'
            }, messageContainer: {
                position: 'fixed',
                bottom: '20px',
                right: '20px',
                width: '300px',
                maxHeight: '200px',
                backgroundColor: '#f1f1f1',
                borderRadius: '8px',
                boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
                padding: '10px',
                zIndex: '1001',
                fontSize: '14px',
                color: '#333',
                display: 'flex',
                flexDirection: 'column'
            }, messageHeader: {
                display: 'flex',
                justifyContent: 'space-between',
                alignItems: 'center',
                borderBottom: '2px solid #ddd',
                paddingBottom: '8px',
                marginBottom: '10px',
                fontWeight: 'bold',
                position: 'sticky',
                top: '0',
                backgroundColor: '#f1f1f1',
                zIndex: '1001'
            }, timeInput: {
                width: '60px', padding: '5px', marginRight: '10px', fontSize: '16px'
            }
        };

        const originalXHROpen = XMLHttpRequest.prototype.open;
        const originalXHRSend = XMLHttpRequest.prototype.send;

        function showAsyncPopup(message, css_style, duration) {
            let $popup = $('<div>').css(css_style);

            $popup.text(message);

            $('body').append($popup);

            setTimeout(() => {
                $popup.fadeOut(300, function () {
                    $(this).remove();
                });
            }, duration);
        }

        function getGMAry_(key) {
            var str = GM_getValue(key);
            return str ? JSON.parse(str) : [];
        }

        function appendGMAry_(key, val) {
            var vals = getGMAry_(key);
            vals.push(val);
            GM_setValue(key, JSON.stringify(vals));
        }

        function clearGM_(key) {
            GM_setValue(key, undefined)
        }

        function enableAllButtons_() {
            var allButtons = document.querySelectorAll('button');
            var targetButtons = Array.prototype.filter.call(allButtons, function (button) {
                var span = button.querySelector('span');
                return span && span.textContent === '选课' && button.hasAttribute('disabled');
            });

            targetButtons.forEach(function (button) {
                button.removeAttribute('disabled');
            });
        }

        function xhrBodyToFetchBody(body) {
            if (body instanceof FormData) {
                return body;
            } else if (typeof body === 'string') {
                try {
                    const json = JSON.parse(body);
                    return JSON.stringify(json);
                } catch (e) {
                    return body;
                }
            } else if (body instanceof ArrayBuffer || body instanceof Blob) {
                return body;
            } else if (typeof body === 'object' && body !== null) {
                return JSON.stringify(body);
            }
            return null;
        }

        XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
            this._url = url;
            originalXHROpen.apply(this, arguments);
        };

        XMLHttpRequest.prototype.send = function (body) {
            if (this._url.endsWith('Xsxk/addGouwuche')) {
                this.addEventListener('readystatechange', function () {
                    if (this.readyState === 4) {
                        new Promise(resolve => setTimeout(resolve, 4000)).then(() => {
                            fetch(this._url, {
                                method: 'POST',
                                headers: FetchHeaders,
                                body: xhrBodyToFetchBody(body),
                                credentials: 'include'
                            }).then(res => res.json()).then(data => {
                                console.log(data);
                                let courseName = data.message.split('课程:')[1];
                                showAsyncPopup("Add course \"" + courseName + "\" to course list", CSSManager.popupNotification, 5000);
                                appendGMAry_('SelectedCourses', {name: courseName, body: xhrBodyToFetchBody(body)});
                            }).catch(err => {
                                console.error(err);
                                showAsyncPopup("Failed to add course to course list", CSSManager.popupErrorNotification, 5000);
                            })
                        });
                    }
                });
            }
            return originalXHRSend.apply(this, arguments);
        };

        function createMessageContainer() {
            let $container = $('<div id="messageContainer">').css(CSSManager.messageContainer);

            let $header = $('<div>').css(CSSManager.messageHeader);

            $header.text('Messages');

            let $closeButton = $('<span>').text('×').css({
                cursor: 'pointer', fontSize: '18px', fontWeight: 'bold', lineHeight: '1'
            });

            $closeButton.on('click', function () {
                $container.remove();
            });

            $header.append($closeButton);
            $container.append($header);
            $container.append($('<div id="messageContent">').css({
                flex: '1', overflowY: 'auto'
            }));

            $('body').append($container);
        }

        function appendMessage(courseName) {
            let $message = $('<div>').text('Success: ' + courseName).css({
                padding: '5px 0', borderBottom: '1px solid #ddd'
            });

            $('#messageContent').append($message);
            // $('#messageContent').scrollTop($('#messageContent')[0].scrollHeight);
        }


        async function startRace_(targetCourses, interval) {
            if (targetCourses.length === 0) {
                alert('Your course list is empty, you need to add at least one course');
                return;
            }
            showAsyncPopup('SUSTech tis cheater: start', CSSManager.popupNotification, 5000);

            if ($('#messageContainer').length === 0) {
                createMessageContainer();
            }

            while (GM_getValue('Start') && targetCourses.length > 0) {
                for (let i = 0; i < targetCourses.length; i++) {
                    fetch('https://tis.sustech.edu.cn/Xsxk/addGouwuche', {
                        method: 'POST', headers: FetchHeaders, body: targetCourses[i].body, credentials: 'include'
                    }).then(res => res.json())
                        .then(data => {
                            if (data.jg === '1') {
                                console.log('Success: ' + targetCourses[i].name);
                                appendMessage(targetCourses[i].name);
                                targetCourses.splice(i, 1);
                                updateCourses(targetCourses);
                            }
                        });
                    await new Promise(resolve => setTimeout(resolve, interval));
                }
            }

            showAsyncPopup('SUSTech tis cheater: stop', CSSManager.popupNotification, 5000);
        }

        function updateCourses(courses) {
            GM_setValue('SelectedCourses', JSON.stringify(courses));
        }

        let hadInit = false;

        async function initMenu() {
            if (hadInit) {
                return;
            }
            hadInit = true;
            GM_registerMenuCommand("Start", () => {
                GM_setValue('Start', '1');
                startRace_(getGMAry_('SelectedCourses'), GM_getValue('Interval', DEFAULT_INTERVAL));
            });

            GM_registerMenuCommand("Schedule Start", () => {
                const $modalOverlay = $('<div>')
                    .css(CSSManager.modalOverlay)
                    .attr('id', 'schedule-modal-overlay');

                const $modal = $('<div>')
                    .css(CSSManager.modal)
                    .attr('id', 'schedule-modal');

                const $title = $('<h2>')
                    .text('Schedule Start')
                    .css(CSSManager.modalTitle);

                const $inputGroup = $('<div>')
                    .css({
                        display: 'flex', justifyContent: 'space-around', margin: '20px 0'
                    });

                const $hourInput = $('<input>')
                    .attr({
                        type: 'number', placeholder: 'HH', min: '0', max: '23'
                    })
                    .css(CSSManager.timeInput);

                const $minuteInput = $('<input>')
                    .attr({
                        type: 'number', placeholder: 'MM', min: '0', max: '59'
                    })
                    .css(CSSManager.timeInput);

                const $secondInput = $('<input>')
                    .attr({
                        type: 'number', placeholder: 'SS', min: '0', max: '59'
                    })
                    .css(CSSManager.timeInput);

                $inputGroup.append($hourInput, $minuteInput, $secondInput);

                const $confirmButton = $('<button>')
                    .text('Confirm')
                    .css(CSSManager.confirmButton)
                    .on('click', () => {
                        const hours = parseInt($hourInput.val(), 10) || 0;
                        const minutes = parseInt($minuteInput.val(), 10) || 0;
                        const seconds = parseInt($secondInput.val(), 10) || 0;

                        const now = new Date();
                        const targetTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes, seconds);

                        let remainingTime = targetTime - now;

                        if (remainingTime <= 0) {
                            targetTime.setDate(targetTime.getDate() + 1);
                            remainingTime = targetTime - now;
                        }

                        if (remainingTime <= 0) {
                            alert('Please enter a valid future time!');
                            return;
                        }

                        $modalOverlay.remove();

                        setTimeout(() => {
                            GM_setValue('Start', '1');
                            startRace_(getGMAry_('SelectedCourses'), GM_getValue('Interval', DEFAULT_INTERVAL));
                        }, remainingTime);

                        alert(`Task scheduled to run at ${targetTime.toLocaleTimeString()} (${Math.floor(remainingTime / 1000)} seconds from now)`);
                    });

                const $cancelButton = $('<button>')
                    .text('Cancel')
                    .css(CSSManager.closeButton)
                    .on('click', () => {
                        $modalOverlay.remove();
                    });

                const $buttonGroup = $('<div>')
                    .css({
                        display: 'flex', justifyContent: 'center', gap: '20px'
                    })
                    .append($cancelButton, $confirmButton);

                $modal.append($title, $inputGroup, $buttonGroup);
                $modalOverlay.append($modal);
                $(window.top.document.body).append($modalOverlay);
            });


            GM_registerMenuCommand("Stop", () => {
                clearGM_('Start')
            });

            GM_registerMenuCommand("Show selected", async () => {
                let courses = getGMAry_('SelectedCourses');
                let $modalOverlay = $('<div>').addClass('sustc-modal-overlay').attr('id', 'sustc-modal-overlay').css(CSSManager.modalOverlay);
                let $modal = $('<div>').css(CSSManager.modal);
                let $modalTitle = $('<h2>').text('Selected Courses').css(CSSManager.modalTitle);
                $modal.append($modalTitle);
                let $courseList = $('<div>');

                if (courses.length === 0) {
                    let $emptyMessage = $('<div>').text('No courses selected').css({
                        textAlign: 'center', fontSize: '16px', color: '#888', padding: '20px 0'
                    });
                    $courseList.append($emptyMessage);
                }

                for (let i = 0; i < courses.length; ++i) {
                    let $courseLine = $('<div>').css(CSSManager.courseLine);
                    let $courseInfo = $('<span>').text((i + 1) + '. ' + courses[i].name);
                    let $deleteButton = $('<button>').text('Delete').css(CSSManager.deleteButton);
                    $deleteButton.on('click', function () {
                        courses.splice(i, 1);
                        updateCourses(courses);
                        $courseLine.remove();
                    });
                    $courseLine.append($courseInfo).append($deleteButton);
                    $courseList.append($courseLine);
                }

                $modal.append($courseList);

                let $closeButton = $('<button>').text('Close').css(CSSManager.closeButton);

                $closeButton.on('click', function () {
                    $modalOverlay.remove();
                });

                $modal.append($closeButton);
                $modalOverlay.append($modal);

                $(window.top.document.body).append($modalOverlay);
            });

            GM_registerMenuCommand("Clear all courses", () => {
                if (confirm('Are you sure to clear all selected courses?')) {
                    clearGM_('SelectedCourses');
                }
            });

            GM_registerMenuCommand("Set Interval", () => {
                let interval = prompt('Set the interval between each request (ms)', GM_getValue('Interval', DEFAULT_INTERVAL));
                GM_setValue('Interval', interval);
            });
        }

        setInterval(enableAllButtons_, 2000);
        if (window.self !== window.top) {
            return;
        }
        clearGM_('Start');
        initMenu();
    });
})(jQuery.noConflict(true));