/**
 * Template function for Wizard objects
 */
var Wizard = function ($) {

    var me = {

        storage: {
            key: null,
            settings: {},
            steps: null,
            title: null,
            activeStep: null,
            minimizedStorageKey: null,
            overlay: null,
            stepGroups: null,
            ajaxRunning: false,
            submitPrevented: null,
            linkPrevented: null,
            minimized: false
        },

        elem: {
            $body: null,
            $container: null,
            $hideTrigger: null,
            $self: null,
            $highlight: null,
            $window: $(window),
            $clickByClickContainer: null
        },

        /**
         * @param {Object} data
         */
        init: function (data) {
            var key = data.key;

            me.storage.key = key;
            me.storage.minimized = data.minimized;
            me.storage.stepGroups = data.step_groups;
            me.storage.settings.title = data.title;
            me.storage.settings.subTitle = data.sub_title;
            me.storage.settings.skipLinkText = data.skip_link_text;

            me.storage.minimizedStorageKey = 'wizard_' + key + '_minimized';

            me.elem.$container = $('#wizard-container');
            me.elem.$hint = $('#wizard-hint');
            me.elem.$hideTrigger = $('#wizard-hide-trigger');
            me.elem.$body = $('body');

            me.generateContainerContents();

            me.nextStep();
        },

        show: function () {
            me.elem.$self.show();
        },

        hide: function () {
            me.elem.$self.slideUp();
        },

        /**
         * @return {boolean}
         */
        isMinimized: function () {
            return me.storage.minimized;
        },

        /**
         * Minimize wizard
         */
        minimize: function () {
            me.storage.minimized = true;
            $('a').unbind("click.wizard");
            $('form').unbind("submit.wizard");
        },

        /**
         * Maximize wizard
         */
        maximize: function () {
            me.storage.minimized = false;
        },

        finishWizard: function() {

            var current, i,
                numberOfStepGroups = Object.keys(me.storage.stepGroups).length,
                numberOfCompletedStepGroups = 0;

            for (var stepGroup in me.storage.stepGroups) {
                if (!me.storage.stepGroups.hasOwnProperty(stepGroup)) {
                    return;
                }

                if(me.storage.stepGroups[stepGroup].completed){
                    numberOfCompletedStepGroups++;
                }
            }

            if(numberOfStepGroups === numberOfCompletedStepGroups){
               me.toggleWizardDetails();
            }
        },

        toggleWizardDetails: function(){
            $('.wizard-details').toggle();
        },

        cancelWizard: function(){
            $.ajax({
                url: 'index.php?module=wizard&action=ajax&cmd=cancel_active_wizard&key=' + me.storage.key,
                method: 'post',
                dataType: 'json',
                success: function (data) {
                    // we don't need to know
                },
                error: function (xhr, ajaxOptions, thrownError) {
                    console.warn(xhr.status, thrownError);
                }
            })
        },

        /**
         *
         * @param {String} param
         *
         * @returns {String}
         */
        getUrlParameter: function (param) {
            var sPageURL = window.location.search.substring(1),
                sURLVariables = sPageURL.split('&'),
                sParameterName,
                i;

            for (i = 0; i < sURLVariables.length; i++) {
                sParameterName = sURLVariables[i].split('=');

                if (sParameterName[0] === param) {
                    return sParameterName[1] === undefined ? true : decodeURIComponent(sParameterName[1]);
                }
            }
        },

        /**
         * Register functionality on standard and custom events
         */
        registerEvents: function () {
            me.elem.$self.find('.wizard-step-group h4').on('click touch', function () {
                $(this).parent().toggleClass('closed');
            });

            me.elem.$self.find('.wizard-small-trigger').on('click touch', function () {
                $(this).parent().toggleClass('closed');
            });

            // Handle clicks on step items
            me.elem.$container.on('click touch', '.wizard-step', function () {
                var $step = $(this);
                var link = $step.data('link');
                var stepKey = $step.data('key');
                var $wizard = $step.parents('.wizard');
                var wizardKey = $wizard.data('key');
                var action = $step.data('action');

                action = action.replace(/'/g, '"');
                action = JSON.parse(action);

                me.storage.activeStep = {
                    key: stepKey,
                    node: $step
                };

                // checks if there is a running ajax request,
                if($.active === 1){
                    $(document).ajaxStop(function () {
                        $(this).unbind("ajaxStop");
                        actionHandling();
                    });
                } else {
                    actionHandling();
                }

                function actionHandling(){
                    if (action.type === 'highlight') {
                        me.elem.$highlight = me.findHighlightElement(action);

                        if (me.elem.$highlight && me.elem.$highlight.length > 0) {

                            if (me.elem.$hint.length > 0) {
                                me.elem.$hint.remove();
                            }

                            me.scrollElementIntoView();

                            me.createHint();

                            me.bindRedirectPrevention();
                        } else if (link !== undefined && link !== "undefined" && link.length > 0) {
                            me.visitUrl(link, wizardKey, stepKey);
                        }
                    }

                    if(action.type === 'click_by_click_assistant'){
                        if(link !== undefined && link !== "undefined"){
                            var cleanLink = "";

                            if(link.charAt(0) === "."){ //sent links usually start with, "./index.php..." - not compareable
                                cleanLink = link.replace('.', '');
                            }

                            if(window.location.href.indexOf(cleanLink) > -1){ // if the user is on the correct page
                                me.createClickByClickAssistant(action.data);
                            } else { // otherwise go to the correct page first
                                me.visitUrl(link, wizardKey, stepKey);
                            }

                        } else {
                            me.createClickByClickAssistant(action.data);
                        }
                    }
                }
            });

            me.elem.$body.on('click touch blur change focus', '.wizard-highlight', function (e) {
                if (e.type === $(this).data('data-event')) {
                    me.completeStep();
                    me.removeHint();
                    me.nextStep();
                }
            });

            $('#wizard-container .progress').on('progress-update', function () {
                var self = $(this);

                if (self.data('value') === self.data('max')) {
                    var parentStepgroup =  self.parents('.wizard-step-group');

                    parentStepgroup.removeClass('in-progress');
                    parentStepgroup.addClass(['complete', 'closed']);

                    me.storage.stepGroups[parentStepgroup.data('name')].completed = true;
                }

                me.finishWizard();
            });

            // A form submit has to wait until a Ajax is done
            $('form').on('submit.wizard', function (e) {
                if(me.storage.minimized){
                    return;
                }

                var self = $(this);
                e.preventDefault();

                me.storage.submitPrevented = self;

                if(!me.storage.ajaxRunning){
                    self.unbind('submit.wizard').submit();
                }
            });

            $('.close-wizard').on('click touch',function () {
                me.elem.$hideTrigger.trigger('click');
                me.cancelWizard();
                me.elem.$container.remove();
                me.elem.$hideTrigger.remove();
            })
        },

        bindRedirectPrevention: function(){
            $('a').on('click.wizard', function (e) {
                if(me.storage.minimized){
                    return;
                }

                var self = $(this);
                e.preventDefault();

                me.storage.linkPrevented = self;
            });
        },

        /**
         *
         * @param {Object} data
         */
        createClickByClickAssistant: function(data){
            me.elem.$clickByClickContainer =
                $('<click-by-click-assistant' +
                    '        id="click-by-click-wizard"' +
                    '        v-if="showAssistant"' +
                    '        @close="showAssistant = false"' +
                    '        :pages="pages"' +
                    '        :allowClose="allowClose"' +
                    '        :pagination="pagination">' +
                    '</click-by-click-assistant>');

            me.elem.$clickByClickContainer.appendTo(me.elem.$body);

            if(data.pagination === undefined){
                data.pagination = false;
            }

            new Vue({
                el: '#click-by-click-wizard',
                data: data
            });
        },

        scrollElementIntoView: function(){
            var element = me.elem.$highlight,
                elementTop = element.offset().top,
                elementBottom = elementTop + element.outerHeight(),
                viewportTop = me.elem.$window.scrollTop(),
                viewportBottom = viewportTop + me.elem.$window.height(),
                elementInViewport =  elementBottom > viewportTop && elementTop < viewportBottom;

            if(elementInViewport){
                return;
            }

            var elementHeight = element.height(),
                viewportHeight = me.elem.$window.height(),
                elementOffset = element.offset().top,
                offset;

            if (elementHeight < viewportHeight) {
                offset = elementOffset - ((viewportHeight / 2) - (elementHeight / 2));
            }

            window.setTimeout(function () {
                window.scrollTo({
                    top: offset,
                    behavior: 'smooth'
                });
            }, 100);
        },

        /**
         * @param {string} url
         * @param {string|null} wizardKey
         * @param {string|null} stepKey
         */
        visitUrl: function (url, wizardKey, stepKey) {
            if(url === undefined){
                return;
            }

            if (typeof wizardKey === 'undefined' && typeof stepKey === 'undefined') {
                window.location.href = me.addParameterToUrl(url);
                return;
            }

            window.location.href = me.addParameterToUrl(url);
        },

        /**
         * @param {string} url
         *
         * @return {string}
         */
        addParameterToUrl: function (url) {
            var finalUrl,
                elementParam = '';

            var hashCharPosition = url.lastIndexOf('#');
            if (hashCharPosition === -1) {
                // Keine Sprungmarke in URL > Random-Parameter wird nicht benötigt
                finalUrl = url;
            } else {
                var randomNumber = Math.floor(Math.random() * Math.floor(9999)),
                    urlStart = url.slice(0, hashCharPosition),
                    urlEnd = url.slice(hashCharPosition);

                finalUrl = urlStart + elementParam + '&rand=' + randomNumber + urlEnd;
            }

            return finalUrl + elementParam;
        },

        /**
         * Creates hint box
         */
        createHint: function () {
            if (me.elem.$highlight.length === 0) {
                return;
            }

            var hintHtml =
                '<div id="wizard-hint"><div class="wizard-hint-arrow"></div>' +
                    '<h2>' + me.storage.activeStep.node.data('hint-title') + '</h2>' +
                    '<p>' + me.storage.activeStep.node.data('hint-content') + '</p>' +
                    '<div class="wizard-hint-action">';

                    if(me.storage.activeStep.node.data('hint-cta') !== false){
                        hintHtml += '<button class="button button-secondary">Weiter</button>'
                    }

            hintHtml +=
                    '</div>' +
                '</div>'

            me.elem.$hint = $(hintHtml).appendTo('body');

            me.elem.$hint.find('.wizard-hint-action').on('click touch', function () {
                me.completeStep();
                me.removeHint();
                me.nextStep();
            });

            var resizeTimeout;
            $(window).on('resize scroll', function () {
                if (me.storage.overlay === null) {
                    return;
                }

                if (!!resizeTimeout) {
                    clearTimeout(resizeTimeout);
                }

                resizeTimeout = setTimeout(function () {
                    me.storage.overlay.focus(me.elem.$highlight);
                    me.positionHintAtHighlight();
                }, 10);
            });

            me.positionHintAtHighlight();
        },

        removeHint: function () {
            if(me.elem.$hint !== undefined && me.elem.$hint !== null){
                me.elem.$hint.remove();
            }

            if(me.storage.overlay !== undefined && me.storage.overlay !== null){
                me.storage.overlay.remove();
                me.storage.overlay = null;
            }

            $('a').unbind('click.wizard');
            $('form').unbind('submit.wizard');
        },

        /**
         * Moves hint to highlighted element
         */
        positionHintAtHighlight: function () {
            if (me.storage.overlay !== null) {
                me.storage.overlay.focus(me.elem.$highlight);
            } else {
                me.storage.overlay = new focusOverlay;
                me.storage.overlay.init(me.elem.$highlight);
            }

            var hintOptions = {
                my: 'left-10px top+15px',
                at: 'left bottom',
                of: me.elem.$highlight
            }

            var hintArrowOptions = {
                my: 'left top+5',
                at: 'left bottom',
                of: me.elem.$highlight
            }

            if(me.elem.$highlight.hasClass('hasDatepicker')){
                me.elem.$hint.find('.wizard-hint-arrow').addClass('point-right');
                hintOptions.my = 'left-230 top-50%';
                hintOptions.at = 'left bottom';

                hintArrowOptions.my = 'right+5 top-24';
                hintArrowOptions.at = 'left bottom';
            }

            window.setTimeout(function () {
                me.elem.$hint.position(hintOptions);
                me.elem.$hint.find('.wizard-hint-arrow').position(hintArrowOptions);
            },100);

            me.elem.$highlight.addClass('hint-added');
            me.elem.$highlight.addClass('wizard-highlight');
        },

        /**
         * Completes a step and informs the backend
         */
        completeStep: function () {
            var stepName = me.storage.activeStep.node.data('step-name'),
                parentGroup = me.storage.activeStep.node.data('parent-group'),
                parentSubgroup = me.storage.activeStep.node.data('parent-sub-group');

            if(me.elem.$highlight){
                me.elem.$highlight.removeClass('wizard-highlight');
            }

            me.storage.activeStep.node.addClass('checked');
            me.updateProgressBar();

            if (me.storage.overlay !== null) {
                me.storage.overlay.remove();
            }

            me.updateSubGroupProgress(parentGroup, parentSubgroup, stepName);
        },

        /**
         *
         * @param {String} groupName
         * @param {String} subGroupName
         * @param {String} stepName
         */
        updateSubGroupProgress: function (groupName, subGroupName, stepName) {
            var steps = me.storage.stepGroups[groupName].sub_groups[subGroupName].steps;
            var currentStep = steps[stepName];

            //set step completed in storage
            currentStep.completed = true;

            // check how many siblings the subgroup has and if they are completed
            var stepCounter = 0,
                completeSteps = 0;
            for (var step in steps) {
                if (!steps.hasOwnProperty(step)) {
                    continue;
                }

                if (steps[step].completed) {
                    completeSteps++;
                }

                stepCounter++;
            }

            if (stepCounter === completeSteps) {
                me.storage.stepGroups[groupName].sub_groups[subGroupName].completed = true;

                me.postCompleteStepGroup(groupName, subGroupName);
            }
        },

        /**
         *
         * @param {String} groupName
         * @param {String} subGroupName
         */
        postCompleteStepGroup: function(groupName, subGroupName){
            me.storage.ajaxRunning = true;

            $.ajax({
                url: 'index.php?module=wizard&action=ajax&cmd=complete_step&key=' + me.storage.key,
                method: 'post',
                data: {
                    step: groupName + '-' + subGroupName,
                    link: '.' + window.location.href.substring(window.location.href.indexOf('/index'))
                },
                dataType: 'json',
                success: function (data) {
                    // we don't need to know
                    console.info('ajax done')
                },
                error: function (xhr, ajaxOptions, thrownError) {
                    console.warn(xhr.status, thrownError);
                }
            }).always(function () {
                me.storage.ajaxRunning = false;

                if(me.storage.submitPrevented){
                    me.storage.submitPrevented.unbind('submit.wizard').submit();
                    me.storage.submitPrevented = null;
                }

                if(me.storage.linkPrevented){
                    window.location.href = me.storage.linkPrevented.attr('href');

                    me.storage.linkPrevented = null;
                }
            });
        },

        /**
         * Finds and triggers the next open step
         * @param {String|null} presetHighlight
         */
        nextStep: function (presetHighlight) {
            if(me.storage.minimized){
                return;
            }

            $('.wizard-step').removeClass('next-step');

            // regular next step
            var incompleteSteps = $('.wizard-step:not(.checked)'),
                nextStep = incompleteSteps[0];

            $(nextStep).addClass('next-step').trigger('click');
            $(nextStep).parents('.wizard-step-group').removeClass("closed");
            $(nextStep).parents('.wizard-step-group').addClass("in-progress");
        },

        /**
         * Looks for a element in DOM
         * @param {String} selector
         * @returns {jQuery|HTMLElement}
         */
        findHighlightElement: function (selector) {

            var selectorString = '',
                element;

            if (selector.node.parent) {
                addSelectors(selector.node.parent);
            }

            if (selector.node.previous_sibling) {
                var initialLength = selectorString.length,
                    processedLength = 0;
                selectorString += initialLength > 0 ? ' ' : '';

                addSelectors(selector.node.previous_sibling);

                processedLength = selectorString.length;

                if (initialLength < processedLength) {
                    selectorString += ' +';
                }
            }

            if (selector.node.self) {
                selectorString += selectorString.length > 0 ? ' ' : '';

                addSelectors(selector.node.self);
            }

            element = $(selectorString).filter(function () { return $(this).css('display') !== 'none'; });

            if (element.length === 0) {
                console.warn('element not found in dom', selectorString);
                return '';
            } else {
                element.data('data-event', selector.complete_event);
            }

            /**
             * Combines
             * @param object
             */
            function addSelectors(object) {
                for (key in object) {
                   if(!object.hasOwnProperty(key)){
                        continue;
                    }

                   var current = object[key];

                    if (key === 'node_name' && current.length > 0) {
                        selectorString += current;

                    } else if (key === 'class' && current.length > 0) {

                        if(current.indexOf(' ') > - 1){
                            current = current.split(' ');

                            for(var i = 0; i < current.length; i++){
                                selectorString += '.' + current[i]
                            }

                        } else {
                            selectorString += '.' + current;
                        }
                    } else if(key === 'contains') {
                        selectorString += ':contains("' + current + '")';
                    } else if(key === 'css_selector'){
                        selectorString += current;
                    } else if (current.length > 0) {
                        selectorString += '[' + key + '="' + current + '"]';
                    }
                }
            }

            if(element.length > 1){
                element = $(element[0]);
            }

            return element;
        },

        /**
         * Updates progress bar on step group
         */
        updateProgressBar: function () {
            var progress = $('.wizard-progress');

            if (progress.length === 0) {
                return;
            }

            var totalSteps = 0;

            for (var j = 0; j < progress.length; j++) {
                var steps = $(progress[j]).parent('ul').find('li'),
                    progressEle = $(progress[j]).find('.progress'),
                    count = 0;

                totalSteps = steps.length;

                for (var i = 0; i < steps.length; i++) {
                    if ($(steps[i]).hasClass('checked')) {
                        count++;
                    }
                }

                progressEle.css('width', count / totalSteps * 100 + '%');
                progressEle.attr('data-value', count);
                progressEle.trigger('progress-update');

            }
        },

        /**
         * Generates the HTML content of a step group
         * @param {string} groupName
         * @param {Object} group
         * @param {Number} groupIndex
         *
         * @returns {string}
         */
        generateStepGroupHtml: function (groupName, group, groupIndex) {
            var stepsTotal = 0,
                stepsFinished = 0,
                self = this,
                groupHtml =
                '<div data-name="' + groupName + '" class="wizard-step-group {{groupActive}} {{groupComplete}} {{groupClosed}}">' +
                '<h4 data-number="{{groupIndex}}">{{groupTitle}}</h4><div class="wizard-group-content"><ul>';

            // subgroup
            var subGroupIndex = 0;

            for (var subGroup in group.sub_groups) {
                if (!group.sub_groups.hasOwnProperty(subGroup)) {
                    continue;
                }

                var subGroupComplete = '';
                if(group.sub_groups[subGroup].completed){
                    subGroupComplete = 'sub-group-complete';

                    stepsFinished += Object.keys(group.sub_groups[subGroup].steps).length;
                }

                groupHtml += '<div class="wizard-sub-group '+ subGroupComplete +'">';

                // create steps
                var stepIndex = 0;
                for (var step in group.sub_groups[subGroup].steps) {
                    if (!group.sub_groups[subGroup].steps.hasOwnProperty(step)) {
                        continue;
                    }

                    groupHtml += self.generateStepHTML(group.sub_groups[subGroup].steps[step], stepIndex, groupName,
                        subGroup, step, group.sub_groups[subGroup].completed);

                    stepsTotal++;
                    stepIndex++;
                }

                groupHtml += '</div>';

                subGroupIndex++;
            }

            groupHtml += '<div class="wizard-progress">' +
                '               <div class="progress" style="width: {{progress}}%" data-value="{{stepsFinished}}" data-max="{{stepsTotal}}"></div>' +
                '         </div></ul></div></div>';

            groupHtml = groupHtml.replace('{{progress}}', stepsFinished / stepsTotal * 100);
            groupHtml = groupHtml.replace('{{stepsFinished}}', stepsFinished);
            groupHtml = groupHtml.replace('{{stepsTotal}}', stepsTotal);
            groupHtml = groupHtml.replace('{{groupIndex}}', groupIndex + 1);
            groupHtml = groupHtml.replace('{{groupTitle}}', group.title);
            groupHtml = groupHtml.replace('{{groupActive}}', group.active ? 'active' : '');
            groupHtml = groupHtml.replace('{{groupComplete}}', group.complete || group.completed ? 'complete closed' : '');
            groupHtml = groupHtml.replace('{{groupClosed}}', group.closed ? 'closed' : '');

            return groupHtml;
        },

        /**
         *
         * @param {Object} item
         * @param {Number} index
         * @param {String} parentGroup
         * @param {String} parentSubGroup
         * @param {String} stepName
         *
         * @returns {*}
         */
        generateStepHTML: function (item, index, parentGroup, parentSubGroup, stepName, parentSubgroupCompleted) {
            var captionHtml = '',
                classes = item.completed === true || parentSubgroupCompleted === true ? 'wizard-step checked' : 'wizard-step';

            if(item.visible !== undefined && item.visible === false){
                classes += ' invisible';
            }

            if (typeof item.title !== 'undefined' && item.title !== null) {
                captionHtml = '<span class="caption">' + item.title + '</span>';
            }

            var itemHtml =
                '<li ' +
                'id="{{id}}" ' +
                'class="{{classes}}" ' +
                'data-key="{{key}}" ' +
                'data-link="{{link}}" ' +
                'data-step-index="{{stepIndex}}" ' +
                'data-action="{{action}}" ' +
                'data-hint-title="{{hintTitle}}" ' +
                'data-hint-cta="{{hintCta}}" ' +
                'data-hint-content="{{hintContent}}"' +
                'data-parent-group="{{parent-group}}"' +
                'data-step-name="{{step-name}}"' +
                'data-parent-sub-group="{{parent-sub-group}}">' +
                '{{captionLine}}' +
                '</li>';

            itemHtml = itemHtml.replace('{{id}}', me.storage.key + '_' + item.position);
            itemHtml = itemHtml.replace('{{classes}}', classes);
            itemHtml = itemHtml.replace('{{link}}', item.link);
            itemHtml = itemHtml.replace('{{key}}', item.position);
            itemHtml = itemHtml.replace('{{stepIndex}}', index);
            itemHtml = itemHtml.replace('{{captionLine}}', captionHtml);
            itemHtml = itemHtml.replace('{{step-name}}', stepName);
            itemHtml = itemHtml.replace('{{parent-sub-group}}', parentSubGroup);
            itemHtml = itemHtml.replace('{{parent-group}}', parentGroup);
            if (item.action) {
                item.action = JSON.stringify(item.action).replace(/"/g, '\'');

                itemHtml = itemHtml.replace('{{action}}', item.action);
                itemHtml = itemHtml.replace('{{hintTitle}}', item.caption);
                itemHtml = itemHtml.replace('{{hintContent}}', item.description);
                itemHtml = itemHtml.replace('{{hintCta}}', item.hint_cta);
            }

            return itemHtml;
        },

        /**
         * Generates the content of the wizard
         */
        generateContainerContents: function () {
            var settings = me.storage.settings,
                subTitle,
                groupsHtml = '',
                content,
                iterationIndex = 0,
                previousGroupComplete = false;

            for (var stepGroup in me.storage.stepGroups) {
                if (!me.storage.stepGroups.hasOwnProperty(stepGroup)) {
                    return;
                }

                if(iterationIndex > 0){
                    me.storage.stepGroups[stepGroup].closed = !previousGroupComplete;
                }

                groupsHtml += me.generateStepGroupHtml(stepGroup, me.storage.stepGroups[stepGroup], iterationIndex);

                previousGroupComplete = me.storage.stepGroups[stepGroup].completed;
                iterationIndex++;
            }

            content =
                '<div class="wizard-small-trigger"></div>'+
                '<div class="wizard-details">' +
                '  <h2>{{titleMinimized}}</h2>' +
                '  <h3>{{subTitle}}</h3>' +
                '  {{groupsHtml}}' +
                '   <span class="close-wizard">Wizard beenden</span>' +
                '</div>';

            content +=
                '<div class="wizard-details wizard-overlay">' +
                '<h2>Wizard erfolgreich beendet!</h2>' +
                '  <p>Du kannst nun zurück zum Learning Dashboard oder hier bleiben</p>' +
                '  <a title="Zurück zum Learning Dashboard" href="index.php?module=learningdashboard&action=list" class="button button-primary">Zurück zum Learning Dashboard</a>' +
                '</div>';

            if (typeof currentStepDescription !== 'undefined' && currentStepDescription !== null) {
                subTitle = '<div class="wizard-description">' + currentStepDescription + '</div>';
            }

            content = content.replace('{{titleMinimized}}', settings.title);
            content = content.replace('{{titleMaximized}}', settings.title);
            content = content.replace('{{groupsHtml}}', groupsHtml);
            content = content.replace('{{subTitle}}', settings.subTitle);

            // Wizard-Element erzeugen und in Wizard-Box packen
            me.elem.$self = $('<div>')
                .addClass('wizard closed')
                .attr('id', 'wizard_' + me.storage.key)
                .data('key', me.storage.key)
                .html(content);
            me.elem.$self.appendTo(me.elem.$container);

            me.registerEvents();
        },

        /**
         * @return {object|null}
         */
        getCurrentStepSettings: function () {
            var currentStep = null;

            // rand-Parameter aus aktueller URL entfernen
            var currentUrl = window.location.href.replace(/&rand=[\d]*/, '');

            me.storage.stepGroups.forEach(function (group) {

                group.steps.forEach(function (step) {
                    // Führenden Punkt aus Schritt-URL entfernen: './index.php?foo' => '/index.php?foo'
                    // Ansonsten ist Vergleich mit aktueller URL nicht möglich
                    var dotPrefixRegex = /\.\/index\.php/;
                    var stepUrl = step.link.replace(dotPrefixRegex, '/index.php');

                    var isCurrentStep = me.stringEndsWith(currentUrl, stepUrl);
                    if (isCurrentStep) {
                        currentStep = step;
                    }
                });
            });

            return currentStep;
        },

        /**
         * @param {string} haystack
         * @param {string} needle
         * @return {boolean}
         */
        stringEndsWith: function (haystack, needle) {
            var position = haystack.length - needle.length;
            if (position < 0) {
                return false;
            }

            var lastIndex = haystack.indexOf(needle, position);

            return lastIndex !== -1 && lastIndex === position;
        }
    };

    return {
        init: me.init,
        show: me.show,
        hide: me.hide,
        completeStep: me.completeStep,
        nextStep: me.nextStep,
        minimize: me.minimize,
        maximize: me.maximize,
        isMinimized: me.isMinimized,
        removeHint: me.removeHint
    };
};


var WizardContainer = (function ($) {

    var me = {

        elem: {
            $container: null,
            $hint: null,
            $hideTrigger: null
        },

        storage: {
            initialized: false,
            fetchedWizardData: null,
            minimized: false,
            wizards: {} // Loaded wizard instances
        },

        init: function () {
            if (!me.isEnabled()) {
                console.info('Wizard ist deaktiviert.');
                return;
            }
            if (me.isInitialized()) {
                console.warn('Wizard ist bereits initialisiert!');
                return;
            }

            me.storage.initialized = true;

            me.fetchWizards().then(me.createContainerPromise).then(me.createWizards);
        },

        show: function () {
            me.elem.$hideTrigger.removeClass('is-hidden');
            me.elem.$container.removeClass('is-hidden');
            me.elem.$container.css({
                opacity: '1',
                display: 'block'
            });

            me.setWizardMinimizedState(false);

            me.storage.minimized = false;
        },

        hide: function () {
            me.elem.$hideTrigger.addClass('is-hidden');
            me.elem.$container.addClass('is-hidden');
            me.elem.$container.css({
                opacity: '0',
                display: 'none'
            });

            $('a').unbind('click.wizard');
            $('form').unbind('submit.wizard');

            me.setWizardMinimizedState(true);

            me.storage.minimized = true;

            for(var wizard in me.storage.wizards){
                me.storage.wizards[wizard].removeHint();
            }
        },

        completeStep: function(){
            for(var wizard in me.storage.wizards){
                me.storage.wizards[wizard].completeStep();
                me.storage.wizards[wizard].nextStep();
            }
        },

        /**
         *
         * @param {Boolean} isMinimized
         */
        setWizardMinimizedState: function(isMinimized) {

            $.ajax({
                url: 'index.php?module=wizard&action=ajax&cmd=set_minimized&value=' + isMinimized,
                method: 'post',
                success: function(data){

                },
                error: function (jqXhr) {
                    console.warn('Fehler: ' + jqXhr.responseJSON.error);
                }
            });
        },

        enable: function () {
            localStorage.setItem('wizard_enabled', 'true');
            me.init();
        },

        isMinimized: function(){
            return me.storage.minimized;
        },

        disable: function () {
            me.hide();
            me.storage.initialized = false;
            me.elem.$container.remove();
            me.elem.$container = null;
            localStorage.setItem('wizard_enabled', 'false');
        },

        /**
         * @return {boolean}
         */
        isEnabled: function () {
            var store = localStorage.getItem('wizard_enabled');

            return typeof store !== 'undefined' && store !== 'false';
        },

        /**
         * @return {boolean}
         */
        isInitialized: function () {
            return me.storage.initialized;
        },

        /**
         * Creates wizard container only if there is at least one wizard
         *
         * @return {Promise}
         */
        createContainerPromise: function () {
            return $.Deferred(function (dfd) {
                if (me.storage.fetchedWizardData === null) {
                    console.info('No active wizards found.');
                    dfd.fail();
                    return;
                }

                me.storage.minimized = me.storage.fetchedWizardData.minimized;

                me.createContainerElement();
                dfd.resolve();
            }).promise();
        },

        /**
         * Creates wizard container and events
         */
        createContainerElement: function () {
            me.elem.$container = $('<div id="wizard-container"></div>').appendTo('body');
            me.elem.$container.css({
                opacity: '0',
                display: 'none'
            });

            // Make container draggable
            me.elem.$container.draggable({
                start: function () {
                    $(this).css({transition: 'none'});
                },
                stop: function () {
                    $(this).css({height: 'auto'}); // otherwise jQuery would set a fixed height
                }
            });

            // Hide/Show wizard container
            me.elem.$hideTrigger = $(
                '<div id="wizard-hide-trigger">' +
                '<div class="wizard-trigger-overlay"></div>' +
                '<div class="wizard-close-x"></div>' +
                '</div>').appendTo('body');

            if(me.storage.minimized){
                me.elem.$hideTrigger.addClass('is-hidden');
            }

            me.elem.$hideTrigger.on('click touch', function (e) {
                e.preventDefault();
                if (me.elem.$hideTrigger.hasClass('is-hidden')) {
                    me.show();
                    me.storage.minimized = false;
                } else {
                    me.hide();
                    me.storage.minimized = true;
                }
            });
        },

        /**
         * Fetch active wizards
         *
         * @return {Deferred} jQuery Promise Object
         */
        fetchWizards: function () {
            var activeWizardEle = $('#active-wizard'),
                key;

            if (activeWizardEle.length > 0) {
                key = JSON.parse(activeWizardEle.text());
            }

            if (key === undefined || typeof key.key !== 'string') {
                return $.Deferred(function (dfd) {
                    dfd.reject();
                }).promise();
            } else {
                return $.ajax({
                    url: 'index.php?module=wizard&action=ajax&cmd=get_by_key&key=' + key.key,
                    method: 'get',
                    success: function (data) {
                        me.storage.fetchedWizardData = data;
                    }
                });
            }
        },

        /**
         *
         */
        createWizards: function () {
            me.createWizard(me.storage.fetchedWizardData);
            if(!me.storage.minimized){
                me.show();
            }
        },

        /**
         * @param {Object} data
         */
        createWizard: function (data) {
            var wizardInstance = new Wizard($);
            var wizardKey = data.key;

            wizardInstance.init(data);
            me.storage.wizards[wizardKey] = wizardInstance;
        },

        /**
         * @param {string} key
         *
         * @return {boolean}
         */
        hasWizard: function (key) {
            return me.storage.wizards.hasOwnProperty(key);
        },

        /**
         * @param {string} key
         *
         * @return {Wizard}
         */
        getWizard: function (key) {
            return me.storage.wizards[key];
        },

        /**
         * @param {String} wizardKey
         */
        skipWizard: function (wizardKey) {
            if (!me.hasWizard(wizardKey)) {
                return;
            }

            $.ajax({
                url: 'index.php?module=wizard&action=ajax&cmd=deactivate_wizard',
                method: 'post',
                data: {
                    wizard: wizardKey
                },
                dataType: 'json',
                success: function () {
                    me.getWizard(wizardKey).hide();
                },
                error: function (jqXhr) {
                    alert('Fehler: ' + jqXhr.responseJSON.error);
                }
            });
        }

    };

    return {
        init: me.init,
        show: me.show,
        hide: me.hide,
        enable: me.enable,
        disable: me.disable,
        completeStep: me.completeStep,
        isMinimized: me.isMinimized
    };

})(jQuery);

$(document).ready(function () {
    WizardContainer.init();
});


var focusOverlay = function () {

    var me = {

        storage: {
            target: {},
            window: {},
            targetOffset: null,
            settings: null,
            platesPositions: ['top', 'right', 'bottom', 'left']
        },

        elem: {
            $container: null,
            $target: null,
            $self: null,
            $window: null,
            $plates: []
        },

        init: function (element) {
            if (element === undefined || element.length === 0) {
                console.warn('Please pass an element to focus');
                return;
            }

            me.elem.$container = $('<div id="focus-overlay"></div>').appendTo('body');
            me.addEventHandler();

            me.focus(element);
        },

        addEventHandler: function () {
            var resizeTimeout;
            $(window).on('resize scroll', function () {
                if (!!resizeTimeout) {
                    clearTimeout(resizeTimeout);
                }
                resizeTimeout = setTimeout(function () {
                    me.storeWindow();
                    me.focus();
                }, 10);
            });
        },

        storeElement: function (element) {
            me.elem.$target = element;
            me.storage.target.offset = element.offset();
            me.storage.target.width = element.outerWidth();
            me.storage.target.height = element.outerHeight();
        },

        storeWindow: function () {
            me.elem.$window = $(window);
            me.storage.window.width = me.elem.$window.outerWidth();
            me.storage.window.height = me.elem.$window.outerHeight();
            me.storage.window.scrollTop = me.elem.$window.scrollTop();
        },

        focus: function (element) {
            element = element || me.elem.$target;
            me.storeElement(element);
            me.storeWindow();

            me.addOverlay();
        },

        remove: function () {
            me.elem.$container.remove();
        },

        addOverlay: function () {
            var current,
                overlay,
                finishedOptions = [],
                overlayTemplate = $('<div class="focus-overlay-plate"></div>');

            for (var i = 0; i < me.storage.platesPositions.length; i++) {
                current = me.storage.platesPositions[i];

                if (me.elem.$plates.length < 4) {
                    overlay = overlayTemplate.clone();
                } else {
                    overlay = me.elem.$plates[i];
                }

                var options = {
                        'width': 0,
                        'height': 0,
                        'top': 0,
                        'right': 0,
                        'bottom': 0,
                        'left': 0
                    };

                if(finishedOptions[0] !== undefined){
                    var topEleHeigt = finishedOptions[0].height;
                }

                if(finishedOptions[1] !== undefined){
                    var rightEleWidth = finishedOptions[1].width;
                }

                if(finishedOptions[2] !== undefined){
                    var bottomEleHeight = finishedOptions[2].height;
                }

                var elementViewportOffset = me.storage.target.offset.top - me.storage.window.scrollTop;

                if (current === 'top') {
                    options.width = '100%';
                    options.height = elementViewportOffset - 10;
                }

                if (current === 'right') {
                    options.width = me.storage.window.width - me.storage.target.offset.left - me.storage.target.width -
                        10;
                    options.height = me.storage.window.height - topEleHeigt;
                    options.top = topEleHeigt;
                    options.left = me.storage.target.offset.left + me.storage.target.width + 10;
                }

                if (current === 'bottom') {
                    options.width = me.storage.window.width - rightEleWidth;
                    options.height = me.storage.window.height - elementViewportOffset -
                        me.storage.target.height - 10;
                    options.top = elementViewportOffset + me.storage.target.height + 10;

                    if(me.elem.$target.hasClass('hasDatepicker')){
                        options.top += 150;
                        options.height -= 150;
                    }
                }

                if (current === 'left') {
                    options.width = me.storage.target.offset.left - 10;
                    options.height = me.storage.window.height - topEleHeigt -
                        bottomEleHeight;
                    options.top = topEleHeigt;
                }

                finishedOptions.push(options)

                if (me.elem.$plates.length < 4) {
                    overlay.addClass(current);
                    overlay.appendTo(me.elem.$container);
                    me.elem.$plates.push(overlay);
                }
            }

            // options are not pushed in for loop, in order to add them all at once. (animation issues)
            me.elem.$plates[0].css(finishedOptions[0]);
            me.elem.$plates[1].css(finishedOptions[1]);
            me.elem.$plates[2].css(finishedOptions[2]);
            me.elem.$plates[3].css(finishedOptions[3]);

        }
    };

    return {
        init: me.init,
        focus: me.focus,
        remove: me.remove
    };
};