/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Create a new dialog.
 *
 * If the height, width, and fullscreen breakpoints options are not set this plugin will look for (and use)
 * any CSS defined styles for height, min/max-height, width, min/max-width. If no CSS styles are defined then the
 * plugin will attempt to calculate these values when the dialog is first opened (based on screen size).
 *
 * The options are specified in the following format:
 *
 * {
 *   header: true,
 *   footer: true,
 *   headerText: 'Dialog Header',
 *   subtitleText: 'Dialog subtitle',
 *   scrollableContentStyle: 'scrollable',
 *   buttons: [{
 *      buttonText: 'Cancel',
 *          color: {
 *              base: '#728E9B',
 *              hover: '#004849',
 *              text: '#ffffff'
 *          },
 *          disabled: isDisabledFunction,
 *      handler: {
 *          click: cancelHandler
 *      }
 *   }, {
 *      buttonText: 'Apply',
 *          color: {
 *              base: '#E3E8EB',
 *              hover: '#C7D2D7',
 *              text: '#004849'
 *          },
 *          disabled: isDisabledFunction,
 *      handler: {
 *          click: applyHandler
 *      }
 *   }],
 *   handler: {
 *      close: closeHandler,
 *      open: openHandler,
 *      resize: resizeHandler
 *   },
 *   height: "55%", //optional. Property can also be set with css (accepts 'px' or '%' values)
 *   width: "34%", //optional. Property can also be set with css (accepts 'px' or '%' values)
 *   min-height: "420px", //optional, defaults to 'height'. Property  can also be set with css (accepts 'px' values)
 *   min-width: "470px" //optional, defaults to 'width'. Property can also be set with css (accepts 'px' values)
 *   responsive: {
 *       x: "true", //optional, default true
 *       y: "true", //optional, default true
 *       fullscreen-height: "420px", //optional, default is original dialog height (accepts 'px' values)
 *       fullscreen-width: "470px", //optional, default is original dialog width (accepts 'px' values)
 *      },
 *      glasspane: "#728E9B" //optional, sets the color of modal glasspane...default if unset is the dialog header color
 * }
 *
 * The content of the dialog MUST be contained in an element with the class `dialog-content`
 * directly under the dialog element.
 *
 * <div id="dialogId">
 *  <div class="dialog-content">
 *      //Dialog Content....
 *  </div>
 * </div>
 *
 * @argument {jQuery} $
 */
(function ($) {

    var isUndefined = function (obj) {
        return typeof obj === 'undefined';
    };

    var isNull = function (obj) {
        return obj === null;
    };

    var isDefinedAndNotNull = function (obj) {
        return !isUndefined(obj) && !isNull(obj);
    };

    var isBlank = function (str) {
        return isUndefined(str) || isNull(str) || str === '';
    };

    // private function for adding buttons
    var addButtons = function (dialog, buttonModel) {
        if (isDefinedAndNotNull(buttonModel)) {
            var buttonWrapper = $('<div class="dialog-buttons"></div>');
            $.each(buttonModel, function (i, buttonConfig) {
                var isDisabled = function () {
                    return typeof buttonConfig.disabled === 'function' && buttonConfig.disabled.call() === true;
                };

                // create the button
                var button = $('<div class="button"></div>').append($('<span></span>').text(buttonConfig.buttonText));

                // add the class if specified
                if (isDefinedAndNotNull(buttonConfig.clazz)) {
                    button.addClass(buttonConfig.clazz);
                }

                // set the color if specified
                if (isDefinedAndNotNull(buttonConfig.color)) {
                    button.css({
                        'background': buttonConfig.color.base,
                        'color': buttonConfig.color.text
                    });
                }

                // check if the button should be disabled
                if (isDisabled()) {
                    button.addClass('disabled-button');
                } else {
                    // enable custom hover if specified
                    if (isDefinedAndNotNull(buttonConfig.color)) {
                        button.hover(function () {
                            $(this).css("background-color", buttonConfig.color.hover);
                        }, function () {
                            $(this).css("background-color", buttonConfig.color.base);
                        });
                    }

                    button.click(function () {
                        var handler = $(this).data('handler');
                        if (isDefinedAndNotNull(handler) && typeof handler.click === 'function') {
                            handler.click.call(dialog);
                        }
                    });
                }

                // add the button to the wrapper
                button.data('handler', buttonConfig.handler).appendTo(buttonWrapper);
            });

            // store the button model to refresh later
            dialog.append(buttonWrapper).data('buttonModel', buttonModel);
        }
    };

    var methods = {

        /**
         * Initializes the dialog.
         *
         * @argument {object} options The options for the plugin
         */
        init: function (options) {
            return this.each(function () {
                // get the combo
                var dialog = $(this).addClass('dialog cancellable modal');
                dialog.css('display', 'none');

                var nfDialogData = {};
                if (isDefinedAndNotNull(dialog.data('nf-dialog'))) {
                    nfDialogData = dialog.data('nf-dialog');
                }

                // ensure the options have been properly specified
                if (isDefinedAndNotNull(options)) {

                    $.extend(nfDialogData, options);

                    //persist data attribute
                    dialog.data('nfDialog', nfDialogData);
                }

                // determine if dialog needs a header
                if (!isDefinedAndNotNull(nfDialogData.header) || nfDialogData.header) {
                    var dialogHeaderText = $('<span class="dialog-header-text"></span>');
                    var dialogHeader = $('<div class="dialog-header"></div>').prepend(dialogHeaderText);

                    // determine if the specified header text is null
                    if (!isBlank(nfDialogData.headerText)) {
                        dialogHeaderText.text(nfDialogData.headerText);
                    }

                    $('<span></span>').addClass("dialog-header-subtitle").text(nfDialogData.subtitleText).appendTo(dialogHeader);


                    dialog.prepend(dialogHeader);
                }

                // determine if dialog needs footer/buttons
                if (!isDefinedAndNotNull(nfDialogData.footer) || nfDialogData.footer) {
                    // add the buttons
                    addButtons(dialog, nfDialogData.buttons);
                }
            });
        },

        /**
         * Sets the handler that is used when the dialog is closed.
         *
         * @argument {function} handler The function to call when hiding the dialog
         */
        setCloseHandler: function (handler) {
            return this.each(function (index, dialog) {

                var nfDialogData = {};
                if (isDefinedAndNotNull($(this).data('nf-dialog'))) {
                    nfDialogData = $(dialog).data('nf-dialog');
                }
                if (!isDefinedAndNotNull(nfDialogData.handler)){
                    nfDialogData.handler = {};
                }
                nfDialogData.handler.close = handler;

                //persist data attribute
                $(dialog).data('nfDialog', nfDialogData);
            });
        },

        /**
         * Sets the handler that is used when the dialog is opened.
         *
         * @argument {function} handler The function to call when showing the dialog
         */
        setOpenHandler: function (handler) {
            return this.each(function (index, dialog) {

                var nfDialogData = {};
                if (isDefinedAndNotNull($(this).data('nf-dialog'))) {
                    nfDialogData = $(dialog).data('nf-dialog');
                }
                if (!isDefinedAndNotNull(nfDialogData.handler)){
                    nfDialogData.handler = {};
                }
                nfDialogData.handler.open = handler;

                //persist data attribute
                $(dialog).data('nfDialog', nfDialogData);
            });
        },

        /**
         * Sets the handler that is used when the dialog is resized.
         *
         * @argument {function} handler The function to call when resizing the dialog
         */
        setResizeHandler: function (handler) {
            return this.each(function (index, dialog) {

                var nfDialogData = {};
                if (isDefinedAndNotNull($(this).data('nf-dialog'))) {
                    nfDialogData = $(dialog).data('nf-dialog');
                }
                if (!isDefinedAndNotNull(nfDialogData.handler)){
                    nfDialogData.handler = {};
                }
                nfDialogData.handler.resize = handler;

                //persist data attribute
                $(dialog).data('nfDialog', nfDialogData);
            });
        },

        /**
         * Updates the button model for the selected dialog.
         *
         * @argument {array} buttons The new button model
         */
        setButtonModel: function (buttons) {
            return this.each(function () {
                if (isDefinedAndNotNull(buttons)) {
                    var dialog = $(this);

                    // remove the current buttons
                    dialog.children('.dialog-buttons').remove();

                    // add the new buttons
                    addButtons(dialog, buttons);
                }
            });
        },

        /**
         * Refreshes the buttons with the existing model.
         */
        refreshButtons: function () {
            return this.each(function () {
                var dialog = $(this);
                var buttons = dialog.data('buttonModel');

                // remove the current buttons
                dialog.children('.dialog-buttons').remove();

                // add the new buttons
                addButtons(dialog, buttons);
            });
        },

        /**
         * Sets the header text of the dialog.
         *
         * @argument {string} text Text to use a as a header
         */
        setHeaderText: function (text) {
            return this.each(function () {
                $(this).find('span.dialog-header-text').text(text);
            });
        },

        setSubtitle: function (text) {
            return this.each(function () {
                $(this).find('span.dialog-header-subtitle').text(text);
            });
        },

        resize: function () {
            var dialog = $(this);
            var dialogContent = dialog.find('.dialog-content');

            var nfDialogData = {};
            if (isDefinedAndNotNull(dialog.data('nf-dialog'))) {
                nfDialogData = dialog.data('nf-dialog');
            }

            //initialize responsive properties
            if (!isDefinedAndNotNull(nfDialogData.responsive)) {
                nfDialogData.responsive = {};

                if (!isDefinedAndNotNull(nfDialogData.responsive.x)) {
                    nfDialogData.responsive.x = true;
                }

                if (!isDefinedAndNotNull(nfDialogData.responsive.y)) {
                    nfDialogData.responsive.y = true;
                }
            } else {
                if (!isDefinedAndNotNull(nfDialogData.responsive.x)) {
                    nfDialogData.responsive.x = true;
                } else {
                    nfDialogData.responsive.x = (nfDialogData.responsive.x == "true" || nfDialogData.responsive.x == true) ? true : false;
                }

                if (!isDefinedAndNotNull(nfDialogData.responsive.y)) {
                    nfDialogData.responsive.y = true;
                } else {
                    nfDialogData.responsive.y = (nfDialogData.responsive.y == "true" || nfDialogData.responsive.y == true) ? true : false;
                }
            }

            if (nfDialogData.responsive.y || nfDialogData.responsive.x) {

                var fullscreenHeight;
                var fullscreenWidth;

                if (isDefinedAndNotNull(nfDialogData.responsive['fullscreen-height'])) {
                    fullscreenHeight = parseInt(nfDialogData.responsive['fullscreen-height'], 10);
                } else {
                    nfDialogData.responsive['fullscreen-height'] = dialog.height() + 'px';

                    fullscreenHeight = parseInt(nfDialogData.responsive['fullscreen-height'], 10);
                }

                if (isDefinedAndNotNull(nfDialogData.responsive['fullscreen-width'])) {
                    fullscreenWidth = parseInt(nfDialogData.responsive['fullscreen-width'], 10);
                } else {
                    nfDialogData.responsive['fullscreen-width'] = dialog.width() + 'px';

                    fullscreenWidth = parseInt(nfDialogData.responsive['fullscreen-width'], 10);
                }

                if (!isDefinedAndNotNull(nfDialogData.width)) {
                    nfDialogData.width = dialog.css('width');
                }

                if (!isDefinedAndNotNull(nfDialogData['min-width'])) {
                    if (parseInt(dialog.css('min-width'), 10) > 0) {
                        nfDialogData['min-width'] = dialog.css('min-width');
                    } else {
                        nfDialogData['min-width'] = nfDialogData.width;
                    }
                }

                //min-width should always be set in terms of px
                if (nfDialogData['min-width'].indexOf("%") > 0) {
                    nfDialogData['min-width'] = ($(window).width() * (parseInt(nfDialogData['min-width'], 10) / 100)) + 'px';
                }

                if (!isDefinedAndNotNull(nfDialogData.height)) {
                    nfDialogData.height = dialog.css('height');
                }

                if (!isDefinedAndNotNull(nfDialogData['min-height'])) {
                    if (parseInt(dialog.css('min-height'), 10) > 0) {
                        nfDialogData['min-height'] = dialog.css('min-height');
                    } else {
                        nfDialogData['min-height'] = nfDialogData.height;
                    }
                }

                //min-height should always be set in terms of px
                if (nfDialogData['min-height'].indexOf("%") > 0) {
                    nfDialogData['min-height'] = ($(window).height() * (parseInt(nfDialogData['min-height'], 10) / 100)) + 'px';
                }

                //resize dialog
                if ($(window).height() < fullscreenHeight) {
                    if (nfDialogData.responsive.y) {
                        dialog.css('height', '100%');
                        dialog.css('min-height', '100%');
                    }
                } else {
                    //set the dialog min-height
                    dialog.css('min-height', nfDialogData['min-height']);
                    if (nfDialogData.responsive.y) {
                        //make sure nfDialogData.height is in terms of %
                        if (nfDialogData.height.indexOf("px") > 0) {
                            nfDialogData.height = (parseInt(nfDialogData.height, 10) / $(window).height() * 100) + '%';
                        }
                        dialog.css('height', nfDialogData.height);
                    }
                }

                if ($(window).width() < fullscreenWidth) {
                    if (nfDialogData.responsive.x) {
                        dialog.css('width', '100%');
                        dialog.css('min-width', '100%');
                    }
                } else {
                    //set the dialog width
                    dialog.css('min-width', nfDialogData['min-width']);
                    if (nfDialogData.responsive.x) {
                        //make sure nfDialogData.width is in terms of %
                        if (nfDialogData.width.indexOf("px") > 0) {
                            nfDialogData.width = (parseInt(nfDialogData.width, 10) / $(window).width() * 100) + '%';
                        }
                        dialog.css('width', nfDialogData.width);
                    }
                }

                dialog.center();

                //persist data attribute
                dialog.data('nfDialog', nfDialogData);
            }

            //apply scrollable style if applicable
            if (dialogContent[0].offsetHeight < dialogContent[0].scrollHeight) {
                // your element has overflow
                if (isDefinedAndNotNull(nfDialogData.scrollableContentStyle)) {
                    dialogContent.addClass(nfDialogData.scrollableContentStyle);
                }
            } else {
                // your element doesn't have overflow
                if (isDefinedAndNotNull(nfDialogData.scrollableContentStyle)) {
                    dialogContent.removeClass(nfDialogData.scrollableContentStyle);
                }
            }

            if (isDefinedAndNotNull(nfDialogData.handler)) {
                var handler = nfDialogData.handler.resize;
                if (isDefinedAndNotNull(handler) && typeof handler === 'function') {
                    // invoke the handler
                    handler.call(dialog);
                }
            }
        },

        /**
         * Shows the dialog.
         */
        show: function () {
            var dialog = $(this);

            var zIndex = dialog.css('z-index');
            if (zIndex === 'auto') {
                if (isDefinedAndNotNull(dialog.data('nf-dialog'))) {
                    zIndex = (isDefinedAndNotNull(dialog.data('nf-dialog')['z-index'])) ?
                        dialog.data('nf-dialog')['z-index'] : 1301;
                } else {
                    zIndex = 1301;
                }
            }
            var openDialogs = $.makeArray($('.dialog:visible'));
            if (openDialogs.length >= 1){
                var zVals = openDialogs.map(function(openDialog){
                    var index;
                    return isNaN(index = parseInt($(openDialog).css("z-index"), 10)) ? 0 : index;
                });
                //Add 2 so that we have room for the glass pane overlay of the new dialog
                zIndex = Math.max.apply(null, zVals) + 2;
            }
            dialog.css('z-index', zIndex);

            var nfDialogData = {};
            if (isDefinedAndNotNull(dialog.data('nf-dialog'))) {
                nfDialogData = dialog.data('nf-dialog');
            }

            var glasspane;
            if (isDefinedAndNotNull(nfDialogData.glasspane)) {
                glasspane = nfDialogData.glasspane;
            } else {
                nfDialogData.glasspane = glasspane = dialog.find('.dialog-header').css('background-color'); //default to header color
            }

            if(top !== window || !isDefinedAndNotNull(nfDialogData.glasspane)) {
                nfDialogData.glasspane = glasspane = 'transparent';
            }

            if (!$('body').find("[data-nf-dialog-parent='" + dialog.attr('id') + "']").is(':visible')) {
                //create glass pane overlay
                $('<div></div>').attr('data-nf-dialog-parent', dialog.attr('id')).addClass("modal-glass").css({
                    "background-color": glasspane,
                    "z-index": zIndex - 1
                }).appendTo($('body'));
            }

            //persist data attribute
            dialog.data('nfDialog', nfDialogData);

            return this.each(function () {
                // show the dialog
                if (!dialog.is(':visible')) {
                    dialog.show();
                    dialog.modal('resize');
                    dialog.center();

                    if (isDefinedAndNotNull(nfDialogData.handler)) {
                        var handler = nfDialogData.handler.open;
                        if (isDefinedAndNotNull(handler) && typeof handler === 'function') {
                            // invoke the handler
                            handler.call(dialog);
                        }
                    }
                }
            });
        },

        /**
         * Hides the dialog.
         */
        hide: function () {
            return this.each(function () {
                var dialog = $(this);

                var nfDialogData = {};
                if (isDefinedAndNotNull(dialog.data('nf-dialog'))) {
                    nfDialogData = dialog.data('nf-dialog');
                }

                if (isDefinedAndNotNull(nfDialogData.handler)) {
                    var handler = nfDialogData.handler.close;
                    if (isDefinedAndNotNull(handler) && typeof handler === 'function') {
                        // invoke the handler
                        handler.call(dialog);
                    }
                }

                // remove the modal glass pane overlay
                $('body').find("[data-nf-dialog-parent='" + dialog.attr('id') + "']").remove();

                if (dialog.is(':visible')) {
                    // hide the dialog
                    dialog.hide();
                }
            });
        }
    };

    $.fn.modal = function (method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else {
            return methods.init.apply(this, arguments);
        }
    };
})(jQuery);