// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
define(['hp/core/EventDispatcher', 'fs/services/status/StatusService',
        'fs/services/status/StartingService',
        'fs/services/restore/RestoreService',
        'fs/model/status/StatusResource', 'hp/services/Log',
        'hp/core/Router', 'hp/core/Localizer', 'jquery'],
function(EventDispatcher, statusService, startingService, restoreService, statusResource, log, router, localizer) {"use strict";

    var StatusPresenter = ( function() {

        // baseline polling interval for server state when we are in one of the status states
        var SERVER_STATE_POLLING_INTERVAL = 5000,

        // amount of time to give the appliance to "go away" when doing a reboot, in ms
        // during this window, we will not deliver serverStateChange events
        REBOOT_SHUTDOWN_TIMEOUT = 30000,

        // amount of time to give for /status/waiting to load before calling reboot backend CGI
        LOAD_WAITING_PAGE_TIME = 1000,

        // restore progress refresh interval in milliseconds
        // (restore can take 40 min--so 10 sec refresh is OK)
        RESTORE_PROGRESS_REFRESH_INTERVAL = 10000,

        // starting progress refresh interval in milliseconds
        STARTING_PROGRESS_REFRESH_INTERVAL = 5000,

        // How long to wait, after a getTaskStatus request times out, before trying again.
        TASK_RESULT_DELAY = 500;
        // msec

        // various states of restart/shutdown operations
        // indicates restart was invoked from the error page
        var ERROR_RESTART ="error-restart",
        // indicates a user initiated restart from "Settings" page
        USER_RESTART = "user-restart",
        // indicates a user initiated shutdown from "Settings" page
        USER_SHUTDOWN= "user-shutdown",
        // no restart/shutdown operation in progress
        RESTART_NONE = "restart-none";
                
        var restartToStatusPageMap = {
            "error-restart" : 'restarting',
            "user-restart" : 'restarting',
            "user-shutdown" : 'shutdown1'
        };

        var restartRequested = RESTART_NONE;
        var stateChanged = false;

        var factoryReset = false;
        var savedState ;
        var taskURI;

        /**
         * @class Presentation logic for the Status page.
         */
        function StatusPresenter() {

            /**
             * Event name on which the view will listen.
             */
            this.SERVER_STATE_CHANGE = 'serverStateChange';

            // For unit testing, allow a stub of the timers created and cleared.
            var timerHost = window;

            var dispatcher = new EventDispatcher(),
            // count of registered event handlers
            activeListeners = 0,
            // name of our event for when the context changes
            eventName = this.SERVER_STATE_CHANGE,
            // return of setInterval() for updating the controller state
            intervalID,
            // undefined if not suspended, else state of the controller when we were suspended
            suspendedState;

            var checkTaskResult; // forward reference function declaration
            var handleTaskStatus; // forward reference function declaration

            var checkRestoreProgress; // forward reference function declaration
            var handleRestoreProgress; // forward reference function declaration

            var checkStartingProgress; // forward reference function declaration

            /**
             * Fire off an event to our registered handlers, if we're not suspended.
             */
            function fireEvent(oldState, newState) {
                if (! suspendedState) {
                    // send the event even if the oldState and the newState are the same
                    // in order to handle situations where the user initially loaded the wrong /status/whatever page
                    dispatcher.fire(eventName, {
                        oldState : oldState,
                        newState : newState
                    });
                }
            }

            /**
             * When the interval timer pops, get the new server state and fire a serverStateChange event.
             */
            function checkServerStatus() {
                var oldState = statusService.getState();
                statusService.refreshState(function(newState) {
                    fireEvent(oldState, newState);
                });
            }

            /**
             * Suspend event delivery until resume() is called.
             */
            function suspend() {
                if (!suspendedState) {
                    suspendedState = statusService.getState();
                }
            }

            /**
             * Resume event delivery. Immediately generate a state change event.
             */
            function resume() {
                if (suspendedState) {
                    var oldState = suspendedState;
                    suspendedState = undefined;
                    fireEvent(oldState, statusService.getState());
                }
            }

            /**
             * Converts a controller state object into the status view name to display
             * (or undefined if no status view should be displayed)
             */
            function viewFromControllerState(state) {
                var returnableState;
                
                if (!state) {
                    // wait for async update
                    return 'waiting';
                }

                if (!savedState || (savedState.state !== state.state) ){
                    stateChanged = true;
                } else {
                    stateChanged = false;
                }
                savedState = state;
                // we started shutdown/reset process but it will take a bit for the controller state
                // to reflect. Hence we set it to the appropriate page for now
                if ( restartRequested !== RESTART_NONE) {
                    returnableState = restartToStatusPageMap[restartRequested];
                    restartRequested = RESTART_NONE;
                    return returnableState;
                }

                if (factoryReset) { // there is a small window , where the controller state is not yet set to FACTORY_RESET
                    if (state.state === statusService.FACTORY_RESET) { // the controller is in the right state
                        factoryReset = false; // reset global flag
                    }
                    return 'reset';
                }

                switch (state.state) {
                     // fall through
                    case statusService.OK:

                    case statusService.STANDBY:
                        if (state.floating && state.usingAddr && -1 === state.floating.indexOf(state.usingAddr)) {
                            return 'useFloating';
                        } else {
                            return undefined;
                        }
                        break; // Only "required" by Sonar
                    case statusService.UNREACHABLE:
                        return 'waiting';
                    case statusService.STARTING:
                        return 'starting';
                    case statusService.RESTARTING:
                        return 'restarting';
                    case statusService.SHUTDOWN1:
                        return 'shutdown1';
                    case statusService.SHUTDOWN2:
                        return 'shutdown2';
                    case statusService.STOPPED:
                        return 'waiting';
                    case statusService.ERROR:
                        return 'error';
                    case statusService.QUORUM_RECOVERY:
                        return 'recovery';
                    case statusService.RESTORE:
                        return 'restore';
                    case statusService.RESTORE_NO_RESET_ERROR:
                        return 'restoreNoResetError';
                    case statusService.UPGRADE:
                        return 'update';
                    case statusService.FAILOVER:
                        return 'unavailable';
                    case statusService.FACTORY_RESET:
                        return 'reset';
                    default:
                        log.warn("Unrecognized controller state: " + (state.state ? state.state : state));
                        return 'error';
                }
            }

            /**
             * Register a callback for the serverStateChange event.  Start the interval timer if it's not already running.
             *
             * @param {string} event Event name ("serverStateChange").
             * @param {function(...):void} callback Function to call when the event is fired.
             */
            this.on = function(event, callback) {
                dispatcher.on(event, callback);
                activeListeners++;
                if (!intervalID) {
                    intervalID = timerHost.setInterval(checkServerStatus, SERVER_STATE_POLLING_INTERVAL);
                    checkServerStatus();
                }
            };
            /**
             * Set the flag that a user initiated restart or shutdown
            */
            this.isRestartRequested = function() {
                return ( (restartRequested === ERROR_RESTART) ||
                         (restartRequested === USER_RESTART) );
            };

            /**
             * Indicates whether a shutdown was requested.
            */
            this.isShutdownRequested = function() {
                return  (restartRequested === USER_SHUTDOWN);
            };
            
            this.hasStateChanged = function() {
                return stateChanged;
            };

            this.setFactoryReset = function(reset) {
                factoryReset = reset;
            };
            this.setUserRestart = function(restart) {
                if (restart) {
                    // Hack for Part 1 (AS6443) - in Part 2 (AS6306), this
                    // browser should perhaps learn its next state by
                    // observing the change in controller state on the
                    // appliance - MAYBE?
                    statusService.forceStateLocally('RESTARTING');
                    restartRequested = USER_RESTART;
                } else {
                    // ditto
                    statusService.forceStateLocally('SHUTDOWN1');
                    restartRequested = USER_SHUTDOWN;
                }
            };

            this.getCachedState = function() {
                return statusService.getState().state;
            };

            /**
             * Remove a callback for the serverStateChange event.  Stop the interval if there are no callbacks remaining.
             *
             * @param {string} event Event name ("serverStateChange").
             * @param {function(...):void} callback Function to call when the event is fired.
             */
            this.off = function(event, callback) {
                dispatcher.off(event, callback);
                activeListeners--;
                if (activeListeners <= 0) {
                    timerHost.clearInterval(intervalID);
                    intervalID = null;
                    activeListeners = 0;
                }
            };

            /**
             * Get the status sub-view name that should be displayed for the current (cached) server state
             * @param {function(...):void} callback If provided, will refresh the controller state and pass the
             *         view to the callback. Otherwise, the cached controller state will be used and the view will
             *         be returned immediately.
             */
            this.getViewForServerStatus = function(callback) {
                if ($.isFunction(callback)) {
                    statusService.refreshState(function(state) {
                        callback(viewFromControllerState(state));
                    });
                } else {
                    return viewFromControllerState(statusService.getState());
                }
            };

            /**
             * Reboot the appliance in response to a user request.  Also redirects the user to
             * the /status/waiting view.
             * @param {Object} callbacks for success and failure of the reboot operation.
             */
            this.rebootAppliance = function(handlers) {
                if (statusService.getState() && statusService.getState().state === statusService.ERROR) {
                    // stop delivering server state change events

                    restartRequested = ERROR_RESTART;
                    suspend();

                    // give the next status page a moment to load, then call the reboot CGI
                    timerHost.setTimeout(function() {
                        statusService.rebootAppliance({

                            success : function(e) {
                                handlers.success();
                            },
                            error : function(e) {
                                restartRequested = RESTART_NONE;
                                resume();
                                if($.isFunction(handlers.error)) {
                                    handlers.error(e);
                                }
                            }
                        });
                    }, LOAD_WAITING_PAGE_TIME);
                    // give the appliance some time to start its reboot before we start delivering state change events again
                    timerHost.setTimeout(resume, REBOOT_SHUTDOWN_TIMEOUT);
                }
            };

            /**
             * Create the support dump.
             */
            this.createSupportDump = function(encrypted, handlers) {
                if (statusService.getState() && statusService.getState().state === statusService.ERROR) {
                    statusResource.createSupportDump(encrypted);
                }
            };

            /**
             * Checks whether support dump file exists or not.
             */
            this.checkDownloadLink =function(data,handlers) {
                if (statusService.getState() && statusService.getState().state === statusService.ERROR) {
                    statusService.checkDownloadLink(data,{
                            success : function() {
                                handlers.success();
                            },
                            error : function() {
                                handlers.error();
                            }
                        });
                 }
            };

            /**
             * Gets the list of floating addresses from the controller state data
             */
            this.getFloatingAddresses = function() {
                return statusService.getState().floating;
            };

            /*
             * Get the Factory reset Task to track factory reset progress
             */
            this.getFactoryResetTaskURI = function(handlers) {
                factoryReset = true;  // we are in the factory reset mode. Need to set this flag so the
                                      // behavior is consistent among different session.
                statusService.getFactoryResetTask({
                    success : function(taskList) {
                        var task = taskList.members.pop();
                        taskURI = task.uri;
                        handlers.success(task.uri);
                    },
                    error : function() {
                        handlers.error();
                    }
                });
            };

            /**
             * @private For unit testing only...
             */
            this._setTimerHost = function(th) {
                timerHost = th;
            };

           /*
            * Start periodic monitoring of the Factory reset status
            */
            this.monitorFactoryResetTask = function(handlers) {
                checkTaskResult(handlers);
            };

            checkTaskResult = function(handlers) {

                statusService.getTaskStatus(taskURI, {
                    success : function(status) {
                        handleTaskStatus(status, handlers);
                    },
                    error : function() {
                        handlers.error();
                    }
                });
            };

            handleTaskStatus = function(status, handlers) {
                var taskState = {
                    state : status.taskStatus,
                    totalSteps : status.totalSteps,
                    completedSteps : status.completedSteps
                };
                switch (status.taskState) {
                    case 'New':
                    case 'Running':
                        handlers.running(taskState);
                        timerHost.setTimeout(function() {
                            checkTaskResult(handlers);
                        }, TASK_RESULT_DELAY);
                        break;

                    case 'Completed':
                        handlers.success(taskState);
                        break;

                    default:
                        // Job failed.
                        handlers.error();
                        break;
                }
            };


           /*
            * Start periodic monitoring of the restore progress
            */
            this.monitorRestoreProgress = function(handlers) {
                checkRestoreProgress(handlers);
            };

            checkRestoreProgress = function(handlers) {
                restoreService.getLastRestoreStatus({
                    success : function(collection) {
                        if (collection.count === 0) {
                            handlers.error();
                        } else {
                            handleRestoreProgress(collection.members[0], handlers);
                        }
                    },
                    error : function() {
                        handlers.error();
                    }
                });
            };

            handleRestoreProgress = function(restoreResource, handlers) {
                switch (restoreResource.status) {
                    case 'NOT_STARTED':
                    case 'IN_PROGRESS':
                        handlers.running(restoreResource);
                        timerHost.setTimeout(function() {
                            checkRestoreProgress(handlers);
                        }, RESTORE_PROGRESS_REFRESH_INTERVAL);
                        break;

                    case 'SUCCEEDED':
                        handlers.success(restoreResource);
                        break;

                    default:
                        // Restore failed.
                        handlers.error();
                        break;
                }
            };

            /*
             * Start periodic monitoring of the starting progress
             */
            this.monitorStartingProgress = function(viewHandlers) {
                checkStartingProgress(viewHandlers);
            };

            checkStartingProgress = function(viewHandlers) {
                startingService.getProgress({
                     waiting : function(webAppStatus) {
                         timerHost.setTimeout(function() {
                             checkStartingProgress(viewHandlers);
                         }, STARTING_PROGRESS_REFRESH_INTERVAL);
                         viewHandlers.waiting(webAppStatus);
                     },
                     progress : function(webAppStatus) {
                         if (webAppStatus.current < webAppStatus.target) {
                             timerHost.setTimeout(function() {
                                checkStartingProgress(viewHandlers);
                             }, STARTING_PROGRESS_REFRESH_INTERVAL);
                         }
                         viewHandlers.progress(webAppStatus);
                     },
                     error : function(webAppStatus) {
                         // The obvious response to an unexpected error would be to route to /status/error but this
                         // is not appropriate because:
                         // 1. If a sufficiently serious error is detected on the backend, the backend will do that
                         //    routing without UI intervention.
                         // 2. If we route to /status/error in the UI, we do it blind. It is likely that this error
                         //    comes at a time when the UI has no visibility into the true status of the appliance
                         //    and cannot know if /status/error is, in fact, an appropriate response.
                         // 3. If the UI has no connectivity to the appliance, none of the remedies presented on the
                         //    error page will work anyway.
                         // So we will ignore these errors and continue polling.
                         log.warn ("Unexpected error during startup:" + webAppStatus.errorCode);
                         timerHost.setTimeout(function() {
                             checkStartingProgress(viewHandlers);
                         }, STARTING_PROGRESS_REFRESH_INTERVAL);
                         viewHandlers.error(webAppStatus);
                     }
                 });
            };

            this.init = function() {
                statusResource.on("createSupportDumpSuccess", function(value) {
                    dispatcher.fire("createDumpSuccess", value);
                });
                statusResource.on("createSupportDumpError", function(value) {
                    dispatcher.fire("createDumpError", value);
                });
            };
        }

        return new StatusPresenter();
    }());
    return StatusPresenter;
});
