// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
/**
 * @type (HpsumIndexService)
 * IndexService - Used to retrieve master pane list for various resources like Baseline, node etc
 * The Baseline list contains all the details required such as Baseline name, location, SPP name, size of contents etc
 * If no baseline exists for the session, then this will be empty list. The UI will display a message to indicate no baseline available.
 * The Nodes list contains details such as Node IP address, type of node etc.
*/
define(['hp/services/IndexFilter', 'hp/services/Log', 'jquery'],
function(IndexFilter, log) { "use strict";

    var HpsumIndexService = (function() {
      
        var DELAY = 100; // ms
        var STATUS_ORDER = ['error', 'warning', 'unknown', 'ok', 'disabled'];
      
        function HpsumIndexService() {
          
          	//This stores all the resource items added into IndexService
          	//Each item is uniquely identified by using Category type and URI
            var items = {}; // category -> [item]
            //Stores all the URIs for all the resources
            var uriMap = {}; // uri -> item
            //Stores the associataion among the various resources
            var associations = []; // {name: name, parent: item, child: item}]
            var descended = {};
            var latency = DELAY;
          
            //Returns resources list based on filter, start index to number of resources needed 
            function deliverIndexResults(resources, start, count, handlers,
                context, filter) {
                if (resources) {
                    var members = resources.slice(start, (start + count));
                    var indexResults = {count: members.length,
                        members: members, total: resources.length, filter: filter};
                    if (handlers && handlers.success) {
                        handlers.success(indexResults);
                    }
                } else {
                    if (handlers && handlers.error) {
                        handlers.error('No items for ' + context);
                    }
                }
            }
            
            //Gets the parent resource for a particular item from associations list
			function getParents(item, depth) {
                var result = {}, parent;
                $.each(associations, function (index, assoc) {
                    if (assoc.child.uri === item.uri) {
                        if (!result[assoc.name]) {
                            result[assoc.name] = [];
                        }
                        parent = {resource: assoc.parent, parents: {}, children: {}};
                        if (depth > 0) {
                            parent.parents = getParents(assoc.parent, depth - 1)
                        }
                        result[assoc.name].push(parent);
                    }
                });
                return result;
            }
            
            //Get all the children associated with a particular item
            function getChildren(item, depth) {
                var result = {}, child;
                $.each(associations, function (index, assoc) {
                    if (assoc.parent.uri === item.uri) {
                        if (!result[assoc.name]) {
                            result[assoc.name] = [];
                        }
                        child = {resource: assoc.child, parents: {}, children: {}};
                        if (depth > 0) {
                            child.children = getChildren(assoc.child, depth - 1);
                        }
                        result[assoc.name].push(child);
                    }
                });
                return result;
            }
            
            //This function returns both parents and children list for a given item based on depth of both parent and child
            function getParentChildTree(item, parentDepth, childDepth) {
                var result = {resource: item,
                    parents: getParents(item, parentDepth - 1),
                    children: getChildren(item, childDepth - 1)};
                return (result);
            }
            
            function getContext(category) {
                var context;
                if ('array' === $.type(category)) {
                    context = ''
                    $.each(category, function (index, cat) {
                        context += cat + ' ';
                    })
                } else {
                    context = category;
                }
                return context;
            }
            
            //Get all the resources of a particular category from items list
            function getResources(category) {
                var resources;
                if ('array' === $.type(category)) {
                    resources = [];
                    $.each(category, function (index, cat) {
                        resources = resources.concat(items[cat]);
                    })
                } else {
                    resources = items[category];
                }
                return resources;
            }
            
            //Checks whether a given item is matching with a particular category
            //Return true or false
            function matchCategory(category, itemCategory) {
            	if(category == 'node')
            		category= ['server','enclosure','switch','fc_switch','host','rack', 'unknown'];
                if (!category) {
                    return true;
                } else if ('array' === $.type(category)) {
                    return (-1 != $.inArray(itemCategory, category));
                } else {
                    return (category === itemCategory);
                }
            }
            
            // http://my.opera.com/GreyWyvern/blog/show.dml/1671288
            function alphanum(a, b) {
              function chunkify(t) {
                var tz = [], x = 0, y = -1, n = 0, i, j;

                while (i = (j = t.charAt(x++)).charCodeAt(0)) {
                  var m = (i == 46 || (i >=48 && i <= 57));
                  if (m !== n) {
                    tz[++y] = "";
                    n = m;
                  }
                  tz[y] += j;
                }
                return tz;
              }

              var aa = chunkify(a);
              var bb = chunkify(b);

              for (var x = 0; aa[x] && bb[x]; x++) {
                if (aa[x] !== bb[x]) {
                  var c = Number(aa[x]), d = Number(bb[x]);
                  if (c == aa[x] && d == bb[x]) {
                    return c - d;
                  } else return (aa[x] > bb[x]) ? 1 : -1;
                }
              }
              return aa.length - bb.length;
            }
            
            function compareStatus(status1, status2) {
                var index1 = STATUS_ORDER.indexOf(status1);
                var index2 = STATUS_ORDER.indexOf(status2);
                if (index1 > index2) {
                  return 1;
                } else if (index1 < index2) {
                  return -1;
                } else {
                  return 0;
                }
            }

            // overridden functions
        	/**
             * @public
             * getIndexResources
             * Get a number of resources for a specific category
             */    
            this.getIndexResources = function (category, start, count, handlers) {
                var resources = getResources(category);
                setTimeout(function () {
                    deliverIndexResults(resources, start, count, handlers,
                        getContext(category));
                }, latency);
            };
        
        	/**
             * @public
             * getFilteredIndexResources
             * Get resources that are matching a specific filter condition
             */
            this.getFilteredIndexResources = function (filter, handlers) {
                // /index/rest/index/resources?category=&userQuery= 
                //    &start=&count=&sort=&filter=
                var resources = getResources(filter.data.category);
                if (! resources) {
                    log.error('No resources for ' + filter.data.category);
                }
                var regexps = [];
                if (filter.data.terms) {
                    regexps = $.map(filter.data.terms, function (term) {
                        return new RegExp(term, 'i');
                    });
                }
                resources = $.grep(resources,
                    function(item, index) {
                    var match = true;
                    
                    if (filter.data.properties) {
                        
                        $.each(filter.data.properties, function (property, value) {
                            if (! item[property]) {
                                match = false;
                            } else {
                                if ('array' === $.type(value)) {
                                    if (-1 === $.inArray(item[property], value)) {
                                        match = false;
                                    }
                                } else {
                                    if (value &&
                                        value.toLowerCase() !==
                                            item[property].toLowerCase()) {
                                        match = false;
                                    }
                                }
                            }
                        });
                    }
                    
                    if (match) {
                        $.each(regexps, function (index, regexp) {
                            if (! item.name.match(regexp) &&
                                (! item.sourceName || ! item.sourceName.match(regexp)) &&
                                (! item.owner || ! item.owner.match(regexp))) {
                                match = false;
                                return false;
                            }
                        });
                    }
                    return match;
                });
                
                // sort, if any
                if (filter.data.sort) {
                    var parts = filter.data.sort.split(':');
                    resources.sort(function (a,b) {
                        if ('asc' === parts[1]) {
                            if ('status' === parts[0]) {
                                return compareStatus(a[parts[0]], b[parts[0]]);
                            } else {
                                return alphanum(a[parts[0]], b[parts[0]]);
                            }
                        } else if ('desc' === parts[1]) {
                            if ('status' === parts[0]) {
                                return compareStatus(b[parts[0]], a[parts[0]]);
                            } else {
                                return alphanum(b[parts[0]], a[parts[0]]);
                            }
                        }
                    });
                }
                
                //console.log("!!! TIS " + resources.length + " for " + filter.toString());
                
                // simulate server exchange
                setTimeout(function () {
                    deliverIndexResults(resources, filter.data.start, filter.data.count,
                        handlers, getContext(filter.data.category), filter);
                }, latency);
            };

			/**
             * @public
             * searchIndexResources
             * Get number of resources from a specific index that are matching a specific filter condition
             */
			this.searchIndexResources = function (filter, start, count, handlers) {
                // /index/rest/index/resources?userQuery=&start=&count=
                var resources = [];
                if ('string' === typeof(filter)) {
                    var realFilter = new IndexFilter();
                    realFilter.setUserQuery(filter);
                    filter = realFilter;
                }
                var match;
                
                var regexps = [];
                if (filter.data.terms) {
                    regexps = $.map(filter.data.terms, function (term) {
                        return new RegExp(term, 'i');
                    });
                }
                
                $.each(uriMap, function (index, item) {
                    match = false;
                    
                    if (matchCategory(filter.data.category, item.category)) {
                        match = true;
                        
                        if (filter.data.properties) {
                            match = false;
                            $.each(filter.data.properties,
                                function (property, value) {
                                    if (item.hasOwnProperty(property) &&
                                        item[property] == value) {
                                        match = true;
                                        return false;
                                    }
                            });
                        }
                        
                        if (match) {
                            if (regexps.length > 0) {
                                match = false;
                                $.each(regexps, function (index, regexp) {
                                    if (item.name.match(regexp)) {
                                        match = true;
                                        return false;
                                    }
                                });
                            }
                        }
                    }
                    
                    if (match) {
                        resources.push(item);
                        if (resources.length >= count) return false;
                    }
                });
                setTimeout(function () {
                    deliverIndexResults(resources, start, count, handlers,
                        filter.getUserQuery(), filter);
                }, latency);
            };

			/**
             * @public
             * suggestions
             * Returns matching values from resources from a specific index that are matching a query string
             * This is used in search
             */
            this.suggestions = function(category, query, start, count, handlers) {
                // /index/rest/index/suggestions?userQuery=&count=
                var result = [];
                var filter = new IndexFilter();
                filter.setUserQuery(query);
                
                var regexps = [];
                if (filter.data.terms) {
                    regexps = $.map(filter.data.terms, function (term) {
                        return new RegExp(term, 'i');
                    });
                }
                
                $.each(uriMap, function (index, item) {
                    if (matchCategory(category, item.category)) {
                        $.each(regexps, function (index, regexp) {
                            if (item.name.match(regexp)) {
                                result.push(item.name);
                                return false;
                            }
                        });
                    }
                    if (result.length >= count) return false;
                });
                setTimeout(function () {
                    handlers.success({suggestions: result});
                }, latency);
            };

            this.getAssociationTrees = function (category, depth, associationNames,
                start, count, handlers) {
                // TODO:
            };
			
			/**
             * @public
             * getIndexForResource
             * Get a matching resource from uriMap list
             */
            this.getIndexForResource = function (uri, handlers) {
                // /index/rest/index/resources?start=0&count=1&filter=_uri:<uri>
                handlers.success(uriMap[uri]);
            };

			//This function is called in hp/presenter/MapPresenter.js, hp/model/Resource.js file, hence required
			/**
             * @public
             * getParentAndChildrenAssociations
             * Returns a list of parent and children associations.
             */
			this.getParentAndChildrenAssociations = function (options) {
                // /index/rest/trees<uri>?category=&parentDepth=&childDepth=
                //    &start=&count=&sort
                var item, result;
                var parentDepth = (options.hasOwnProperty('parentDepth') ?
                    options.parentDepth : 5);
                var childDepth = (options.hasOwnProperty('childDepth') ?
                    options.childDepth : 5);

                if (options.hasOwnProperty('uri')) {
                    item = uriMap[options.uri];
                    setTimeout(function () {
                        if (item) {
                            options.handlers.success(
                                getParentChildTree(item, parentDepth, childDepth));
                        } else {
                            options.handlers.error("No trees for " + options.uri)
                        }
                    }, latency);
                } else if (options.hasOwnProperty('category')) {
                    result = {total: 0, trees: []};
                    var resources = getResources(options.category);
                    $.each(resources, function(index, item) {
                        result.trees.push(
                            getParentChildTree(item, parentDepth, childDepth));
                    });
                    result.total = result.trees.length;
                    setTimeout(function () {
                        options.handlers.success(result);
                    }, latency);
                }
            };
            
            /**
             * @public
             * getCategories
             * Get all the categories stored in the items list
             */
            this.getCategories = function (handlers) {
                var result = [];
                $.each(items, function (category, list) {
                    result.push(category);
                })
                handlers.success(result);
            };
            
            // simulated for HpsumREST
            /**
             * @public
             * simulatedGetURI
             * Get a matching item for an URI from uriMap list
             */
            this.simulatedGetURI = function (uri, handlers) {
                setTimeout(function () {
                    if (uriMap[uri]) {
                        handlers.success(uriMap[uri]);
                    } else {
                        handlers.error({errorMessage: "No resource at: " + uri});
                    }
                }, latency);
            };
            
            // custom functions for HpsumMock
            /**
             * @public
             * addCategory
             * Add a unique category into items list
             */
            this.addCategory = function (category) {
                if (! items[category]) items[category] = [];
            }
            
            /**
             * @public
             * addItem
             * Add a new item into items and uriMap list. 
             */
            this.addItem = function (category, item) {
                if (! items[category]) items[category] = [];
                items[category].push(item);
                uriMap[item.uri] = item;
                return item;
            };
            
            this.addAssociation = function (name, parent, child) {
                associations.push({name: name, parent: parent, child: child});
            };
            
            this.removeAssociations = function (name, uri) {
                associations = $.grep(associations,
                    function (assoc) {
                        return (assoc.name !== name ||
                            (assoc.child.uri !== uri && assoc.parent.uri !== uri));
                    });
            };
            
            /**
             * @public
             * getSimulatedItems
             * Get all the items of a given category. 
             */
                        /**
             * Retrieves the associations from the index service
             * @param {string} startObjUri - The starting object uri (optionally null)
             * to look at when getting associations.
             * @param {string} endObjUri - The ending object uri (optionally null)
             * to look at when getting associations.
             * @param {string} associationName - One of a set association names
             * (optionally null).
             * @param {string} relationship - Boolean value (optionally null).
             * @param {object} handlers - Success and Error handlers.
             */
            this.getAssociations = function (startObjUri, endObjUri, associationName,
                relationship, handlers) {
                var params = [];
                if (startObjUri) {
                    params.push("startObjUri=" + startObjUri);
                }
                if (endObjUri) {
                    params.push("endObjUri=" + endObjUri);
                }
                if (associationName) {
                    params.push("associationName=" + associationName);
                }
                if (relationship) {
                    params.push("hasARelationship=" + relationship);
                }
                var request = "/index/rest/index/associations?" + params.join('&');
               // REST.getURI(request, handlers);
                this.getParentAndChildrenAssociations({
                        uri: startObjUri,
                        parentDepth: 1,
                        childDepth: 1,
                        handlers: {
                            success: function (tree) {
                                handlers.success(tree);
                            },
                            error: function (errorMessage) {
                                handlers.error( errorMessage);
                            }}
                    });
               
               
            };
            
			this.getSimulatedItems = function (category) {
                return items[category];
            };
            
            /**
             * @public
             * getSimulatedItem
             * Get matching item from uriMap list for a given uri 
             */
            this.getSimulatedItem = function (uri) {
                return uriMap[uri];
            };
            
            // custom functions for the HPSUM presenters
            this.updateItems = function (uris, item) {
                var oldItem;
                if ('string' === typeof(uris))
                {
                    oldItem = uriMap[uris];
                    oldItem.name = item.name;
                    oldItem.status = item.status;
                }
                else {
                $.each(uris, function (index, uri) {
                    oldItem = uriMap[uri];
                    oldItem.name = item.name;
                });
                }
            };
            
            //delete all the items for all given uris
            this.deleteItems = function (res) {
            	var uris=[];
            	if('string' === typeof(res))
            	{
            		uris[0]=res;
            	}
            	else
            	  uris =res;
                $.each(uris, function (index, uri) {
                    var category = uriMap[uri].category;
                    // remove from uriMap
                    delete uriMap[uri];
                    // remove from category
                    items[category] = $.grep(items[category],
                        function (item) {
                            return item.uri !== uri;
                        });
                    // remove associations
                    associations = $.grep(associations,
                        function (assoc) {
                            return (assoc.parent.uri !== uri &&
                                assoc.child.uri !== uri);
                        });
                });
            };
            
            this.getLatency = function () {
                return latency;
            };
            
            this.setLatency = function(latencyArg) {
                latency = latencyArg;
            };
        }

        return new HpsumIndexService();
    }());
  
    return HpsumIndexService;
});

  