// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
define(['hp/view/DialogHeader',
    'hp/view/DialogNotification',
    'hp/core/Localizer',
    'text!hpPages/core/dialog_overlay.html',
    'jquery'],
function (DialogHeader, DialogNotification, localizer, dialogOverlayHtml) {
"use strict";

    // Manages a dialog's context, header, and notification messages.
    // There are two types of dialogs that can be managed with DialogView:
    //
    // ## Routed
    //
    // Dialogs that have corresponding routes. There can be at most one
    // routed dialog instantiated at any one time. Callers should
    //
    // 1.  create dialog contents in an HTML file whose primary element is
    //     a `<div class="hp-dialog"></div>`.
    // 2.  create a javascript view module to interact with the Pager in which:
    //     1. require `hp/view/DialogView` as `DialogView`
    //     2. instantiate `var dialog = new DialogView`
    //     3. in the view `init()`, call `dialog.init()`
    //     4. in the view `pause()`, call `dialog.pause()`
    //     5. in the view `resume()`, call `dialog.resume()`
    // 3.  configure a route to map the appropriate route location to use
    //     the above page and view.
    //
    // Alternatively, if the contents of the dialog are completely static, you can
    // use `hp/view/DialogView` directly in the route configuration, skipping the
    // javascript view step 2 above. Typically, you will want your own javascript
    // view to customize any notification messages appropriately.
    //
    // ##Inline
    //
    // Dialogs that are not routed. There can be more than one of these, although
    // at most one is recommended. Callers should pass the contents of the dialog
    // in the `init()` call.
    //
    // 1.  create dialog contents in an HTML file who's primary element is
    //     a `<div class="hp-dialog"></div>`.
    // 2.  in the javascript file handling the control that launches the dialog
    //     1. require the above HTML file
    //     2. require `hp/view/DialogView` as `DialogView`
    //     3. when the dialog should be launched
    //         1. instantiate `var dialog = new DialogView() d`
    //         2. initialize
    //
    //                 dialog.init({contents: <html file reference>,
    //                     ok: <ok function, return false to inhibit closing dialog>,
    //                     cancel: <cancel function>,
    //                 });

    var DialogView = (function () {

        var ROUTED_OVERLAY = '#hp-dialog-overlay';
        var ROUTED_CONTAINER = '#hp-dialog-container';
        var INLINE_OVERLAY = '.hp-dialog-overlay';
        var INLINE_CONTAINER = '.hp-dialog-container';
        var DIALOG = '.hp-dialog';
        var DEPENDENCIES = '.hp-dependencies';
        var VARIABLE_CONTENTS = '.hp-dialog-variable-contents';
        var OK = '.hp-ok';
        var CANCEL = '.hp-cancel';
        var PRIMARY_BUTTON = '.hp-primary';
        var ACTIVE = 'hp-active';
        var ENTER = '13';
        var ESCAPE = '27';

        // ### new DialogView()
        // Create a new DialogView.
        // 
        // DEPRECATED: single argument Object with properties as follows:
        //
        // - **contents** contents of dialog
        // - **ok** the function called when the user clicks ok.
        // - **cancel** the function called when the user clicks cancel.
        //
        // This parameter is DEPRECATED, callers should use `new` followed by `init()`.

        function DialogView(newArgs) {
          
            var overlay;
            var container;
            var component;
            var header = new DialogHeader();
            var notification = new DialogNotification();
            var cancelCallback;
            var okCallback;
            var cleanup; // Forward references so Sonar won't complain
            
            // How much height is available, accounting for fixed elements?
            function calculateVariableContentsHeight() {
                var available = container.parent().height();
                var top;
                if ($(DEPENDENCIES, component).length > 0) {
                    if ($(DEPENDENCIES, component).is(':visible')) {
                        top = $(DEPENDENCIES, component).offset().top;
                    } else {
                        top = $(DEPENDENCIES, component).parent().offset().top +
                            $(DEPENDENCIES, component).parent().outerHeight();
                    }
                    available -= top;
                }
                if ($('> header', component).length > 0) {
                    available -= $('> header', component).outerHeight();
                }
                if ($('.hp-prompt', component).length > 0) {
                    available -= $('.hp-prompt', component).outerHeight();
                }
                if ($('> footer, > form > footer', component).length > 0) {
                    available -= $('> footer, > form > footer', component).outerHeight();
                }
                    
                return available;
            }
            
            // How much width is available so we can show the header and footer?
            function calculateMinWidth() {
                var headerWidth = $('header > *', component).outerWidth();
                var footerWidth = Math.max(
                    $('footer .hp-controls', component).outerWidth(),
                    $('footer .hp-form-controls', component).outerWidth());
                if ($('footer #hp-form-state', component).length > 0) {
                    footerWidth += 200; // allow room for minimal form state
                }
                return Math.max(headerWidth, footerWidth);
            }
            
            // Adjust allowed widths and heights when resized
            function onResize() {
                var maxHeight = calculateVariableContentsHeight();
                var minWidth = calculateMinWidth();
                var maxWidth = Math.min(920, $(window).width() - 40); // 40 is for padding
                $(DEPENDENCIES, component).css('max-height', maxHeight);
                $(VARIABLE_CONTENTS, component).css('max-height', maxHeight);
                $(component).css({'min-width': Math.min(minWidth, maxWidth),
                    'max-width': maxWidth});
                notification.resize();
            }
            
            // User clicked cancel
            function onCancel(event) {
                if (cancelCallback) {
                    cancelCallback();
                }
                cleanup();
                if (event) {
                    event.preventDefault();
                }
            }
            
            // User clicked OK
            function onOk(event) {
                var doCleanup = true;
                if (okCallback) {
                    // Pass the button that was clicked in case
                    // the caller has multiple OK buttons.
                    if (false === okCallback(this)) {
                        doCleanup = false;
                    }
                }
                if (doCleanup) {
                    cleanup();
                }
                if (event) {
                    event.preventDefault();
                }
            }
            
            // User has pressed a key
            //
            // Note: this function does not capture backspace.
            // Browser backspace behavior should be respected
            function onKeyDown(event) {
                var keyCode = '' + (event.which ? event.which : event.keyCode);
                
                if (overlay[0] === $('.hp-dialog-overlay').last()[0]) {
                    if (keyCode === ENTER &&  
                        !/^(button|reset|submit|textarea)$/i.test(event.target.type)) {
                        // ENTER key anywhere in form except the buttons
                        // Trigger the handler depending on which button has hp-primary class
                        //
                        // Need to manually move focus to the button to simulate an actual user click.
                        //
                        // If user clicks on the button, the button becomes focused at mouse-down, and 
                        // click handler is invoked at mouse-click.  However, when the button click is 
                        // triggered internally, the focus event comes after the click. The focus needs
                        // to be moved away from the form to prevent redundant jquery.validate.reset()
                        // calls during validation.
                        // 
                        // See also: uihptour/src/main/webapp/js/tour/view/DialogAddView.js
                        //
                        $('footer .hp-primary', component).focus().trigger('click');
                        event.preventDefault();
                    } else if (keyCode === ESCAPE) {
                        // ESCAPE key anywhere
                        onCancel(event);
                    }
                }
            }
            
            // add global DOM listeners
            function addListeners() {
                $(document).on('keydown', onKeyDown);
                $(window).on('resize', onResize);
                setTimeout(onResize, 50);
            }
            
            // remove global DOM listeners
            function removeListeners() {
                $(document).off('keydown', onKeyDown);
                $(window).off('resize', onResize);
            }
            
            // remove inline elements from the DOM
            cleanup = function () {
                if (container.hasClass('hp-dialog-container')) {
                    // inline
                    removeListeners();
                    $(OK, component).off('click', onOk);
                    $(CANCEL, component).off('click', onCancel);
                    container.removeClass(ACTIVE);
                    overlay.removeClass(ACTIVE);
                    $(component).detach();
                    overlay.remove();
                }
            };
            
            // Common initialization
            function initialize(args) {
                if (args) {
                    okCallback = args.ok;
                    cancelCallback = args.cancel;
                }
                
                container.addClass(ACTIVE);
                overlay.addClass(ACTIVE);
                
                header.init(DIALOG + ' > header', component);
                notification.init(component);
                
                $(PRIMARY_BUTTON+':enabled', component).focus();
                $(OK, component).on('click', onOk);
                $(CANCEL, component).on('click', onCancel);
                
                addListeners();
            }
            
            // Inline specific initialization
            function initializeInline(args) {
                overlay = $(dialogOverlayHtml).filter(INLINE_OVERLAY);
                localizer.localizeDom(overlay);
                container = $(INLINE_CONTAINER, overlay);
                component = $(args.contents);
                localizer.localizeDom(component);
                container.append(component);
                $('#hp-body-div').append(overlay);
                
                initialize(args);
            }
            
            // routed specific initialization
            function initializeRouted(args) {
                overlay = $(ROUTED_OVERLAY);
                container = $(ROUTED_CONTAINER, overlay);
                component = $(DIALOG, container);
                
                initialize(args);
            }
            
            // for old style initialization via new, deprecated, callers should use init()
            if (newArgs) {
                if (window.console) {
                    window.console.warn("Convert usage of DialogView to use 'new' then 'init'");
                }
                initializeInline(newArgs);
            }
            
            // ### init([args])
            // Call after `new` to initialize.
            //
            // Takes a single optional argument object with properties as follows:
            //
            // - **contents** element to put inside the dialog. Use this form for inline dialogs.
            // - **ok** function that will be called when the user clicks the button with `hp-ok` class.
            //     You can return false from this function to prevent the dialog from being closed.
            // - **cancel** function that will be called when the user clicks the button with
            //     `hp-cancel` class.
            this.init = function (args) {
                if (args && args.hasOwnProperty('contents')) {
                    initializeInline(args);
                } else {
                    initializeRouted();
                }
            };
            
            
            // ### resize()
            // Call when resetting view, or
            // call after loading data lazily
            this.resize = function () {
                setTimeout(onResize, 50);
            };
            
            // ### pause()
            // Call when in routed mode to pause
            this.pause = function () {
                removeListeners();
            };
            
            // ### resume()
            // Call when in routed mode to resume
            this.resume = function () {
                addListeners();
            };
            
            // ### cancel()
            // Cancels the dialog
            this.cancel = function () {
                onCancel();
            };
            
            // ### setMessage(notif)
            //
            // Sets one or more notification messages using a single object argument with
            // the following properties:
            //
            // - **status** one of 'ok', 'error', 'warning', 'info'
            // - **summary** minimal text summary, should be one line
            // - **details** longer description that can have line breaks and links
            //     Will use `hpTextFormat` to format Markdown text.
            // - **resolution** multi-line recommendation on how the user should proceed
            //     Will use `hpTextFormat` to format Markdown text.
            // - **actions** Array of action links
            // - **children** Array of sub-notification objects
            //
            // The only property that is required is the summary.
            this.setMessage = function(notif) {
                notification.setMessage(notif);
            };
            
            // ### clearMessage()
            // Clear any notification messages.
            this.clearMessage = function() {
                notification.clearMessage();
            };
        }

        return DialogView;
    }());

    return DialogView;
});
