// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
define(['hp/services/REST', 'hp/services/Log', 'jquery'],
function(REST, log) {"use strict";

    var StatusService = ( function() {

        var CONTROLLER_STATE_URL = '/controller-state.json',
            REBOOT_APPLIANCE_URL = '/cgi-bin/status/reboot-appliance.cgi',
            SUPPORT_DUMP_URL = '/rest/appliance/support-dumps';

        /**
         * @class Service to retrieve the controller-state.json file from the server.
         */
        function StatusService() {

            // At some point, get this into Development Settings
            var LOG_LEVEL = 0;
            
            // For unit testing, allow a stub of window.location.
            var locationHost = window.location;
            
            // Public constants for the possible controller states.
            this.OK = 'OK';
            this.ERROR = 'ERROR';
            this.STARTING = 'STARTING';
            this.QUORUM_RECOVERY = 'QUORUM_RECOVERY';
            this.UNREACHABLE = 'UNREACHABLE';
            this.STOPPED = 'STOPPED';
            this.RESTORE = 'RESTORE';
            this.RESTORE_NO_RESET_ERROR = 'RESTORE_NO_RESET_ERROR';
            this.UPGRADE = 'UPGRADE';
            this.FAILOVER = 'FAILOVER';
            this.STANDBY = 'STANDBY';
            this.FACTORY_RESET = 'FACTORY_RESET';
            this.RESTARTING = 'RESTARTING';
            this.SHUTDOWN1 = 'SHUTDOWN1';
            this.SHUTDOWN2 = 'SHUTDOWN2';

            var SUPPORT_DUMP_ERROR_CODE = 'CI';
            
            var TASK_STATUS_TIMEOUT = 5000; // msec
            
            /*
             * Policy Issues:
             * 1. There is a maximum amount of time we are willing to wait between a user interacting with
             *    the UI and that user being notified that the appliance is UNREACHABLE. Current UX
             *    guidance in 5-10 seconds.
             * 2. There is, by program agreement, an amount of time (STATE_REQUEST_TIMEOUT) that we allow
             *    for a response to a UI request for a response from the appliance. 
             * 3. Between these two numbers, we will make every attempt to ensure that the appliance has an
             *    opportunity to respond with it's current status in order to avoid transient waiting
             *    states. Currently, detection is almost certain in 4-10 seconds.
             * 4. Unless otherwise specified, the number of retrieved values from the server to switch to
             *    a new state is policyModel['default']['default']['changeStateAfter']. This value is > 1 for
             *    the OK and UNREACHABLE states to provide hysteresis to dampen transitions back and forth between
             *    these states when the network is saturated.
             * 5. Unless otherwise specified, the number of retrieved values from the server to remain in
             *    the current state is policyModel['default']['default']['continueStateAfter'].
             * 6. Unless otherwise specified, the offset intervals for polling for retries is defined by
             *    policyModel['default']['default']['retryInterval']. The notable exception is that if the last
             *    stateDetected was UNREACHABLE, there is little point in polling aggressively. We expect
             *    the first ajax request to timeout and confirm the UNREACHABLE state. If a state is received
             *    that has a faster polling rate, we speed-up accordingly.
             * The values returned from this model guide the state transitions reported by this service. We search
             * for and return the first value found in the policyModel in this order:
             *      1. policyModel[currentState][nextState]
             *      2. policyModel[currentState]['default']
             *      3. policyModel['default'][nextState]
             *      4. policyModel['default']['default']
             * This model should never fail to provide a value - EVER.
             * Please note:
             *      * Values for returnState must be explicit. Returning false will lead to wrong results.
             */
            var STATE_REQUEST_TIMEOUT = 5000; // msec
            var newPolicyModel = {
                    OK : {
                        UNREACHABLE : {
                            changeStateAfter : 4   // Don't let a transient UNREACHABLE trigger the Waiting page.
                        }
                    },
                    UNREACHABLE : {
                        OK : {
                            changeStateAfter : 4   // If an occasional OK slips through a saturated network, don't leave the Waiting page.
                        },
                        UNREACHABLE : {
                            retryInterval : 10000   // Once in UNREACHABLE we don't need or want to retry aggressively.
                        }
                    },
                    RESTARTING : {
                        ERROR : {
                            returnState : 'ERROR'         // Always let the ERROR state be displayed
                        },
                        STARTING : {
                            returnState : 'STARTING'      // Let the STARTING state take effect.
                        },
                        'default' : {
                            returnState : 'RESTARTING',   // As soon as Restarting is shown, stick there until STARTING.
                            changeStateAfter : 1,         // We expect UNREACHABLE, so we don't need to confirm it.
                            retryInterval : 10000         // And we don't need or want to retry aggressively.
                        }
                    },
                    SHUTDOWN1 : {
                        ERROR : {
                            returnState : 'ERROR'         // Always let the ERROR state be displayed
                        },
                        UNREACHABLE : {
                            returnState : 'SHUTDOWN2'     // When UNREACHABLE is detected, go to the next state.
                        },
                        SHUTDOWN2 : {
                            returnState : 'SHUTDOWN2'     // When SHUTDOWN2 is detected, go with it.
                        },
                        'default' : {
                            returnState : 'SHUTDOWN1',    // As soon as Shutting down is shown, stick there until STOPPED or UNREACHABLE.
                            changeStateAfter : 1,         // We expect UNREACHABLE, so we don't need to confirm it.
                            retryInterval : 10000         // And we don't need or want to retry aggressively.
                        }
                    },
                    SHUTDOWN2 : {
                        ERROR : {
                            returnState : 'ERROR'         // Always let the ERROR state be displayed
                        },
                        STARTING : {
                            returnState : 'STARTING'      // Let the STARTING state take effect.
                        },
                        'default' : {
                            returnState : 'SHUTDOWN2',    // As soon as SHUTDOWN2 is shown, stick there until STARTING or ERROR.
                            changeStateAfter : 1,         // We expect UNREACHABLE, so we don't need to confirm it.
                            retryInterval : 10000         // And we don't need or want to retry aggressively.
                        }
                    },
                    'default' : {
                        'default' : {                 // By default:
                            returnState : false,      // Let the state retrieved from the appliance take effect. False only works here.
                            continueStateAfter : 1,   // Stay in the same state as soon as it is detected.
                            changeStateAfter : 1,     // Change to a new state as soon as it is detected.
                            retryInterval : 1000      // Retries are 1 second apart.
                        }
                    }
            };
            var policyModel = { name: 'root', subModel: newPolicyModel };
            
            // Private variables caching the last state reported by this service
            // to the UI.
            var stateDetected = { state: 'OK' }; // Assume OK to start
            var stateForced = false;
            var timeDetected = new Date();
            
            function logMsg(level, context, message) {
                var prefix;
                // someday? if (level <= developmentSettings.routingLogLevel()) {
                if (level <= LOG_LEVEL) {
                    prefix = "STATUS " + context.timestamp + " - ";
                    prefix = prefix + Date.now() + " - ";
                    log.log(prefix + message);
                }
            }
            
            function logState(label, state) {
                var result;
                if (state !== undefined) {
                    if (state.state !== undefined) {
                        result = state.state;
                        if (state.floating !== undefined) {
                            result += '|' + state.floating;
                        }
                    } else {
                        result = state;
                    }
                } else {
                    result = 'undefined';
                }
                return label + ' = ' + result;
            }
            
            function logAjax (level, context, mode) {
                if (level <= LOG_LEVEL) {
                    logMsg(level, context, mode + ': ' + logState('stateAtRequest', context.stateAtRequest)
                            + logState(', stateRetrieved', context.stateRetrieved)
                            + ' (' + (context.numberReceived + 1) + ' of ' + context.numberRequired + ')');
                }
            };
            
            /*
             * For every synchronous or asynchronous call to refreshStatus, a ___ConfirmedStateRequest
             * object is created. These objects semi-independently attempt to determine the state of the
             * appliance, performing retries as needed by looping (for the synchronous case) or launching
             * a burst of preemptive AJAX requests at fixed intervals.The strategy for this
             * process will be documented above each of those functions.
             * 
             * The functions within ContextDependant are intended to perform the common logic required
             * to request status from the appliance, keep track of responses as they are received, and
             * guide the implementation of the retry policy defined by DEFAULT_RETRIES_... and 
             * confirmationRetryRequirements as defined above.
             * 
             * Each function will be documented in turn.
             */
            function ContextDependantUilities (context) {
                
                /*
                 * As state values are received from the appliance, they are counted in key/value pairs by supplying an
                 * increment of +1 (see countNumberReceived). A 0 (see getNumberReceived) may be used with this function
                 * to read a value without changing it. This function (accessNumberRecieved) is a helper function that
                 * actually does the work for both use cases.
                 */
                function accessNumberReceived (context, key, increment) {
                    var delta = 0;
                    if (increment) {
                        delta = increment;
                    }
                    if (context.numbersReceived[key]) {
                        context.numbersReceived[key] += delta;
                    } else {
                        context.numbersReceived[key] = delta;
                    }
                    context.numberReceived = context.numbersReceived[key];
                    logMsg(4, context, 'accessNumberReceived(' + context.stateAtRequest.state + ', ' + key + ', ' + increment + ') returns ' + context.numberReceived);
                    return context.numberReceived;
                }
                
                /**
                 * Check the controller state with respect to https and http communication
                 * 
                 */
                function checkApplianceStatusInCaseOfCertificateChange (context, oldState, newState) {
                    function checkControllerState(handlers) {
                        jQuery.support.cors = true;
                        var destUrl;
                        var hostname = locationHost.href.split('#')[0];
                        var uri = (hostname ? hostname.split('//')[1] : null);

                        destUrl = (uri ? 'https://' + uri : '/') + 'controller-state.json';

                        $.ajax({
                            url : destUrl,
                            type : 'GET',
                            success : function () {
                                logMsg(3, context, 'checkControllerState (success): destUrl = ' + destUrl);
                                handlers.success();
                            },
                            error : function () {
                                logMsg(3, context, 'checkControllerState (error): destUrl = ' + destUrl);
                                handlers.error();
                            },
                            dataType : "json",
                            cache: false,
                            timeout: 3000
                        });
                    }
                    //The below is to check the appliance status in case of certificate change.
                    //The Internet Explorer is able to detect the change in certificate, but not the other browsers
                    if (newState.state === "UNREACHABLE" && oldState.state !== "UNREACHABLE" && ! $.browser.msie ) {
                        //https
                        checkControllerState({
                            success: function() {//don't do anything
                            },
                            error: function() {
                                locationHost.reload();
                            }
                        });
                    }
                }
                
                this.countNumberReceived = function (context, key) {
                    return accessNumberReceived (context, key, 1);
                };
                
                this.getNumberReceived = function (context, key) {
                    return accessNumberReceived (context, key, 0);
                };
                
                /*
                 * All the special cases are defined in the policyModel object. This function determines the right value to use
                 * for any value in the context of any given state.
                 * Parameters:
                 *      key: The name of the value to be looked up
                 *      currentState: The state of the appliance as best known to this service OR as specifically forced by the UI
                 *      nextState: The potential next state (retrieved) that is being considered.
                 * The values returned by this function guide the state transitions reported by this service. We search for and
                 * return the first value found in the policyModel in this order:
                 *      1. policyModel[currentState][nextState]
                 *      2. policyModel[currentState]['default']
                 *      3. policyModel['default'][nextState]
                 *      4. policyModel['default']['default']
                 * This function should never fail to return a value.
                 */
                this.getPolicyValueForState = function (context, key, currentState, nextState) {
                    var policyValue;
                    
                    function retrievePolicyValue (context, key, currentState, nextState) {
                        var currentStateModel;   // The part of the policyModel that applies to the currentState, if it exists, or default.
                        var nextStateModel;      // The part of the policyModel that applies to the nextState, if it exists, or default.
                        var value;               // The value within the model defined for the key if it exists.
                        
                        function getSubModel (model, state) {
                            if (state) {
                                // If something is provided ...
                                if (state.state) {
                                    // ... and it is a state object, then get the value for the name of that state, ...
                                    return getSubModel (model, state.state);
                                } else {
                                    // ... otherwise, it is a string. Return the sub-model for that state. ...
                                    if (state && model && model.subModel && model.subModel[state]) {
                                        return { name: state, subModel: model.subModel[state] };
                                    } else {
                                        return { name: 'empty', subModel: {} };
                                    }
                                }
                            } else {
                                // ... but, if we don't know the state, return the default.
                                return getSubModel (model, 'default');
                            }
                        }
                        
                        // Get the sub-model for the current state, or the default ...
                        currentStateModel = getSubModel(policyModel, currentState);
                        
                        if (currentStateModel) {
                            // ... then get the sub-sub-model for the next state, or the default ...
                            nextStateModel = getSubModel(currentStateModel, nextState);
                            
                            // ... then, if the sub-sub-model exists ...
                            if (nextStateModel && nextStateModel.subModel) {
                                // ... get the value from the appropriate section of the policyModel ...
                                value = nextStateModel.subModel[key];
                                // ... and if there is a value, ...
                                if (value) {
                                    // ... log it ...
                                    logMsg(3, context, 'getPolicyValueForState(' + key + ', ' + currentStateModel.name + ', ' + nextStateModel.name + ') returns ' + value);
                                }
                            }
                        }
                        // ... and return the value. If the value is undefined, we will try again with different states.
                        return value;
                    }
                    
                    policyValue = retrievePolicyValue (context, key, currentState, nextState);
                    policyValue = policyValue || retrievePolicyValue (context, key, currentState, 'default');
                    policyValue = policyValue || retrievePolicyValue (context, key, 'default', nextState);
                    policyValue = policyValue || retrievePolicyValue (context, key, 'default', 'default');
                    if (undefined === policyValue) {
                        log.warn('No status policy model value for: ' + key);
                    }
                    return policyValue;
                };

                /*
                 * This function allows the policy for required retries to be accessed after taking into consideration
                 * the state of the appliance (as known by the UI) before the new state is requested. The number of
                 * responses required to transition to (or remain in) the state is returned by this function..
                 */
                this.getNumberRequired = function (context, state) {
                    if (context.numbersRequired[state]) {
                        // no action required.
                    } else {
                        if (state === context.stateAtRequest.state) {
                            context.numbersRequired[state] = this.getPolicyValueForState(context, 'continueStateAfter', stateDetected, state);
                        } else {
                            context.numbersRequired[state] = this.getPolicyValueForState(context, 'changeStateAfter', stateDetected, state);
                        }
                    }
                    context.numberRequired = context.numbersRequired[state];
                    logMsg(4, context, 'getNumberRequired(' + context.stateAtRequest.state + ', ' + state + ') returns ' + context.numberRequired);
                    return context.numberRequired;
                };
                
                /*
                 * This function provides a single point for logging, cleaning up, and returning exactly one state
                 * value for every refreshState request. If a value is returned, it should be returned to the calling
                 * program directly or by calling it's callback function. If null is returned, then this function
                 * is being used to absorb and ignore incoming asynchronous responses after the new state has already
                 * been determined.
                 */
                this.getReturnableState = function (context) {
                    var forcedState;

                    if (context.awaitingState) {
                        context.awaitingState = false;
                        if (context.timeOfRequest >= timeDetected) {
                            timeDetected = context.timeOfRequest;
                            forcedState = this.getPolicyValueForState(context, 'returnState', stateDetected, context.stateConfirmed);
                            if (forcedState) {
                                context.forced = true;
                                // context.stateRetrieved = { state: forcedState };
                                // stateDetected = context.stateRetrieved;
                                context.stateForced = { state: forcedState };
                                stateDetected = context.stateForced;
                            } else {
                                context.forced = false;
                                if (context.stateConfirmed) {
                                    stateDetected = context.stateConfirmed;
                                    checkApplianceStatusInCaseOfCertificateChange(context, context.stateAtRequest, context.stateRetrieved);
                                }
                            }
                        }
                        logMsg(1, context, 'getReturnableState(' + context.stateAtRequest.state + '|' + context.stateConfirmed.state + ') returns ' + stateDetected.state);
                        return stateDetected;
                    }
                    logMsg(1, context, 'getReturnableState(' + context.stateAtRequest.state + '|' + context.stateConfirmed.state + ') called while not awaitingState');
                    return null;
                };
                
                /*
                 * ajaxStateRequest used to return (or callback) the value it received from the appliance
                 * directly. Now it, returns the value to to the ___ConfirmedStateRequest context to be
                 * applied to the retry policy.
                 */
                this.ajaxStateRequest = function (context, callback) {
                    var async = $.isFunction(callback);
                    function makeAjaxStateRequest (context, callback) {
                        $.ajax({
                            url: CONTROLLER_STATE_URL,
                            dataType: 'json',
                            timeout: STATE_REQUEST_TIMEOUT,
                            cache: false,
                            success: function(state, requestStatus, xhr) {
                                context.stateRetrieved = state;
                                if (async) {
                                    logAjax(2, context, 'asyncS');
                                    callback(context.stateRetrieved);
                                }
                            },
                            error: function(xhr, requestStatus) {
                                // can't use "this.UNREACHABLE", because the context is changed
                                context.stateRetrieved = { state: 'UNREACHABLE' };
                                if (async) {
                                    logAjax(2, context, 'asyncE');
                                    callback(context.stateRetrieved);
                                }
                            },
                            async: async
                        });
                        if (!async) {
                            logAjax(2, context, '  sync');
                            return context.stateRetrieved;
                        }
                    }
                    
                    if (stateForced) {
                        context.forced = true;
                        context.stateAtRequest = stateDetected;
                        context.stateRetrieved = { state : 'N/A'};
                        context.stateForced = stateForced;
                        stateDetected = stateForced;
                        stateForced = false;
                        if (async) {
                            logAjax(2, context, 'FORCED asyncS');
                            callback(stateDetected);
                        } else {
                            logAjax(2, context, 'FORCED   sync');
                            return stateDetected;
                        }
                    } else {
                        context.forced = false;
                        if (async) {
                            makeAjaxStateRequest (context, callback);
                        } else {
                            return makeAjaxStateRequest (context, callback);
                        }
                    }
                };
            }
            
            /*
             * The AsyncConfirmedStateRequest will issue AJAX requests for state, listing an internal function as the callback
             * until sufficient responses have been returned to determine the state of the appliance.
             * 
             * Any responses returned after the state is determined from requests issued before the state is determined will be ignored.
             * 
             * A single initial request is launched followed by a staggered but overlapping burst of confirmation requests. This is done
             * to minimize the time required to detect an UNREACHABLE state while minimizing the likelihood of returning a false indication
             * that the appliance cannot be reached.
             */
            function AsyncConfirmedStateRequest (realCallback) {
                var confirmationContext = this;
                var retryHandle = null;
                
                confirmationContext.awaitingState = true;
                confirmationContext.stateRetrieved = { state : "unset" };
                confirmationContext.stateAtRequest = stateDetected;
                confirmationContext.timeOfRequest = new Date();
                confirmationContext.timestamp = confirmationContext.timeOfRequest.toISOString().substr(14,5);
                confirmationContext.label = confirmationContext.timeOfRequest.getSeconds();
                
                confirmationContext.numberReceived = 0;
                confirmationContext.numberRequired = 1;   // at least
                confirmationContext.numbersReceived = {};
                confirmationContext.numbersRequired = {};
                
                var utils = new ContextDependantUilities();
                
                function sendSingleRequest() {
                    logMsg(4, confirmationContext, 'sendSingleRequest');
                    utils.ajaxStateRequest (confirmationContext, confirmationCallback);
                }
                
                function startRetries() {
                    logMsg(4, confirmationContext, 'startRetries, retryHandle = ' + retryHandle);
                    if (confirmationContext.appropriateRetryIntervalForState) {
                        confirmationContext.retryInterval = confirmationContext.appropriateRetryIntervalForState;
                        logMsg(4, confirmationContext, 'from context, retryInterval = ' + confirmationContext.retryInterval + ', retryHandle = ' + retryHandle);
                    } else {
                        confirmationContext.retryInterval = utils.getPolicyValueForState(confirmationContext, 'retryInterval', stateDetected, confirmationContext.stateAtRequest);
                        logMsg(4, confirmationContext, 'from policy, retryInterval = ' + confirmationContext.retryInterval + ', retryHandle = ' + retryHandle);
                    }
                    retryHandle = setInterval(sendSingleRequest, confirmationContext.retryInterval);
                }
                
                function stopRetries() {
                    if (retryHandle) {
                        logMsg(4, confirmationContext, 'clearInterval(retryHandle); - retryHandle = ' + retryHandle);
                        clearInterval(retryHandle);
                        return(true);
                    }
                    return(false);
                }
                
                /*
                 * The heart of the beast - will receive responses and coordinate retries until the state is determined.
                 * It will then call the real callback from the client request with the result.
                 */
                function confirmationCallback(state) {
                    var returnableState = null;
                    
                    confirmationContext.numberReceived = utils.countNumberReceived(confirmationContext, state.state);
                    confirmationContext.numberRequired = utils.getNumberRequired(confirmationContext, state.state);
                    
                    if (confirmationContext.numberReceived >= confirmationContext.numberRequired) {
                        stopRetries();
                        confirmationContext.stateConfirmed = state;
                        returnableState = utils.getReturnableState(confirmationContext);
                        if (returnableState) {
                            realCallback(returnableState);
                        }
                    }
                    
                    confirmationContext.appropriateRetryIntervalForState = utils.getPolicyValueForState(confirmationContext, 'retryInterval', stateDetected, state);
                    if (confirmationContext.appropriateRetryIntervalForState < confirmationContext.retryInterval) {
                        // Speed up polling if appropriate.
                        logMsg(4, confirmationContext, 'Speed up polling if appropriate, retryHandle = ' + retryHandle);
                        if (stopRetries()) {
                            startRetries();
                        }
                    }
                }
                
                /*
                 * Launches the original asynchronous request to state giving an internal function as the callback.
                 */
                this.requestState = function () {
                    logMsg(2, confirmationContext, '========================= AsyncConfirmedStateRequest.requestState');
                    confirmationContext.awaitingState = true;
                    startRetries();
                    sendSingleRequest();
                };
            }
            
            /*
             * The SyncConfirmedStateRequest will issue AJAX requests for state in a loop until sufficient responses have been returned
             * to determine the state of the appliance.
             * 
             * As soon as a response is returned from which the confirmed state can be determined that state will be returned.
             * 
             * A single initial request is issued and if it times out (or otherwise returns an UNREACHABLE response), subsequent
             * retries will be issued in a serialized manner until the state is determined. This can lead to long detection times
             * for an UNREACHABLE state if every retry must timeout but the synchronous variant of the refreshState() call is rarely
             * used - perhaps only in unit testing. It is not recommended.
             */
            function SyncConfirmedStateRequest() {
                var confirmationContext = this;
                
                confirmationContext.awaitingState = true;
                confirmationContext.stateRetrieved = { state : "unset" };
                confirmationContext.retries = 0;
                confirmationContext.stateAtRequest = stateDetected;
                confirmationContext.timeOfRequest = new Date();
                confirmationContext.timestamp = confirmationContext.timeOfRequest.toISOString().substr(14,5);
                confirmationContext.label = confirmationContext.timeOfRequest.getSeconds();
                
                confirmationContext.numberReceived = 0;
                confirmationContext.numberRequired = 1;   // at least
                confirmationContext.numbersReceived = {};
                confirmationContext.numbersRequired = {};

                var utils = new ContextDependantUilities();
                
                this.requestState = function () {
                    var numberReceived;
                    var numberRequired;
                    
                    logMsg(2, confirmationContext, '========================= SyncConfirmedStateRequest.requestState');
                    
                    confirmationContext.stateRetrieved = utils.ajaxStateRequest(confirmationContext);
                    
                    numberReceived = utils.countNumberReceived(confirmationContext, confirmationContext.stateRetrieved.state);
                    numberRequired = utils.getNumberRequired(confirmationContext, confirmationContext.stateRetrieved.state);

                    while (numberReceived < numberRequired) {
                        confirmationContext.stateRetrieved = utils.ajaxStateRequest(confirmationContext);
                        numberReceived = utils.countNumberReceived(confirmationContext, confirmationContext.stateRetrieved.state);
                        numberRequired = utils.getNumberRequired(confirmationContext, confirmationContext.stateRetrieved.state);
                    }
                    confirmationContext.stateConfirmed = confirmationContext.stateRetrieved;
                    
                    stateDetected = utils.getReturnableState(confirmationContext);
                    timeDetected = confirmationContext.timeOfRequest;
                    return stateDetected;
                };
            }
            
            /**
             * @private For unit testing only...
             */
            this._setLocationHost = function(locHost) {
                locationHost = locHost;
            };
            
            /**
             * @private For unit testing only...
             */
            this._setLogLevel = function(logLevel) {
                LOG_LEVEL = logLevel;
            };
            
            /**
             * Allow the presenter to force the UI into a state, independent of the controller state returned by the appliance.
             * First use of this feature is to allow the user who is shutting down or restarting the appliance to see a different
             * series of screens that confirm the process.
             * 
             * It is not allowed to force the state away from ERROR
             *
             * @param { state: 'XXX' } where XXX is one of the defined states listed in the constants above
             */
            
            this.forceStateLocally = function (forcedState) {
                if ('ERROR' !== stateDetected.state) {
                    stateForced = { state: forcedState };
                }
            };
            
            /**
             * Return the cached value of the controller state (one of the six constants above).
             *
             * @return {string} The state.
             */
            this.getState = function() {
                return stateDetected;
            };

            /**
             * Update the controller state from the server.
             *
             * @param {function(string):void} callback passed new controller state.  If not a function, the
             *        state is updated synchronously.
             * @return {string} The new state, if no callback is specified.
             */
            this.refreshState = function(clientCallback) {
                var async = $.isFunction(clientCallback);
                
                if (async) {
                    var asyncRequest = null;
                    asyncRequest = new AsyncConfirmedStateRequest(clientCallback).requestState();
                } else {
                    return (new SyncConfirmedStateRequest().requestState());
                }
            };

            /**
             * Sends a request to the server to reboot.  The backend only reboots if the controller state is this.ERROR.
             * @param {Object} Callbacks for error and success of the reboot operation.
             */
            this.rebootAppliance = function(handlers) {
                REST.postObject(REBOOT_APPLIANCE_URL, null, handlers);
            };

            /**
             * Sends a request to create support dump with specific error code.
             */
            this.createSupportDump = function(encrypted, handlers) {
                var data = {errorCode : SUPPORT_DUMP_ERROR_CODE};
                data.encrypt = encrypted;
                REST.postObject(SUPPORT_DUMP_URL, data, handlers);
            };

            /**
             * Make a head call to check the status of support dump file.
             */
            this.checkDownloadLink=function(data,handlers){
                REST.headURI(data, handlers);
            };

           /**
             * Get the status of a task tracker task.
             *            
             * @param {string} uri The uri to use to get the task status
             * @param {{success:function(TaskResourceDTO):void, error:function(ErrorMessage):void}
             *     handlers Handler functions for success and error conditions.
             */
            this.getTaskStatus = function( uri, handlers) {
                // Get the standard Ajax options for a REST GET call.
                var options = REST.getAjaxOptions(uri, 'GET', handlers);
                // Set a short timeout on the call.
                options.timeout = TASK_STATUS_TIMEOUT;
                REST.getURI(uri,handlers,options);
                
                // Issue the call.
               // $.ajax(options);
            };
            
            /**
             * Get the Factory Reset task
             *  
             * @param {{success:function(TaskResourceDTO):void, error:function(ErrorMessage):void}
             *     handlers Handler functions for success and error conditions.
             */
            this.getFactoryResetTask = function( handlers) {
                REST.getURI('/cluster/rest/taskt',handlers);
            };
        }

        return new StatusService();

    }());

    return StatusService;

});
