// --
// Copyright (C) 2001-2021 OTRS AG, https://otrs.com/
// Copyright (C) 2021 Znuny GmbH, https://znuny.org/
// --
// This software comes with ABSOLUTELY NO WARRANTY. For details, see
// the enclosed file COPYING for license information (GPL). If you
// did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
// --
// nofilter(TidyAll::Plugin::OTRS::JavaScript::UnloadEvent)

"use strict";

var Core = Core || {};

/**
 * @namespace Core.App
 * @memberof Core
 * @author OTRS AG
 * @description
 *      This namespace contains main app functionalities.
 */
Core.App = (function (TargetNS) {

    if (!Core.Debug.CheckDependency('Core.App', 'Core.Exception', 'Core.Exception')) {
        return false;
    }

    if (!Core.Debug.CheckDependency('Core.App', 'Core.Config', 'Core.Config')) {
        return false;
    }

    /**
     * @private
     * @name SerializeData
     * @memberof Core.App
     * @function
     * @returns {String} Query string of the data.
     * @param {Object} Data - The data that should be converted.
     * @description
     *      Converts a given hash into a query string.
     */
    function SerializeData(Data) {
        var QueryString = '';
        $.each(Data, function (Key, Value) {
            QueryString += encodeURIComponent(Key) + '=' + encodeURIComponent(Value) + ';';
        });
        return QueryString;
    }

    /**
     * @name BindWindowUnloadEvent
     * @memberof Core.App
     * @function
     * @param {String} Namespace - Namespace for which the event should be bound.
     * @param {Function} CallbackFunction - Function which should be executed once the event is fired.
     * @description
     *      Binds a crossbrowser compatible unload event to the window object
     */
    TargetNS.BindWindowUnloadEvent = function (Namespace, CallbackFunction) {

        if (!$.isFunction(CallbackFunction)) {
            return;
        }

        // we need a special handling for all IE's before 11, because these
        // don't know the pagehide event but support the non-standard
        // unload event.
        if ($.browser.msie && parseInt($.browser.version, 10) < 11) {
            $(window).on('unload.' + Namespace, function () {
                CallbackFunction();
            });
        }
        else {
            $(window).on('pagehide.' + Namespace, function () {
                CallbackFunction();
            });
        }
    };

    /**
     * @name UnbindWindowUnloadEvent
     * @memberof Core.App
     * @function
     * @param {String} Namespace - Namespace for which the event should be removed.
     * @description
     *      Unbinds a crossbrowser compatible unload event to the window object
     */
    TargetNS.UnbindWindowUnloadEvent = function (Namespace) {
        $(window).off('unload.' + Namespace);
        $(window).off('pagehide.' + Namespace);
    };

    /**
     * @name GetSessionInformation
     * @memberof Core.App
     * @function
     * @returns {Object} Hash with session data, if needed.
     * @description
     *      Collects session data in a hash if available.
     */
    TargetNS.GetSessionInformation = function () {
        var Data = {};
        if (!Core.Config.Get('SessionIDCookie')) {
            Data[Core.Config.Get('SessionName')] = Core.Config.Get('SessionID');
            Data[Core.Config.Get('CustomerPanelSessionName')] = Core.Config.Get('SessionID');
        }
        Data.ChallengeToken = Core.Config.Get('ChallengeToken');
        return Data;
    };

    /**
     * @name BrowserCheck
     * @memberof Core.App
     * @function
     * @returns {Boolean} True if the used browser is *not* on the black list.
     * @param {String} Interface - The interface we are in (Agent or Customer)
     * @description
     *      Checks if the used browser is not on the OTRS browser blacklist
     *      of the agent interface.
     */
    TargetNS.BrowserCheck = function (Interface) {
        var AppropriateBrowser = true,
            BrowserBlackList = Core.Config.Get('BrowserBlackList::' + Interface);
        if (typeof BrowserBlackList !== 'undefined') {
            $.each(BrowserBlackList, function (Key, Value) {
                if ($.isFunction(Value)) {
                    if (Value()) {
                        AppropriateBrowser = false;
                    }
                }
            });
            return AppropriateBrowser;
        }
        alert(Core.Language.Translate('Error: Browser Check failed!'));
    };

    /**
     * @name BrowserCheckIECompatibilityMode
     * @memberof Core.App
     * @function
     * @returns {Boolean} True if the used browser is IE in Compatibility Mode.
     * @description
     *      Checks if the used browser is IE in Compatibility Mode.
     *      IE11 in Compatibility Mode is not recognized.
     */
    TargetNS.BrowserCheckIECompatibilityMode = function () {
        var IE7 = ($.browser.msie && $.browser.version === '7.0');

        // if not IE7, we cannot be in compatibilty mode
        if (!IE7) {
            return false;
        }

        // IE8,9,10,11 in Compatibility Mode will claim to be IE7.
        // See also http://msdn.microsoft.com/en-us/library/ms537503%28v=VS.85%29.aspx
        if (
                navigator &&
                navigator.userAgent &&
                (
                    navigator.userAgent.match(/Trident\/4.0/) ||
                    navigator.userAgent.match(/Trident\/5.0/) ||
                    navigator.userAgent.match(/Trident\/6.0/) ||
                    navigator.userAgent.match(/Trident\/7.0/)
                )
            ) {

            return true;
        }

        // if IE7 but no Trident 4-7 is found, we are in a real IE7
        return false;
    };

    /**
     * @name Ready
     * @memberof Core.App
     * @function
     * @param {Function} Callback - The callback function to be executed.
     * @description
     *      This functions callback is executed if all elements and files of this page are loaded.
     */
    TargetNS.Ready = function (Callback) {
        if ($.isFunction(Callback)) {
            $(document).ready(function () {
                try {
                    Callback();
                }
                catch (Error) {
                    Core.Exception.HandleFinalError(Error);
                }
            });
        }
        else {
            Core.Exception.ShowError('No function parameter given in Core.App.Ready', 'TypeError');
        }

        TargetNS.Subscribe('Core.App.AjaxErrorResolved', function() {

            var $DialogObj = $('#AjaxErrorDialog'),
                CountDown = 20;

            window.clearInterval(TargetNS.AjaxConnectionCheckInterval);
            delete TargetNS.AjaxConnectionCheckInterval;

            if (!$('body').hasClass('ConnectionErrorDetected')) {
                return false;
            }

            $('body').removeClass('ConnectionErrorDetected');

            // if there is already a dialog, we just exchange the content
            if ($('#AjaxErrorDialogInner').find('.NoConnection').is(':visible')) {
                $('#AjaxErrorDialogInner').find('.NoConnection').hide();
                $('#AjaxErrorDialogInner').find('.ConnectionReEstablished').show().delay(1000).find('.Icon').addClass('Green');
            }
            else {

                $DialogObj.find('.NoConnection').hide();
                $DialogObj.find('.ConnectionReEstablished').show().find('.Icon').addClass('Green');

                Core.UI.Dialog.ShowDialog({
                    HTML : $DialogObj,
                    Title : '',     // Header is hidden.
                    Modal : true,
                    CloseOnClickOutside : false,
                    CloseOnEscape : false,
                    PositionTop: '100px',
                    PositionLeft: 'Center',
                    Buttons: [
                        {
                            Label: Core.Language.Translate("Reload page"),
                            Class: 'Primary ReloadButton',
                            Function: function () {
                                location.reload();
                            }
                        },
                        {
                            Label: Core.Language.Translate("Close this dialog"),
                            Function: function () {
                                clearInterval(TargetNS.TimerID);
                                delete TargetNS.TimerID;

                                if (TargetNS.AjaxConnectionCheckInterval) {
                                    clearInterval(TargetNS.AjaxConnectionCheckInterval);
                                    delete TargetNS.AjaxConnectionCheckInterval;
                                }

                                Core.UI.Dialog.CloseDialog($('#AjaxErrorDialogInner'));
                            }
                        }
                    ],
                    AllowAutoGrow: true
                });

                // Hide
                $('#AjaxErrorDialogInner').closest('.Dialog').find('>.Header').hide();

                // the only possibility to close the dialog should be the button
                $('#AjaxErrorDialogInner').closest('.Dialog').find('.Close').remove();
            }

            // Do not reload the page if user made any modification.
            if (!Core.Form.IsFormModified()) {
                TargetNS.TimerID = window.setInterval(function() {
                    CountDown--;

                    $('.ReloadButton span').text(Core.Language.Translate("Reload page (%ss)", CountDown));
                    if (CountDown == 0) {
                        clearInterval(TargetNS.TimerID);

                        // Reload the page.
                        window.location.reload();
                    }
                }, 1000);
            }
        });

        // Check for AJAX connection errors and show overlay in case there is one.
        TargetNS.Subscribe('Core.App.AjaxError', function() {

            var $DialogObj = $('#AjaxErrorDialog');

            // set a body class to remember that we detected the error
            $('body').addClass('ConnectionErrorDetected');

            // Only show one dialog at a time. Do not show the dialog if no connection error dialog was displayed
            //   previously, leave it open since it might point to a more serious issue.
            if ($('#AjaxErrorDialogInner').find('.NoConnection').is(':visible')) {
                return false;
            }

            // do ajax calls on a regular basis to see whether the connection has been re-established
            if (!TargetNS.AjaxConnectionCheckInterval) {
                TargetNS.AjaxConnectionCheckInterval = window.setInterval(function(){
                    Core.AJAX.FunctionCall(Core.Config.Get('CGIHandle'), null, function () {
                        TargetNS.Publish('Core.App.AjaxErrorResolved');
                    }, 'html');
                }, 5000);
            }

            // If a connection warning dialog is open but shows the "Connection re-established" notice, show the warning again.
            //   This could happen if the connection had been lost but also re-established in the meantime,
            //   or there were some communication errors encountered.
            if ($('#AjaxErrorDialogInner').find('.ConnectionReEstablished').is(':visible')) {
                $('#AjaxErrorDialogInner').find('.ConnectionReEstablished').hide().prev('.NoConnection').show();
                $('.ReloadButton span').text(Core.Language.Translate("Reload page"));
                clearInterval(TargetNS.TimerID);
                delete TargetNS.TimerID;
                return false;
            }

            // Show 'No Connection' dialog content.
            $DialogObj.find('.ConnectionReEstablished').hide();
            $DialogObj.find('.NoConnection').show();

            Core.UI.Dialog.ShowDialog({
                HTML: $DialogObj,
                Title: '',  // Header is hidden.
                Modal: true,
                CloseOnClickOutside: false,
                CloseOnEscape: false,
                PositionTop: '100px',
                PositionLeft: 'Center',
                Buttons: [
                    {
                        Label: Core.Language.Translate("Reload page"),
                        Class: 'Primary ReloadButton',
                        Function: function () {
                            location.reload();
                        }
                    },
                    {
                        Label: Core.Language.Translate("Close this dialog"),
                        Function: function () {
                            clearInterval(TargetNS.TimerID);
                            delete TargetNS.TimerID;

                            if (TargetNS.AjaxConnectionCheckInterval) {
                                clearInterval(TargetNS.AjaxConnectionCheckInterval);
                                delete TargetNS.AjaxConnectionCheckInterval;
                            }

                            Core.UI.Dialog.CloseDialog($('#AjaxErrorDialogInner'));
                        }
                    }
                ],
                AllowAutoGrow: true
            });

            // Hide
            $('#AjaxErrorDialogInner').closest('.Dialog').find('>.Header').hide();

            // the only possibility to close the dialog should be the button
            $('#AjaxErrorDialogInner').closest('.Dialog').find('.Close').remove();
        });
    };

    /**
     * @name InternalRedirect
     * @memberof Core.App
     * @function
     * @param {Object} Data - The query data (like: {Action: 'MyAction', Subaction: 'Add'})
     * @description
     *      Performs an internal redirect based on the given data parameters.
     *      If needed, session information like SessionID and ChallengeToken are appended.
     */
    TargetNS.InternalRedirect = function (Data) {
        var URL;
        URL = Core.Config.Get('Baselink') + SerializeData(Data);
        URL += SerializeData(TargetNS.GetSessionInformation());
        window.location.href = URL;
    };

    /**
     * @name EscapeSelector
     * @memberof Core.App
     * @function
     * @returns {String} The escaped selector.
     * @param {String} Selector - The original selector (e.g. ID, class, etc.).
     * @description
     *      Escapes the special characters (. :) in the given jQuery Selector
     *      jQ does not allow the usage of dot or colon in ID or class names
     *      An overview of special characters that should be quoted can be found here:
     *      https://api.jquery.com/category/selectors/
     */
    TargetNS.EscapeSelector = function (Selector) {
        if (Selector && Selector.length) {
            return Selector.replace(/( |#|:|\.|\[|\]|@|!|"|\$|%|&|<|=|>|'|\(|\)|\*|\+|,|\?|\/|;|\\|\^|{|}|`|\||~)/g, '\\$1');
        }
        return '';
    };

    /**
     * @name EscapeHTML
     * @memberof Core.App
     * @function
     * @returns {String} The escaped string.
     * @param {String} StringToEscape - The string which is supposed to be escaped.
     * @description
     *      Escapes the special HTML characters ( < > & ") in supplied string to their
     *      corresponding entities.
     */
    TargetNS.EscapeHTML = function (StringToEscape) {
        var HTMLEntities = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;'
        };

        if (!StringToEscape) {
            return '';
        }

        return StringToEscape.replace(/[&<>"]/g, function(Entity) {
            return HTMLEntities[Entity] || Entity;
        });
    };

    /**
     * @name HumanReadableDataSize
     * @memberof Core.App
     * @function
     * @returns {String} Result string.
     * @param {Number} Size - Bytes which needs to be formatted
     * @description
     *      Formats bytes as human readable text (like 45.6 MB).
     */
    TargetNS.HumanReadableDataSize = function (Size) {

        var SizeStr,
            ReadableSize;

        var FormatSize = function(Size) {
            var ReadableSize,
                Integer,
                Float,
                Separator;

            if (Number.isInteger(Size)) {
                ReadableSize = Size.toString();
            }
            else {

                // Get integer and decimal parts.
                Integer = Math.floor(Size);
                Float   = Math.round((Size - Integer) * 10);

                Separator = Core.Language.DecimalSeparatorGet() || '.';

                // Format size with provided decimal separator.
                ReadableSize = Integer.toString() + Separator.toString() + Float.toString();
            }

            return ReadableSize;
        }

        // Use convention described on https://en.wikipedia.org/wiki/File_size
        if (Size >= Math.pow(1024, 4)) {

            ReadableSize = FormatSize(Size / Math.pow(1024, 4));
            SizeStr = Core.Language.Translate('%s TB', ReadableSize);
        }
        else if (Size >= Math.pow(1024, 3)) {

            ReadableSize = FormatSize(Size / Math.pow(1024, 3));
            SizeStr = Core.Language.Translate('%s GB', ReadableSize);
        }
        else if (Size >= Math.pow(1024, 2)) {

            ReadableSize = FormatSize(Size / Math.pow(1024, 2));
            SizeStr = Core.Language.Translate('%s MB', ReadableSize);
        }
        else if (Size >= 1024) {

            ReadableSize = FormatSize(Size / 1024);
            SizeStr = Core.Language.Translate('%s KB', ReadableSize);
        }
        else {
            SizeStr = Core.Language.Translate('%s B', Size);
        }

        return SizeStr;
    }

    /**
     * @name Publish
     * @memberof Core.App
     * @function
     * @param {String} Topic - The channel to publish on
     * @param {Array} Args - The data to publish. Each array item is converted into an ordered arguments on the subscribed functions.
     * @description
     *      Publish some data on a named topic.
     */
    TargetNS.Publish = function (Topic, Args) {
        $.publish(Topic, Args);
    };

    /**
     * @name Subscribe
     * @memberof Core.App
     * @function
     * @returns {Array} A handle which can be used to unsubscribe this particular subscription
     * @param {String} Topic - The channel to subscribe to
     * @param {Function} Callback - The handler event. Anytime something is published on a subscribed channel, the callback will be called with the published array as ordered arguments.
     * @description
     *      Register a callback on a named topic.
     */
    TargetNS.Subscribe = function (Topic, Callback) {
        return $.subscribe(Topic, Callback);
    };

    /**
     * @name Unsubscribe
     * @memberof Core.App
     * @function
     * @param {Array} Handle - The return value from a $.subscribe call
     * @description
     *      Disconnect a subscribed function for a topic.
     */
    TargetNS.Unsubscribe = function (Handle) {
        $.unsubscribe(Handle);
    };

    /**
     * @name Init
     * @memberof Core.App
     * @function
     * @description
     *      This function initializes the special functions.
     */
    TargetNS.Init = function () {
        var RefreshSeconds = parseInt(Core.Config.Get('Refresh'), 10) || 0;

        if (RefreshSeconds !== 0) {
            window.setInterval(function() {

                // If there are any open overlay dialogs, don't refresh
                if ($('.Dialog:visible').length) {
                    return;
                }

                // If there are open child popup windows, don't refresh
                if (Core && Core.UI && Core.UI.Popup && Core.UI.Popup.HasOpenPopups()) {
                    return;
                }
                // Now we can reload
                window.location.reload();
            }, RefreshSeconds * 1000);
        }

        // Initialize return to previous page function.
        TargetNS.ReturnToPreviousPage();
    };

    /**
     * @name ReturnToPreviousPage
     * @memberof Core.App
     * @function
     * @description
     *      This function bind on click event to return on previous page.
     */
    TargetNS.ReturnToPreviousPage = function () {

        $('.ReturnToPreviousPage').on('click', function () {

            // Check if an older history entry is available
            if (history.length > 1) {
            history.back();
            return false;
            }

            // If we're in a popup window, close it
            if (Core.UI.Popup.CurrentIsPopupWindow()) {
                Core.UI.Popup.ClosePopup();
                return false;
            }

            // Normal window, no history: no action possible
            return false;
        });
    };

    Core.Init.RegisterNamespace(TargetNS, 'APP_MODULE');

    return TargetNS;
}(Core.App || {}));
