// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
define(['hp/model/Session',
        'hp/model/DevelopmentSettings',
        'hp/core/UrlFragment',
        'hp/core/Localizer',
        'hp/services/Log',
        'jquery'],
function(session, devSettings, urlFragment, Localizer, log) {"use strict";

    var LinkTargetBuilder = (function() {

        // Regexp for simple name value pairs in JSON string format.
        var SIMPLE_JSON = /\{[\s]*"[^"]+"[\s]*:[\s]*"[^"]*"[\s]*(,[\s]*"[^"]+"[\s]*:[\s]*"[^"]*"[\s]*){0,}\}/g;

        /**
         * @constructor
         * @type {LinkTargetBuilder}
         */
        function LinkTargetBuilder() {

            var menuConfigs = [];

            /**
             * Does the current user have authorization for this menuConfig and
             * optional action?
             * @params {Object} menuConfig
             * @params {string} action
             */
            function isAuthorized(menuConfig, action) {
                var result = true;
                if (menuConfig.experimental && !devSettings.useExperimentalFeatures()) {
                    result = false;
                }
                if (result && menuConfig.authCategory) {
                    if (action) {
                        // check if user is authorized to perform specified action
                        result = session.canPerformAction(menuConfig.authCategory, action);
                    } else {
                        // check if user is authorized for the specified category.  Ignore action.
                        result = session.canViewCategory(menuConfig.authCategory);
                    }
                }
                return result;
            }
            
            function associated(menuConfig, indexCategory) {
                var length, result;
                if (menuConfig && menuConfig.associatedCategories && indexCategory) {
                    length = menuConfig.associatedCategories.length;
                    for (var i=0; i<length; i++) {
                        var assoc = menuConfig.associatedCategories[i];
                        if (assoc.category === indexCategory) {
                            result = assoc;
                            break;
                        }
                    }
                }
                return result;
            }

            /**
             * Find the menuConfig for the URI.
             * @param {string} uri The URI for a resource
             * @returns {Object} The menuConfig that corresponds to the uri
             */
            function configForUri(uri) {
                var index, menuConfig, match, length = menuConfigs.length;
                for (index = 0; index < length; index += 1) {
                    menuConfig = menuConfigs[index];
                    if (menuConfig.resourceUriPattern) {
                        match = uri.match(menuConfig.resourceUriPattern);
                        if (match && match.length > 0) {
                            break;
                        }
                    }
                    menuConfig = null;
                }
                return menuConfig;
            }

            /**
             * Find the menuConfig for the index category.
             * @param {string} category The index category name
             * @returns {Object} The menuConfig that corresponds to the uri
             */
            function configForIndexCategory(indexCategory) {
                var index, menuConfig, length = menuConfigs.length;
                var result;
                for (index = 0; index < length && ! result; index += 1) {
                    menuConfig = menuConfigs[index];
                    if (menuConfig.indexCategory) {
                        if ($.isArray(menuConfig.indexCategory)) {
                            if (! $.isArray(indexCategory)) {
                                if ($.inArray(indexCategory,
                                    menuConfig.indexCategory) !== -1) {
                                    result = menuConfig;
                                }
                            } else {
                                // both are arrays
                                if ($(menuConfig.indexCategory).
                                    not(indexCategory).length <
                                    menuConfig.indexCategory.length) {
                                    result = menuConfig;
                                }
                            }
                        }
                        else if (indexCategory === menuConfig.indexCategory) {
                            result = menuConfig;
                        }
                    }
                    if (! result && associated(menuConfig, indexCategory)) {
                        result = menuConfig;
                    }
                }
                return result;
            }
            
            function configFor(indexCategory, uri) {
                var menuConfig;
                if (indexCategory) {
                    menuConfig = configForIndexCategory(indexCategory);
                } else if (uri) {
                    menuConfig = configForUri(uri);
                }
                return menuConfig;
            }

            function isHelpUrl(url) {
                var isHelp;
                isHelp = (url && url.match(/^http[s]*:\/\//) && (url.split('/')[3] == 'doc#'));
                return isHelp;
            }
            
            function makeLocationForConfig(menuConfig, assoc, uri, view, action, params) {
                var location;
                if (menuConfig && ! assoc && isAuthorized(menuConfig, action)) {
                    if (uri && !menuConfig.excludeUri) {
                        location = urlFragment.createFragment(menuConfig.location,
                            view ? view : 'show', [uri], null, params);
                    } else {
                        location = urlFragment.createFragment(menuConfig.location,
                            menuConfig.excludeUri ? null : (view ? view : 'show'), null, null, params);
                    }
                }
                return location;
            }
            
            function makeLocation(uri, view, indexCategory, action, params) {
                var menuConfig, location, assoc;

                menuConfig = configFor(indexCategory, uri);
                assoc = associated(menuConfig, indexCategory);

                location = makeLocationForConfig(menuConfig, assoc, uri, view,
                    action, params);

                return location;
            }

            function linkHtml(name, href, target) {
                var anchor = $('<a></a>').attr('href', href);
                anchor.text(name);
                if (target) {
                    anchor.attr('target', target);
                }
                return $('<div></div>)').append(anchor).html();
            }
            
            function makeLink(name, uri, view, indexCategory, action, target, params, assocParams) {
                var menuConfig, location, assoc, link = name;

                menuConfig = configFor(indexCategory, uri);
                assoc = associated(menuConfig, indexCategory);

                if (!assoc || assoc.redirect) {
                    location = makeLocationForConfig(menuConfig, null, 
                        !assoc ? uri : null, 
                        view, action, 
                        // if this is am assoc.redirect, include the assocParams if available
                        assoc && assoc.redirect && assocParams ? 
                            (params ? assocParams.concat(params) : assocParams) :
                            params);

                    if (location) {
                        link = linkHtml(name, '#' + location, target);
                    }
                    else if (isHelpUrl(uri)) {
                        link = linkHtml(name, uri, 'hphelp');
                    }
                } else {
                    // construct associated link
                    link = $('<a></a>').text(name).
                        attr('data-assoc-name', assoc.association).
                        attr('data-assoc-uri', uri);
                }

                return link;
            }

            function getEmbeddedSubstitutions(text) {
                var substitutions = [],
                    substitution,
                    embeddedJson,
                    i;
                if (text) {
                    embeddedJson = text.match(SIMPLE_JSON);
                    if (embeddedJson) {
                        for (i = 0; i < embeddedJson.length; i++) {
                            try {
                                var json = embeddedJson[i];
                                substitution = JSON.parse(json);
                                if (substitution && substitution.name) {
                                    substitution.token = json.slice(1,json.length-1);
                                    substitutions.push(substitution);
                                }
                            }
                            catch(err) {
                                // ignore it;
                                substitution = null;
                            }
                        }
                    }
                }
                return substitutions;
            }
            
            /**
             * Replace all instances of "{token}" in a string with the specified value.
             * @param {string} message
             * @param {string} token
             * @param {string} value
             * @returns The message with the tokens replaced by the specified value
             */
            function replaceToken(message, token, value) {
                var text,
                    regex = new RegExp('{' + token + '}', "g");
                text = message.replace(regex, value);
                return text;
            }

            function replaceTokensWithLinks(message, substitutions) {
                var text = message,
                    subs = getEmbeddedSubstitutions(message),
                    target = null,
                    link;

                if (substitutions) {
                    target = substitutions.target;
                    subs = substitutions.concat(subs);
                }
                 
                if (subs) {
                    $.each(subs, function(i,substitution) {
                        if ((substitution.token || substitution.uri) && substitution.name) {
                            link = makeLink(substitution.name, substitution.uri, substitution.view,
                                substitution.indexCategory, substitution.action, target, substitution.params);
                            text = replaceToken(text,
                                substitution.token ? substitution.token : substitution.uri, link);
                        }
                    });
                }
                return text;
            }
             
            this.indexCategoryForUri = function(uri) {
                var menuConfig = configForUri(uri);
                return menuConfig ? menuConfig.indexCategory : null;
            };

            /**
             * @param {string} category The index category name
             * @returns {boolean} true if the UI has a toplevel page that
             *            handles the category and the user is authorized
             *            to view that category. False otherwise.
             */
            this.hasViewablePage = function(indexCategory) {
                var menuConfig = configForIndexCategory(indexCategory);
                return (menuConfig && isAuthorized(menuConfig));
            };
            
            /**
             * @param {string} category
             * @returns {string} the menu label for this category
             */
            this.categoryLabel = function(indexCategory) {
                var menuConfig = configForIndexCategory(indexCategory);
                var assoc = associated(menuConfig, indexCategory);
                var result = indexCategory;
                if (assoc) {
                    result = assoc.label;
                } else if (menuConfig) {
                    result = menuConfig.label;
                }
                return result;
            };
            
            /**
             * @returns {Array} the categories configured
             */
            this.categories = function () {
                var result = [];
                $.each(menuConfigs, function (index, config) {
                    if (config.indexCategory &&
                        (! config.hasOwnProperty('visualize') || config.visualize)) {
                        result.push(config.indexCategory);
                    }
                });
                // flatten
                result = $.map(result, function (c) { return c;});
                return result;
            };

            /**
             * Create a link to the appropriate page in the UI for a resource given
             * its uri.
             * @param {string} name The name to use on the link
             * @param {string} uri The URI for the resource
             * @param {string} view (optional) The view name to go to in the UI.
             *          Defaults to 'show'
             * @param {string} category (optional) The category for the resource.
             *          If not specified, this will attempt to determine the
             *          category from the uri.
             * @param {string} action (optional) The authorization action required
             *          for this link.
             * @param {string} target (optional) The target window for the link.
             * @param {Array} params (optional) The parameters (name=value)
             * @returns The html for the link.
             *          If there is no appropriate page for the resource,
             *          the user doesn't have permission for the category or
             *          the category can't be determined then just the name
             *          is returned (no link.)
             */
            this.makeLink = makeLink;
            
            /**
             * Generate a location reference for the specified context.
             *
             * @param {string} uri The URI for the resource
             * @param {string} view (optional) The view name to go to in the UI.
             *          Defaults to 'show'
             * @param {string} category (optional) The category for the resource.
             *          If not specified, this will attempt to determine the
             *          category from the uri.
             * @param {string} action (optional) The authorization action required
             *          for this link.
             * @param {Array} params (optional) The parameters (name=value)
             * @returns The location for the link.
             *          If there is no appropriate page for the resource, the user
             *          doesn't have permission for the category or the category
             *          can't be determined then nothing is returned.
             */
            this.makeLocation = makeLocation;
            
            /**
             * Create a link to the appropriate page in the UI for some
             * number of related resources.
             * @param {string} indexCategory The category for the resources.
             * @param {number} count The number of resources
             * @param {Array} params The parameters to include in the location.
             *    Typically, this will include "f_an=(ASSOCIATION_NAME)" and
             *    "f_euri=..." or "f_suri=..." to cause the master pane to filter.
             * @param {string} view (optional) The view name to go to in the UI.
             *          Defaults to 'show'
             * @returns The html for the link.
             *          If there is no appropriate page for the resource,
             *          the user doesn't have permission for the category
             *          or the category can't be determined then just a label
             *          is returned (no link.)
             */
            this.makeCountLink = function (indexCategory, count, params, view) {
                var menuConfig,
                    location,
                    name,
                    label,
                    link;

                menuConfig = configForIndexCategory(indexCategory);
                label = menuConfig ? menuConfig.label : indexCategory;
                
                if (count === 1) {
                    // TODO: singularize label, put in menuConfig
                    name = count + ' ' + label.toLowerCase();
                } else {
                    name = count + ' ' + label.toLowerCase();
                }

                if (menuConfig && isAuthorized(menuConfig)) {
                    location = urlFragment.createFragment(menuConfig.location,
                        view ? view : 'show', null, null, params);
                    link = '<a href="#' + location + '">' + name + '</a>';
                }
                else {
                    link = name;
                }

                return link;
            };

            /**
             * Replaces tokens in a message with values from an array of name/uri pairs.
             *
             * @param {string} message The message.
             * @param {Array} substitutions should be an array of objects.
             *     Each element in the array should have a uri field and a name field.
             *     Any occurrences of "{uri}" in message (where uri is the uri field
             *     value from the substitution element) are replaced by an html link
             *     created from the uri and name values.
             */
            this.replaceTokensWithLinks = replaceTokensWithLinks;

            this.replaceToken = replaceToken;

            this.addMenuItem = function (menuConfig) {
                menuConfigs.push(menuConfig);
            };

            this.formatRestError = function(msg, error) {
                var errorMessage,
                    substitutions,
                    restMsg;

                if (error.data) {
                    if ($.isArray(error.data)) {
                        substitutions = error.data;
                    }
                    /*
                    else if ($.type(error.data === "string")) {
                        try {
                            substitutions = JSON.parse(error.data);
                            substitutions = $.isArray(substitutions) ? substituations : [substitutions];
                        }
                        catch(err) {
                            // just leave substitutions null if we can't turn it into a real object
                        }
                    }
                    */
                    else {
                        substitutions = [ error.data ];
                    }
                }
                errorMessage = (substitutions) ?
                        replaceTokensWithLinks(error.errorMessage, substitutions) :
                        error.errorMessage;
                restMsg = Localizer.getString('core.rest_error_fmt',
                    [msg, errorMessage, error.resolution]);
                return restMsg;
            };

        }

        return new LinkTargetBuilder();
    }());

    return LinkTargetBuilder;
});

