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

    var NotificationResource = (function () {
        
        function NotificationResource() {

            var dispatcher = new EventDispatcher();
            var version;
            var filter;
            var autoNotifications = [];   // from the index service
            var manualNotifications = []; // added within the UI
            var autoSelect = null;
            var selection = {}; // {uri: ...} or {timestamp: ...}
            var lastData = null; // cache of last fired data
            var preFilter = null;
            // cache to speed up parent checking
            var indexUris;
            var pruneDuplicates = false;  //indicates whether notifcations with the same summaries should be pruned
            var indexQueried = false; // whether we've gotten results from the index service
            
            function areDuplicates(notif, notif2) { 
                               
                if (notif2.summary && notif.summary && (notif.summary === notif2.summary)) { 
                    if (notif2.uri && notif.uri && notif2.uri !== notif.uri) {                      
                  // if notif in the table is changing leave it
                        return !notif2.changing 
                    }                  
                    return true;                                   
                } else {
                    return false;
                }            
            }            
           
            
            function indexFor(notifications, notif) {
                var notif2, length = notifications.length;
                var result = -1;
                for (var i=0; i<length; i++) {
                    notif2 = notifications[i];
  
                    if (( (notif2.uri && notif.uri &&                   
                       (notif2.uri === notif.uri)) ||                        
                       (pruneDuplicates && areDuplicates(notif, notif2)) ) || 
                       (! notif.uri && ! notif2.uri &&
                       notif.timestamp === notif2.timestamp &&
                       notif.summary === notif2.summary)) {  
                                                 
                             result = i;
                             break;
                    }
                }
                return result;
            }
            
            // until everyone is using the latest index service, filter locally
            function workaroundFilter(indexResult) {
                var result = true;
                if ('true' === indexResult.attributes.hidden) {
                    result = false;
                } else if ('Cleared' === indexResult.attributes.state) {
                    result = false;
                } else if ('Background' === indexResult.attributes.taskType) {
                    result = false;
                } else if ('true' === indexResult.attributes.lifeCycle) {
                    result = false;
                } else if (indexResult.attributes.parentTaskUri) {
                    result = false;
                }
                return result;
            }
            
            // Since we don't have a way to filter out tasks who's parent we already have,
            // we filter for this situation here.
            function haveTaskParent(indexResult, members) {
                var result;
                if ('tasks' === indexResult.category &&
                    indexResult.attributes.parentTaskUri) {
                    if (! indexUris) {
                        indexUris = {};
                        for (var i=(members.length-1); i>=0; i--) {
                            indexUris[members[i].uri] = true;
                        }
                    }
                    result = indexUris[indexResult.attributes.parentTaskUri];
                }
                return result;
            }
            
            function convertIndexResultToNotification(indexResult) {
                var notification = {
                    category: indexResult.category,
                    sourceCategory:
                        indexResult.attributes.associatedResourceCategory,
                    indexResource: true
                };
                notificationFormatter.format(notification, indexResult);
                return notification;
            }
            
            function compareNotificationTimes(a, b) {
                return ((a.timestamp > b.timestamp) ? -1 :
                    ((a.timestamp < b.timestamp) ? 1 : 0));
            }
            
            function mergeManual(data) {
                var length = manualNotifications.length;
                var notification, index;
                for (var i=0; i<length; i++) {
                    notification = manualNotifications[i];
                    index = indexFor(data.notifications, notification);
                    if (-1 === index) { // doesn't exist
                        data.notifications.push(notification);
                    } else if (notification.timestamp >
                        data.notifications[index].timestamp) { // manual is newer
                        data.notifications[index] = notification;
                    }
                }
            }
            
            function summarize(data) {
                var length = data.notifications.length;
                for (var i=0; i<length; i++) {
                    var notification = data.notifications[i];
                    if ((selection.uri && notification.uri &&
                         notification.uri === selection.uri) ||
                        (! selection.uri && ! notification.uri &&
                          notification.timestamp === selection.timestamp)) {
                        data.selection.index = i;
                        data.selection.notification = notification;
                    }
                    data.summary[notification.status || 'unknown'] += 1;
                    data.summary.total += 1;
                }
            }
            
            function rememberSelection(data) {
                if (data.selection.notification) {
                    if (data.selection.notification.uri) {
                        selection = {uri: data.selection.notification.uri};
                    } else {
                        selection = {timestamp: data.selection.notification.timestamp};
                    }
                } else {
                    selection = {};
                }
            }
            
            function flagStatusChanges(data) {
                var length = data.notifications.length;
                for (var i=0; i<length; i++) {
                    var notification = data.notifications[i];
                    var index = indexFor(lastData.notifications, notification);
                    if (-1 === index) {
                        notification._statusChanged = true;
                    } else {
                        var priorNotification = lastData.notifications[index];
                        if (notification.status && priorNotification.status &&
                            priorNotification.status !== notification.status) {
                            notification._statusChanged = true;
                        } else {
                            delete notification._statusChanged; // clear manual
                        }
                    }
                }
            }
            
            function buildAndDeliver() {
                var data = {
                    notifications: [],
                    indexQueried: indexQueried,
                    selection: {
                        index: -1,
                        notification: null,
                        resource: null // when expanded
                    },
                    summary: {error: 0, warning: 0, ok: 0,
                        disabled: 0, unknown: 0, total: 0}
                };
                
                // start with auto
                data.notifications.push.apply(data.notifications, autoNotifications);
                if (preFilter) {
                    preFilter(data);
                }
                // merge in manual
                mergeManual(data);
                // sort by time
                data.notifications.sort(compareNotificationTimes);
                // summarize
                summarize(data);
                
                if (autoSelect && data.notifications.length > 0) {
                    autoSelect(data);
                    rememberSelection(data);
                }
                
                if (lastData) {
                    flagStatusChanges(data);
                }
                
                lastData = data;
                dispatcher.fire("notificationsChange", lastData);
            }
            
            function selectIndex(index) {
                if (lastData && lastData.selection.index !== index) {
                    if (index >= 0) {
                        lastData.selection.index = index;
                        lastData.selection.notification = lastData.notifications[index];
                    } else {
                        lastData.selection.index = index;
                        lastData.selection.notification = null;
                    }
                    lastData.selection.resource = null;
                    rememberSelection(lastData);
                    dispatcher.fire("selectionChange", lastData);
                }
            }
            
            // Service events
            
            function onIndexResultsSuccess(indexResults) {
                var indexResult, notification, length = indexResults.count;
                
                indexQueried = true;
                // convert to notifications
                autoNotifications = [];
                indexUris = undefined;
                for (var i=0; i<length; i++) {
                    indexResult = indexResults.members[i];
                    if ((version >= 2 || workaroundFilter(indexResult)) &&
                        ! haveTaskParent(indexResult, indexResults.members)) {
                        notification = convertIndexResultToNotification(indexResult);
                        autoNotifications.push(notification);
                    }
                }
                
                buildAndDeliver();
            }

            function onIndexResultsError(errorInfo) {
                dispatcher.fire("notificationsError", errorInfo);
            }
            
            function onResourceSuccess(resource, status, xhr) {
                // don't fire unless this is the latest uri
                if (lastData.selection.gettingUri === resource.uri) {
                    lastData.selection.resource = resource;
                    delete lastData.selection.gettingUri;
                    notificationFormatter.format(lastData.selection.notification, resource);
                    dispatcher.fire('resourceChange', lastData);
                }
            }
            
            function onResourceError(errorInfo, xhr) {
                log.warn(errorInfo.errorMessage);
                delete lastData.selection.gettingUri;
                dispatcher.fire('resourceError', errorInfo);
            }
            
            this.init = function (args) {
                version = indexService.version();
                
                filter = new IndexFilter();
                filter.ensureDefaults(['alerts', 'tasks'], 0, 20);
                filter.setSort('created', 'desc');
                
                if (version >= 2) {
                    // no cleared alerts
                    filter.setProperty('state',
                        ['locked', 'active', 'pending', 'running',
                         'completed', 'error', 'warning']);
                    // turn off hidden tasks
                    filter.setNotProperty('hidden', 'true');
                    // turn off background tasks
                    filter.setNotProperty('taskType', 'Background');
                    // turn off lifecycle alerts
                    filter.setNotProperty('lifeCycle', 'true');
                    // turn off validation errors
                    filter.setNotProperty('stateReason', 'ValidationError');
                }
                
                if (args) {
                    if (args.hasOwnProperty('autoSelect')) {
                        autoSelect = args.autoSelect;
                    }
                    preFilter = args.preFilter;
                    if (args.hasOwnProperty('pruneDuplicates')) {
                        pruneDuplicates = true;
                    }
                }
            };
            
            this.filter = function () {
                return filter;
            };
            
            this.getNotifications = function () {
                indexService.getFilteredIndexResources(filter, {
                    success: onIndexResultsSuccess,
                    error: onIndexResultsError
                });
            };
            
            this.getSelectionDetails = function () {
                var notification, uri;
                if (lastData && lastData.selection.notification) {
                    notification = lastData.selection.notification;
                    //if this notification came from the index service, get the full resource.
                    if (notification.indexResource) {
                        uri = notification.uri;
                        // if we can get an item, and need to get an item
                        // don't re-get what we're already getting
                        if (uri && ! lastData.selection.resource &&
                            uri !== lastData.selection.gettingUri) {
                            lastData.selection.gettingUri = uri;
                            REST.getURI(uri, {
                                success : onResourceSuccess,
                                error : onResourceError
                            }, {});
                        }
                    } else {
                        dispatcher.fire('resourceChange', lastData);
                    }
                }
            };
            
            this.selectCurrent = function () {
                if (lastData) {
                    selectIndex(lastData.selection.index);
                }
            };
            
            this.selectUri = function (uri) {
                if (lastData) {
                    var length = lastData.notifications.length;
                    for (var i=0; i<length; i++) {
                        if (uri === lastData.notifications[i].uri) {
                            selectIndex(i);
                            break;
                        }
                    }
                }
            };
            
            this.selectTimestamp = function (timestamp) {
                if (lastData) {
                    var length = lastData.notifications.length;
                    for (var i=0; i<length; i++) {
                        if (timestamp === lastData.notifications[i].timestamp) {
                            selectIndex(i);
                            break;
                        }
                    }
                }
            };
            
            this.selectNext = function () {
                if (lastData && lastData.selection.index <
                    (lastData.notifications.length - 1)) {
                    selectIndex(lastData.selection.index + 1);
                }
            };
            
            this.selectPrevious = function () {
                if (lastData && lastData.selection.index > 0) {
                    selectIndex(lastData.selection.index - 1);
                }
            };
            
            this.deselect = function () {
                selectIndex(-1);
            };
            
            this.add = function (notification) {
                var index;
            /*    if (! notification.hasOwnProperty('uri')) {
                    // Starting Atlas 2.1, all notifications are required to have a 
                    // 'uri' property. Log a warning message for now until the change
                    // is enforced.
                    log.warn('Adding a notification without a "uri" property.');
                }*/
                if (! notification.hasOwnProperty('timestamp')) {
                    notification.timestamp = (new Date()).toISOString();
                }
                if ('info' === notification.status) {
                    // convert 'info' to no status
                    delete notification.status;
                }
                index = indexFor(manualNotifications, notification);
                if (-1 === index) { // add
                    manualNotifications.push(notification);
                } else { // update
                    manualNotifications[index] = notification;
                }
                // pre-sort these to speed up ongoing sorting later
                manualNotifications.sort(compareNotificationTimes);
                buildAndDeliver();
            };
            
            this.remove = function (notification) {
                var index = indexFor(manualNotifications, notification);
                if (index >= 0) {
                    manualNotifications.splice(index, 1);
                }
                buildAndDeliver();
            };
            
            this.clear = function () {
                if (lastData) {
                    autoNotifications = [];
                    manualNotifications = [];
                    selection = {};
                    lastData = null;
                    indexQueried = false;
                    buildAndDeliver();
                }
            };
            
            /**
             * @public
             * Add a listener for a specified event.
             * @param {string} eventName The name of the event.
             * @param {function(...)}
             */
            this.on = function (eventName, callback) {
                dispatcher.on(eventName, callback);
            };
            
            this.off = function (eventName, callback) {
                dispatcher.off(eventName, callback);
            };
        }

        return NotificationResource;
    }());

    return NotificationResource;
});
