/*
 * Queue - A Queueing system that can be used to handle labs in higher education
 * Copyright (C) 2016-2024  Delft University of Technology
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
/**
 * Finds the selected elements or the options that are selected.
 * @param select The select box to find options of.
 * @returns {*} The selected options as an array.
 */
function findSelected(select) {
    const selected = select.val();
    if (typeof selected === "undefined" || selected.length === 0) {
        return select
            .find("option")
            .map(function () {
                return this.value;
            })
            .toArray();
    }
    return selected;
}

/**
 * Checks the info of a request to see whether it is conform to the currently active filtering options.
 * This constant is created by directly applying a function context finding the currently selected options and
 * creating a function to actually filter an incoming request.
 * @type {function(any): boolean} A function to check incoming requests on whether they should be displayed.
 */
const inFilter = (() => {
    let selectedLabs;
    let selectedAssignments;
    let selectedRooms;
    let selectedOnlineModes;
    let selectedStatuses;
    let selectedTypes;

    $(() => {
        // Look up the currently selected filter options
        selectedLabs = findSelected($("#lab-select"));
        selectedAssignments = findSelected($("#assignment-select"));
        selectedRooms = findSelected($("#room-select"));
        selectedOnlineModes = findSelected($("#onlineMode-select"));
        selectedStatuses = findSelected($("#status-select"));
        selectedTypes = findSelected($("#request-type-select"));
    });

    // Return a function checking whether an incoming request passes the filters
    return event => {
        const locationConstraintMet =
            event["roomId"] && !event["onlineMode"]
                ? selectedRooms.includes(event["roomId"].toString())
                : !event["roomId"] && event["onlineMode"]
                ? selectedOnlineModes.includes(event["onlineMode"])
                : false;

        return (
            selectedLabs.includes(event["labId"].toString()) &&
            selectedAssignments.includes(event["assignmentId"].toString()) &&
            selectedStatuses.includes(event["status"]) &&
            selectedTypes.includes(event["requestType"]) &&
            locationConstraintMet
        );
    };
}).apply();

/**
 * Checks if the event matches the user's language preference.
 *
 * @param event The event to check
 */
function matchesLanguage(event) {
    switch (event.language) {
        case "ANY":
            return true;
        case "DUTCH_ONLY":
            return userLanguage === "ANY" || userLanguage === "DUTCH_ONLY";
        case "ENGLISH_ONLY":
            return userLanguage === "ANY" || userLanguage === "ENGLISH_ONLY";
        default:
            return false;
    }
}

useWebsockets();

document.addEventListener("SocketConnected", function (event) {
    let client = event.client;

    client.subscribe("/user/topic/request-table", msg => {
        const url = new URL(window.location);
        const page = url.searchParams.get("page");
        if (page && page > 0) return;

        const event = JSON.parse(msg.body);
        if ((event.type === "request-created" || event.type === "request-available") && inFilter(event) && matchesLanguage(event)) {
            appendToRequestTable(event);
            increaseGetNextCounter(event["labId"]);
        } else if (event.type !== "request-created" && event.type !== "request-available") {
            switch (event.type) {
                case "request-taken":
                    decreaseGetNextCounter(event["labId"]);
                    updateAssigned(event["id"], event["takenBy"]);
                    removeFromRequestTable(event["id"]);
                    break;
                case "request-revoked":
                    decreaseGetNextCounter(event["labId"]);
                    removeFromRequestTable(event["id"]);
                    break;
                case "request-forwarded-to-any":
                    if (inFilter(event)) {
                        prependToRequestTable(event);
                        increaseGetNextCounter(event["labId"]);
                    }
                    break;
                case "request-forwarded-to-person":
                    if (event["forwardedTo"] === authenticatedId && inFilter(event)) {
                        prependToRequestTable(event);
                        increaseGetNextCounter(event["labId"]);
                    }
                    break;
                default:
                    console.log(`Could not classify message from web socket: ${event}`);
                    return;
            }
        }
    });
});

/**
 * Use Handlebars to compile a template for each request and fill in the template directly using the request
 * info sent through the web socket message.
 * @param event The event that occured with information on the created request.
 */
function prependToRequestTable(event) {
    // Get the request template and fill it in
    const source = $("#request-entry-template").html();
    const template = Handlebars.compile(source);
    const html = template(event);

    // Add the compiled HTML to the request table with a fade in effect.
    $(html).hide().prependTo("#request-table tbody").fadeIn();
    addEvents(event.id);

    $("#no-requests-info").hide();
}

/**
 * Use Handlebars to compile a template for each request and fill in the template directly using the request
 * info sent through the web socket message.
 * @param event The event that occured with information on the created request.
 */
function appendToRequestTable(event) {
    // Get the request template and fill it in
    const source = $("#request-entry-template").html();
    const template = Handlebars.compile(source);
    const html = template(event);

    // Add the compiled HTML to the request table with a fade in effect.
    $(html).hide().appendTo("#request-table tbody").fadeIn();
    addEvents(event.id);

    $("#no-requests-info").hide();
}

function addEvents(requestId) {
    document.getElementById(`request-${requestId}`).addEventListener("click", function (event) {
        if (["a", "button"].includes(event.target.tagName.toLowerCase())) return;
        window.location = `/request/${requestId}`;
    });
    document.getElementById(`request-${requestId}`).addEventListener("keydown", function (event) {
        if (event.key !== "Enter" || ["a", "button"].includes(event.target.tagName.toLowerCase())) return;
        window.location = `/request/${requestId}`;
    });
}

/**
 * Removed a request from the request table.
 * @param id {number} The id of the request to remove.
 */
function removeFromRequestTable(id) {
    const rowSelector = selectRow(id);
    rowSelector.fadeOut();
    setTimeout(() => {
        rowSelector.remove();

        const amtRequests = $("tr[id^='request-']").length;
        if (amtRequests === 0) {
            $("#no-requests-info").show();
        }
    }, 500);
}

/**
 * Updates the status of a request with the given id in the request table to the given status.
 * @param id {number} The id of the request to update.
 * @param status {string} The status to update the request to.
 */
function updateStatus(id, status) {
    // Change the status badge to display the new status
    const statusSelector = selectStatus(id);
    statusSelector.text(status);
    statusSelector.removeClass();
    statusSelector.addClass("badge badge-pill bg-info text-white");

    // Change the background color of the row based on the status
    const rowSelector = selectRow(id);
    rowSelector.removeClass();
    rowSelector.addClass("text-white");
    rowSelector.addClass(bgColorDict[status]);
}

/**
 * Updates the text saying who is currently assigned to a request with the given id.
 * @param id {number} The id of the request to change.
 * @param assigned {string} The name of the person who is now assigned to the request.
 */
function updateAssigned(id, assigned) {
    const assignedSelector = selectAssigned(id);
    assignedSelector.text(assigned);
}

/**
 * Decreases the lab get-next button counter and disables it if the counter drops to 0.
 * @param labId The id of the lab to update the get-next button for.
 */
function decreaseGetNextCounter(labId) {
    const lab = $(`#get-next-${labId}`);
    const span = $(`#span-${labId}`);

    // language=RegExp
    let counter = parseInt(span.text().match(/\d+/)[0]);
    if (counter !== 0) {
        counter = counter - 1;
        span.text(`(${counter})`);
        if (counter === 0) {
            lab.attr("disabled", "");
            span.hide();
        }
    }
}

/**
 * Increases the lab get-next button counter and enables it if necessary.
 * @param labId {number} The id of the lab to update the get-next button for.
 */
function increaseGetNextCounter(labId) {
    const lab = $(`#get-next-${labId}`);
    const span = $(`#span-${labId}`);
    const count = parseInt(span.text().match(/\d+/)[0]) + 1;

    lab.removeAttr("disabled");
    span.text(`(${count})`);
    span.show();
}

/**
 * Selects the row with the request with the given id on it.
 * @param id {number} The id of the request to select.
 * @returns {*|jQuery.fn.init|jQuery|HTMLElement}
 */
function selectRow(id) {
    return $("#request-" + id);
}

/**
 * Selects the status span of the request with the given id.
 * @param id {number} The id of the request to select.
 * @returns {*|jQuery.fn.init|jQuery|HTMLElement}
 */
function selectStatus(id) {
    return $("#status-" + id);
}

/**
 * Selects the assigned data of the request with the given id.
 * @param id {number} The id of the request to select.
 * @returns {*|jQuery.fn.init|jQuery|HTMLElement}
 */
function selectAssigned(id) {
    return $("#assigned-" + id);
}

/**
 * A dictionary mapping statuses to a colour.
 * @type {{REVOKED: string, NOTFOUND: string, FORWARDED: string, PROCESSING: string, PENDING: string, APPROVED: string, REJECTED: string}}
 */
bgColorDict = {
    PENDING: "bg-pending",
    PROCESSING: "bg-processing",
    APPROVED: "bg-approved",
    REJECTED: "bg-rejected",
    FORWARDED: "bg-forwarded",
    REVOKED: "bg-revoked",
    NOTFOUND: "bg-notfound",
};

/**
 * Adds to each request in the table an event listener that will redirect the user to the request page when clicked,
 * or will open the request page in a new tab when the user presses Ctrl + Click.
 */
document.addEventListener("DOMContentLoaded", function () {
    document.querySelectorAll(".request").forEach(r =>
        r.addEventListener("click", function (event) {
            if (["a", "button"].includes(event.target.tagName.toLowerCase())) return;
            if (event.ctrlKey || event.metaKey) {
                window.open(`/request/${r.dataset.request}`, "_blank");
            } else {
                window.location = `/request/${r.dataset.request}`;
            }
        })
    );
    document.querySelectorAll(".request").forEach(r =>
        r.addEventListener("keydown", function (event) {
            if (event.key !== "Enter" || ["a", "button"].includes(event.target.tagName.toLowerCase())) return;
            if (event.ctrlKey || event.metaKey) {
                window.open(`/request/${r.dataset.request}`, "_blank");
            } else {
                window.location = `/request/${r.dataset.request}`;
            }
        })
    );
    // Deals with middle-clicking a request to open it in a new tab
    document.querySelectorAll(".request").forEach(r =>
        r.addEventListener("mousedown", function (event) {
            if (event.which === 2 || event.button === 4) {
                event.preventDefault();
            }
        })
    );
    document.querySelectorAll(".request").forEach(r =>
        r.addEventListener("mouseup", function (event) {
            if (event.which === 2 || event.button === 4) {
                window.open(`/request/${r.dataset.request}`, "_blank");
            }
        })
    );
});
