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

    var TaskFollower = ( function() {

        var DELAY = '5000', // milliseconds
            NEW_STATES = {
                New:'New'
            }, // "New" state, task is undergoing user input parameter validation
            RUNNING_STATES = {
                Running:'Running'
            }, // "Running" state, task has passed user input parameter validation
            ERROR_STATES = {
                Error:'Error'
            },
            FINISHED_STATES = {
                Terminated:'Terminated',
                Completed:'Completed',
                Error:'Error',
                Killed:'Killed',
                Warning:'Warning'
            };

        /**
         * @type {TaskFollower}
         * @constructor
         * This class watches tasks in the TaskTracker service and notifies listeners of changes.
         */
        function TaskFollower() {

            var tasks = [], // taskInfo's for tasks we're still monitoring.
                tasksByUri = {}, // map of taskInfo's for tasks keyed by task uri
                running = false,
                timer = null,// true if the timer is running
                delay = DELAY;

            /**
             * Forward reference to the start() function, so that Sonar won't complain about calling it before it's defined.
             */
            var start = function() {
            };

            function isTaskChanged(newestTask, taskInfo) {
                // should be able to check "modified" once task tracker sets this appropriately
                // For now we'll just check for state and resultText changes
                var isChanged;

                isChanged = (!taskInfo.task) ||
                    (taskInfo.task.taskState !== newestTask.taskState) ||
                    (taskInfo.task.taskStatus !== newestTask.taskStatus) ||
                    (taskInfo.task.modified !== newestTask.modified);

                return isChanged;
            }

            function isTaskFinished(task) {
                var finished = task ? FINISHED_STATES.hasOwnProperty(task.taskState) : false;
                return finished;
            }

            function isTaskNew(task) {
                var newState = task ? NEW_STATES.hasOwnProperty(task.taskState) : false;
                return newState;
            }
            
            function isTaskRunning(task) {
                var runningState = task ? RUNNING_STATES.hasOwnProperty(task.taskState) : false;
                return runningState;
            }
            
            function isTaskError(task) {
                var errorState = task ? ERROR_STATES.hasOwnProperty(task.taskState) : false;
                return errorState;
            }

            function queueTask(taskInfo) {
                if (taskInfo.handlers.length > 0) {
                    tasks.push(taskInfo);
                    start();
                }
            }

            function onPollTaskSuccess(task, taskInfo) {
                //log.info('poll success - ' + task.uri + ' - ' + task.resultText + ' - ' + task.state)
                // If the task changed since we last looked at it notify listeners
                if (isTaskChanged(task, taskInfo)) {
                    // put the most recent task into taskInfo
                    taskInfo.task = task;
                    $.each(taskInfo.handlers, function (i, handlers) {
                        if (handlers && handlers.changed) {
                            handlers.changed(task);
                        }
                    });
                }

                if (isTaskFinished(task)) {
                    // forget the task since its done.
                    if (tasksByUri.hasOwnProperty(task.uri)) {
                        delete tasksByUri[task.uri];
                    }
                }
                else {
                    // put it back in the list to be watched since it's still running
                   queueTask(taskInfo);
                }
            }

            function onPollTaskError(error, taskInfo) {
                $.each(taskInfo.handlers, function (i, handlers) {
                    if (handlers.error) {
                        handlers.error(error);
                    }
                });

                // put it back in the list to try again
                queueTask(taskInfo);
            }

            function pollTask(taskInfo) {
                //log.info('polling ' + taskInfo.uri);
                REST.getURI(taskInfo.uri, {
                    success: function (task) {
                        onPollTaskSuccess(task, taskInfo);
                    },
                    error: function (error) {
                        onPollTaskError(error, taskInfo);
                    }
                });
            }

            /**
             * @private
             * Callback called when timer pops. We'll re-poll the tasks we're watching.
             */
            function done() {
                //log.info('timer fired - tasks.length=' + tasks.length);
                running = false;
                timer = null;
                while (tasks.length > 0) {
                    pollTask(tasks.shift());
                }
                start();
            }

            start = function() {
                // if there are tasks to watch and the timer isn't already running, start it.
                if (! running && tasks.length > 0) {
                    running = true;
                    timer = setTimeout(done, delay);
                }
            };

            /**
             * @param {Object} task The task
             * @return true if task is in a terminal state.
             */
            this.isTaskFinished = isTaskFinished;
            
            /**
             * @param {Object} task The task
             * @return true if task is in new state.
             */
            this.isTaskNew = isTaskNew;
            
            /**
             * @param {Object} task The task
             * @return true if task is in running state.
             */
            this.isTaskRunning = isTaskRunning;
            
            /**
             * @param {Object} task The task
             * @return true if task is in error state.
             */            
            this.isTaskError = isTaskError;
            
            /**
             * Watch a task for changes. A task which is not in a finished state is continually
             * polled for updates/changes.
             * @param {string or TaskResource} taskUri The uri of the task to watch
             * @param {Object} handlers The handlers for change events and errors. This can contain:
             *          changed: function (task) { ... }
             *          error: function(error) { ... }
             */
            this.watchTask = function (task_or_uri, handlers) {

                var uri,
                    task,
                    taskInfo;

                if ( typeof task_or_uri === "string") {
                    uri = task_or_uri;
                } else {
                    uri = task_or_uri.uri;
                    task = task_or_uri;
                }

                if (!isTaskFinished(task)) {
                    if (tasksByUri.hasOwnProperty(uri)) {
                        // already monitoring this one
                        taskInfo = tasksByUri[uri];
                        taskInfo.handlers.push(handlers);
                    }
                    else {
                        taskInfo = {
                                uri: uri,
                                task: task,
                                handlers: [ handlers ]
                        };
                        tasksByUri[uri] = taskInfo;

                        if (!taskInfo.task) {
                            // request the task if we don't have it yet
                            pollTask(taskInfo);
                        }

                        queueTask(taskInfo);
                    }
                }
            };

            this.stopWatchingTask = function(taskUri, handlers) {
                var taskInfo,
                handlerIndex;
                if (tasksByUri.hasOwnProperty(taskUri)) {
                    taskInfo = tasksByUri[taskUri];
                    handlerIndex = $.inArray(handlers, taskInfo.handlers);
                    if ( handlerIndex !=-1) {
                        taskInfo.handlers.splice(handlerIndex, 1);
                    }
                    if (taskInfo.handlers.length === 0) {
                        delete tasksByUri[taskUri];
                    }
                }
            };

            this.stop = function() {
                if (timer) {
                    clearTimeout(timer);
                }
            };

            this.clear = function() {
                tasks = [];
                tasksByUri = {};
            };

            this.setPollingInterval = function(delayInMilliseconds) {
                delay = delayInMilliseconds;
            };
        }

        return new TaskFollower();
    }());

    return TaskFollower;
});

