/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */

/**
 * Icinga.UI
 *
 * Our user interface
 */
(function(Icinga, $) {

    'use strict';

    Icinga.UI = function (icinga) {

        this.icinga = icinga;

        this.currentLayout = 'default';

        this.debug = false;

        this.debugTimer = null;

        this.timeCounterTimer = null;

        this.mobileMenu = false;
    };

    Icinga.UI.prototype = {

        initialize: function () {
            $('html').removeClass('no-js').addClass('js');
            this.enableTimeCounters();
            this.triggerWindowResize();
            this.fadeNotificationsAway();
        },

        fadeNotificationsAway: function() {
            var icinga = this.icinga;
            $('#notifications li')
                .not('.fading-out')
                .not('.persist')
                .addClass('fading-out')
                .delay(7000)
                .fadeOut('slow',
            function() {
                icinga.ui.fixControls();
                $(this).remove();
            });

        },

        toggleDebug: function() {
            if (this.debug) {
                return this.disableDebug();
            } else {
                return this.enableDebug();
            }
        },

        enableDebug: function () {
            if (this.debug === true) { return this; }
            this.debug = true;
            this.debugTimer = this.icinga.timer.register(
                this.refreshDebug,
                this,
                1000
            );
            this.fixDebugVisibility();

            return this;
        },

        fixDebugVisibility: function () {
            if (this.debug) {
                $('#responsive-debug').css({display: 'block'});
            } else {
                $('#responsive-debug').css({display: 'none'});
            }
            return this;
        },

        disableDebug: function () {
            if (this.debug === false) { return; }

            this.debug = false;
            this.icinga.timer.unregister(this.debugTimer);
            this.debugTimer = null;
            this.fixDebugVisibility();
            return this;
        },

        reloadCss: function () {
            var icinga = this.icinga;
            icinga.logger.info('Reloading CSS');
            $('link').each(function() {
                var $oldLink = $(this);
                if ($oldLink.hasAttr('type') && $oldLink.attr('type').indexOf('css') > -1) {
                    var $newLink = $oldLink.clone().attr(
                        'href',
                        icinga.utils.addUrlParams(
                            $(this).attr('href'),
                            { id: new Date().getTime() }
                        )
                    ).on('load', function() {
                        icinga.ui.fixControls();
                        $oldLink.remove();
                    });
                    $newLink.appendTo($('head'));
                }
            });
        },

        enableTimeCounters: function () {
            this.timeCounterTimer = this.icinga.timer.register(
                this.refreshTimeSince,
                this,
                1000
            );
            return this;
        },

        disableTimeCounters: function () {
            this.icinga.timer.unregister(this.timeCounterTimer);
            this.timeCounterTimer = null;
            return this;
        },

        /**
         * Focus the given element and scroll to its position
         *
         * @param   {string}    element     The name or id of the element to focus
         * @param   {object}    $container  The container containing the element
         */
        focusElement: function(element, $container) {
            var $element = $('#' + element, $container);

            if (! $element.length) {
                // The name attribute is actually deprecated, on anchor tags,
                // but we'll possibly handle links from another source
                // (module etc) so that's used as a fallback
                $element = $('[name="' + element.replace(/'/, '\\\'') + '"]', $container);
            }

            if ($element.length) {
                if (typeof $element.attr('tabindex') === 'undefined') {
                    $element.attr('tabindex', -1);
                }

                $element.focus();
                $container.scrollTop(0);
                $container.scrollTop($element.first().position().top);
            }
        },

        moveToLeft: function () {
            var col2 = this.cutContainer($('#col2'));
            var kill = this.cutContainer($('#col1'));
            this.pasteContainer($('#col1'), col2);
            this.fixControls();
            this.icinga.behaviors.navigation.trySetActiveByUrl($('#col1').data('icingaUrl'));
        },

        cutContainer: function ($col) {
            var props = {
              'elements': $('#' + $col.attr('id') + ' > *').detach(),
              'data': {
                'data-icinga-url': $col.data('icingaUrl'),
                'data-icinga-refresh': $col.data('icingaRefresh'),
                'data-last-update': $col.data('lastUpdate'),
                'data-icinga-module': $col.data('icingaModule')
              },
              'class': $col.attr('class')
            };
            this.icinga.loader.stopPendingRequestsFor($col);
            $col.removeData('icingaUrl');
            $col.removeData('icingaRefresh');
            $col.removeData('lastUpdate');
            $col.removeData('icingaModule');
            $col.removeAttr('class').attr('class', 'container');
            return props;
        },

        pasteContainer: function ($col, backup) {
            backup['elements'].appendTo($col);
            $col.attr('class', backup['class']); // TODO: ie memleak? remove first?
            $col.data('icingaUrl', backup['data']['data-icinga-url']);
            $col.data('icingaRefresh', backup['data']['data-icinga-refresh']);
            $col.data('lastUpdate', backup['data']['data-last-update']);
            $col.data('icingaModule', backup['data']['data-icinga-module']);
        },

        triggerWindowResize: function () {
            this.onWindowResize({data: {self: this}});
        },

        /**
         * Our window got resized, let's fix our UI
         */
        onWindowResize: function (event) {
            var self = event.data.self;

            if (self.layoutHasBeenChanged()) {
                self.icinga.logger.info(
                    'Layout change detected, switching to',
                    self.currentLayout
                );
            }
            self.fixControls();
            self.refreshDebug();
        },

        /**
         * Returns whether the layout is too small for more than one column
         *
         * @returns {boolean}   True when more than one column is available
         */
        hasOnlyOneColumn: function () {
            return this.currentLayout === 'poor' || this.currentLayout === 'minimal';
        },

        layoutHasBeenChanged: function () {

            var layout = $('html').css('fontFamily').replace(/['",]/g, '');
            var matched;

            if (null !== (matched = layout.match(/^([a-z]+)-layout$/))) {
                if (matched[1] === this.currentLayout &&
                    $('#layout').hasClass(layout)
                ) {
                    return false;
                } else {
                    $('#layout').removeClass(this.currentLayout + '-layout').addClass(layout);
                    this.currentLayout = matched[1];
                    if (this.currentLayout === 'poor' || this.currentLayout === 'minimal') {
                        this.layout1col();
                    }
                    return true;
                }
            }
            this.icinga.logger.error(
                'Someone messed up our responsiveness hacks, html font-family is',
                layout
            );
            return false;
        },

        /**
         * Returns whether only one column is displayed
         *
         * @returns {boolean}   True when only one column is displayed
         */
        isOneColLayout: function () {
            return ! $('#layout').hasClass('twocols');
        },

        layout1col: function () {
            if (this.isOneColLayout()) { return; }
            this.icinga.logger.debug('Switching to single col');
            $('#layout').removeClass('twocols');
            this.closeContainer($('#col2'));
            this.disableCloseButtons();

            // one-column layouts never have any selection active
            this.icinga.behaviors.actiontable.clearAll();
        },

        closeContainer: function($c) {
            $c.removeData('icingaUrl');
            $c.removeData('icingaRefresh');
            $c.removeData('lastUpdate');
            $c.removeData('icingaModule');
            this.icinga.loader.stopPendingRequestsFor($c);
            $c.html('');
            this.fixControls();
        },

        layout2col: function () {
            if (! this.isOneColLayout()) { return; }
            this.icinga.logger.debug('Switching to double col');
            $('#layout').addClass('twocols');
            this.fixControls();
            this.enableCloseButtons();
        },

        getAvailableColumnSpace: function () {
            return $('#main').width() / this.getDefaultFontSize();
        },

        setColumnCount: function (count) {
            if (count === 3) {
                $('#main > .container').css({
                    width: '33.33333%'
                });
            } else if (count === 2) {
                $('#main > .container').css({
                    width: '50%'
                });
            } else {
                $('#main > .container').css({
                    width: '100%'
                });
            }
        },

        setTitle: function (title) {
            document.title = title;
            return this;
        },

        getColumnCount: function () {
            return $('#main > .container').length;
        },

        /**
         * Assign a unique ID to each .container without such
         *
         * This usually applies to dashlets
         */
        assignUniqueContainerIds: function() {
            var currentMax = 0;
            $('.container').each(function() {
                var $el = $(this);
                var m;
                if (!$el.attr('id')) {
                    return;
                }
                if (m = $el.attr('id').match(/^ciu_(\d+)$/)) {
                    if (parseInt(m[1]) > currentMax) {
                         currentMax = parseInt(m[1]);
                    }
                }
            });
            $('.container').each(function() {
                var $el = $(this);
                if (!!$el.attr('id')) {
                    return;
                }
                currentMax++;
                $el.attr('id', 'ciu_' + currentMax);
            });
        },

        refreshDebug: function () {

            var size = this.getDefaultFontSize().toString();
            var winWidth = $( window ).width();
            var winHeight = $( window ).height();
            var loading = '';

            $.each(this.icinga.loader.requests, function (el, req) {
                if (loading === '') {
                    loading = '<br />Loading:<br />';
                }
                loading += el + ' => ' + req.url;
            });

            $('#responsive-debug').html(
                '   Time: ' +
                this.icinga.utils.formatHHiiss(new Date()) +
                '<br />    1em: ' +
                size +
                'px<br />    Win: ' +
                winWidth +
                'x'+
                winHeight +
                'px<br />' +
                ' Layout: ' +
                this.currentLayout +
                loading
            );
        },

        /**
         * Refresh partial time counters
         *
         * This function runs every second.
         */
        refreshTimeSince: function () {
            $('.time-ago, .time-since').each(function (idx, el) {
                var partialTime = /(\d{1,2})m (\d{1,2})s/.exec(el.innerHTML);
                if (partialTime !== null) {
                    var minute = parseInt(partialTime[1], 10),
                        second = parseInt(partialTime[2], 10);
                    if (second < 59) {
                        ++second;
                    } else {
                        ++minute;
                        second = 0;
                    }
                    el.innerHTML = el.innerHTML.substr(0, partialTime.index) + minute.toString() + 'm '
                        + second.toString() + 's' + el.innerHTML.substr(partialTime.index + partialTime[0].length);
                }
            });

            $('.time-until').each(function (idx, el) {
                var partialTime = /(-?)(\d{1,2})m (\d{1,2})s/.exec(el.innerHTML);
                if (partialTime !== null) {
                    var minute = parseInt(partialTime[2], 10),
                        second = parseInt(partialTime[3], 10),
                        invert = partialTime[1];
                    if (invert.length) {
                        // Count up because partial time is negative
                        if (second < 59) {
                            ++second;
                        } else {
                            ++minute;
                            second = 0;
                        }
                    } else {
                        // Count down because partial time is positive
                        if (second === 0) {
                            if (minute === 0) {
                                // Invert counter
                                minute = 0;
                                second = 1;
                                invert = '-';
                            } else {
                                --minute;
                                second = 59;
                            }
                        } else {
                            --second;
                        }
                    }
                    el.innerHTML = el.innerHTML.substr(0, partialTime.index) + invert + minute.toString() + 'm '
                        + second.toString() + 's' + el.innerHTML.substr(partialTime.index + partialTime[0].length);
                }
            });
        },

        createFontSizeCalculator: function () {
            var $el = $('<div id="fontsize-calc">&nbsp;</div>');
            $('#layout').append($el);
            return $el;
        },

        getDefaultFontSize: function () {
            var $calc = $('#fontsize-calc');
            if (! $calc.length) {
                $calc = this.createFontSizeCalculator();
            }
            return $calc.width() / 1000;
        },

        /**
         * Initialize all TriStateCheckboxes in the given html
         */
        initializeTriStates: function ($html) {
            var self = this;
            $('div.tristate', $html).each(function(index, item) {
                var $target  = $(item);

                // hide input boxess and remove text nodes
                $target.find("input").hide();
                $target.contents().filter(function() { return this.nodeType === 3; }).remove();

                // has three states?
                var triState = $target.find('input[value="unchanged"]').size() > 0 ? 1 : 0;

                // fetch current value from radiobuttons
                var value  = $target.find('input:checked').first().val();

                $target.append(
                  '<input class="tristate-dummy" ' +
                        ' data-icinga-old="' + value + '" data-icinga-tristate="' + triState + '" type="checkbox" ' +
                        (value === '1' ? 'checked ' : ( value === 'unchanged' ? 'indeterminate="true" ' : ' ' )) +
                  '/> <b style="visibility: hidden;" class="tristate-changed"> (changed) </b>'
                );
                if (triState) {
                  // TODO: find a better way to activate indeterminate checkboxes after load.
                  $target.append(
                    '<script type="text/javascript"> ' +
                      ' $(\'input.tristate-dummy[indeterminate="true"]\').each(function(i, el){ el.indeterminate = true; }); ' +
                    '</script>'
                  );
                }
            });
        },

        /**
         * Set the value of the given TriStateCheckbox
         *
         * @param value     {String}  The value to set, can be '1', '0' and 'unchanged'
         * @param $checkbox {jQuery}  The checkbox
         */
        setTriState: function(value, $checkbox) {
            switch (value) {
                case ('1'):
                    $checkbox.prop('checked', true).prop('indeterminate', false);
                    break;
                case ('0'):
                    $checkbox.prop('checked', false).prop('indeterminate', false);
                    break;
                case ('unchanged'):
                    $checkbox.prop('checked', false).prop('indeterminate', true);
                    break;
            }
        },

        initializeControls: function (parent) {
            if ($(parent).closest('.dashboard').length || $('#layout').hasClass('fullscreen-layout')) {
                return;
            }

            $('.controls', parent).each(function (idx, el) {
                var $el = $(el);

                if (! $el.next('.fake-controls').length) {

                    var newdiv = $('<div class="fake-controls"></div>');
                    newdiv.css({
                        height: $el.css('height')
                    });
                    $el.after(newdiv);
                }
            });

            this.fixControls(parent);
        },

        disableCloseButtons: function () {
            $('a.close-toggle').hide();
        },

        enableCloseButtons: function () {
            $('a.close-toggle').show();
        },

        fixControls: function ($parent) {
            var self = this;
            if ($('#layout').hasClass('fullscreen-layout')) {
                return;
            }

            if ('undefined' === typeof $parent) {

                if (! $('#layout').hasClass('fullscreen-layout')) {
                    $('#header').css({height: 'auto'});
                    if ($('#layout').hasClass('minimal-layout')) {
                        if (! this.mobileMenu) {
                            $('#header').css({top: $('#sidebar').height() + 'px'});
                            $('#main').css({
                                top: $('#header').height() + $('#sidebar').height()
                            });
                            $('#sidebar').css({
                                zIndex: 2
                            }).on('click', function (e) {
                                var $sidebar = $('#sidebar');
                                var $target = $(e.target);
                                var href = $target.attr('href');
                                if (href) {
                                    if (href !== '#') {
                                        $sidebar.removeClass('expanded');
                                    }
                                } else if (! $target.is('input')) {
                                    $sidebar.toggleClass('expanded');
                                }
                                $('#search').keypress(function(e) {
                                    if (e.which === 13) {
                                        $sidebar.removeClass('expanded');
                                    }
                                });
                            }).prepend('<i class="icon-menu" style="vertical-align: middle;"></i>').css({
                                paddingBottom: 32
                            });
                            this.mobileMenu = true;
                        }
                    } else {
                        $('#main').css({top: $('#header').css('height')});
                        $('#sidebar').css({top: $('#header').height() + 'px'});
                    }
                    $('#header').css({height: $('#header').height() + 'px'});
                    $('#inner-layout').css({top: $('#header').css('height')});
                }
                $('.container').each(function (idx, container) {
                    self.fixControls($(container));
                });

                return;
            }

            if ($parent.closest('.dashboard').length) {
                return;
            }

            // Enable this only in case you want to track down UI problems
            // self.icinga.logger.debug('Fixing controls for ', $parent);

            $('.controls', $parent).each(function (idx, el) {
                var $el = $(el);

                if ($el.closest('.dashboard').length) {
                    return;
                }

                var $fake = $el.next('.fake-controls');
                var y = $parent.scrollTop();

                $el.css({
                    position : 'fixed',
                    top      : $parent.offset().top,
                    // Firefox gives 1px too much depending on total width.
                    // TODO: find a better solution for -1
                    width    : ($fake.outerWidth() - 1) + 'px'
                });

                $fake.css({
                    height  : $el.css('height'),
                    display : 'block'
                });
            });
        },

        toggleFullscreen: function () {
            $('#layout').toggleClass('fullscreen-layout');
            this.fixControls();
        },

        getWindowId: function () {
            if (! this.hasWindowId()) {
                return undefined;
            }
            return window.name.match(/^Icinga_([a-zA-Z0-9]+)$/)[1];
        },

        hasWindowId: function () {
            var res = window.name.match(/^Icinga_([a-zA-Z0-9]+)$/);
            return typeof res === 'object' && null !== res;
        },

        setWindowId: function (id) {
            this.icinga.logger.debug('Setting new window id', id);
            window.name = 'Icinga_' + id;
        },

        destroy: function () {
            // This is gonna be hard, clean up the mess
            this.icinga = null;
            this.debugTimer = null;
            this.timeCounterTimer = null;
        }
    };

}(Icinga, jQuery));
