/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

ChromeUtils.defineModuleGetter(this, "Feeds",
  "resource:///modules/Feeds.jsm");

/**
 * LinkToolbarHandler is a Singleton that displays LINK elements
 * and nodeLists of LINK elements in the Link Toolbar.  It
 * associates the LINK with a corresponding LinkToolbarItem based
 * on it's REL attribute and the toolbar item's ID attribute.
 * LinkToolbarHandler is also a Factory and will create
 * LinkToolbarItems as necessary.
 */
function LinkToolbarHandler()
{
  this.items = new Array();
  this.hasItems = false;
}

LinkToolbarHandler.prototype.handle =
function(element)
{
  // XXX: if you're going to re-enable handling of anchor elements,
  //    you'll want to change this to AnchorElementDecorator
  var linkElement = new LinkElementDecorator(element);

  if (linkElement.isIgnored()) return;

  for (var i = 0; i < linkElement.relValues.length; i++) {
    // Skip "alternate" when we have e.g. "alternate XXX".
    if (linkElement.relValues.length > 1 &&
        linkElement.relValues[i] == "alternate")
      continue; 

    var linkType = LinkToolbarHandler.getLinkType(linkElement.relValues[i], element);
    if (linkType) {
      if (!this.hasItems) {
        this.hasItems = true;
        linkToolbarUI.activate();
      }
      this.getItemForLinkType(linkType).displayLink(linkElement);
    }
  }
}

LinkToolbarHandler.getLinkType =
function(relAttribute, element)
{
  var isFeed = false;
  switch (relAttribute.toLowerCase()) {
    case "top":
    case "origin":
      return "top";

    case "up":
    case "parent":
      return "up";

    case "start":
    case "begin":
    case "first":
      return "first";

    case "next":
    case "child":
      return "next";

    case "prev":
    case "previous":
      return "prev";

    case "end":
    case "last":
      return "last";

    case "author":
    case "made":
      return "author";

    case "contents":
    case "toc":
      return "toc";

    case "feed":
      isFeed = true;
      // fall through
    case "alternate":
      if (Feeds.isValidFeed(element, element.nodePrincipal, isFeed)) {
        return "feed";
      }

      if (!isFeed) {
        return "alternate";
      }
      // fall through
    case "prefetch":
      return null;

    default:
      return relAttribute.toLowerCase();
  }
}

LinkToolbarHandler.prototype.getItemForLinkType =
function(linkType) {
  if (!(linkType in this.items && this.items[linkType]))
    this.items[linkType] = LinkToolbarHandler.createItemForLinkType(linkType);

  return this.items[linkType];
}

LinkToolbarHandler.createItemForLinkType =
function(linkType)
{
  if (!document.getElementById("link-" + linkType))
    return new LinkToolbarTransientMenu(linkType);

  // XXX: replace switch with polymorphism
  var element = document.getElementById("link-" + linkType);
  switch (element.getAttribute("type") || element.localName) {
    case "toolbarbutton":
      return new LinkToolbarButton(linkType);

    case "menuitem":
      return new LinkToolbarItem(linkType);

    case "menu":
      return new LinkToolbarMenu(linkType);

    default:
      return new LinkToolbarTransientMenu(linkType);
  }
}

LinkToolbarHandler.prototype.clearAllItems =
function()
{
  // Hide the 'miscellaneous' separator
  document.getElementById("misc-separator").setAttribute("collapsed", "true");

  // Disable the individual items
  for (var linkType in this.items)
    this.items[linkType].clear();

  // Store the fact that the toolbar is empty
  this.hasItems = false;
}

var linkToolbarHandler = new LinkToolbarHandler();

function LinkElementDecorator(element) {
  /*
   * XXX: this is an incomplete decorator, because it doesn't implement
   *      the full Element interface.  If you need to use a method
   *      or member in the Element interface, just add it here and
   *      have it delegate to this.element
   *
   * XXX: would rather add some methods to Element.prototype instead of
   *    using a decorator, but Element.prototype is no longer exposed
   *      since the XPCDOM landing, see bug 83433
   */

  if (!element) return; // skip the rest on foo.prototype = new ThisClass calls

  this.element = element;

  this.rel = LinkElementDecorator.convertRevMade(element.rel, element.rev);
  if (this.rel)
    this.relValues = this.rel.split(" ");
  this.rev = element.rev;
  this.title = element.title;
  this.href = element.href;
  this.hreflang = element.hreflang;
  this.media = element.media;
  this.longTitle = null;
}

LinkElementDecorator.prototype.isIgnored =
function()
{
  if (!this.rel) return true;
  for (var i = 0; i < this.relValues.length; i++)
    if (/^stylesheet$|^icon$|^fontdef$|^p3pv|^schema./i.test(this.relValues[i]))
      return true;
  return false;
}

LinkElementDecorator.convertRevMade =
function(rel, rev)
{
  if (!rel && rev && /\bmade\b/i.test(rev))
    return rev;
  else
    return rel;
}

LinkElementDecorator.prototype.getTooltip =
function()
{
  return this.getLongTitle() || this.href;
}

LinkElementDecorator.prototype.getLabel =
function()
{
  return this.getLongTitle() || this.rel;
}

LinkElementDecorator.prototype.getLongTitle =
function()
{
  if (this.longTitle == null)
    this.longTitle = this.makeLongTitle();

  return this.longTitle;
}

LinkElementDecorator.prototype.makeLongTitle =
function()
{
  let prefix = "";

  // XXX: lookup more meaningful and localized version of media,
  //   i.e. media="print" becomes "Printable" or some such
  // XXX: use localized version of ":" separator
  if (this.media && !/\ball\b|\bscreen\b/i.test(this.media))
    prefix += this.media + ": ";
  if (this.hreflang) {
    try {
      let languageBundle = document.getElementById("languageBundle");
      let regionBundle = document.getElementById("regionBundle");

      // In case hreflang contains region code.
      let ISOcode = this.hreflang.split("-");

      prefix += languageBundle.getString(ISOcode[0].toLowerCase());

      // Test if region code exists.
      if (ISOcode[1])
       prefix += " (" + regionBundle.getString(ISOcode[1].toLowerCase()) + ")";
    }
    catch (e) {
      // Return if language or region is not recognized.
      prefix += gNavigatorBundle.getFormattedString("unknownLanguage",
                                                    [this.hreflang]);
    }

    prefix += ": ";
  }

  return this.title ? prefix + this.title : prefix;
}

function AnchorElementDecorator(element) {
  this.constructor(element);
}
AnchorElementDecorator.prototype = new LinkElementDecorator;

AnchorElementDecorator.prototype.getLongTitle =
function()
{
  return this.title ? this.__proto__.getLongTitle.apply(this)
      : getText(this.element);
}

AnchorElementDecorator.prototype.getText =
function(element)
{
  return condenseWhitespace(getTextRecursive(element));
}

AnchorElementDecorator.prototype.getTextRecursive =
function(node)
{
  var text = "";
  node.normalize();
  if (node.hasChildNodes()) {
    for (var i = 0; i < node.childNodes.length; i++) {
      if (node.childNodes.item(i).nodeType == Node.TEXT_NODE)
        text += node.childNodes.item(i).nodeValue;
      else if (node.childNodes.item(i).nodeType == Node.ELEMENT_NODE)
        text += getTextRecursive(node.childNodes.item(i));
    }
  }

  return text;
}

AnchorElementDecorator.prototype.condenseWhitespace =
function(text)
{
  return text.replace(/\W*$/, "").replace(/^\W*/, "").replace(/\W+/g, " ");
}
