// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
define(['hp/core/Router',
    'hp/core/HelpMenu',
    'hp/core/Localizer',
    'hp/core/EventDispatcher',
    'hp/core/UrlFragment',      
    'hp/view/NotificationDetailsView',
    'text!hpPages/core/form_state.html',
    'text!hpPages/core/no_details.html',
    'text!hpPages/core/header_help.html',
    'jquery',
    'hp/lib/jquery.hpStatus',
    'hp/lib/jquery.hpTextFormat'],
function(router, helpMenu, localizer, EventDispatcher, urlFragment,
    notificationDetailsView, formStateHtml, noDetailsHtml, headerHelpHtml) {
"use strict";

    // Manages the form state messages and controls in a dialog footer.
    
    var FormStateView = (function() {
    
        var CHANGES = '#hp-form-changes';
        var CONTROL = '#hp-form-changes-control';
        var COUNT = CHANGES + ' .hp-count';
        var LIST = CHANGES + ' ol';
        var MESSAGE = '#hp-form-message';
        var MESSAGE_TEXT = MESSAGE + ' .hp-form-message-text';
        var MESSAGE_DETAILS = MESSAGE + ' .hp-form-message-details';
        var MESSAGE_STATUS = MESSAGE + ' .hp-status';
        var NAVIGATION_DIALOG = '#hp-form-navigate-away-dialog';
        var BODY = '#hp-body-div';
        var INPUT = 'input:not(type[hidden]), select, textarea';
        var ACTIVE = 'hp-active';
        var ACTIVATEABLE = 'hp-activateable';
        var PROCEED_BUTTON = '#hp-form-navigate-away-proceed';
        var CANCEL_BUTTON = '#hp-form-navigate-away-cancel';
        var HTML_REGEXP = new RegExp('<\\w.*>');
      
        // ### new FormStateView()
        // Create a new FormStateView.
        function FormStateView() {

            var dispatcher = new EventDispatcher();
            var self = null;
            var form = null;
            var container = null;
            var template = null;
            var count = 0;
            var routePattern = null;
            var trackChanges = true;
            var autoTrack = false;
            var initialValues = {};
            var navDialog = null;
            var selectingMessage = false;
            
            // hide changes indicator
            function hideChanges() {
                $(BODY).unbind('click', hideChanges);
                $(CHANGES, container).removeClass(ACTIVE);
            }

            // show changes indicator
            function showChanges() {
                $(CHANGES, container).addClass(ACTIVE);
                // delay to avoid flickering
                setTimeout(function () {$(BODY).bind('click', hideChanges);}, 50);
            }
            
            // hide change details flyout
            function hideDetails() {
                if (!selectingMessage) {
                    $(BODY).unbind('click', hideDetails);
                    $(MESSAGE, container).removeClass(ACTIVE);
                } else {
                    selectingMessage = false;
                }
            }

            // show change details flyout
            function showDetails() {
                $(MESSAGE, container).addClass(ACTIVE);
                // delay to avoid flickering
                setTimeout(function () {$(BODY).bind('click', hideDetails);}, 50);
            }

            // set the current message contents
            function setMsg(args) {
              
                notificationDetailsView.reset($(MESSAGE_DETAILS, container));
         
                if (args.details || args.children) {
                    notificationDetailsView.update($(MESSAGE_DETAILS, container), args);
                    $(MESSAGE, container).addClass(ACTIVATEABLE);
                } else if (args.errorMessage) {
                    var details = [];
                    if (args.errorMessage.errorMessage) {
                        details.push(args.errorMessage.errorMessage);
                    }
                    if (args.errorMessage.resolution) {
                        details.push(args.errorMessage.resolution);
                    }
                    if (HTML_REGEXP.test(details.join('\n'))) { //test to see whether this an htnml or text
                        $(MESSAGE_DETAILS, container).html(details.join('<br>'));
                    }else {
                        $(MESSAGE_DETAILS, container).hpTextFormat(details.join('\n'));
                    } 
                    $(MESSAGE, container).addClass(ACTIVATEABLE);
                } else {
                    $(MESSAGE_DETAILS, container).empty();
                    if (args.changing) {
                        $(MESSAGE, container).addClass(ACTIVATEABLE);
                    } else {
                        $(MESSAGE, container).removeClass(ACTIVATEABLE);
                    }
                }
            }
            
            // set message contents and layout
            function setMessageImpl(args) {           
                if (args.status) {
                    $(MESSAGE_STATUS, container).show().
                        hpStatus(args.status, args.changing);
                } else {
                    $(MESSAGE_STATUS, container).hide();
                }
                if (args.summary === '') {
                    // Collapse the expanded yellow message panel if there is no
                    // message to be displayed
                    hideDetails();
                }
                $(MESSAGE_TEXT, container).text(args.summary);
                   
                setMsg(args);               
                
                // stretch to buttons
                var controls = $('.hp-form-controls');
                $(MESSAGE, container).css('right',
                    ($('#hp-form-state').width() -
                     (controls.length > 0 ? controls.position().left : 0)));
                if ($(MESSAGE, container).hasClass(ACTIVATEABLE)) {
                    if ('error' === args.status || 'warning' === args.status) {
                        showDetails();
                    }
                }
            }

            // remove an update indication
            function unUpdateImpl(id, label) {
                // see if have a change record for this id
                var change = $('.hp-form-change[data-input-id=' + id + ']');
                if (change.length > 0) {
                    count -= 1;
                    $(COUNT, container).text(count);
                    if (0 === count) {
                        // don't show the change indicator if there aren't any changes
                        $(CHANGES, container).hide();
                        setMessageImpl({summary: '', details: ''});
                    } else {
                        setMessageImpl({summary:
                            localizer.getString('core.formState.unchanged',
                              [label])});
                    }
                    change.remove();
                }
            }

            // add/set an update indication
            function updateImpl(id, label, newValue, initialValue, message, hideValue) {
                var change, defaultMessage;

                if (undefined != initialValue) {
                    initialValues[id] = initialValue;
                }
                
                if ((newValue === initialValues[id]) && !hideValue) {
                    // The user has returned to the original value, remove the change record, 
                    // unless it is a value to be hidden (i.e. password).  A password field
                    // stays "changed" if user started modifying it.
                    unUpdateImpl(id, label);
                } else {
                    change = $('.hp-form-change[data-input-id=' + id + ']');
                    // We might already have a change record if the user updates
                    // the same value multiple times. Only create a new one if one
                    // doesn't exist.
                    if (change.length === 0) {
                        count += 1;
                        $(COUNT, container).text(count);
                        change = template.clone();
                        change.attr('data-input-id', id);
                        $('.hp-form-change-label', change).text(label);
                        $(LIST, container).append(change);
                    }
                    $('.hp-form-change-value', change).text(newValue);
                    $(CHANGES, container).show();
                    
                    if (message){
                        setMessageImpl({summary:message});
                    }
                    else {
                        if (localizer.getString('core.formState.checkedValue') === newValue) {
                            defaultMessage =
                                localizer.getString('core.formState.checked', [label]);
                        } else if (localizer.getString('core.formState.uncheckedValue') === newValue) {
                            defaultMessage =
                                localizer.getString('core.formState.unchecked', [label]);
                        } else {
                            defaultMessage = hideValue ? 
                                localizer.getString('core.formState.changed_label_only', [label]) : 
                                localizer.getString('core.formState.changed', [label, newValue]);
                        }
                        setMessageImpl({
                            summary : defaultMessage
                        });
                    }

                }
                
                // Inform the Router that we are tracking changes and
                // might want to inhibit navigating away from this page.
                // The Router will call canNavigateTo() on any attempt to
                // change the location.
                router.trackFormState(self);
                
                dispatcher.fire('change',
                    {id: id, label: label, value: newValue});
            }

            // get the id to use for an element
            function getId(elem) {
              // special case radio buttons. Use 'name' for id
              if ($(elem).is(':radio')) {
                  return $(elem).attr('name');
              } else {
                  return $(elem).attr('id');
              }
            }

            // get the file name to use for an element
            function getFileName(elem) {
                var filepath;
                // If the event triggered on this element is from drag and drop,
                // then the file name is stored in the "droppedFile" attribute
                var attr = $(elem).attr('dropped-file');
                if (typeof attr !== 'undefined' && attr !== false) {
                    return (attr ? attr : null);
                }
                if ($(elem)[0].files && $(elem)[0].files[0]) {
                    // if short file name is available (FF and Chrome), return it
                    return $(elem)[0].files[0].name;
                }   // else extract the file name from the path 
                filepath = $(elem).val();
                // get rid of whitespace on the ends and pull of the filename from the end 
                // (allows either \ or / as separators.)
                var parts = $.trim(filepath).split(/[\\\/]/);
                return parts.length > 0 ? parts[parts.length - 1] : filepath;
            }

            // get checkbox's value
            function getCheckboxValue(elem){
                // if this is an hpToggle, use the values from that
                var hpToggle = $('#' + $(elem).attr('id') + '-hpToggle');
                if (hpToggle.length > 0) {
                    if ($(elem).is(':checked')) {
                        return $('.hp-on', hpToggle).text();
                    } else {
                        return $('.hp-off', hpToggle).text();
                    }
                } else {
                    return ($(elem).is(':checked') ?
                        localizer.getString('core.formState.checkedValue') :
                        localizer.getString('core.formState.uncheckedValue'));
                }
            }

            // get the current value of a form element
            function getValue(elem) {
              var returnValue,
                  radioId;
              
              if ($(elem).is(':checkbox')) {
                  returnValue =  getCheckboxValue(elem);
              } else if ($(elem).is(':radio')) {
                  // construct value to be the label for the
                  // currently selected radio of the same name
                  radioId = $('input[name=' + $(elem).attr('name') +
                      ']:checked', form).attr('id');
                  returnValue =  $('label[for="' + radioId + '"]').text();
              } else if ($(elem).is(':password')) {
                  returnValue =  Array($(elem).val().length).join('*');
              } else if ($(elem).is('select')) {
                  returnValue =  $('option:selected', elem).text();
              } else if ($(elem).is(':file')) {
                  returnValue =  getFileName(elem);
              } else {
                  returnValue =  $(elem).val();
              }
              
              return returnValue;
            }

            // react to a form element changing
            function onChange() {
                var id = getId($(this));
                var label = $('label[for=' + id + '], label[data-name=' + id + ']').first();
                // only track things that we have a label for
                if (label.length > 0) {
                    updateImpl(id, label.text(), getValue($(this)), null, null, $(this).is(':password'));
                }
            }

            // show changes if we aren't
            function onClickControl() {
                if (! $(CHANGES, container).hasClass(ACTIVE)) {
                    showChanges();
                }
            }

            // expand details when user clicks on the message summary
            function onClickMessage() {
                if (! $(MESSAGE, container).hasClass(ACTIVE)) {
                    showDetails();
                }
            }

            // If the user moves the mouse while pressing, assume he is
            // selecting text.
            function onMouseMoveMessage() {
                selectingMessage = true;
            }

            // mouse is up
            function onMouseUpMessage() {
                $(BODY).unbind('mousemove', onMouseMoveMessage);
                $(BODY).unbind('mouseup', onMouseUpMessage);
            }

            // mouse id down, see if it moves
            function onMouseDownMessage() {
                $(BODY).bind('mousemove', onMouseMoveMessage);
                $(BODY).bind('mouseup', onMouseUpMessage);
            }

            // a form element has lost focus, it is no longer active
            function onBlur(event) {
                $(event.target).parents('.hp-form-content').
                    removeClass('hp-active');
            }

            // a form element has gained focus, it is now active
            function onFocus(event) {
                $(event.target).parents('.hp-form-content').
                    addClass('hp-active');
            }

            // mouse is over a form element
            function onOverContent(event) {
                var messageContainer = $('.hp-message-container', event.target);
                messageContainer.toggleClass('hp-empty',
                    $.grep(messageContainer.children(), function (e) {
                        return ('none' !== $(e).css('display') &&
                            'hidden' !== $(e).css('visibility'));
                    }).length === 0);
            }

            // reset the internal memory of initial form element values
            function resetInitialValues() {
                initialValues = {};
                if (autoTrack) {
                    // check all inputs
                    $(INPUT, form).each(function(index, elem) {
                        // make sure it has an id
                        var id = getId($(elem));
                        if (id) {
                            // and that we have a label for that id
                            var label = $('label[for=' + id +
                                ']:not(label[generated=true])');
                            if (label.length > 0) {
                                initialValues[id] = getValue($(elem));
                            }
                        }
                    });
                }
            }
            
            // Update hp-anchor-uri links with the current parameters
            // See also: MasterPaneView.onFilterChange() 
            function resetAnchorUris() {
                urlFragment.replaceHrefParametersFromFragment($('.hp-form-controls', form), router.location());
            }

            // ### init(args)
            // Call after `new` to initialize with the form to track and
            // configure behavior.
            //
            // Takes a single argument object with properties as follows:
            //
            // - **form** the form to track, jQuery object or DOM id
            // - **routePattern** if the user navigates away from this pattern with
            //     outstanding changes, he will be warned. If this is not specified,
            //     any attempt to change the location will issue a prompt if there
            //     are changes outstanding. This is primarily here for forms that
            //     use stacked panels to allow local navigation.
            // - **trackChanges** whether to reserve space for tracking changes
            //     to the form. If false, `update()`, `unUpdate()` have no effect,
            //     and the form never pauses routing for confirmation before
            //     leaving.
            // - **autoTrack** whether to automatically track changes to the form.
            //     In order to do this, form inputs must have unique IDs and
            //     must have corresponding `<label for="">` tags using the `for`
            //     attribute. Defaults to false. If the caller does not turn this on,
            //     he must use the `update()` and `unUpdate()` functions to manage
            //     the form changes himself. Automatically turns on `trackChanges`.
            //
            // Remember to `reset()` the form state on view `pause()` or after you
            // update the contents of the form inputs with data, such as in `onItemChange()`.
            this.init = function (args) {
                self = this;
                form = args.form;
                routePattern = args.routePattern;
                autoTrack = args.autoTrack;
                if (autoTrack) {
                    trackChanges = true;
                } else if (args.hasOwnProperty('trackChanges')) {
                    trackChanges = args.trackChanges;
                }
                
                container = $(formStateHtml);
                localizer.localizeDom(container);
                template = $('#hp-form-change-template', container).
                    remove().attr('id', '').show();
                
                $('> .hp-footer', form).prepend(container);
                $(MESSAGE, container).mousedown(onMouseDownMessage);
                $(MESSAGE, container).click(onClickMessage);
                $(CHANGES, container).css('display', 'inline-block').hide();
                if (! trackChanges) {
                    $(MESSAGE, container).css('left', 0);
                } else {
                    $(CONTROL, container).click(onClickControl);
                }
                
                // put help in header
                if ($('.hp-details-header-controls', $(form).parent()).length === 0) {
                    var headerHelp = $(headerHelpHtml);
                    localizer.localizeDom(headerHelp);
                    $('.hp-details-header', $(form).parent()).append(headerHelp);
                }
                helpMenu.setLocation(router.location());
                
                if (autoTrack) {
                    $(form).on('change', INPUT, onChange);
                    resetInitialValues();
                }
                
                $(form).on('focus', INPUT, onFocus);
                $(form).on('blur', INPUT, onBlur);
                $(form).on('mouseover', '.hp-form-content', onOverContent);
                
                // NOTE: The following code is DEPRECATED, and will be removed in sprint 28.
                //       The user should add the class 'hp-has-help' to <label class='hp-optional'>
                //       element which also has a <label class='hp-help'> sibling.
                // When active/hover, hide 'optional' if 'help' exists 
                $('.hp-form-content label.hp-optional', form).each(function(index) {
                    if ($(this).siblings().hasClass('hp-help') ||
                        // in case the optional is outside an hp-message-container, deprecated
                        $(this).siblings().hasClass('hp-message-container')) {
                        $(this).addClass('hp-has-help');
                    }
                });
                
                resetAnchorUris();
            };
            
            // ### evaluate()
            // NOTE: This function is DEPRECATED, will be removed in 2013
            //
            // Evaluate the form contents for new input fields.
            // Callers should call this when they dynamically add `<input>`
            // controls after initializing or reseting.
            this.evaluate = function () {
                if (window.console) {
                    window.console.warn('FormStateView.evaluate() is deprecated');
                }
            };
            
            // ### reset()
            // Resets the form state to its initial state.
            //
            // Callers should call this when they `pause()` their views to disconnect
            // the form state from the router, when they initialize the form input
            // field contents, and after successfully initiating the add/edit.
            this.reset = function () {
                var context = $(form).parent();
                $('#hp-no-details', context).remove();
                context.removeClass('hp-empty');
                $(LIST).empty();
                count = 0;
                $(COUNT, container).text(count);
                $(CHANGES, container).hide();
                setMessageImpl({summary: '', details: ''});
                resetInitialValues();
                router.trackFormState(null);
                resetAnchorUris();
            };
            
            // ### update(id, label, newValue, initialValue, message)
            // Manually track a change to a form input.
            //
            // If the user specifies an `initialValue`, any subsequent 
            // change back to that value will remove the change record.
            //
            // - **message** pre-formatted string for cases where the 
            //     default state changes descriptions "Changed: <initial> to <new>" do 
            //     not make sense.
            this.update = function (id, label, newValue, initialValue, message) {
                if (trackChanges) {
                    updateImpl(id, label, newValue, initialValue,message);
                }
            };
            
            // ### unUpdate(id, label)
            // Manually untrack a change to a form input.
            this.unUpdate = function (id, label) {
                if (trackChanges) {
                    unUpdateImpl(id, label);
                }
            };
            
            // ### setMessage(args)
            // Update the form state message.
            //
            // Takes a single argument object with properties as follows:
            //
            // - **summary** a one line summary
            // - **status** one of 'ok', 'error', 'warning', 'info'
            // - **changing** true|false, whether the status should be
            //     anotated as changing
            // - **details** longer description that can have line breaks and links
            // - **errorMessage** a REST error object containing an errorMessage
            //     and resolution.
            // - **actions** array of Markdown syntax or HTML links
            // - **children** Array of sub-notification objects, with their own
            //     `status`, `details`, and `actions`
            //
            // A `summary` must be provided.
            // `details` and `errorMessage` are optional, but the are mutually
            // exclusive.
            this.setMessage = function (args) {
                setMessageImpl(args);
            };
            
            // ### noItem(errorMessage)
            this.noItem = function (errorMessage) {
                var context = $(form).parent();
                var noDetails = $(noDetailsHtml);
                localizer.localizeDom(noDetails);
                $('#hp-no-details', context).remove();
                context.addClass('hp-empty');
                context.append(noDetails);
            };
            
            // ### on(eventName, callback)
            this.on = function(eventName, callback) {
                dispatcher.on(eventName, callback);
            };
            
            // ### off(eventName, callback)
            this.off = function(eventName, callback) {
                dispatcher.off(eventName, callback);
            };
            
            // ### canNavigateTo(location, handlers)
            // Exclusively for use by the Router
            this.canNavigateTo = function (location, handlers) {
                if (! trackChanges || 0 === count ||
                    (routePattern && location.match(routePattern))) {
                    // proceed if we don't have any changes or if the location
                    // matches the allowed pattern
                    handlers.proceed(routePattern && location.match(routePattern));
                } else {
                    // Create a dialog to make the user aware that they will lose
                    // changes if they navigate away.
                    // http://pluksza.blogspot.com/2011/06/jquery-ui-dialog-open-good-practise.html
                    if (! navDialog) {
                        navDialog = $(NAVIGATION_DIALOG, container);

                        navDialog.dialog({
                            autoOpen: false,
                            draggable: false,
                            modal: true,
                            resizable: false,
                            dialogClass: 'hp-simple-dialog'
                            });          

                        $(PROCEED_BUTTON, navDialog).on('click', function (ev) {
                            navDialog.dialog('close');
                            handlers.proceed();
                        });

                        $(CANCEL_BUTTON, navDialog).on('click', function (ev) {
                            navDialog.dialog('close');
                            handlers.revert();
                        });
                    }
                    navDialog.dialog('open');
                }
            };
        }

        return FormStateView;
    }());

    return FormStateView;
});
