/*
This file is part of SystemTestPortal.
Copyright (C) 2017  Institute of Software Technology, University of Stuttgart

SystemTestPortal is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

SystemTestPortal 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with SystemTestPortal.  If not, see <http://www.gnu.org/licenses/>.
*/

$.getScript("/static/js/util/ajax.js");
$.getScript("/static/js/util/common.js");

/** Adds the listeners on page load */
function initializeExecutionClickListener() {

    $("#buttonExecuteFirstTestStep").on("click", buttonExecuteFirstTestStepPressed);
    $("#buttonExecuteNextTestStep").on("click", buttonExecuteNextTestStepPressed);
    $("#buttonSummaryFinish").on("click", buttonSummaryFinishPressed);
    $("#buttonAbort").on("click", buttonAbortPressed);
    $("#buttonPause").on("click", buttonPausePressed);

    var $check = $(".preconditionCheckbox"), el;
    $check
   .data('checked',0)
   .click(function(e) {
        el = $(this);
        clearSelection();
        preconditionSelection(e,el)
    });

}

var timerID;
var secondsElement;
var minutesElement;
var hoursElement;
$(window).on('load', initTimer());

var seqProtocol;
var caseProtocol;
var stepProtocol;

var isInSequenceExecution;

/** Sequence start/summary page */
var isSequence;

var initHours;
var initMinutes;
var initSeconds;

/** Will start the test execution timer */
function initTimer() {

    secondsElement = $('#timeSeconds');
    minutesElement = $('#timeMinutes');
    hoursElement = $('#timeHours');

    let sec = parseInt(secondsElement.text());
    let min = parseInt(minutesElement.text());
    let hour = parseInt(hoursElement.text());

    secondsElement.text(sec.toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping: false}));
    minutesElement.text(min.toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping: false}));
    hoursElement.text(hour.toString());

    resumeTimer();
}

function buttonPausePressed() {

    clearInterval(timerID);

    $("#buttonPause")
        .off()
        .on("click", resumeTimer);

    $(".buttonPauseIcon")
        .removeClass("fa-play fa-pause")
        .addClass("fa-play");
}

function resumeTimer() {

    clearInterval(timerID);
    timerID = setInterval(tickSec, 1000);

    $("#buttonPause")
        .off()
        .on("click", buttonPausePressed);

    $(".buttonPauseIcon")
        .removeClass("fa-pause fa-play")
        .addClass("fa-pause");
}

/**
 * Displays an abort dialog which will delegate back to overview on abort confirm
 * @param event the event which invokes this method
 */
function buttonAbortPressed(event) {
    showAbortModalWith(() => ajaxRequestFragment(event, getTestURL().toString(), "", "GET"));
}

/**
 * Saves inputs and requests for first step execution page
 * @param event the event which invokes this method
 */
function buttonExecuteFirstTestStepPressed(event) {

    if (isInSequenceExecution) {
        // only update the init sequence protocol on the start page of the sequence execution
        if (isSequence)
            initProtocol(seqProtocol);
        else {
            initProtocol(caseProtocol);
            storeCaseProtocol(caseProtocol);
        }
        // update with added protocolIDs of caseProtocols
        storeSeqProtocol(seqProtocol);
    } else {
        initProtocol(caseProtocol);
        storeCaseProtocol(caseProtocol);
    }
    ajaxRequestFragment(event, currentURL().toString(), getExecutionStartPageData(), "POST");
}

/**
 * Saves inputs and requests for next step execution page
 * @param event the event which invokes this method
 */
function buttonExecuteNextTestStepPressed(event) {
    caseProtocol = retrieveCaseProtocol();
    appendStepProtocol(caseProtocol);
    storeCaseProtocol(caseProtocol);
    ajaxRequestFragment(event, currentURL().toString(), getExecutionStepPageData(), "POST");
}

/**
 * Saves inputs and redirects
 * @param event the event which invokes this method
 */
function buttonSummaryFinishPressed(event) {
    disableAbortWarnings();
    if (!isSequence) {
        caseProtocol = retrieveCaseProtocol();
        appendCaseSummary(caseProtocol);
    
        const path = currentURL().removeLastSegments(1).toString();
        const data = getSummaryPageData();

        if (data.case === "0" || data.case === "-1")
            ajaxRequestFragmentWithHistory(event, currentURL().toString(), data, "POST", path);
        else
            ajaxRequestFragment(event, currentURL().toString(), data, "POST");

        sessionStorage.removeItem("caseProtocol");
    } else {
        window.location.replace(currentURL().removeLastSegments(1).toString());
        sessionStorage.removeItem("seqProtocol");
    }
}

/** Shows next second on the timer */
function tickSec() {

    let sec = parseInt(secondsElement.text()) + 1;
    if (sec >= 60) {
        sec -= 60;
        tickMin()
    }
    secondsElement.text(sec.toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping: false}));
}

/** Shows next minute on the timer */
function tickMin() {

    let min = parseInt(minutesElement.text()) + 1;
    if (min >= 60) {
        min -= 60;
        tickHour();
    }
    minutesElement.text(min.toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping: false}));
}

/** Shows next hour on the timer */
function tickHour() {

    const hour = parseInt(hoursElement.text()) + 1;
    hoursElement.text(hour.toString());
}

/**
 * Initializes the case protocol.
 * Sets meta data for the protocol.
 */
function initProtocol(protocol) {

    if (!isInSequenceExecution || isSequence) {
        protocol.SUTVersion =  $('#inputTestObjectSUTVersions').val();
        protocol.SUTVariant =  $('#inputTestObjectSUTVariants').val();
    } else {
        protocol.SUTVersion = $('#sutVersText').text();
        protocol.SUTVariant = $('#sutVarText').text();
    }

    const results = JSON.parse(getPreconditionResults());
    for (let i=0; i < protocol.PreconditionResults.length; i++)
        protocol.PreconditionResults[i].Result = results[i];

    protocol.IsAnonymous = $("#optionAnonymous:checkbox:checked").length > 0;

}
/** Returns an object with the information given on the execution start page */
function getExecutionStartPageData() {
    return {
        fragment: true,
        step: 0,
        case: $('#inputTestCaseNumber').val(),
        duration: JSON.stringify(getDuration(getTimeInSeconds())),
        caseProtocol : JSON.stringify(caseProtocol),
        seqProtocol: JSON.stringify(seqProtocol),
        isInSequenceExecution: isInSequenceExecution
    }
}

function appendStepProtocol(protocol) {
    stepProtocol.Visited = true;
    stepProtocol.ObservedBehavior = $('#inputTestStepActualResult').val();
    stepProtocol.Comment = $('#inputTestStepNotes').val();
    stepProtocol.Result = parseInt($("input:radio[name ='testResults']:checked").attr('id'));
    protocol.StepProtocols.push(stepProtocol);

    const seconds = getTimeInSeconds() - getInitTimeSeconds();
    stepProtocol.NeededTime = getDuration(seconds);
}
/** Returns an object with the information given on the execution step page */
function getExecutionStepPageData() {
    return {
        fragment: true,
        step: $('#inputTestStepNumber').val(),
        case: $('#inputTestCaseNumber').val(),
        duration: JSON.stringify(getDuration(getTimeInSeconds())),
        caseProtocol: JSON.stringify(caseProtocol),
        isInSequenceExecution: isInSequenceExecution
    }
}
function appendCaseSummary(protocol) {
    protocol.Comment = $('#inputTestComment').val();
    protocol.Result = parseInt($("input:radio[name ='testResults']:checked").attr('id'));
    
    const seconds = getTimeInSeconds() - getInitTimeSeconds();
    protocol.OtherNeededTime = getDuration(seconds);
}
/** Returns an object with the information given on the summary page */
function getSummaryPageData() {
    return {
        fragment: true,
        step: $('#inputTestStepNumber').val(),
        case: $('#inputTestCaseNumber').val(),
        duration: JSON.stringify(getDuration(getTimeInSeconds())),
        caseProtocol: JSON.stringify(caseProtocol),
        seqProtocol: JSON.stringify(seqProtocol),
        isInSequenceExecution: isInSequenceExecution
    }
}

/** Sets up abort listener displaying a modal if user accidentally hits non execution controls. */
function initializeAbortListener() {
    enableAbortWarnings();
}

/** Turns on abort warnings if the user accidentally hits non execution controls. */
function enableAbortWarnings() {

    $("a, img")
        .not($(".No-Warning-On-Current-Action-Abort"))
        .not($("#tabTestCases a"))
        .addClass("Warning-On-Current-Action-Abort-Active")
        .on("click.execution-abort", event => showAbortModalWith(() => $(event.target)[0].click()));
}

/** Turns off abort warnings if the user accidentally hits non execution controls. */
function disableAbortWarnings() {

    $(".Warning-On-Current-Action-Abort-Active")
        .off("click.execution-abort")
        .removeClass("Warning-On-Current-Action-Abort-Active");
}

/**
 * Sets up and shows the abort modal
 * @param abortFunction {function} action when pressing "Abort"
 * @returns {boolean} always false to prevent event default action
 */
function showAbortModalWith(abortFunction) {

    const abortModal = $("#execution-abort-modal");
    const confirmAbortButton = abortModal.find("#buttonAbortConfirm");

    // on abort pressed
    confirmAbortButton.on("click", () => {
        abortModal.on('hidden.bs.modal', abortFunction);
        disableAbortWarnings();
        return true;
    });

    abortModal
        .on('hidden.bs.modal', () => confirmAbortButton.off("click"))
        .modal("show");

    return false;
}

/**
 * Will be invoked when the user hints one of the precondition checkboxes
 * @param event - User press event
 * @param element - The element which got pressed
 */
function preconditionSelection(event, element) {

    switch (element.data('checked')) {

        // unchecked, going checked
        case 0:
            element.data('checked', 1);
            element.prop('indeterminate', false);
            break;

        // checked, going indeterminate
        case 1:
            element.data('checked', 2);
            element.prop('indeterminate', true);
            element.prop('checked', true);
            break;

        // indeterminate, going unchecked
        default:
            element.data('checked', 0);
            element.prop('indeterminate', false);
            element.prop('checked', false);
    }
}

/**
 * Returns the precondition results.
 * @returns {string} - An array with preconditions as JSON string
 */
function getPreconditionResults() {

    const checkboxes = document.getElementsByClassName("preconditionCheckbox");
    const results = [];

    for (let i = 0; i < checkboxes.length; i++){
        if (checkboxes[i].indeterminate)
            results.push("partially fulfilled");
        else if (checkboxes[i].checked)
            results.push("fulfilled");
        else
            results.push("not fulfilled");
    }

    return JSON.stringify(results)
}

/**
 * Stores the case protocol in the session storage.
 * @param protocol
 */
function storeCaseProtocol(protocol) {
    sessionStorage.removeItem("caseProtocol");
    sessionStorage.setItem("caseProtocol", JSON.stringify(protocol));
}

/**
 * Returns the test case protocol.
 * @returns {any}
 */
function retrieveCaseProtocol() {
    return JSON.parse(sessionStorage.getItem("caseProtocol"));
}

/**
 * Stores the sequences protocol in the session storage.
 * @param protocol
 */
function storeSeqProtocol(protocol) {
    sessionStorage.removeItem("seqProtocol");
    sessionStorage.setItem("seqProtocol", JSON.stringify(protocol));
}

/**
 * Transforms the given seconds into a {hours, minutes, seconds} object
 * @param seconds
 * @returns {{hours: number, minutes: number, seconds: number}}
 */
function getDuration(seconds) {
    const hours = Math.floor(seconds / 36000);
    seconds = seconds - hours * 3600;
    const minutes = Math.floor(seconds / 60);
    seconds = seconds - minutes * 60;

    return {
        hours: hours,
        minutes: minutes,
        seconds: seconds
    }
}

/**
 * Gets the current shown time in seconds.
 * @returns {number}
 */
function getTimeInSeconds() {
    const hours = parseInt($('#timeHours').text());
    const minutes = parseInt($('#timeMinutes').text());
    const seconds = parseInt($('#timeSeconds').text());

    return hours*3600 + minutes*60 + seconds;
}

/**
 * Returns the duration in seconds of the current step.
 * @returns {number}
 */
function getInitTimeSeconds() {
    return initHours*3600 + initMinutes*60 + initSeconds;
}