// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.

define(['fs/presenter/settings/RestoreEventDispatcher',
        'fs/services/restore/RestoreService',
        'fs/services/settings/SettingsService',
        'fs/presenter/settings/BackupStatusPresenter',
        'hp/core/Localizer',
        'hp/model/Upload',
        'hp/core/Router'],
function(RestoreEventDispatcher, restoreService, settingsService,
        backupStatusPresenter, localizer, Upload, router) { "use strict";
    /*
     * Theory of operation for the Restore User Interface
     * 
     * The user interface for Restore from backup has been significantly revised. The features driving this revision
     * include:
     * 
     *   1. The process of uploading a backup file and then restoring it is distinctly separate in the new UI. Where
     *      the old UI would require an upload and start it the moment a file was selected, the new UI looks first
     *      at the content of the staging area to determine if a backup is already present on the appliance.
     *          
     *       a. If a staged backup is present, the Restore from backup dialog will present information about staged
     *          backup file and provide the opportunity for the user to restore it directly without downloading and
     *          uploading it first.
     *          
     *       b. If no staged backup is present, the Restore from backup dialog will present the Uploader widget allowing
     *          the user to drag and drop or select a backup file. The user may then ...
     *          
     *           i.   Upload the file into the staging area. Once the upload starts, the dialog will be dismissed.
     *           ii.  Upload and Restore the backup in a single step presented in a full page UI.
     *           
     *   2. The user may reopen the Restore from upload dialog during the upload process. The dialog will pick up the
     *      current status of the upload process, including progress, and display it just as though it had never been
     *      dismissed.
     *      
     * The split of the process into two separate views and the ability of the user to "come and go" from the Restore
     * from backup dialog has driven following changes:
     * 
     *   1. The actual processes have been moved from the RestoreUpload to RestorePresenter (and unit tests have been
     *      created to test them).
     *      
     *   2. Two primary views (UploadThenRestoreView and UploadAndRestoreView) have been created to support the Restore
     *      from backup dialog (restoreAttendedDialog.html) and the full page UI (restoreUnattendedPage.html)
     *      respectively. The old restore.html has been removed The old RestoreUploadView.js remains, but only as a
     *      bridge over an otherwise breaking change for Fusion (injection of Fusion's customized warning on the Restore
     *      from backup dialog would fail if this code was removed. These views do very little except connect user
     *      controls on the dialog or page to the process.
     *      
     *   3. Two support views do most of the heavy lifting
     *      of hiding and showing information on the page as well as capturing upload and validation failures for the
     *      activity flyout. These views persist outside the context of user driven navigation and remain active (though
     *      not necessarily visible) throughout the entire process.
     *      
     *   4. The views communicate with the process in in RestorePresenter primarily by calling public methods to set
     *      the *_MODE, the *_STEP, or UPLOAD_PROGESS (see below).
     *      
     *   5. The process communicates with the views (primary and support) by firing events through its dedicated
     *      RestoreEventDispatcher. This dispatcher wraps the EventDispatcher some logic that will replay the events,
     *      if appropriate, if and when the user reopens the Restore from backup dialog.
     */
    var RestorePresenter = (function() {
        var API_VERSION = 100;
        
        var UPLOAD_URI = '/rest/backups/archive'; // POST to upload a backup file.
        // Error code returned when IE8/9 fails to put upload response in iframe
        var IE8Or9_EMPTY_RESPONSE_ERROR_CODE = "IE8Or9EmptyResponse";
        
        var FLASH_DELAY = 1000,
            LONG_FLASH_DELAY = 2000,
            UPLOADED = 'UPLOADED',
            SUCCEEDED = 'SUCCEEDED';
        
        var NOT_REPLAYABLE = 0,
            UPLOAD_MODE = 100,
            UPLOAD_STEP = 101,
            UPLOAD_PROGRESS = 102,
            STAGED_MODE = 200,
            NOT_STAGED_MODE = 300;
        
        var restorePresenter;
        var uploadProcess = null;
        var fileSelection = null;
        var restoreCanceled = false;
        /**
         * @constructor
         */
        function RestorePresenter() {
            
            // When a staged backup is detected on the appliance, save the info here.
            var stagedBackupInfo;
            
            // Maximum backup size from DB settings
            var maxBackupSize = null;
            
            // For unit testing, allow a stub of the timers created and cleared.
            var timerHost = window;
            
            // name of setting for the maximum backup size
            var MAX_BACKUP_SIZE_SETTING_NAME = 'max-backup-size';
            
            // polling interval for restore status (in milliseconds)
            var RESTORE_POLL_INTERVAL = 100;
            
            // Used to communicate with multiple Restore views.
            var dispatcher = new RestoreEventDispatcher();
            
            /*
             * Pass file selection from the restore dialog through the presenter so that it can be used by both views.
             */
            function setFileSelection (selection) {
                fileSelection = selection;
                dispatcher.fire(UPLOAD_STEP, "selectedStep", fileSelection);
            }
            function getFileSelection () {
                return fileSelection;
            }
            function clearFileSelection () {
                fileSelection = null;
            }
            
            // Stay in the Restore UI with the current file selection and process intact.
            function softReset() {
                dispatcher.fire(NOT_REPLAYABLE, "softReset");
            }
            // Exit the Restore UI but leave the current file selection and process intact.
            // The presumption is that the user will come back to a running process or at least
            // sufficient context to determine the nature of any errors.
            function softExit() {
                softReset();
                router.replaceWith("/settings");
            }
            // Reset the process, but keep the current file selection and stay in the Restore UI.
            function mediumReset() {
                dispatcher.fire(NOT_REPLAYABLE, "mediumReset");
                clearFileSelection();
                dispatcher.clearAllReplayableEvents();
            }
            // Exit the Restore UI but leave the current file selection intact.
            function mediumExit() {
                mediumReset();
                softExit();
            }
            // Reset the current file selection and process, but stay in the Restore UI.
            function hardReset() {
                dispatcher.fire(NOT_REPLAYABLE, "hardReset");
                mediumReset();
            }
            // Reset everything and leave the Restore UI.
            function hardExit() {
                hardReset();
                mediumExit();
            }
            
            function fireErrorEvent(mode, name, message, details) {
                var errorInfo = {
                    summary : message,
                    details : details,
                    status : "error"
                };
                dispatcher.fire(mode, name, errorInfo);
            }
            
            function reportErrorDetails(mode, name, errorInfo){
                fireErrorEvent(mode, name, errorInfo.message, 
                    errorInfo.recommendedActions ? errorInfo.recommendedActions.join(' ') : null);
            }
            
            function recoverFromErrors() {
                timerHost.setTimeout(hardExit, FLASH_DELAY);
            }
            
            function onGetMaxBackupSizeSuccess(data) {
                maxBackupSize = data.value;
            }
            
            function onGetMaxBackupSizeError(data) {
                maxBackupSize = null;
            }
            
            function onStartRestoreSuccess() {
                var nextStep = function () {
                    router.go("/settings");
                };
                dispatcher.fire(NOT_REPLAYABLE, "restoreModeStartSuccess");
                timerHost.setTimeout(nextStep, FLASH_DELAY);
            }

            function onStartRestoreError(errorInfo){
                function nextStep () {
                    hardExit();
                }
                dispatcher.fire(NOT_REPLAYABLE, "restoreModeStartError", errorInfo);
                timerHost.setTimeout(nextStep, FLASH_DELAY);
            }
            
            function startRestoreImpl(handlers, backupUri) {
                router.replaceWith("/settings");
                restoreService.startRestore({
                            success: function(data) {
                                         checkRestoreStatus(data, handlers);
                            },
                            error: function(errorInfo) {
                                          handlers.error(errorInfo);
                            } 
                }, backupUri);
            }
            
            function performRestore() {
                var restoreHandlers = {success: onStartRestoreSuccess,
                                       error:   onStartRestoreError};
                if (! restoreCanceled) {
                    startRestoreImpl(restoreHandlers, stagedBackupInfo.uri);
                }
                restoreCanceled = false;
            }
            function performDelayedRestore() {
                performRestore(); // Per UX input 1/17/2014 - just blast right on through.
            }
            
            function checkRestoreStatus(data, handlers) {
                if (data.status == 'FAILED') {
                    handlers.error({message: data.errorMessage , 
                                    recommendedActions: data.resolutionMessage});
                } else if (data.progressStep != 'PREPARING_TO_RESTORE' &&
                    data.progressStep != 'UNKNOWN') {
                    handlers.success();
                } else {
                    timerHost.setTimeout(function() {
                        restoreService.getLastRestoreStatus({
                            success : function(collection) {
                                checkRestoreStatus(collection.members[0], handlers);
                            },
                            error : function(errorInfo) {
                                handlers.error(errorInfo);
                            }
                        });
                    }, RESTORE_POLL_INTERVAL);
                }
            }
            
            // Validation Handlers
            function validationSuccess(data, uploadInfo) {
                function nextStep () {
                    softReset();
                    dispatcher.fire(UPLOAD_STEP, 'uploadSuccess', data);
                    dispatcher.clearReplay(UPLOAD_MODE);
                    clearFileSelection();
                }
                
                var handlers = {
                    success: function (stagedInfo) {
                        data = stagedInfo;
                    },
                    error : function () {
                        // Do nothing - just means we didn't get the software version
                    }
                };
                getStagedStatus(handlers);
                dispatcher.fire(UPLOAD_STEP, 'validatedStep', data);
                nextStep(); // Per UX input 1/13/2014 - Don't flash validatedStep
            }
            function validationFailure(data) {
                softReset();
                dispatcher.clearAllReplayableEvents();
                dispatcher.fire(UPLOAD_STEP, 'validationFailure', data);
                dispatcher.clearAllReplayableEvents();
            }
            
            function onUploadSuccess(data, uploadInfo) {
                function waitForUploadedFlashAndValidate () {
                    if (data.status === SUCCEEDED) {
                        validationSuccess(data, uploadInfo);
                    } else {
                        validationFailure(data);
                    }
                }
                timerHost.setTimeout(waitForUploadedFlashAndValidate, LONG_FLASH_DELAY);
            }
            
            function onGetLastBackupError(errorInfo) {
                softReset();
                reportErrorDetails(UPLOAD_MODE, "uploadError", errorInfo);
            }
            
            function onGetLastBackupSuccess(backup) {
                if (backup) {
                    if (backup.backupType === UPLOADED) {
                        if (backup.status === SUCCEEDED) {
                             validationSuccess(backup);
                        } else {
                             validationFailure(backup);
                        }
                    } else {
                        fireErrorEvent(UPLOAD_MODE, "uploadError",
                            localizer.getString('fs.settings.restore.uploadOverwritten'),
                            localizer.getString('fs.settings.restore.retryUpload')
                        );
                    }
                } else {
                    fireErrorEvent(UPLOAD_MODE, "uploadError",
                        localizer.getString('fs.settings.restore.uploadFailed'),
                        localizer.getString('fs.settings.restore.retryOrSelectAlternate')
                    );
                }
            }
            
            function onUploadIE8Or9Problem() {
                // Since IE9 (and IE 8) don't provide the upload response,
                // get the last backup resource to find out the upload status.
                backupStatusPresenter.getLastBackupStatus(
                     {success: onGetLastBackupSuccess, 
                      error: onGetLastBackupError
                     }
                 );
             }
            
            function onUploadError(errorInfo) {
                // IE9 (and IE8) don't always provide the upload response
                if (errorInfo.errorCode === IE8Or9_EMPTY_RESPONSE_ERROR_CODE) {
                    onUploadIE8Or9Problem();
                } else {
                    softReset();
                    reportErrorDetails(UPLOAD_MODE, "uploadError", errorInfo);
                }
            }
            
            // Call this function to start upload selected file 
            function doFileUpload () {
                var selection,
                    options;
                
                function deregisterHandlers () {
                    uploadProcess.off('success');
                    uploadProcess.off('error');
                    uploadProcess.off('progressChange');
                }
                function onSuccess (result) {
                    var data = result.data;
                    var uploadInfo = result.uploadInfo;
                    deregisterHandlers();
                    // data is the data returned from the appliance.
                    // uploadInfo contains the file uploaded
                    onUploadSuccess(data, uploadInfo);
                }
                function onError (result) {
                    var errorInfo = result.errorInfo;
                    deregisterHandlers();
                    // errorInfo is the error object returned from the appliance.
                    onUploadError(errorInfo);
                }
                function onProgressChange (result) {
                    // IE 8/9 do not fire this event.
                    var data, percentComplete, remainingTime, progress;
                    
                    function flashUploadedStep () {
                        function nextStep () {
                            dispatcher.fire(UPLOAD_STEP, 'validatingStep');
                        }
                        dispatcher.fire(UPLOAD_STEP, 'uploadedStep');
                        timerHost.setTimeout(nextStep, FLASH_DELAY);
                    }
                    
                    data = result.data;
                    percentComplete = Math.round((data.loaded / data.total) * 100);
                    remainingTime = uploadProcess.getRemainingTime();
                    progress = {'percentComplete' : percentComplete,
                                'remainingTime' : remainingTime};
                    dispatcher.fire(UPLOAD_PROGRESS, 'uploadProgressWithBar', progress);
                    
                    // if (data.loaded === data.total) { TODO: Change back when QXCR1001303866 is fixed.
                    if (100 == percentComplete) {
                        flashUploadedStep();
                    }
                }
                function provideMessageForIE8andIE9() {
                    // IE 8/9 will not fire the progressChange event
                    dispatcher.fire(UPLOAD_PROGRESS, 'uploadProgressNoBar', localizer.getString('fs.settings.restore.uploadProgressUnknowable'));
                }
                function registerHandlers () {
                    uploadProcess.on('success', onSuccess);
                    uploadProcess.on('error', onError);
                    uploadProcess.on('progressChange', onProgressChange);
                }
                function delayStart() {
                    dispatcher.fire(UPLOAD_STEP, 'uploadingStep');
                    provideMessageForIE8andIE9();
                    uploadProcess.start();
                }
                
                selection = getFileSelection();
                if (selection) {
                    options = selection.options;
                    uploadProcess = new Upload();
                    options.uploadUri = UPLOAD_URI;
                    options.apiVersion = API_VERSION;
                    uploadProcess.init(options);
                    registerHandlers();
                    timerHost.setTimeout(delayStart, 1000);
                } else {
                    restorePresenter.switchToUploadMode();
                }
            }
            
            function getStagedStatus(handlers) {
                var localHandlers = {
                        success: function (stagedInfo) {
                            if (stagedInfo) {
                                if (SUCCEEDED === stagedInfo.status) {
                                    stagedBackupInfo = stagedInfo;
                                    handlers.success(stagedInfo);
                                } else {
                                    stagedBackupInfo = undefined;
                                    handlers.error();
                                }
                            } else {
                                stagedBackupInfo = undefined;
                                handlers.error();
                            }
                        },
                        error : function () {
                            stagedBackupInfo = undefined;
                            handlers.error();
                        }
                };
                backupStatusPresenter.getLastBackupStatus(localHandlers);
            }
            function decorateStagedMode() {
                var handlers = {
                        success: function (stagedInfo) {
                            dispatcher.fire(STAGED_MODE, "stagedMode", stagedInfo);
                        },
                        error : function () {
                            dispatcher.fire(NOT_STAGED_MODE, "noStagedMode");
                        }
                };
                getStagedStatus(handlers);
            }
            function decorateValidatedStep() {
                var handlers = {
                        success: function (stagedInfo) {
                            dispatcher.fire(UPLOAD_STEP, "validatedStep", stagedInfo);
                        },
                        error : function () {
                            hardExit();
                        }
                };
                getStagedStatus(handlers);
            }
            
            this.determineStartingMode = function () {
                var handlers = {
                        success: function (stagedInfo) {
                            dispatcher.fire(STAGED_MODE, "stagedMode", stagedInfo);
                        },
                        error : function () {
                            if (dispatcher.shouldEventsBeReplayed(UPLOAD_MODE, "uploadMode")) {
                                dispatcher.replayEvents(UPLOAD_MODE);
                            } else {
                                dispatcher.fire(UPLOAD_MODE, "uploadMode");
                                dispatcher.fire(UPLOAD_STEP, "selectingStep");
                            }
                        }
                };
                dispatcher.fire(NOT_REPLAYABLE, "initialization");
                getStagedStatus(handlers);
            }
            
            /**
             * @private For unit testing only...
             */
            this._setTimerHost = function(th) {
                timerHost = th;
            };
            
            this.on = function(eventName, callback) {
                dispatcher.on(eventName, callback);
            };

            this.off = function(eventName, callback) {
                dispatcher.off(eventName, callback);
            };
            
            this.registerPresenterEventHandlers = function (handlers) {
                var handler, key;
                for (key in handlers) {
                    if (handlers.hasOwnProperty(key)) {
                        handler = handlers[key];
                        if ((handler != undefined) && (handlers.hasOwnProperty(key))) {
                            dispatcher.on(key, handler);
                        }
                    }
                }
            };
            this.unregisterPresenterEventHandlers = function (handlers) {
                var handler, key;
                for (key in handlers) {
                    if (handlers.hasOwnProperty(key)) {
                        handler = handlers[key];
                        if ((handler != undefined) && (handlers.hasOwnProperty(key))) {
                            dispatcher.off(key, handler);
                        }
                    }
                }
            };
            
            this.switchToUploadMode = function () {
                router.go("/settings/restore/appliance");
                if (dispatcher.shouldEventsBeReplayed(UPLOAD_MODE, "uploadMode")) {
                    dispatcher.replayEvents(UPLOAD_MODE);
                } else {
                    dispatcher.fire(UPLOAD_MODE, "uploadMode");
                    dispatcher.fire(UPLOAD_STEP, "selectingStep");
                }
            };
            this.switchToStagedMode = function () {
                dispatcher.clearAllReplayableEvents();
                router.go("/settings/restore/appliance");
                decorateStagedMode();
            };
            this.switchToUploadAndRestoreMode = function () {
                dispatcher.clearAllReplayableEvents();
                router.go("/applianceUploadAndRestore");
            };
            this.switchToUploadAndRestoreModeContinued = function () {
                dispatcher.clearAllReplayableEvents();
                dispatcher.fire(UPLOAD_MODE, "uploadAndRestoreMode", getFileSelection());
            };
            this.switchToRestoreMode = function () {
                dispatcher.fire(NOT_REPLAYABLE, "restoreMode");
                restoreCanceled = false;
                timerHost.setTimeout(performRestore, FLASH_DELAY);
            };
            this.switchToDelayedRestoreMode = function () {
                dispatcher.fire(NOT_REPLAYABLE, "restoreMode");
                restoreCanceled = false;
                performDelayedRestore();  // Per UX input 1/17/2014 - just blast right on through.
            };
            
            this.setFileSelection = setFileSelection;
            this.getFileSelection = getFileSelection;
            this.clearFileSelection = clearFileSelection;
            
            /**
             * Start restore.  Success handler invoked when controller state has
             * changed to restore.
             *
             * @param {{success:function():void, error:function(ErrorInfo):void}
             *     handlers Handler functions for success and error conditions.
             * @param URI of backup to restore
             */
            this.startRestore = function(viewHandlers, backupUri) {
                startRestoreImpl(viewHandlers, backupUri);
            };
            
            this.startFileUpload = function () {
                doFileUpload();
            };
            this.stopFileUpload = function () {
                uploadProcess.cancel();
                dispatcher.fire(UPLOAD_STEP, 'uploadStopped');
                dispatcher.clearReplay(UPLOAD_MODE);
                mediumReset();
            };
            this.cancelRestore = function () {
                restoreCanceled = true;
                hardExit();
            };
            /**
             * Get maximum backup size
             */
            this.getMaxBackupSize = function() {
                return maxBackupSize;
            };
            
            this.softReset = softReset;
            this.softExit = softExit;
            this.mediumReset = mediumReset;
            this.mediumExit = mediumExit;
            this.hardReset = hardReset;
            this.hardExit = hardExit;
            this.recoverFromErrors = recoverFromErrors;
            
            /**
             * Initialize
             */
            this.init = function() {
                settingsService.getSetting(MAX_BACKUP_SIZE_SETTING_NAME, 
                    {success: onGetMaxBackupSizeSuccess, error: onGetMaxBackupSizeError});
            };
        }
        
        restorePresenter = new RestorePresenter();
        return restorePresenter;
    }());
    return RestorePresenter;
});
