text: /**
   * Copyright JS Foundation and other contributors, http://js.foundation
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   **/
  
  RED.palette = (function() {
  
      var exclusion = ['config','unknown','deprecated'];
      var coreCategories = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced'];
  
      var categoryContainers = {};
  
  
      function createCategory(originalCategory,rootCategory,category,ns) {
          if ($("#palette-base-category-"+rootCategory).length === 0) {
              createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
          }
          $("#palette-container-"+rootCategory).show();
          if ($("#palette-"+category).length === 0) {
              $("#palette-base-category-"+rootCategory).append('<div id="palette-'+category+'"></div>');
          }
      }
      function createCategoryContainer(originalCategory,category, labelId) {
          var label = RED._(labelId, {defaultValue:category});
          label = (label || category).replace(/_/g, " ");
          var catDiv = $('<div id="palette-container-'+category+'" class="palette-category palette-close hide">'+
              '<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-angle-down"></i><span>'+label+'</span></div>'+
              '<div class="palette-content" id="palette-base-category-'+category+'">'+
              '<div id="palette-'+category+'-input"></div>'+
              '<div id="palette-'+category+'-output"></div>'+
              '<div id="palette-'+category+'-function"></div>'+
              '</div>'+
              '</div>').appendTo("#palette-container");
          catDiv.data('category',originalCategory);
          catDiv.data('label',label);
          categoryContainers[category] = {
              container: catDiv,
              close: function() {
                  catDiv.removeClass("palette-open");
                  catDiv.addClass("palette-closed");
                  $("#palette-base-category-"+category).slideUp();
                  $("#palette-header-"+category+" i").removeClass("expanded");
              },
              open: function() {
                  catDiv.addClass("palette-open");
                  catDiv.removeClass("palette-closed");
                  $("#palette-base-category-"+category).slideDown();
                  $("#palette-header-"+category+" i").addClass("expanded");
              },
              toggle: function() {
                  if (catDiv.hasClass("palette-open")) {
                      categoryContainers[category].close();
                  } else {
                      categoryContainers[category].open();
                  }
              }
          };
  
          $("#palette-header-"+category).on('click', function(e) {
              categoryContainers[category].toggle();
          });
      }
  
      function setLabel(type, el,label, info) {
          var nodeWidth = 82;
          var nodeHeight = 25;
          var lineHeight = 20;
          var portHeight = 10;
  
          var words = label.split(/[ -]/);
  
          var displayLines = [];
  
          var currentLine = words[0];
          var currentLineWidth = RED.view.calculateTextWidth(currentLine, "palette_label", 0);
  
          for (var i=1;i<words.length;i++) {
              var newWidth = RED.view.calculateTextWidth(currentLine+" "+words[i], "palette_label", 0);
              if (newWidth < nodeWidth) {
                  currentLine += " "+words[i];
                  currentLineWidth = newWidth;
              } else {
                  displayLines.push(currentLine);
                  currentLine = words[i];
                  currentLineWidth = RED.view.calculateTextWidth(currentLine, "palette_label", 0);
              }
          }
          displayLines.push(currentLine);
  
          var lines = displayLines.join("<br/>");
          var multiLineNodeHeight = 8+(lineHeight*displayLines.length);
          el.css({height:multiLineNodeHeight+"px"});
  
          var labelElement = el.find(".palette_label");
          labelElement.html(lines).attr('dir', RED.text.bidi.resolveBaseTextDir(lines));
  
          el.find(".palette_port").css({top:(multiLineNodeHeight/2-5)+"px"});
  
          var popOverContent;
          try {
              var l = "<p><b>"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"</b></p>";
              if (label != type) {
                  l = "<p><b>"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"</b><br/><i>"+type+"</i></p>";
              }
              popOverContent = $(l+(info?info:$("script[data-help-name='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
                                  .filter(function(n) {
                                      return (this.nodeType == 1 && this.nodeName == "P") || (this.nodeType == 3 && this.textContent.trim().length > 0)
                                  }).slice(0,2);
          } catch(err) {
              // Malformed HTML may cause errors. TODO: need to understand what can break
              // NON-NLS: internal debug
              console.log("Error generating pop-over label for ",type);
              console.log(err.toString());
              popOverContent = "<p><b>"+label+"</b></p><p>"+RED._("palette.noInfo")+"</p>";
          }
  
          el.data('popover').setContent(popOverContent);
      }
  
      function setIcon(element,sf) {
          var iconElement = element.find(".palette_icon");
          var icon_url = RED.utils.getNodeIcon(sf._def,sf);
          iconElement.attr("style", "background-image: url("+icon_url+")");
      }
  
      function escapeNodeType(nt) {
          return nt.replace(" ","_").replace(".","_").replace(":","_");
      }
  
      function addNodeType(nt,def) {
          var nodeTypeId = escapeNodeType(nt);
          if ($("#palette_node_"+nodeTypeId).length) {
              return;
          }
          if (exclusion.indexOf(def.category)===-1) {
  
              var originalCategory = def.category;
              var category = def.category.replace(/ /g,"_");
              var rootCategory = category.split("-")[0];
  
              var d = document.createElement("div");
              d.id = "palette_node_"+nodeTypeId;
              d.type = nt;
  
              var label = /^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
              if (typeof def.paletteLabel !== "undefined") {
                  try {
                      label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
                  } catch(err) {
                      console.log("Definition error: "+nt+".paletteLabel",err);
                  }
              }
  
              $('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
  
              d.className="palette_node";
  
              if (def.icon) {
                  var icon_url = RED.utils.getNodeIcon(def);
                  var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
                  $('<div/>',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer);
              }
  
              d.style.backgroundColor = RED.utils.getNodeColor(nt,def);
  
              if (def.outputs > 0) {
                  var portOut = document.createElement("div");
                  portOut.className = "palette_port palette_port_output";
                  d.appendChild(portOut);
              }
  
              if (def.inputs > 0) {
                  var portIn = document.createElement("div");
                  portIn.className = "palette_port palette_port_input";
                  d.appendChild(portIn);
              }
  
              createCategory(def.category,rootCategory,category,(coreCategories.indexOf(rootCategory) !== -1)?"node-red":def.set.id);
  
              $("#palette-"+category).append(d);
  
              $(d).data('category',rootCategory);
  
              d.onmousedown = function(e) { e.preventDefault(); };
  
              var popover = RED.popover.create({
                  target:$(d),
                  trigger: "hover",
                  width: "300px",
                  content: "hi",
                  delay: { show: 750, hide: 50 }
              });
              $(d).data('popover',popover);
  
              // $(d).popover({
              //     title:d.type,
              //     placement:"right",
              //     trigger: "hover",
              //     delay: { show: 750, hide: 50 },
              //     html: true,
              //     container:'body'
              // });
              $(d).click(function() {
                  RED.view.focus();
                  var helpText;
                  if (nt.indexOf("subflow:") === 0) {
                      helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"")||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>');
                  } else {
                      helpText = $("script[data-help-name='"+d.type+"']").html()||('<span class="node-info-none">'+RED._("sidebar.info.none")+'</span>');
                  }
                  RED.sidebar.info.set(helpText,RED._("sidebar.info.nodeHelp"));
              });
              var chart = $("#chart");
              var chartOffset = chart.offset();
              var chartSVG = $("#chart>svg").get(0);
              var activeSpliceLink;
              var mouseX;
              var mouseY;
              var spliceTimer;
              var paletteWidth;
              var paletteTop;
              $(d).draggable({
                  helper: 'clone',
                  appendTo: 'body',
                  revert: true,
                  revertDuration: 50,
                  containment:'#main-container',
                  start: function() {
                      paletteWidth = $("#palette").width();
                      paletteTop = $("#palette").parent().position().top + $("#palette-container").position().top;
                      RED.view.focus();
                  },
                  stop: function() { d3.select('.link_splice').classed('link_splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}},
                  drag: function(e,ui) {
  
                      // TODO: this is the margin-left of palette node. Hard coding
                      // it here makes me sad
                      //console.log(ui.helper.position());
                      ui.position.left += 17.5;
  
                      if (def.inputs > 0 && def.outputs > 0) {
                          mouseX = ui.position.left-paletteWidth+(ui.helper.width()/2) - chartOffset.left + chart.scrollLeft();
                          mouseY = ui.position.top-paletteTop+(ui.helper.height()/2) - chartOffset.top + chart.scrollTop();
                          if (!spliceTimer) {
                              spliceTimer = setTimeout(function() {
                                  var nodes = [];
                                  var bestDistance = Infinity;
                                  var bestLink = null;
                                  if (chartSVG.getIntersectionList) {
                                      var svgRect = chartSVG.createSVGRect();
                                      svgRect.x = mouseX;
                                      svgRect.y = mouseY;
                                      svgRect.width = 1;
                                      svgRect.height = 1;
                                      nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
                                      mouseX /= RED.view.scale();
                                      mouseY /= RED.view.scale();
                                  } else {
                                      // Firefox doesn't do getIntersectionList and that
                                      // makes us sad
                                      mouseX /= RED.view.scale();
                                      mouseY /= RED.view.scale();
                                      nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
                                  }
  
                                  for (var i=0;i<nodes.length;i++) {
                                      if (d3.select(nodes[i]).classed('link_background')) {
                                          var length = nodes[i].getTotalLength();
                                          for (var j=0;j<length;j+=10) {
                                              var p = nodes[i].getPointAtLength(j);
                                              var d2 = ((p.x-mouseX)*(p.x-mouseX))+((p.y-mouseY)*(p.y-mouseY));
                                              if (d2 < 200 && d2 < bestDistance) {
                                                  bestDistance = d2;
                                                  bestLink = nodes[i];
                                              }
                                          }
                                      }
                                  }
                                  if (activeSpliceLink && activeSpliceLink !== bestLink) {
                                      d3.select(activeSpliceLink.parentNode).classed('link_splice',false);
                                  }
                                  if (bestLink) {
                                      d3.select(bestLink.parentNode).classed('link_splice',true)
                                  } else {
                                      d3.select('.link_splice').classed('link_splice',false);
                                  }
                                  if (activeSpliceLink !== bestLink) {
                                      if (bestLink) {
                                          $(ui.helper).data('splice',d3.select(bestLink).data()[0]);
                                      } else {
                                          $(ui.helper).removeData('splice');
                                      }
                                  }
                                  activeSpliceLink = bestLink;
                                  spliceTimer = null;
                              },200);
                          }
                      }
                  }
              });
  
              var nodeInfo = null;
              if (nt.indexOf("subflow:") === 0) {
                  $(d).dblclick(function(e) {
                      RED.workspaces.show(nt.substring(8));
                      e.preventDefault();
                  });
                  nodeInfo = marked(def.info||"");
              }
              setLabel(nt,$(d),label,nodeInfo);
  
              var categoryNode = $("#palette-container-"+rootCategory);
              if (categoryNode.find(".palette_node").length === 1) {
                  categoryContainers[rootCategory].open();
              }
  
          }
      }
  
      function removeNodeType(nt) {
          var nodeTypeId = escapeNodeType(nt);
          var paletteNode = $("#palette_node_"+nodeTypeId);
          var categoryNode = paletteNode.closest(".palette-category");
          paletteNode.remove();
          if (categoryNode.find(".palette_node").length === 0) {
              if (categoryNode.find("i").hasClass("expanded")) {
                  categoryNode.find(".palette-content").slideToggle();
                  categoryNode.find("i").toggleClass("expanded");
              }
          }
      }
  
      function hideNodeType(nt) {
          var nodeTypeId = escapeNodeType(nt);
          var paletteNode = $("#palette_node_"+nodeTypeId);
          paletteNode.hide();
          var categoryNode = paletteNode.closest(".palette-category");
          var cl = categoryNode.find(".palette_node");
          var c = 0;
          for (var i = 0; i < cl.length; i++) {
              if ($(cl[i]).css('display') === 'none') { c += 1; }
          }
          if (c === cl.length) { categoryNode.hide(); }
      }
  
      function showNodeType(nt) {
          var nodeTypeId = escapeNodeType(nt);
          var paletteNode = $("#palette_node_"+nodeTypeId);
          var categoryNode = paletteNode.closest(".palette-category");
          categoryNode.show();
          paletteNode.show();
      }
  
      function refreshNodeTypes() {
          RED.nodes.eachSubflow(function(sf) {
              var paletteNode = $("#palette_node_subflow_"+sf.id.replace(".","_"));
              var portInput = paletteNode.find(".palette_port_input");
              var portOutput = paletteNode.find(".palette_port_output");
  
              if (portInput.length === 0 && sf.in.length > 0) {
                  var portIn = document.createElement("div");
                  portIn.className = "palette_port palette_port_input";
                  paletteNode.append(portIn);
              } else if (portInput.length !== 0 && sf.in.length === 0) {
                  portInput.remove();
              }
  
              if (portOutput.length === 0 && sf.out.length > 0) {
                  var portOut = document.createElement("div");
                  portOut.className = "palette_port palette_port_output";
                  paletteNode.append(portOut);
              } else if (portOutput.length !== 0 && sf.out.length === 0) {
                  portOutput.remove();
              }
              setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||""));
              setIcon(paletteNode,sf);
  
              var currentCategory = paletteNode.data('category');
              var newCategory = (sf.category||"subflows");
              if (currentCategory !== newCategory) {
                  var category = newCategory.replace(/ /g,"_");
                  createCategory(newCategory,category,category,"node-red");
  
                  var currentCategoryNode = paletteNode.closest(".palette-category");
                  var newCategoryNode = $("#palette-"+category);
                  newCategoryNode.append(paletteNode);
                  if (newCategoryNode.find(".palette_node").length === 1) {
                      categoryContainers[category].open();
                  }
  
                  paletteNode.data('category',newCategory);
                  if (currentCategoryNode.find(".palette_node").length === 0) {
                      if (currentCategoryNode.find("i").hasClass("expanded")) {
                          currentCategoryNode.find(".palette-content").slideToggle();
                          currentCategoryNode.find("i").toggleClass("expanded");
                      }
                  }
  
  
  
              }
          });
      }
  
      function filterChange(val) {
          var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
          $("#palette-container .palette_node").each(function(i,el) {
              var currentLabel = $(el).find(".palette_label").text();
              if (val === "" || re.test(el.id) || re.test(currentLabel)) {
                  $(this).show();
              } else {
                  $(this).hide();
              }
          });
  
          for (var category in categoryContainers) {
              if (categoryContainers.hasOwnProperty(category)) {
                  if (categoryContainers[category].container
                          .find(".palette_node")
                          .filter(function() { return $(this).css('display') !== 'none'}).length === 0) {
                      categoryContainers[category].close();
                  } else {
                      categoryContainers[category].open();
                  }
              }
          }
      }
  
      function init() {
  
          RED.events.on('registry:node-type-added', function(nodeType) {
              var def = RED.nodes.getType(nodeType);
              addNodeType(nodeType,def);
              if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
                  def.onpaletteadd.call(def);
              }
          });
          RED.events.on('registry:node-type-removed', function(nodeType) {
              removeNodeType(nodeType);
          });
          RED.events.on('registry:node-set-enabled', function(nodeSet) {
              for (var j=0;j<nodeSet.types.length;j++) {
                  showNodeType(nodeSet.types[j]);
                  var def = RED.nodes.getType(nodeSet.types[j]);
                  if (def && def.onpaletteadd && typeof def.onpaletteadd === "function") {
                      def.onpaletteadd.call(def);
                  }
              }
          });
          RED.events.on('registry:node-set-disabled', function(nodeSet) {
              for (var j=0;j<nodeSet.types.length;j++) {
                  hideNodeType(nodeSet.types[j]);
                  var def = RED.nodes.getType(nodeSet.types[j]);
                  if (def && def.onpaletteremove && typeof def.onpaletteremove === "function") {
                      def.onpaletteremove.call(def);
                  }
              }
          });
          RED.events.on('registry:node-set-removed', function(nodeSet) {
              if (nodeSet.added) {
                  for (var j=0;j<nodeSet.types.length;j++) {
                      removeNodeType(nodeSet.types[j]);
                      var def = RED.nodes.getType(nodeSet.types[j]);
                      if (def && def.onpaletteremove && typeof def.onpaletteremove === "function") {
                          def.onpaletteremove.call(def);
                      }
                  }
              }
          });
  
          $("#palette > .palette-spinner").show();
  
          $("#palette-search input").searchBox({
              delay: 100,
              change: function() {
                  filterChange($(this).val());
              }
          })
  
          var categoryList = coreCategories;
          if (RED.settings.paletteCategories) {
              categoryList = RED.settings.paletteCategories;
          } else if (RED.settings.theme('palette.categories')) {
              categoryList = RED.settings.theme('palette.categories');
          }
          if (!Array.isArray(categoryList)) {
              categoryList = coreCategories
          }
          categoryList.forEach(function(category){
              createCategoryContainer(category, category, "palette.label."+category);
          });
  
          $("#palette-collapse-all").on("click", function(e) {
              e.preventDefault();
              for (var cat in categoryContainers) {
                  if (categoryContainers.hasOwnProperty(cat)) {
                      categoryContainers[cat].close();
                  }
              }
          });
          $("#palette-expand-all").on("click", function(e) {
              e.preventDefault();
              for (var cat in categoryContainers) {
                  if (categoryContainers.hasOwnProperty(cat)) {
                      categoryContainers[cat].open();
                  }
              }
          });
      }
      function getCategories() {
          var categories = [];
          $("#palette-container .palette-category").each(function(i,d) {
              categories.push({id:$(d).data('category'),label:$(d).data('label')});
          })
          return categories;
      }
      return {
          init: init,
          add:addNodeType,
          remove:removeNodeType,
          hide:hideNodeType,
          show:showNodeType,
          refresh:refreshNodeTypes,
          getCategories: getCategories
      };
  })();
  