Array.prototype.contains = function(element) {
    for(var i = 0; i < this.length; i++) {
        if(this[i] === element) {
            return true;
        }
    }

    return false;
};

function isActionBlocked(action, caller) {

    if(BlockedActions && BlockedActions.length) {

        var contextCaller = SiteManager.GetContextCaller(caller, action.allowedCallerTypes);
        if( ! contextCaller) {
            return false;
        }

        for(var i = 0; i < BlockedActions.length; i++) {

            if (BlockedActions[i][1] == '*' && BlockedActions[i][0] == action.id ) {
                continue;
            }

            if(BlockedActions[i][1] != action.id && BlockedActions[i][1] != '*') {
                continue;
            }

            if(BlockedActions[i][1] != '*' && BlockedActions[i][2] == contextCaller.caller_type && BlockedActions[i][3] == contextCaller.id) {
                return true;
            }

            var blockedCaller = ActionCallerManager.GetCallerByTypeAndId(BlockedActions[i][2], BlockedActions[i][3]);
            if( ! blockedCaller) {
                continue;
            }
            var contextTopCaller = SiteManager.EnsureTopLevelCaller(contextCaller);
            var blockedTopCaller = SiteManager.EnsureTopLevelCaller(blockedCaller);
            if( ! contextTopCaller || ! blockedTopCaller) {
                continue;
            }
            
            if(BlockedActions[i][1] == '*' && blockedTopCaller.caller_type == contextTopCaller.caller_type && blockedTopCaller.id == contextTopCaller.id) {
                return true;
            }
        }
    }
    return false;
}



/**
 * ActionResponse helper class
 */

var ActionResponse = {};

ActionResponse.IsSuccess = function(data) {
    if(data.signal !== undefined) {
        return data.signal == 'success';
    }

    var result = false;

    for(var i = 0; i < data.length; i++) {
        if(data[i].signal == 'success') {
            result = true;
            break;
        }
    }

    return result;
};

ActionResponse.IsFailure = function(data) {
    if(data.signal !== undefined) {
        return data.signal == 'failure';
    }

    var result = false;

    for(var i = 0; i < data.length; i++) {
        if(data[i].signal == 'failure') {
            result = true;
            break;
        }
    }

    return result;
}



/**
 * AjaxLoader class
 */
var AjaxLoader = function(actionId, callerType, callerId, iconClass) {
    this.actionId = actionId;
    this.callerType = callerType;
    this.callerId = callerId;
    this.iconClass = iconClass;
    if(this.iconClass) {
        this.iconSelector = '.' + this.iconClass.replace(/[\s]+/g, '.');
    } else {
        this.iconSelector = null;
    }
    this.running = false;
};

AjaxLoader.prototype.$container = function() {
    var selector = '[data-action-id="' + this.actionId + '"]';
    if(this.callerType) {
        selector += '[data-caller-type="' + this.callerType + '"]';
    }
    if(this.callerId) {
        selector += '[data-caller-id="' + this.callerId + '"]';
    }
    return jQuery('body').find(selector);
}

AjaxLoader.prototype.Show = function() {
    if(this.running) {
        return;
    }

    var loaderClass = 'ajax-loader-' + (this.$container().hasClass('btn-default') ? 'dark' : 'light');
    if(this.iconSelector) {
        var $icon = this.$container().find(this.iconSelector);
        $icon.removeClass('fa glyphicon');
        $icon.addClass(loaderClass);
    } else {
        this.$container().prepend(jQuery('<i/>').addClass(loaderClass));
    }

    if( ! this.$container().hasClass('dnc-sm-running')) {
        this.$container().addClass('dnc-sm-running');
    }
    this.running = true;
};

AjaxLoader.prototype.Hide = function() {
    if( ! this.running) {
        return;
    }

    var loaderClass = 'ajax-loader-' + (this.$container().hasClass('btn-default') ? 'dark' : 'light');
    if(this.iconSelector) {
        var addClass = (this.iconClass.match(/^fa/) ? 'fa' : 'glyphicon');
        var $icon = this.$container().find('.' + loaderClass);
        $icon.removeClass(loaderClass);
        $icon.addClass(addClass);
    } else {
        this.$container.find(loaderClass).remove();
    }
    
    this.$container().removeClass('dnc-sm-running');
    this.running = false;
};



/**
 * ActionCallerManager class
 * Allows for adding and removing callers to and from the caller registry.
 */

var ActionCallerManager = {};

ActionCallerManager.RemoveCaller = function(type, id) {
    var callerConfig = ActionCallerConfig[type];
    if(callerConfig.singleton) {
        delete ActionCallersRegistry[type];
    } else {
        delete ActionCallersRegistry[type][id];
    }
};

ActionCallerManager.AddCaller = function(caller) {
    var callerConfig = ActionCallerConfig[caller.caller_type];
    if(callerConfig.singleton) {
        ActionCallersRegistry[caller.caller_type] = caller;
    } else if(callerConfig.parent_type && callerConfig.parent_property) {
        ActionCallersRegistry[caller.caller_type][caller[callerConfig.parent_property].id] = caller;
    } else {
        ActionCallersRegistry[caller.caller_type][caller.id] = caller;
    }
};

ActionCallerManager.GetCaller = function(type, id) {
    var callerConfig = ActionCallerConfig[type];
    if( ! callerConfig) {
        return null;
    }
    if(callerConfig.singleton) {
        return ActionCallersRegistry[type];
    } else {
        return ActionCallersRegistry[type][id];
    }
};

ActionCallerManager.GetCurrent = function(caller) {
    if( ! caller) {
        return null;
    }
    var callerConfig = ActionCallerConfig[caller.caller_type];
    if(callerConfig.singleton) {
        return ActionCallerManager.GetCaller(caller.caller_type);
    } else if(callerConfig.parent_type && callerConfig.parent_property) {
        var parentId = caller[callerConfig.parent_property].id;
        return ActionCallerManager.GetCaller(caller.caller_type, parentId);
    } else {
        return ActionCallerManager.GetCaller(caller.caller_type, caller.id);
    }
};

ActionCallerManager.GetCallerByTypeAndId = function(type, id) {
    var callerConfig = ActionCallerConfig[type];

    if (!callerConfig) {
        return null;
    }

    if(callerConfig.singleton) {
        return ActionCallerManager.GetCaller(type);
    } else if( ! callerConfig.parent_type) {
        return ActionCallerManager.GetCaller(type, id);
    } else if(callerConfig.parent_property && ActionCallersRegistry[type]){
        var foundCaller = null;
        jQuery.each(ActionCallersRegistry[type], function(parentId, checkCaller) {
            if(checkCaller.id == id) {
                foundCaller = checkCaller;
            }
        });

        if(foundCaller) {
            return ActionCallerManager.GetCaller(foundCaller.caller_type, foundCaller[callerConfig.parent_property].id);
        }
    }

    return null;
};

ActionCallerManager.GetCallerExtensions = function(caller) {
    var result = [];
    if( ! caller) {
        return result;
    }
    jQuery.each(ActionCallerConfig, function(type, config) {
        if( ! config.parent_type || config.parent_type != caller.caller_type) {
            return;
        }

        result[result.length] = ActionCallerManager.GetCaller(type, caller.id);
    });

    return result;
};

ActionCallerManager.GetCallerIndicators = function(caller) {
    var result = [];
    if( ! caller) {
        return result;
    }
    var regId = caller.caller_type + '.' + caller.id;
    if(CallerIndicators[regId] && CallerIndicators[regId].length > 0) {
        for(var i = 0; i < CallerIndicators[regId].length; i++) {
            result[result.length] = CallerIndicators[regId][i];
        }
    }

    var extensions = this.GetCallerExtensions(caller);
    if(extensions.length > 0) {
        for(var i = 0; i < extensions.length; i++) {
            var regId = extensions[i].caller_type + '.' + extensions[i].id;
            if(CallerIndicators[regId] && CallerIndicators[regId].length > 0) {
                for(var j = 0; j < CallerIndicators[regId].length; j++) {
                    result[result.length] = CallerIndicators[regId][j];
                }
            }
        }
    }

    return result;
};



/**
 * SiteManager class
 * Provides methods for handling action callers and dependencies between them.
 */

var SiteManager = {};

SiteManager.EnsureTopLevelCaller = function(caller) {
    if( ! caller) {
        return null;
    }
    var callerConfig = ActionCallerConfig[caller.caller_type];
    if(callerConfig.parent_type && callerConfig.parent_property) {
        if(callerConfig.singleton) {
            return ActionCallerManager.GetCaller(callerConfig.parent_type);
        } 

        var parentId = caller[callerConfig.parent_property].id;
        return ActionCallerManager.GetCaller(callerConfig.parent_type, parentId);
    }

    return ActionCallerManager.GetCurrent(caller);
};

SiteManager.GetContextCaller = function(caller, allowedCallerTypes) {
    if(
        ! allowedCallerTypes ||
        ! allowedCallerTypes.length ||
        ! caller ||
        allowedCallerTypes.contains(caller.caller_type)
    ) {
        return ActionCallerManager.GetCurrent(caller);
    }

    var callerConfig = ActionCallerConfig[caller.caller_type];
    if(callerConfig.parent_type && allowedCallerTypes.contains(callerConfig.parent_type)) {
        return this.EnsureTopLevelCaller(caller);
    } else if( ! callerConfig.parent_type) {
        var childCoords = false;
        jQuery.each(ActionCallerConfig, function(type, config) {
            if(
                childCoords ||
                type == caller.caller_type ||
                config.parent_type != caller.caller_type ||
                ! allowedCallerTypes.contains(type)
            ) {
                return;
            }

            if(callerConfig.singleton) {
                childCoords = { x: type, y: null };
            } else {
                childCoords = { x: type, y: caller.id };
            }
        });

        if(childCoords && allowedCallerTypes.contains(childCoords.x)) {
            return ActionCallerManager.GetCaller(childCoords.x, childCoords.y);
        }
    }

    return null;
};

SiteManager.GetCallerProperty = function(caller, property) {
    if( ! caller) {
        return null;
    }
    var propertyTab = property.split('.');
    var i = 0;
    var prop = caller[propertyTab[i]];
    while(prop && propertyTab[++i]) {
        prop = prop[propertyTab[i]];
    }
    value = prop;

    return value;
};



/**
 * SMDisplay class
 * Provides methods handling Site Manager page elements.
 */

var SMDisplay = {};

SMDisplay.GetDataSelector = function(caller) {
    if( ! caller) {
        return '';
    }
    var selector = '[data-type="' + caller.caller_type + '"]';
    if(caller.id) {
        selector += '[data-id="' + caller.id + '"]';
    }
    return selector;
};

SMDisplay.$SelectTreeNode = function(caller) {
    return jQuery('body').find('.ptm-keyword' + this.GetDataSelector(caller));
};

SMDisplay.$SelectDetails = function(caller) {
    return jQuery('body').find('.ptm-result' + this.GetDataSelector(caller));
};

SMDisplay.RefreshTreeDisplay = function(caller) {
    caller = SiteManager.EnsureTopLevelCaller(caller);
    if( ! caller) {
        return null;
    }

    var $treeNode = this.$SelectTreeNode(caller);
    if($treeNode && $treeNode.size() > 0) {
        $treeNode.find('.keyword-content').html(caller.keyword);
        return true;
    }

    return false;
};

SMDisplay.RefreshDetailsDisplay = function(caller) {
    caller = SiteManager.EnsureTopLevelCaller(caller);
    if( ! caller ) {
        return;
    }
    var keyword = caller.keyword;
    if(caller.caller_type == 'primary-keyword' && ! keyword) {
        keyword = '<i>Configure your Primary Keyword</i>';
    }
    this.$SelectDetails(caller).find('.keyword-content').html(keyword);
    var $details = this.$SelectDetails(caller).find('.ptm-result__details');
    var $actions = this.$SelectDetails(caller).find('.ptm-result__actions');
    $details.html('');

    var groups = {
        default: []
    };

    // Display the basic node details
    var callerConfig = ActionCallerConfig[caller.caller_type];
    if(callerConfig.details) {
        for(var i = 0; i < callerConfig.details.length; i++) {
            var $row = SMRenderer.$RenderDetailsRow(caller, callerConfig.details[i]);

            if($row) {
                var groupName = callerConfig.details[i].group || 'default';
                groups[groupName] = groups[groupName] || [];
                groups[groupName].push($row);
            }

        }
    }

    // Look for current node type extensions
    jQuery.each(ActionCallerConfig, function(type, config) {
        if(
            type == caller.caller_type ||
            ! config.parent_type ||
            config.parent_type != caller.caller_type ||
            ! config.details ||
            config.details.length == 0
        ) {
            return;
        }

        for(var i = 0; i < config.details.length; i++) {
            $row = SMRenderer.$RenderDetailsRow(ActionCallerManager.GetCaller(type, caller.id), config.details[i]);

            if($row) {
                var groupName = config.details[i].group || 'default';
                groups[groupName] = groups[groupName] || [];
                groups[groupName].push($row);
            }
        }
    });

    // render groups
    jQuery.each(groups, function(key, group) {
        if (group.length) {
            if (key !== 'default')
                $details.append('<hr/>');

            if (DetailLabelsJSON[key]) {
                var label = SMRenderer.$RenderGroupLabel(DetailLabelsJSON[key]);
                $details.append(label);
            }

            $details.append(group);
        }
    })
};

SMDisplay.RemoveNode = function(caller) {
    caller = SiteManager.EnsureTopLevelCaller(caller);
    if( ! caller) {
        return;
    }

    if(caller.caller_type == 'category-keyword') {
        this.$SelectTreeNode(caller).parent().remove();
    } else {
        this.$SelectTreeNode(caller).remove();
    }
    this.$SelectDetails(caller).remove();
};

SMDisplay._placeTreeContent = function(caller, $content, refId) {
    if( ! caller) {
        return; 
    }

    if(caller.caller_type == 'primary-keyword') {
        this.$SelectTreeNode(caller).replaceWith($content);
    } else if(caller.caller_type == 'category-keyword') {
        var $container = null;
        if(caller.parent_category_keyword && caller.parent_category_keyword.id) {
            $container = jQuery('#ptm-tree').find('.ptm-categoryKeyword[data-id="' + caller.parent_category_keyword.id + '"]')
                .parent()
                .children('.ptm-toggleContent');
        } else {
            $container = jQuery('#ptm-tree').find('.ptm-sidebar__content');
        }
        if( ! $container || ! $container.size()) {
            return;
        }

        $container.append($content);    // According to changes suggested DNC-340, category keywords should always be added at the end of the sub-nodes list.
    } else if(caller.caller_type == 'content-keyword') {
        if( ! caller.category_keyword || ! caller.category_keyword.id) {
            return;
        }

        var $container = jQuery('.ptm-categoryKeyword[data-id=' + caller.category_keyword.id + ']')
            .parent()
            .children('.ptm-toggleContent');

        if ($container.children('.ptm-keyword').size() > 0) {
            $container.children('.ptm-keyword:last').after($content);
        } else {
            $container.prepend($content)
        }
    }
};

SMDisplay._placeDetailsContent = function(caller, $content, refId) {
    if( ! caller) {
        return;
    }

    if(caller.caller_type == 'primary-keyword') {
        this.$SelectDetails(caller).replaceWith($content);
    } else if(caller.caller_type == 'category-keyword' || caller.caller_type == 'content-keyword') {
        jQuery('#ptm-details').append($content);
        $content.hide();
    }
};

SMDisplay.RenderNode = function(caller, refId) {
    caller = SiteManager.EnsureTopLevelCaller(caller);
    if( ! caller) {
        return;
    }
    var ajaxData = {
        action: 'dashnex-site-manager-render-node',
        type: caller.caller_type
    };
    if(caller.id) {
        ajaxData.id = caller.id;
    }

    var that = this;
    var handler = jQuery.ajax({
        beforeSend: function() {},
        complete: function() {},
        data: ajaxData,
        dataType: 'json',
        error: function(jqXHR, textStatus, errorThrown){
            console.log('Rendering keyword node failed.');
            console.log(textStatus);
            console.log(errorThrown);
            console.log(jqXHR);
            console.log(jqXHR.responseText);
        },
        success: function(data, textStatus, jqXHR) {
            if( ! data || data.length == 0) {
                console.log('Rendering keyword node finished with unknown result.');
                console.log(data);
                console.log(textStatus);
                console.log(jqXHR);
                return;
            }

            jQuery.each(data, function(i, response) {
                if(response.signal == 'failure') {
                    var msg = 'Rendering keyword node ERROR';
                    if(response.message) {
                        msg += ': ' + response.message;
                    } else { 
                        msg += '.';
                    }
                    console.log(msg);
                } else if(response.signal == 'info') {
                    var msg = 'Rendering keyword node INFO';
                    if(response.message) {
                        msg += ': ' + response.message;
                    } else {
                        msg += '.';
                    }
                    console.log(msg);
                } else if(response.signal == 'success') {
                    var msg = 'Rendering keyword node SUCCESS';
                    console.log(msg);

                    that._placeTreeContent(caller, jQuery(response.data.tree), refId);
                    that._placeDetailsContent(caller, jQuery(response.data.details), refId);
                }
            });
        },
        type: 'POST',
        url: ajaxurl
    });

    handler.done(function() {
        setTimeout(function() {
            SMDisplay.$SelectTreeNode(caller).css('background', '#6f3');
            SMDisplay.$SelectTreeNode(caller).animate(
                {backgroundColor: '#fff'}, 
                2000, 
                null, 
                function() {
                    SMDisplay.$SelectTreeNode(caller).css('background', '');
                }
            );
        }, 100);
    });

    return handler;
};

SMDisplay.RenderTopActions = function(caller) {
    caller = SiteManager.EnsureTopLevelCaller(caller);
    var actions = [];
    var that = this;
    jQuery.each(SiteManagerActions, function(actionId, action) { 
        if(
            action.groupId != '_basic-create' || (
                action.allowedCallerTypes &&
                action.allowedCallerTypes.length &&
                caller && 
                ! action.allowedCallerTypes.contains(caller.caller_type)
            )
        ) {
            return;
        }

        actions[actions.length] = action;
    });

    $container = jQuery('#ptm-_add-actions');
    if(actions.length == 0) {
        $container.html('');
    } else {
        $container.html('Add: ');
        jQuery.each(actions, function(i, action) {
            $action = SMRenderer.$RenderTopAction(caller, action);
            $container.append($action);
            $container.append(' ');
        });
    }
};

SMDisplay.ResetActions = function(caller) {
    caller = SiteManager.EnsureTopLevelCaller(caller);
    if( ! caller) {
        return;
    }

    var $container = this.$SelectDetails(caller).find('.ptm-result__actions');
    $container.find('.ptm-row__button').remove();
    $container.find('hr').remove();
};

SMDisplay.RenderActionGroup = function(caller, groupId, addBreak) {
    var actions = [];
    var that = this;

    jQuery.each(SiteManagerActions, function(actionId, action) { 
        if(action.groupId != groupId) {
            return;
        }

        var contextCaller = SiteManager.GetContextCaller(caller, action.allowedCallerTypes);
        if( ! contextCaller) {
            return;
        }

        if(action.conditions) {
            var conditionsMet = true;
            for(var i = 0; i < action.conditions.length; i++) {
                var condition = action.conditions[i];
                var prop = condition[0];
                var propVal = SiteManager.GetCallerProperty(contextCaller, prop);
                var op = condition[1];
                var val = condition[2];
                var conditionResult = false;

                switch(op) {
                    case '==':
                        conditionResult = (propVal == val);
                        break;
                    case '!=':
                        conditionResult = (propVal != val);
                        break;
                    case '<':
                        conditionResult = (propVal < val);
                        break;
                    case '<=':
                        conditionResult = (propVal <= val);
                        break;
                    case '>':
                        conditionResult = (propVal > val);
                        break;
                    case '>=':
                        conditionResult = (propVal >= val);
                        break;
                }

                if( ! conditionResult) {
                    conditionsMet = false;
                    break;
                }
            }

            if( ! conditionsMet) {
                return;
            }
        }

        actions[actions.length] = action;
    });
    if(actions.length == 0) {
        return;
    }

    var $container = this.$SelectDetails(caller).find('.ptm-result__actions');
    if(addBreak === true) {
        $container.append('<hr/>');
    }
    if(SiteManagerActionGroupLabels[groupId]) {
        var $label = SMRenderer.$RenderGroupLabel(SiteManagerActionGroupLabels[groupId]);
        if($label) {
            $container.append($label);
        }
    }
    for(var i = 0; i < actions.length; i++) {
        var action = actions[i];

        var contextCaller = SiteManager.GetContextCaller(caller, action.allowedCallerTypes);
        if( ! contextCaller) {
            continue;
        }

        $container.append(SMRenderer.$RenderAction(contextCaller, action));
    }
};

SMDisplay.RenderCustomActionGroups = function(caller) {
    var groups = [];
    var that = this;

    jQuery.each(SiteManagerActions, function(actionId, action) {
        var groupId = action.groupId;
        if(groupId.match(/^_/)) {
            return;
        }

        if( ! groups.contains(groupId)) {
            groups[groups.length] = groupId;
        }
    });

    for(var i = 0; i < groups.length; i++) {
        this.RenderActionGroup(caller, groups[i], true);
    }
};

SMDisplay.RefreshDetails = function(caller) {
    caller = SiteManager.EnsureTopLevelCaller(caller);
    this.RenderTopActions(caller);
    this.RefreshDetailsDisplay(caller);
    this.ResetActions(caller);
    this.RenderActionGroup(caller, '_basic-create', false);
    this.RenderActionGroup(caller, '_basic-wp', true);
    this.RenderCustomActionGroups(caller);
    this.RefreshAjaxLoaders(caller);
};

SMDisplay.RefreshTreeNode = function(caller) {
    caller = SiteManager.EnsureTopLevelCaller(caller);
    var res = this.RefreshTreeDisplay(caller);
    if(res === false) {
        this.RenderNode(caller);
    }
};

SMDisplay.RefreshAjaxLoaders = function(caller) {
    jQuery.each(ActionLoaders, function(groupId, ajaxLoader) {
        if(ajaxLoader.loader.running === true) {
            ActionLoaders[groupId].loader.Hide();
            ActionLoaders[groupId].loader.Show();
        }
    });
};

SMDisplay.EnableIndicator = function(data) {
    if (!data.indicator_id || !data.caller_type) {
        return;
    }
    
    var caller = ActionCallerManager.GetCallerByTypeAndId(data.caller_type, data.caller_id);
    caller = SiteManager.EnsureTopLevelCaller(caller);
    if (!caller) {
        return;
    }

    var $callerNode = this.$SelectTreeNode(caller);
    var $indicators = $callerNode.children('.legend__item');
    var $indicator = SMRenderer.$RenderIndicator(data.indicator_id);
    var isToggled = $callerNode.children('.fa-toggleIcon').size() > 0;
    if ($indicator) {
        if ($indicators.size() > 0) {
            var added = false;
            $indicators.each(function() {
                if (added) {
                    return;
                }
                var $this = jQuery(this);
                if ($this.attr('data-id') > $indicator.attr('data-id')) {
                    $this.before($indicator);
                    added = true;

                    return;
                }
            });

            if (!added) {
                $indicators.after($indicator);
            }
        } else if(isToggled) {
            $callerNode.children('.fa-toggleIcon').after($indicator);
        } else {
            $callerNode.prepend($indicator);
        }
    }
};

SMDisplay.DisableIndicator = function(data) {
    if( ! data.indicator_id || ! data.caller_type) {
        return;
    }

    var caller = ActionCallerManager.GetCallerByTypeAndId(data.caller_type, data.caller_id);

    if((typeof caller == 'undefined') || !(caller = SiteManager.EnsureTopLevelCaller(caller))) {
        return;
    }

    this.$SelectTreeNode(caller).find('.legend__item[data-id="' + data.indicator_id + '"]').remove();
}



/**
 * SMRenderer class
 * Provides methods for creating jQuery elements, used for modifying Site Manager display.
 */

var SMRenderer = {};

SMRenderer.$RenderDetailsRow = function(caller, row_data) {
    if( ! caller || ! row_data) {
        return null;
    }
    
    var $row = jQuery('<div/>').addClass('row');
    var $label = jQuery('<div/>').addClass('col-xs-5').addClass('ptm-info__label').append(row_data.label + ':');
    var value = '';
    if(row_data.value) {
        value = row_data.value;
    } else if(row_data.property) {
        value = SiteManager.GetCallerProperty(caller, row_data.property);
    }
    if (value === null)
        return null;

    var $value = jQuery('<div/>')
        .addClass('col-xs-7')
        .addClass('ptm-info__value')
        .attr('data-caller-type', caller.caller_type)
        .attr('data-caller-property', row_data.property)
        .append(value);
    if(caller.id) {
        $value.attr('data-caller-id', caller.id);
    }
    $row.append($label).append($value);
    return $row;
};

SMRenderer.$RenderTopAction = function(caller, action) {
    if( ! caller || ! action) {
        return null;
    }

    var $anchor = jQuery('<a/>')
        .addClass('dnc-sm-action')
        .attr('data-action-id', action.id)
        .attr('data-caller-type', caller.caller_type)
        .attr('data-caller-id', caller.id ? caller.id : '')
        .attr('href', 'javascript:void(0);')
        .html(action.label.replace('Add ', '').replace(' Keyword', ''));

    return $anchor;
};

SMRenderer.$RenderGroupLabel = function(label) {
    if( ! label) {
        return null;
    }

    var $label = jQuery('<div/>')
        .addClass('row')
        .addClass('ptm-row__button')
        .append(
            jQuery('<div/>')
                .addClass('col-xs-12')
                .addClass('ptm-info__value')
                .css('text-align', 'center')
                .append(label)
        );

    return $label;
};

SMRenderer.$RenderAction = function(caller, action) {
    if( ! caller || ! action) {
        return null;
    }

    var $row = jQuery('<div/>').addClass('row').addClass('ptm-row__button');
    $row.append(jQuery('<div/>').addClass('col-xs-5').addClass('ptm-info__button').append(this.$_renderActionButton(caller, action)));
    $row.append(jQuery('<div/>').addClass('col-xs-7').addClass('ptm-info__hint').append(this.$_renderActionHint(caller, action)));

    return $row;
};

SMRenderer.$RenderIndicator = function(indicatorId) {
    if( ! indicatorId) {
        return null;
    }

    var config = Indicators[indicatorId];
    if( ! config) {
        return null;
    }

    var $div = jQuery('<div/>').addClass('legend__item').attr('data-id', indicatorId);
    var $i = jQuery('<i/>').addClass(config.icon ? config.icon : 'fa fa-circle');
    if(config.color) {
        $i.css('color', config.color);
    }
    if(config.label || config.description) {
        $i.attr('data-trigger', 'hover')
            .attr('data-toggle', 'popover')
            .attr('data-placement', 'top');
        if(config.label) {
            $i.attr('data-title', config.label);
        }
        if(config.description) {
            $i.attr('data-content', config.description);
        }
        $i.popover();
    }
    $div.append($i);

    return $div;
};

SMRenderer.$_renderActionButton = function(caller, action) {
    var $button = jQuery('<button/>')
        .addClass('btn')
        .addClass('btn-primary')
        .addClass('form-control')
        .addClass('dnc-sm-action')
        .attr('data-action-id', action.id);
    if(action.allowedCallerTypes && action.allowedCallerTypes.length && caller) {
        $button
            .attr('data-caller-type', caller.caller_type)
            .attr('data-caller-id', caller.id ? caller.id : '');
    }

    if(isActionBlocked(action, caller)) {
        $button.attr('disabled', 'disabled');
    }

    if(action.allowedCallerTypes && action.allowedCallerTypes.length && caller) {
        var callerConfig = ActionCallerConfig[caller.caller_type];
        if(callerConfig.parent_type && callerConfig.parent_property) {
            $button.attr('data-caller-parent-id', caller[callerConfig.parent_property] ? caller[callerConfig.parent_property].id : '');
        }
    }

    if(action.icon) {
        var iconClasses = action.icon.split(' ');
        var $icon = jQuery('<i/>');
        for(var j = 0; j < iconClasses.length; j++) {
            $icon.addClass(iconClasses[j]);
        }
        $button.append($icon);
    }

    if(action.label) {
        $button.append(action.label);
    }

    return $button;
};

SMRenderer.$_renderActionHint = function(caller, action) {
    var $description = jQuery('<p/>');
    if(action.shortDescription) {
        $description.append(action.shortDescription);
    }

    return $description;
};