//Author      : @arboshiki
/**
 * Generates random string of n length.
 * String contains only letters and numbers
 *
 * @param {int} n
 * @returns {String}
 */
Math.randomString = function(n) {
    var text = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    for (var i = 0; i < n; i++)
        text += possible.charAt(Math.floor(Math.random() * possible.length));

    return text;
};

/**
 * This function is for HTML element style attribute.
 * It converts the style attribute to css object
 *
 * @returns {object}
 */
String.prototype.getCss = function(){
    var css = {};
    var style = this.valueOf().split(';');
    for (var i = 0; i < style.length; i++) {
        style[i] = $.trim(style[i]);
        if (style[i]) {
            var s = style[i].split(':');
            css[$.trim(s[0])] = $.trim(s[1]);
        }
    }
    return css;
};
String.prototype.trim = function(){
    return this.replace(/^\s+|\s+$/g, "");
};

String.prototype.toCamel = function(){
    return this.replace(/(\-[a-z])/g, function($1){return $1.toUpperCase().replace('-','');});
};

String.prototype.toDash = function(){
    return this.replace(/([A-Z])/g, function($1){return "-"+$1.toLowerCase();});
};
String.prototype.toUnderscore = function(){
    return this.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});
};

/**
 * Checks if number is between two numbers
 *
 * @param {number} num1
 * @param {number} num2
 * @param {boolean} including "include these numbers in comparison or not" default false
 * @returns boolean
 */
Number.prototype.isBetween = function(num1, num2, including){
    if ( ! including){
        if (this.valueOf() < num2 && this.valueOf() > num1) {
            return true;
        }
    }else{
        if (this.valueOf() <= num2 && this.valueOf() >= num1) {
            return true;
        }
    }
    return false;
};

/**
 * Inserts element at specific index in given elements children
 *
 * @param {number} i
 * @param {string} selector
 * @returns {undefined}
 */
$.fn.insertAt = function(i, selector) {
    var object = selector;
    if (typeof selector === 'string'){
        object = $(selector);
    }

    i = Math.min(object.children().length, i);
    if(i == 0) {
        object.prepend(this);
        return this;
    }
    var oldIndex = this.data('index');

    this.attr('data-index', i);
    object.find(">*:nth-child(" + i + ")").after(this);
    object.children().each(function(index, el){
        var $el = $(el);
        if (oldIndex < i && index > oldIndex && index <= i){
            $el.attr('data-index', parseInt($el.data('data-index'), 10) - 1);
        } else if (oldIndex >= i && index > i && index <= oldIndex){
            $el.attr('data-index', parseInt($el.attr('data-index'), 10) + 1);
        }
    });
    return this;
};

$.fn.disableSelection = function() {
    return this
             .attr('unselectable', 'on')
             .css('user-select', 'none')
             .on('selectstart', false);
};

$.fn.enableSelection = function() {
    return this
             .removeAttr('unselectable')
             .css('user-select', 'initial')
             .off('selectstart');
};

$(function(){
    var LobiPanel = function($el, options) {
//------------------------------------------------------------------------------
//----------------PROTOTYPE VARIABLES-------------------------------------------
//------------------------------------------------------------------------------
        this.$el = null;
        this.$options = {};
        this.hasRandomId = false;
        this.storage = null;
//------------------------------------------------------------------------------
//-----------------PRIVATE VARIABLES--------------------------------------------
//------------------------------------------------------------------------------
        var $heading,
            $body,
            innerId,
            storagePrefix = 'lobipanel_',
            me = this;
//------------------------------------------------------------------------------
//-----------------PRIVATE FUNCTIONS--------------------------------------------
//------------------------------------------------------------------------------
        var _processInput = function(options){
            if ( ! options) {
                options = {};
            }

            var opts = _getOptionsFromAttributes();
//            window.console.log(opts);
            options = $.extend({}, $.fn.lobiPanel.DEFAULTS, me.storage, options, opts);
            var objects = ['unpin', 'reload', 'expand', 'minimize', 'close', 'editTitle'];
            for (var i=0; i<objects.length; i++){
                var prop = objects[i];
                if (typeof options[prop] === 'object'){
                    options[prop] = $.extend({}, $.fn.lobiPanel.DEFAULTS[prop], options[prop], opts[prop]);
                }
            }
            return options;
        };

        var _init = function(){
            me.$el.addClass('lobipanel');

            $heading.append(_generateControls());
//------------------------------------------------------------------------------
            var parent = me.$el.parent();
            _appendInnerIdToParent(parent, innerId);
            _enableSorting();
            _adjustForScreenSize();
            _onToggleIconsBtnClick();
            _enableResponsiveness();
            _setBodyHeight();
            if (me.$options.autoload){
                me.load();
            }
            var maxWidth = 'calc(100% - '+$heading.find('.dropdown-menu').children().length * $heading.find('.dropdown-menu li').first().outerWidth()+"px)";
            $heading.find('.panel-title').css('max-width', maxWidth);

            _triggerEvent("init");
        };
        var _generateControls = function(){
            var dropdown = _generateDropdown();
            var menu = dropdown.find('.dropdown-menu');
            if (me.$options.editTitle !== false){
                menu.append(_generateEditTitle());
            }
            if (me.$options.unpin !== false){
                menu.append(_generateUnpin());
            }
            if (me.$options.reload !== false){
                menu.append(_generateReload());
            }
            if (me.$options.minimize !== false){
                menu.append(_generateMinimize());
            }
            if (me.$options.expand !== false){
                menu.append(_generateExpand());
            }
            if (me.$options.close !== false){
                menu.append(_generateClose());
            }
            menu.find('>li>a').on('click', function (ev) {
                ev.preventDefault();
                ev.stopPropagation();
            });
            return dropdown;
        };
        var _generateDropdown = function(){
            return $('<div class="dropdown"></div>')
                    .append('<ul class="dropdown-menu dropdown-menu-right"></ul>')
                    .append('<div class="dropdown-toggle" data-toggle="dropdown"><span class="'+LobiPanel.PRIVATE_OPTIONS.iconClass+' '+me.$options.toggleIcon+'"></div>');
        };
        var _generateEditTitle = function(){
            var options = me.$options.editTitle;
            var control = $('<a data-func="editTitle"></a>');
            control.append('<i class="'+LobiPanel.PRIVATE_OPTIONS.iconClass+' '+options.icon+'"></i>');
            if (options.tooltip && typeof options.tooltip === 'string') {
                control.append('<span class="control-title">'+options.tooltip+'</span>');
                control.attr('data-tooltip', options.tooltip);
            }

            _onEditTitleClick(control);
            return $('<li></li>').append(control);
        };
        var _onEditTitleClick = function(control){
            control.on('mousedown', function(ev){
                ev.stopPropagation();
            });
            control.on('click', function (ev) {
                ev.stopPropagation();
                $heading.find('[data-func="editTitle"]').tooltip('hide');
                if (me.isTitleEditing()){
                    me.finishTitleEditing();
                }else{
                    me.startTitleEditing();
                }
            });
        };
        var _generateUnpin = function(){
            var options = me.$options.unpin;
            var control = $('<a data-func="unpin"></a>');
            control.append('<i class="'+LobiPanel.PRIVATE_OPTIONS.iconClass+' '+options.icon+'"></i>');
            if (options.tooltip && typeof options.tooltip === 'string') {
                control.append('<span class="control-title">'+options.tooltip+'</span>');
                control.attr('data-tooltip', options.tooltip);
            }
            _onUnpinClick(control);
            return $('<li></li>').append(control);
        };
        var _onUnpinClick = function(control){
            control.on('mousedown', function(ev){
                ev.stopPropagation();
            });
            control.on('click', function() {
                me.togglePin();
            });
        };
        var _generateReload = function(){
            var options = me.$options.reload;
            var control = $('<a data-func="reload"></a>');
            control.append('<i class="'+LobiPanel.PRIVATE_OPTIONS.iconClass + ' ' + options.icon + '"></i>');
            if (options.tooltip && typeof options.tooltip === 'string') {
                control.append('<span class="control-title">'+options.tooltip+'</span>');
                control.attr('data-tooltip', options.tooltip);
            }
            _onReloadClick(control);
            return $('<li></li>').append(control);
        };
        var _onReloadClick = function(control){
            control.on('mousedown', function(ev){
                ev.stopPropagation();
            });
            control.on('click', function() {
                me.load({
                    callback: function(){
                        control.tooltip('hide');
                    }
                });
            });
        };
        var _generateMinimize = function(){
            var options = me.$options.minimize;
            var control = $('<a data-func="minimize"></a>');
            control.append('<i class="'+LobiPanel.PRIVATE_OPTIONS.iconClass+' '+ options.icon + '"></i>');
            if (options.tooltip && typeof options.tooltip === 'string') {
                control.append('<span class="control-title">'+options.tooltip+'</span>');
                control.attr('data-tooltip', options.tooltip);
            }
            _onMinimizeClick(control);
            return $('<li></li>').append(control);
        };
        var _onMinimizeClick = function(control){
            control.on('mousedown', function(ev){
                ev.stopPropagation();
            });
            control.on('click', function(ev) {
                ev.stopPropagation();
                me.toggleMinimize();
            });
        };
        var _generateExpand = function(){
            var options = me.$options.expand;
            var control = $('<a data-func="expand"></a>');
            control.append('<i class="'+LobiPanel.PRIVATE_OPTIONS.iconClass+' '+options.icon+'"></i>');
            if (options.tooltip && typeof options.tooltip === 'string') {
                control.append('<span class="control-title">'+options.tooltip+'</span>');
                control.attr('data-tooltip', options.tooltip);
            }
            _onExpandClick(control);
            return $('<li></li>').append(control);
        };
        var _onExpandClick = function(control) {
            control.on('mousedown', function(ev){
                ev.stopPropagation();
            });
            control.on('click', function(ev) {
                ev.stopPropagation();
                me.toggleSize();
            });
        };
        var _generateClose = function() {
            var options = me.$options.close;
            var control = $('<a data-func="close"></a>');
            control.append('<i class="' + LobiPanel.PRIVATE_OPTIONS.iconClass + ' ' + options.icon + '"></i>');
            if (options.tooltip && typeof options.tooltip === 'string') {
                control.append('<span class="control-title">'+options.tooltip+'</span>');
                control.attr('data-tooltip', options.tooltip);
            }
            _onCloseClick(control);
            return $('<li></li>').append(control);
        };
        var _onCloseClick = function(control) {
            control.on('mousedown', function(ev){
                ev.stopPropagation();
            });
            control.on('click', function(ev) {
                ev.stopPropagation();
                control.tooltip('hide');
                me.close();
            });
        };
        var _getMaxZIndex = function(){
            var panels = $('.lobipanel.panel-unpin:not(.panel-minimized.panel-expanded)'),
                style,
                max,
                cur;
            if (panels.length === 0){
                return {
                    "id": "",
                    "z-index": LobiPanel.PRIVATE_OPTIONS.initialZIndex
                };
            }
            style = $(panels[0]).attr('style');
            var id = $(panels[0]).data('inner-id');
            if ( ! style){
                max = LobiPanel.PRIVATE_OPTIONS.initialZIndex;
            }else{
                max = style.getCss()['z-index'];
            }
            for (var i = 1; i<panels.length; i++){
                style = $(panels[i]).attr('style');
                if ( ! style){
                    cur = 0;
                }else{
                    cur = style.getCss()['z-index'];
                }
                if (cur > max){
                    id = $(panels[i]).data('inner-id');
                    max = cur;
                }
            }
            return {
                "id"        : id,
                "z-index"   : parseInt(max, 10)
            };
        };
        var _onPanelClick = function(){
            me.$el.on('mousedown.lobiPanel', function(){
                if (me.isPinned() ||
                        me.isMinimized() ||
                        me.isOnFullScreen()) {
                    return false;
                }
                me.bringToFront();
            });
        };
        var _offPanelClick = function(){
            me.$el.off('mousedown.lobiPanel');
        };
        var _changeClassOfControl = function(el){
            el = $(el);
            var opts = me.$options[el.attr('data-func')];
            if ( ! opts.icon){
                return;
            }
            el.find('.'+LobiPanel.PRIVATE_OPTIONS.iconClass).toggleClass(opts.icon).toggleClass(opts.icon2);
        };
        var _getFooterForMinimizedPanels = function(){
            //we grab footer where minimized panels should go
            var minimizedCtr = $('.'+LobiPanel.PRIVATE_OPTIONS.toolbarClass);
            //if panel does not exist we create it and append to body
            if (minimizedCtr.length === 0){
                minimizedCtr = $('<div class="'+LobiPanel.PRIVATE_OPTIONS.toolbarClass+'"></div>');
                $('body').append(minimizedCtr);
            }
            return minimizedCtr;
        };
        var _expandOnHeaderClick = function(){
            $heading.on('click.lobiPanel', function(){
                me.maximize();
                me.bringToFront();
            });
        };
        var _removeExpandOnHeaderClick = function(){
            $heading.off('click.lobiPanel');
        };
        var _getAvailableWidth = function(calcWidth){
            if (me.$options.maxWidth) {
                calcWidth = Math.min(calcWidth, me.$options.maxWidth);
            }
            if (me.$options.minWidth) {
                calcWidth = Math.max(calcWidth, me.$options.minWidth);
            }
            return calcWidth;
        };
        var _getAvailableHeight = function(calcHeight){
            if (me.$options.maxHeight) {
                calcHeight = Math.min(calcHeight, me.$options.maxHeight);
            }
            if (me.$options.minHeight) {
                calcHeight = Math.max(calcHeight, me.$options.minHeight);
            }
            return calcHeight;
        };
        var _calculateBodyHeight = function(h){
            return h  - $heading.outerHeight() - me.$el.find('.panel-footer').outerHeight();
        };
        var _calculateBodyWidth = function(w){
            return w - 2;
        };
        var _appendInnerIdToParent = function(parent, innerId){
            //If this is first lobipanel element of its parent
            if (parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr) === undefined){
                parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr, innerId);
            }
            //This means that parent already has LobiPanel instance
            else{
                //if parent already has panel innerId than we do nothing
                if (parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr).indexOf(innerId) > -1) {
                    return;
                }
                var innerIds = parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr);
                parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr, innerIds+" "+innerId);
            }
            me.$el.attr('data-index', me.$el.index());
        };
        var _insertInParent = function(){
            //find its parent element
            var parent = $('[' + LobiPanel.PRIVATE_OPTIONS.parentAttr + '~=' + innerId + ']');
            me.$el.insertAt(me.$el.attr('data-index'), parent);
        };
        var _generateWindow8Spinner = function(){
            var template = ['<div class="spinner spinner-windows8">',
                        '<div class="wBall">',
                            '<div class="wInnerBall">',
                            '</div>',
                        '</div>',
                        '<div class="wBall">',
                            '<div class="wInnerBall">',
                            '</div>',
                        '</div>',
                        '<div class="wBall">',
                            '<div class="wInnerBall">',
                            '</div>',
                        '</div>',
                        '<div class="wBall">',
                            '<div class="wInnerBall">',
                            '</div>',
                        '</div>',
                        '<div class="wBall">',
                            '<div class="wInnerBall">',
                            '</div>',
                        '</div>',
                    '</div>'].join("");
            return $('<div class="spinner-wrapper">'+template+'</div>');
        };
        var _enableSorting = function(){
            var parent = me.$el.parent();
            if (parent.hasClass('ui-sortable')){
                parent.sortable("destroy");
            }
            if (me.$options.sortable){
                me.$el.addClass('lobipanel-sortable');
                parent.addClass('lobipanel-parent-sortable');
            }else{
                me.$el.removeClass('lobipanel-sortable');
            }
            parent.sortable({
                connectWith: '.lobipanel-parent-sortable',
                items: '.lobipanel-sortable',
                handle: '.panel-heading',
                cursor: 'move',
                placeholder: 'lobipanel-placeholder',
                forcePlaceholderSize: true,
                opacity: 0.7,
                revert: 300,
                update: function (event, ui) {
                    var innerId = ui.item.data('inner-id');
                    _removeInnerIdFromParent(innerId);
                    _appendInnerIdToParent(ui.item.parent(), innerId);
                    _updateDataIndices(ui.item);
                    _triggerEvent('dragged');
                }
            });
        };
        var _disableSorting = function () {
            var parent = me.$el.parent();
            if (parent.hasClass('ui-sortable')) {
                parent.sortable("destroy");
            }
        };
        var _updateDataIndices = function(panel){
            var items = panel.parent().find('> *');
            items.each(function(index, el){
                $(el).attr('data-index', index);
            });
        };
        var _removeInnerIdFromParent = function(innerId){
            var parent = $('[' + LobiPanel.PRIVATE_OPTIONS.parentAttr + '~=' + innerId + ']');
            var innerIds = parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr).replace(innerId, '').trim().replace(/\s{2,}/g, ' ');
            parent.attr(LobiPanel.PRIVATE_OPTIONS.parentAttr, innerIds);
        };
        var _onToggleIconsBtnClick = function(){
            $heading.find('.toggle-controls').on('click.lobiPanel', function(){
                me.$el.toggleClass("controls-expanded");
            });
        };
        var _adjustForScreenSize = function(){
            me.disableTooltips();
            if ($(window).width() > 768 && me.$options.tooltips){
                me.enableTooltips();
            }
            if (me.isOnFullScreen()){
                $body.css({
                    width: _calculateBodyWidth(me.$el.width()),
                    height: _calculateBodyHeight(me.$el.height())
                });
            }
        };
        var _enableResponsiveness = function(){
            $(window).on('resize.lobiPanel', function(){
                _adjustForScreenSize();
            });
        };
        var _setBodyHeight = function(){
            if (me.$options.bodyHeight !== 'auto') {
                $body.css({
                    'height': me.$options.bodyHeight,
                    overflow: 'auto'
                });
            }
        };
        var _getOptionsFromAttributes = function(){
            var $el = me.$el;
            var options = {};
            for (var key in $.fn.lobiPanel.DEFAULTS){
                var k = key.toDash();
                var val = $el.data(k);
                if (val !== undefined){
                    if (typeof $.fn.lobiPanel.DEFAULTS[key] !== 'object'){
                        options[key] = val;
                    }else{
                        options[key] = eval('('+val+')');
                    }
                }
            }
            return options;
        };
        var _saveState = function(state){
            if (!me.hasRandomId && me.$options.stateful){
                me.storage.state = state;
                _saveLocalStorage(me.storage);
            }
        };
        var _saveLocalStorage = function(storage){
            localStorage.setItem(storagePrefix+innerId, JSON.stringify(storage));
        };
        var _applyState = function(state){
            switch (state){
                case 'unpinned':
                    me.unpin();
                    break;
                case 'minimized':
                    me.unpin();
                    me.minimize();
                    break;
                case 'collapsed':
                    me.minimize();
                    break;
                case 'fullscreen':
                    me.toFullScreen();
                    break;
                default:
                    break;
            }
        };

        var _applyIndex = function (index) {
            if (index !== null){
                me.$el.insertAt(index, me.$el.parent());
            }
        };

        var _triggerEvent = function(eventType){
            var args = Array.prototype.slice.call(arguments, 1);
            args.unshift(me);
            me.$el.trigger(eventType+'.lobiPanel', args);
        };
//------------------------------------------------------------------------------
//----------------PROTOTYPE FUNCTIONS-------------------------------------------
//------------------------------------------------------------------------------
        /**
         * Checks if panel is initialized. Panel is initialized if it has
         * lobipanel class and data-inner-id="" attribute
         *
         * @returns {boolean}
         */
        this.isPanelInit = function(){
            return me.$el.hasClass('lobipanel') && me.$el.data('inner-id');
        };
        /**
         * Checks if panel is pinned or unpinned
         *
         * @returns {Boolean}
         */
        this.isPinned = function(){
            return !me.$el.hasClass('panel-unpin');
        };
        /**
         * Pin the panel
         *
         * @returns {LobiPanel}
         */
        this.pin = function(){
            _triggerEvent("beforePin");
            //hide the tooltip
            $heading.find('[data-func="unpin"]').tooltip('hide');
            //disable resize functionality
            me.disableResize();
            me.disableDrag();
            _enableSorting();
            //remove on panel click event (which brings the panel into front)
            _offPanelClick();
            //remove panel-unpin class
            me.$el.removeClass('panel-unpin')
                //save current position, z-index and size to use it for later unpin
                .attr('old-style', me.$el.attr('style'))
                .removeAttr('style')
                .css('position', 'relative');
            $body.css({
                width: '',
                height: ''
            });
            _setBodyHeight();
            _insertInParent();
            _saveState('pinned');
            _triggerEvent("onPin");
            return me;
        };
        /**
         * Unpin the panel
         *
         * @returns {LobiPanel}
         */
        this.unpin = function(){
            _triggerEvent('beforeUnpin');
            if (me.$el.hasClass("panel-collapsed")){
                return me;
            }
            _disableSorting();
            $heading.find('[data-func="unpin"]').tooltip('hide');
            if (me.$el.attr('old-style')) {
                me.$el.attr('style', me.$el.attr('old-style'));
            } else {
                var width = me.$el.width();
                var height = me.$el.height();
//                var left = Math.round($(window).width() / 2 - width / 2);
              var left = Math.max(0, (($(window).width() - me.$el.outerWidth()) / 2));
//                var top = Math.round($(window).height() / 2 - height / 2);
                var top = Math.max(0, (($(window).height() - me.$el.outerHeight()) / 2));
                me.$el.css({
                    left: left,
                    top: top,
                    width: width,
                    height: height
//                    right: $(window).width() - left - width + 2,
//                    bottom: $(window).height() - top - height + 2
                });
            }
            var res = _getMaxZIndex();
            me.$el.css('z-index', res['z-index'] + 1);
            _onPanelClick();

            me.$el.addClass('panel-unpin');
            $('body').append(me.$el);

            var panelWidth = _getAvailableWidth(me.$el.width());
            var panelHeight = _getAvailableHeight(me.$el.height());
            me.$el.css({
                position: 'fixed',
                width: panelWidth,
                height: panelHeight
            });
            //we give .panel-body to width and height in order .panel-body to start scroling
            var bHeight = _calculateBodyHeight(panelHeight);
            var bWidth = _calculateBodyWidth(panelWidth);
            $body.css({
                width: bWidth,
                height: bHeight
            });

            if (me.$options.draggable) {
                me.enableDrag();
            }
            if (me.$options.resize !== 'none'){
                me.enableResize();
            }
            _saveState('unpinned');
            _triggerEvent('onUnpin');
            return me;
        };
        /**
         * Toggles (pin or unpin) the panel
         *
         * @returns {LobiPanel}
         */
        this.togglePin = function(){
            if (this.isPinned()){
                this.unpin();
            }else{
                this.pin();
            }
            return me;
        };
        /**
         * Checks if panel is minimized or not. It does not matter if panel is pinned or not
         *
         * @returns {Boolean}
         */
        this.isMinimized = function(){
            return me.$el.hasClass('panel-minimized') || me.$el.hasClass('panel-collapsed');
        };
        /**
         * Minimize the panel. If panel is pinned it is minimized on its place
         * if panel is unpinned it is minimized at the bottom of the page
         *
         * @returns {LobiPanel}
         */
        this.minimize = function(){
            _triggerEvent("beforeMinimize");
            if (me.isMinimized()){
                return me;
            }
            if (me.isPinned()) {
                $body.slideUp();
                me.$el.find('.panel-footer').slideUp();
                me.$el.addClass('panel-collapsed');
                _saveState('collapsed');
                _changeClassOfControl($heading.find('[data-func="minimize"]'));
            } else {
                me.disableTooltips();
                $heading.find('[data-func="minimize"]').tooltip('hide');
                //get footer where we need to put panel
                var footer = _getFooterForMinimizedPanels();
                //find other panels which are already inside footer
                var children = footer.find('>*');
                var left, top;
                //get top coordinate of footer
                top = footer.offset().top;
                //if there are no other panels inside footer, this panel will be first
                //and its left coordinate will be footer's left coordinate
                if (children.length === 0) {
                    left = footer.offset().left;
                } else {
                    //if there exist panels inside footer, then this panel's left
                    //coordinate will be last panel's (in footer) right coordinate
                    var ch = $(children[children.length - 1]);
                    left = ch.offset().left + ch.width();
                }
                //if panel was not expanded and it was jus unpin we need to save
                //panel's style
                if (!me.$el.hasClass('panel-expanded')) {
                    me.$el.attr('old-style', me.$el.attr('style'));
                }
                me.$el.animate({
                    left: left,
                    top: top,
                    width: 200,
                    height: footer.height()
                }, 100, function () {
                    //if panel was expanded on full screen before we minimize it
                    //after minimization we remove 'panel-expanded' class and we change icon
                    if (me.$el.hasClass('panel-expanded')) {
                        me.$el.removeClass('panel-expanded');
                        me.$el.find('.panel-heading [data-func=expand] .' + LobiPanel.PRIVATE_OPTIONS.iconClass)
                                .removeClass(me.$options.expand.icon2)
                                .addClass(me.$options.expand.icon)
                                ;
                    }
                    //we add 'panel-minimized' class
                    me.$el.addClass('panel-minimized');
                    me.$el.removeAttr('style');
                    me.disableDrag();
                    me.disableResize();
                    _expandOnHeaderClick();
                    //animation was made and panel is positioned in place we it must be
                    //so we append panel into footer
                    footer.append(me.$el);
                    $('body').addClass('lobipanel-minimized');
                    var maxWidth = 'calc(100% - '+$heading.find('.dropdown-menu li>a:visible').length * $heading.find('.dropdown-menu li>a:visible').first().outerWidth()+"px)";
                    $heading.find('.panel-title').css('max-width', maxWidth);
                    _saveState('minimized');
                    _triggerEvent("onMinimize");
                });
            }
            return me;
        };
        /**
         * Maximize the panel. This method works for minimized panel.
         * If panel is pinned it's maximized on its place.
         * If panel is unpinned it's maximized on position from which it was minimized
         *
         * @returns {LobiPanel}
         */
        this.maximize = function(){
            _triggerEvent("beforeMaximize");
            if ( ! me.isMinimized()){
                return me;
            }
            if (me.isPinned()){
                $body.slideDown();
                me.$el.find('.panel-footer').slideDown();
                me.$el.removeClass('panel-collapsed');
                _saveState('pinned');
                _changeClassOfControl($heading.find('[data-func="minimize"]'));
            }else{
                me.enableTooltips();
                //we get css style which was saved before minimization
                var css = me.$el.attr('old-style').getCss();
                //we give panel these css properties, coz animation work
                me.$el.css({
                    position: css.position || 'fixed',
                    'z-index': css['z-index'],
                    left: me.$el.offset().left,
                    top: me.$el.offset().top,
                    width: me.$el.width(),
                    height: me.$el.height()
                });
                //we append panel into body
                $('body').append(me.$el);
                //It is not possible to make animations to these propeties and we remove it
                delete css['position'];
                delete css['z-index'];
//            css['position'] = 'absolute';
                //and we animate panel to its saved style
                me.$el.animate(css, 100, function () {
                    //we remove position property from style, before 'panel-unpin'
                    //class has it to absolute
                    me.$el.css('position', '');
                    me.$el.removeClass('panel-minimized');
                    //as panel is already in its place we remove 'old-style' property
                    me.$el.removeAttr('old-style');
                    if (me.$options.draggable){
                        me.enableDrag();
                    }
                    me.enableResize();
                    _removeExpandOnHeaderClick();
                    //If there are no other elements inside footer, remove it also
                    var footer = _getFooterForMinimizedPanels();
                    if (footer.children().length === 0) {
                        footer.remove();
                    }
                    $('body').removeClass('lobipanel-minimized')
                        .addClass('lobipanel-minimized');
                    var maxWidth = 'calc(100% - ' + $heading.find('.dropdown-menu li').length * $heading.find('.dropdown-menu li').first().outerWidth() + "px)";
                    $heading.find('.panel-title').css('max-width', maxWidth);
                    _saveState('unpinned');
                    _triggerEvent("onMaximize");
                });
            }
            return me;
        };
        /**
         * Toggles (minimize or maximize) the panel state.
         *
         * @returns {LobiPanel}
         */
        this.toggleMinimize = function(){
            if (me.isMinimized()){
                me.maximize();
            }else{
                me.minimize();
            }
            return me;
        };
        /**
         * Checks if panel is on full screen
         *
         * @returns {Boolean}
         */
        this.isOnFullScreen = function(){
            return me.$el.hasClass('panel-expanded');
        };
        /**
         * Expands the panel to full screen size
         *
         * @returns {LobiPanel}
         */
        this.toFullScreen = function(){
            _triggerEvent("beforeFullScreen");
            if (me.$el.hasClass("panel-collapsed")){
                return me;
            }
            _changeClassOfControl($heading.find('[data-func="expand"]'));
            $heading.find('[data-func="expand"]').tooltip('hide');
            var res = _getMaxZIndex();
            //if panel is pinned or minimized, its position is not absolute and
            //animation will not work correctly so we change its position and
            //other css properties and we append panel into body
            if (me.isPinned() || me.isMinimized()) {
                me.enableTooltips();
                me.$el.css({
                    position: 'fixed',
                    "z-index": res["z-index"] + 1,
                    left: me.$el.offset().left,
                    top: me.$el.offset().top - $(window).scrollTop(),
                    width: me.$el.width(),
                    height: me.$el.height()
                });
                $('body').append(me.$el);
                //If we are expanding panel to full screen from footer and in footer there are no more elements
                //remove footer also
                var footer = _getFooterForMinimizedPanels();
                if (footer.children().length === 0) {
                    footer.remove();
                }
            }else{
                $body.css({
                    width: '',
                    height: ''
                });
                _setBodyHeight();
            }
            //if panel is not minimized we save its style property, because when
            //toSmallSize() method is called panel needs to have style, it had before calling method
            // toFullScreen()
            if ( ! me.isMinimized()) {
                me.$el.attr('old-style', me.$el.attr('style'));
                me.disableResize();
            } else {
                me.$el.removeClass('panel-minimized');
                _removeExpandOnHeaderClick();
            }
            //get toolbar
            var toolbar = $('.'+LobiPanel.PRIVATE_OPTIONS.toolbarClass);
            var toolbarHeight = toolbar.outerHeight() || 0;
            me.$el.animate({
                width: $(window).width(),
                height: $(window).height() - toolbarHeight,
                left: 0,
                top: 0
            }, me.$options.expandAnimation, function () {
                me.$el.css({
                    width: '',
                    height: '',
                    right: 0,
                    bottom: toolbarHeight
                });
                me.$el.addClass('panel-expanded');
                $('body').css('overflow', 'hidden');
                $body.css({
                    width: _calculateBodyWidth(me.$el.width()),
                    height: _calculateBodyHeight(me.$el.height())
                });
                me.disableDrag();
                if (me.isPinned()){
                    _disableSorting();
                }
                _saveState('fullscreen');
                _triggerEvent("onFullScreen");
            });
            return me;
        };
        /**
         * Collapse the panel to small size
         *
         * @returns {LobiPanel}
         */
        this.toSmallSize = function(){
            _triggerEvent("beforeSmallSize");
            _changeClassOfControl($heading.find('[data-func="expand"]'));
            $heading.find('[data-func="expand"]').tooltip('hide');
            var css = me.$el.attr('old-style').getCss();
            //we get css properties from old-style (saved before expanding)
            //and we animate panel to this css properties
            me.$el.animate({
                left: css.left,
                top: css.top,
                width: css.width,
                height: css.height,
                right: css.right,
                bottom: css.bottom
            }, me.$options.collapseAnimation, function () {
                //we remove old-style as we do not need it
                me.$el.removeAttr('old-style');
                //if panel is pinned we also remove its style attribute and we
                //append panel in its parent element
                if (!me.$el.hasClass('panel-unpin')) {
                    me.$el.removeAttr('style');
                    _insertInParent();
                    _enableSorting();
                } else {
                    if (me.$options.draggable) {
                        me.enableDrag();
                    }
                    me.enableResize();
                }
                me.$el.removeClass('panel-expanded');
                $('body').css('overflow', 'auto');
                var bWidth = '';
                var bHeight = '';
                if ( ! me.isPinned()){
                    bWidth = _calculateBodyWidth(me.getWidth());
                    bHeight = _calculateBodyHeight(me.getHeight());
                    _saveState('unpinned');
                }else if (me.$options.bodyHeight !== 'auto'){
                    bHeight = me.$options.bodyHeight;
                    _saveState('pinned');
                }
                $body.css({
                    width: bWidth,
                    height: bHeight
                });
                _triggerEvent("onSmallSize");
            });
            return me;
        };
        /**
         * Toggles (changes to full screen size or to small size) the panel size
         *
         * @returns {LobiPanel}
         */
        this.toggleSize = function(){
            if (me.isOnFullScreen()){
                me.toSmallSize();
            }else{
                me.toFullScreen();
            }
            return me;
        };
        /**
         * Closes the panel. Removes it from document
         *
         * @returns {LobiPanel}
         */
        this.close = function(){
            _triggerEvent('beforeClose');
            me.$el.hide(100, function() {
                if (me.isOnFullScreen()){
                    $('body').css('overflow', 'auto');
                }
                me.$el.remove();
                var footer = _getFooterForMinimizedPanels();
                if (footer.children().length === 0) {
                    footer.remove();
                }
                _triggerEvent('onClose');
            });
            return me;
        };
        /**
         * Moves unpinned panel to given position.
         * This method will do nothing if panel is pinned
         *
         * @param {number} left
         * @param {number} top
         * @returns {LobiPanel}
         */
        this.setPosition = function(left, top){
            //this method works only if panel is not pinned
            if (me.isPinned()){
                return me;
            }
            me.$el.animate({
               'left': left,
               'top' : top
            }, 100);
            return me;
        };
        /**
         * Set the width of the panel
         *
         * @param {number} w
         * @returns {LobiPanel}
         */
        this.setWidth = function(w){
            if (me.isPinned()){
                return me;
            }
            var bWidth = _calculateBodyWidth(w);
            me.$el.animate({
                width: w
            }, 100);
            $body.animate({
                width: bWidth
            }, 100);
            return me;
        };
        /**
         * Set the height of the panel
         *
         * @param {number} h
         * @returns {LobiPanel}
         */
        this.setHeight = function(h){
            if (me.isPinned()){
                return me;
            }
            var bHeight = _calculateBodyHeight(h);
            me.$el.animate({
                height: h
            }, 100);
            $body.animate({
                height: bHeight
            }, 100);
            return me;
        };
        /**
         * Set size (width and height) of the panel
         *
         * @param {number} w
         * @param {number} h
         * @returns {LobiPanel}
         */
        this.setSize = function(w, h){
            if (me.isPinned()){
                return me;
            }
            var bHeight = _calculateBodyHeight(h);
            var bWidth = _calculateBodyWidth(w);
            me.$el.animate({
                height: h,
                width: w
            }, 100);
            $body.animate({
                height: bHeight,
                width: bWidth
            }, 100);
            return me;
        };
        /**
         * Get the position of the panel.
         * Returns object where x is left coordinate and y is top coordinate
         *
         * @returns {Object}
         */
        this.getPosition = function(){
            var offset = me.$el.offset();
            return {
                x: offset.left,
                y: offset.top
            };
        };
        /**
         * Get width of the panel
         *
         * @returns {number}
         */
        this.getWidth = function(){
            return me.$el.width();
        };
        /**
         * Get height of the panel
         *
         * @returns {number}
         */
        this.getHeight = function(){
            return me.$el.height();
        };
        /**
         * If panel is overlapped by another panel this panel will be shown on front
         * (this panel will overlap other panels)
         *
         * @returns {LobiPanel}
         */
        this.bringToFront = function(){
            _triggerEvent("beforeToFront");
            var res = _getMaxZIndex();
            if (res['id'] === me.$el.data('inner-id')) {
                return me;
            }
            me.$el.css('z-index', res['z-index'] + 1);
            _triggerEvent("onToFront");
            return me;
        };
        /**
         * Enable dragging of panel
         *
         * @returns {LobiPanel}
         */
        this.enableDrag = function(){
            me.$el.draggable({
                handle: '.panel-heading'
            });
            return me;
        };
        /**
         * Disable dragging of the panel
         *
         * @returns {LobiPanel}
         */
        this.disableDrag = function(){
            if (me.$el.hasClass('ui-draggable')){
                me.$el.draggable("destroy");
            }
            return me;
        };
        /**
         * Enable resize of the panel
         *
         * @returns {LobiPanel}
         */
        this.enableResize = function(){
            var handles = false;
            if (me.$options.resize === 'vertical'){
                handles = 'n, s';
            }else if (me.$options.resize === 'horizontal'){
                handles = 'e, w';
            }else if (me.$options.resize === 'both'){
                handles = 'all';
            }
            if ( ! handles){
                return;
            }
            me.$el.resizable({
                minWidth: me.$options.minWidth,
                maxWidth: me.$options.maxWidth,
                minHeight: me.$options.minHeight,
                maxHeight: me.$options.maxHeight,
                handles: handles,
                start: function(){
                    me.$el.disableSelection();
                    _triggerEvent('resizeStart');
                },
                stop: function(){
                    me.$el.enableSelection();
                    _triggerEvent('resizeStop');
                },
                resize: function(){
                    var bHeight = _calculateBodyHeight(me.$el.height());
                    var bWidth = _calculateBodyWidth(me.$el.width());
                    $body.css({
                        width: bWidth,
                        height: bHeight
                    });
                    _triggerEvent("onResize");
                }
            });
            return me;
        };
        /**
         * Disable resize of the panel
         *
         * @returns {LobiPanel}
         */
        this.disableResize = function(){
            if (me.$el.hasClass('ui-resizable')){
                me.$el.resizable("destroy");
            }
            return me;
        };
        /**
         * Start spinner of the panel loading
         *
         * @returns {LobiPanel}
         */
        this.startLoading = function(){
            var spinner = _generateWindow8Spinner();
            me.$el.append(spinner);
            var sp = spinner.find('.spinner');
            sp.css('margin-top', 50);
            return me;
        };
        /**
         * Stop spinner of the panel loading
         *
         * @returns {LobiPanel}
         */
        this.stopLoading = function(){
            me.$el.find('.spinner-wrapper').remove();
            return me;
        };
        /**
         * Set url. This url will be used to load data when Reload button is clicked
         * or user calls .load() method without url parameter
         *
         * @param {string} url
         * @returns {LobiPanel}
         */
        this.setLoadUrl = function(url){
            me.$options.loadUrl = url;
            return me;
        };
        /**
         * Load data into .panel-body.
         * params object is in format
         * {
         *      url: '', //Optional: load url
         *      data: 'PlainObject or String', //Optional: A plain object or string of parameters which is sent to the server with the request.
         *      callback: 'function' //Optional: callback function which is called when load is finished
         * }
         *
         * @param {Object} params
         * @returns {LobiPanel}
         */
        this.load = function(params){
            params = params || {};
            if (typeof params === 'string'){
                params = {url: params};
            }
            var url = params.url || me.$options.loadUrl,
                data = params.data || {},
                callback = params.callback || null;

            if ( ! url){
                return me;
            }
            _triggerEvent("beforeLoad");
            me.startLoading();
            $body.load(url, data, function(result, status, xhr){
                if (callback && typeof callback === 'function'){
                    callback(result, status, xhr);
                }
                me.stopLoading();
                _triggerEvent("loaded", result, status, xhr);
            });
            return me;
        };
        /**
         * Destroy the LobiPanel instance
         *
         * @returns {jQuery}
         */
        this.destroy = function(){
            me.disableDrag();
            me.disableResize();
            me.$options.sortable = false;
            _enableSorting();
            _removeInnerIdFromParent(innerId);
            me.$el.removeClass('lobipanel')
                    .removeAttr('data-inner-id')
                    .removeAttr('data-index')
                    .removeData('lobiPanel');
            $heading.find('.dropdown').remove();
            return me.$el;
        };
        /**
         * Creates input field to edit panel title
         *
         * @returns {LobiPanel}
         */
        this.startTitleEditing = function(){
            var title = $heading.find('.panel-title').text().trim();
            var input = $('<input value="'+title+'"/>');
            input.on('keydown', function(ev){
                if (ev.which === 13){
                    me.finishTitleEditing();
                }else if (ev.which === 27){
                    me.cancelTitleEditing();
                }
            });
            $heading.find('.panel-title')
                    .data('old-title', title)
                    .html("").append(input);
            input[0].focus();
            input[0].select();
            _changeClassOfControl($heading.find('[data-func="editTitle"]'));
            return me;
        };
        /**
         * Check if panel title is being edited (if it is in edit process)
         *
         * @returns {Boolean}
         */
        this.isTitleEditing = function(){
            return $heading.find('.panel-title input').length > 0;
        };
        /**
         * Cancel the panel new title and return to previous title when it is changed but not saved
         *
         * @returns {LobiPanel}
         */
        this.cancelTitleEditing = function(){
            var title = $heading.find('.panel-title');
            title.html(title.data('old-title'))
                    .find('input').remove();
            _changeClassOfControl($heading.find('[data-func="editTitle"]'));
            return me;
        };
        /**
         * Finish the panel title editing process and save new title
         *
         * @returns {LobiPanel}
         */
        this.finishTitleEditing = function(){
            var input = $heading.find('input');
            $heading.find('.panel-title').html(input.val());
            input.remove();
            _changeClassOfControl($heading.find('[data-func="editTitle"]'));
            return me;
        };
        /**
         * Enable tooltips on panel controls
         *
         * @returns {LobiPanel}
         */
        this.enableTooltips = function () {
            if ($(window).width() < 768){
                return me;
            }
            var controls = $heading.find('.dropdown-menu>li>a');
            controls.each(function (index, el) {
                var $el = $(el);
                $el.attr('data-toggle', 'tooltip')
                        .attr('data-title', $el.data('tooltip'))
                        .attr('data-placement', 'bottom')
                        ;
            });
            controls.each(function(ind, el){
                $(el).tooltip({
                    container: 'body',
                    template: '<div class="tooltip lobipanel-tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
                });
            });
            return me;
        };
        /**
         * Disable tooltips on panel controls
         *
         * @returns {LobiPanel}
         */
        this.disableTooltips = function(){
            $heading.find('.dropdown-menu>li>a').tooltip('destroy');
            return me;
        };
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
        this.$el = $el;
        if ( ! me.$el.data('inner-id')){
            me.hasRandomId = true;
            me.$el.attr('data-inner-id', Math.randomString(10));
        }

        innerId = me.$el.data('inner-id');
        if (!me.hasRandomId){
            me.storage = localStorage.getItem(storagePrefix+innerId);
            me.storage = JSON.parse(me.storage) || {};
        }

        this.$options = _processInput(options);
        $heading = this.$el.find('>.panel-heading');
        $body = this.$el.find('>.panel-body');
        _init();
        _applyState(me.$options.state);
        _applyIndex(me.$options.initialIndex);
    };

    $.fn.lobiPanel = function(option){
        var args = arguments,
            ret = null;
        this.each(function() {

            var $this = $(this);
            var data = $this.data('lobiPanel');
            var options = typeof option === 'object' && option;

            if (!data) {
                $this.data('lobiPanel', (data = new LobiPanel($this, options)));
            }
            if (typeof option === 'string') {
                args = Array.prototype.slice.call(args, 1);
                ret = data[option].apply(data, args);
            }
        });
        return ret;
    };
    LobiPanel.PRIVATE_OPTIONS = {
        //We need to know what is the parent of the panel, that's why we add
        //this attribute to parent element and it contains space seperated inner-ids of all its child lobipanel
        parentAttr: 'data-lobipanel-child-inner-id',
        toolbarClass: 'lobipanel-minimized-toolbar', //This class is added to container which contains all minimized panels
        //First instance on lobiPanel will get this z-index css property.
        //Every next instance will get 1 + previous z-index
        initialZIndex       : 10000,
        //This class is attached to every panel-control icon
        iconClass : 'panel-control-icon'
    };
    $.fn.lobiPanel.DEFAULTS = {
        //Makes <b>unpinned</b> panel draggable
        //Warning!!! This requires jquery ui draggable widget to be included
        draggable: true,
        //Makes <b>pinned</b> panels sortable
        //Warning!!! This requires jquery ui sortable widget to be included
        sortable: false,
        //jquery ui sortable plugin option.
        //To avoid any problems this option must be same for all panels which are direct children of their parent
        connectWith: '.ui-sortable',
        //This parameter accepts string ['both', 'vertical', 'horizontal', 'none']. none means disable resize
        resize: 'both',
        //Minimum width <b>unpin, resizable</b> panel can have.
        minWidth: 200,
        //Minimum height <b>unpin, resizable</b> panel can have.
        minHeight: 100,
        //Maximum width <b>unpin, resizable</b> panel can have.
        maxWidth: 1200,
        //Maximum height <b>unpin, resizable</b> panel can have.
        maxHeight: 700,
        //The url which will be used to load content. If not provided reload button will do nothing
        loadUrl: "",
        //If loadUrl is provided plugin will load content as soon as plugin is initialized
        autoload: true,
        bodyHeight: 'auto',
        //This will enable tooltips on panel controls
        tooltips: true,
        toggleIcon: 'glyphicon glyphicon-cog',
        expandAnimation: 100,
        collapseAnimation: 100,
        state: 'pinned', // Initial state of the panel. Available options: pinned, unpinned, collapsed, minimized, fullscreen
        initialIndex: null, // Initial index of the panel among its siblings
        stateful: false, // If you set this to true you must specify data-inner-id. Plugin will save (in localStorage) it's states such as
                         // pinned, unpinned, collapsed, minimized, fullscreen, position among it's siblings
                         // and apply them when you reload the browser
        unpin: {
            icon: 'glyphicon glyphicon-move', //You can user glyphicons if you do not want to use font-awesome
            tooltip: 'Unpin'               //tooltip text, If you want to disable tooltip, set it to false
        },
        reload: {
            icon: 'glyphicon glyphicon-refresh', //You can user glyphicons if you do not want to use font-awesome
            tooltip: 'Reload'           //tooltip text, If you want to disable tooltip, set it to false
        },
        minimize: {
            icon: 'glyphicon glyphicon-minus', //icon is shown when panel is not minimized
            icon2: 'glyphicon glyphicon-plus', //icon2 is shown when panel is minimized
            tooltip: 'Minimize'         //tooltip text, If you want to disable tooltip, set it to false
        },
        expand: {
            icon: 'glyphicon glyphicon-resize-full', //icon is shown when panel is not on full screen
            icon2: 'glyphicon glyphicon-resize-small', //icon2 is shown when pane is on full screen state
            tooltip: 'Fullscreen'       //tooltip text, If you want to disable tooltip, set it to false
        },
        close: {
            icon: 'glyphicon glyphicon-remove', //You can user glyphicons if you do not want to use font-awesome
            tooltip: 'Close'            //tooltip text, If you want to disable tooltip, set it to false
        },
        editTitle: {
            icon: 'glyphicon glyphicon-pencil',
            icon2: 'glyphicon glyphicon-floppy-disk',
            tooltip: 'Edit title'
        }
    };

    $('.lobipanel').lobiPanel();
});

