# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
#
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (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.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is this file as it was released on January 3, 2001.
#
# The Initial Developer of the Original Code is
# Jonas Sicking.
# Portions created by the Initial Developer are Copyright (C) 2000
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#   Jonas Sicking <sicking@bigfoot.com> (Original Author)
#   Gervase Markham <gerv@gerv.net>
#   Heikki Toivonen <heikki@netscape.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

const XLinkNS = "http://www.w3.org/1999/xlink";
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const XMLNS = "http://www.w3.org/XML/1998/namespace";
const XHTMLNS = "http://www.w3.org/1999/xhtml";
var gMetadataBundle;
var gLangBundle;
var gRegionBundle;
var nodeView;
var htmlMode = false;

var onLink   = false;
var onImage  = false;
var onInsDel = false;
var onQuote  = false;
var onMisc   = false;
var onTable  = false;
var onTitle  = false;
var onLang   = false;

// Interface for image loading content
const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;

const prefs = Components.classes["@mozilla.org/preferences-service;1"].
              getService(Components.interfaces.nsIPrefBranch);

const nsICacheService = Components.interfaces.nsICacheService;
const cacheService = Components.classes["@mozilla.org/network/cache-service;1"]
                     .getService(nsICacheService);
var httpCacheSession = cacheService.createSession("HTTP", 0, true);
httpCacheSession.doomEntriesIfExpired = false;
var ftpCacheSession = cacheService.createSession("FTP", 0, true);
ftpCacheSession.doomEntriesIfExpired = false;

const PREF_PINGS_ENABLED = "browser.send_pings";
const PREF_PINGS_MAX_PER_LINK = "browser.send_pings.max_per_link";
const PREF_PINGS_REQUIRE_SAME_HOST = "browser.send_pings.require_same_host";

/**
 * This function generates an array of pings that will be sent if the given
 * anchor element is clicked.  It basically duplicates the pref checking logic
 * found in nsWebShell.cpp.  It might be nice to expose that functionality on
 * some interface that both of these sections of code could share.
 *
 * @param elem
 *   An anchor or area element
 * @return 
 *   An array of URL strings corresponding to the pings that would occur if
 *   the element's href were loaded.
 */
function getPings(elem)
{
  var result = [];

  var enabled = prefs.getBoolPref(PREF_PINGS_ENABLED);
  if (!enabled)
    return result;
  var maxPings = prefs.getIntPref(PREF_PINGS_MAX_PER_LINK);
  if (maxPings == 0)
    return result;
  var requireSameHost = prefs.getBoolPref(PREF_PINGS_REQUIRE_SAME_HOST);

  const ios =
      Components.classes["@mozilla.org/network/io-service;1"].
      getService(Components.interfaces.nsIIOService);

  var doc = elem.ownerDocument;
  var docURI = ios.newURI(doc.documentURI, doc.characterSet, null);

  // The URL strings returned by elem.ping are absolute URLs.
  var pings = elem.ping;
  if (!pings)
    return result;
  pings = pings.split(" ");
  for (var i = 0; i < pings.length; ++i) {
    if (requireSameHost) {
      var uri = ios.newURI(pings[i], doc.characterSet, null);
      if (uri.asciiHost == docURI.asciiHost)
        result.push(pings[i]);
    } else {
      result.push(pings[i]);
    }
    if (result.length == maxPings)
      break;
  }

  return result;
}

function onLoad()
{
  gMetadataBundle = document.getElementById("bundle_metadata");
  gLangBundle = document.getElementById("bundle_languages");
  gRegionBundle = document.getElementById("bundle_regions");
  
  showMetadataFor(window.arguments[0]);
  nodeView = window.arguments[0].ownerDocument.defaultView;
}

function showMetadataFor(elem)
{
  // skip past non-element nodes
  while (elem && elem.nodeType != Node.ELEMENT_NODE)
    elem = elem.parentNode;

  if (!elem) {
    alert(gMetadataBundle.getString("unableToShowProps"));
    window.close();
  }

  if (elem.ownerDocument.getElementsByName && !elem.ownerDocument.namespaceURI)
    htmlMode = true;
  
  // htmllocalname is "" if it's not an html tag, or the name of the tag if it is.
  var htmllocalname = "";
  if (isHTMLElement(elem,"")) { 
    htmllocalname = elem.localName.toLowerCase();
  }
  
  // We only look for images once
  checkForImage(elem, htmllocalname);
  
  // Walk up the tree, looking for elements of interest.
  // Each of them could be at a different level in the tree, so they each
  // need their own boolean to tell us to stop looking.
  while (elem && elem.nodeType == Node.ELEMENT_NODE) {
    htmllocalname = "";
    if (isHTMLElement(elem,"")) { 
      htmllocalname = elem.localName.toLowerCase();
    }

    if (!onLink)   checkForLink(elem, htmllocalname);
    if (!onInsDel) checkForInsDel(elem, htmllocalname);
    if (!onQuote)  checkForQuote(elem, htmllocalname);
    if (!onTable)  checkForTable(elem, htmllocalname);
    if (!onTitle)  checkForTitle(elem, htmllocalname);
    if (!onLang)   checkForLang(elem, htmllocalname);
      
    elem = elem.parentNode;
  }
  
  // Decide which sections to show
  var onMisc = onTable || onTitle || onLang;
  if (!onMisc)   hideNode("misc-sec");
  if (!onLink)   hideNode("link-sec");
  if (!onImage)  hideNode("image-sec");
  if (!onInsDel) hideNode("insdel-sec");
  if (!onQuote)  hideNode("quote-sec");

  // Fix the Misc section visibilities
  if (onMisc) {
    if (!onTable) hideNode("misc-tblsummary");
    if (!onLang)  hideNode("misc-lang");
    if (!onTitle) hideNode("misc-title");
  }

  // Get rid of the "No properties" message. This is a backstop -
  // it should really never show, as long as nsContextMenu.js's
  // checking doesn't get broken.
  if (onLink || onImage || onInsDel || onQuote || onMisc)
    hideNode("no-properties")
}


function checkForImage(elem, htmllocalname)
{
  var img;
  var imgType; // "img" = <img>
               // "object" = <object>
               // "input" = <input type=image>
               // "background" = css background (to be added later)
  var ismap = false;

  if (htmllocalname === "img") {
    img = elem;
    imgType = "img";

  } else if (htmllocalname === "object" &&
             elem.type.substring(0,6) == "image/" &&
             elem.data) {
    img = elem;
    imgType = "object";

  } else if (htmllocalname === "input" &&
             elem.type.toUpperCase() == "IMAGE") {
    img = elem;
    imgType = "input";

  } else if (htmllocalname === "area" || htmllocalname === "a") {

    // Clicked in image map?
    var map = elem;
    ismap = true;
    setAlt(map);

    while (map && map.nodeType == Node.ELEMENT_NODE && !isHTMLElement(map,"map") )
      map = map.parentNode;

    if (map && map.nodeType == Node.ELEMENT_NODE) {
      img = getImageForMap(map);
      var imgLocalName = img && img.localName.toLowerCase();
      if (imgLocalName == "img" || imgLocalName == "object")
          imgType = imgLocalName;
    }
  }

  if (img) {

    var imgURL = imgType == "object" ? img.data : img.src;
    setInfo("image-url", imgURL);
    var size = getSize(imgURL);

    if (size != -1) {
      var kbSize = size / 1024;
      kbSize = Math.round(kbSize*100)/100;
      setInfo("image-filesize", gMetadataBundle.getFormattedString("imageSize", [kbSize, size]));
    } else {
      setInfo("image-filesize", gMetadataBundle.getString("imageSizeUnknown"));
    }

    var imageRequest = img.QueryInterface(nsIImageLoadingContent)
                          .getRequest(nsIImageLoadingContent.CURRENT_REQUEST);
    var image = imageRequest && imageRequest.image;
    var imageType = "";
    if (imageRequest) {
      imageType = imageRequest.mimeType;
      var imageMimeType = /^image\/(.*)/.exec(imageType);
      if (imageMimeType) {
        imageType = imageMimeType[1].toUpperCase();
        if (image && image.numFrames > 1)
          imageType = gMetadataBundle.getFormattedString("animatedImageType",
                                                         [imageType, image.numFrames]);
        else
          imageType = gMetadataBundle.getFormattedString("imageType", [imageType]);
      }
    }
    setInfo("image-type", imageType);

    var imageSize = "";
    if (img.width) {
      if (image && (image.width != img.width || image.height != img.height))
        imageSize = gMetadataBundle.getFormattedString("imageDimensionsScaled",
                                                       [image.width, image.height,
                                                        img.width,   img.height]);
      else
        imageSize = gMetadataBundle.getFormattedString("imageDimensions", [img.width, img.height]);
    }        
    setInfo("image-size", imageSize);
     
    if (imgType == "img") {
      setInfo("image-desc", img.longDesc);
    } else {
      setInfo("image-desc", "");
    }
    
    onImage = true;
  }

  if (!ismap) {
   if (imgType == "img" || imgType == "input") {
     setAlt(img);
   } else {
     hideNode("image-alt");
   }
  }
}

function checkForLink(elem, htmllocalname)
{
  if ((htmllocalname === "a" && elem.href != "") ||
    htmllocalname === "area") {

    setInfo("link-lang", convertLanguageCode(elem.getAttribute("hreflang")));
    setInfo("link-url",  elem.href);
    setInfo("link-type", elem.getAttribute("type"));
    setInfo("link-rel",  elem.getAttribute("rel"));
    setInfo("link-rev",  elem.getAttribute("rev"));
    setInfo("link-ping", getPings(elem).join('\n'));

    var target = elem.target;

    switch (target) {
    case "_top":
      setInfo("link-target", gMetadataBundle.getString("sameWindowText"));
      break;
    case "_parent":
      setInfo("link-target", gMetadataBundle.getString("parentFrameText"));
      break;
    case "_blank":
      var where = "Window";
      var newWindowPref = prefs.getIntPref("browser.link.open_newwindow");
      if (newWindowPref == 3)
        where = "Tab";
      setInfo("link-target", gMetadataBundle.getString("new" + where + "Text"));
      break;
    case "":
    case "_self":
      if (elem.ownerDocument.defaultView) {
        if (elem.ownerDocument != elem.ownerDocument.defaultView.content.document)
          setInfo("link-target", gMetadataBundle.getString("sameFrameText"));
        else
          setInfo("link-target", gMetadataBundle.getString("sameWindowText"));
      } else {
        hideNode("link-target");
      }
      break;
    default:
      setInfo("link-target", "\"" + target + "\"");
    }

    onLink = true;
  }

  else if (elem.getAttributeNS(XLinkNS, "href") != "") {
    var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                              .getService(Components.interfaces.nsIIOService);
    var url = elem.getAttributeNS(XLinkNS, "href");
    try {
        var baseURI = ioService.newURI(elem.baseURI, elem.ownerDocument.characterSet, null);
        url = ioService.newURI(url, elem.ownerDocument.characterSet, baseURI).spec;
    } catch (e) {}
    setInfo("link-url", url);
    setInfo("link-lang", "");
    setInfo("link-type", "");
    setInfo("link-rel", "");
    setInfo("link-rev", "");
    setInfo("link-ping", "");

    switch (elem.getAttributeNS(XLinkNS,"show")) {
    case "embed":
        setInfo("link-target", gMetadataBundle.getString("embeddedText"));
        break;
    case "new":
        setInfo("link-target", gMetadataBundle.getString("newWindowText"));
        break;
    case "":
    case "replace":
        if (elem.ownerDocument != elem.ownerDocument.defaultView.content.document)
            setInfo("link-target", gMetadataBundle.getString("sameFrameText"));
        else
            setInfo("link-target", gMetadataBundle.getString("sameWindowText"));
        break;
    default:
        setInfo("link-target", "");
        break;
    }
    
    onLink = true;
  }
}

function checkForInsDel(elem, htmllocalname)
{
  if ((htmllocalname === "ins" || htmllocalname === "del") &&
    (elem.cite || elem.dateTime)) {
    setInfo("insdel-cite", elem.cite);
    setInfo("insdel-date", elem.dateTime);
    onInsDel = true;
  } 
}


function checkForQuote(elem, htmllocalname)
{
  if ((htmllocalname === "q" || htmllocalname === "blockquote") && elem.cite) {
    setInfo("quote-cite", elem.cite);
    onQuote = true;
  } 
}

function checkForTable(elem, htmllocalname)
{
  if (htmllocalname === "table" && elem.summary) {
    setInfo("misc-tblsummary", elem.summary);
    onTable = true;
  }
}

function checkForLang(elem, htmllocalname)
{
  if ((htmllocalname && elem.lang) || elem.getAttributeNS(XMLNS, "lang")) {
    var abbr;
    if (htmllocalname && elem.lang)
      abbr = elem.lang;
    else
      abbr = elem.getAttributeNS(XMLNS, "lang");
        
    setInfo("misc-lang", convertLanguageCode(abbr));
    onLang = true;
  }
}
    
function checkForTitle(elem, htmllocalname)
{
  if (htmllocalname && elem.title) {
    setInfo("misc-title", elem.title);
    onTitle = true;
  }    
}

/*
 * Set text of node id to value
 * if value="" the node with specified id is hidden.
 * Node should be have one of these forms
 * <xul:label id="id-text" value=""/>
 * <xul:description id="id-text"/>
 */
function setInfo(id, value)
{
  if (!value) {
    hideNode(id);
    return;
  }

  var node = document.getElementById(id+"-text");

  if (node.namespaceURI == XULNS && node.localName == "label" ||
     (node.namespaceURI == XULNS && node.localName == "textbox")) {
    node.setAttribute("value",value);

  } else if (node.namespaceURI == XULNS && node.localName == "description") {
    while (node.hasChildNodes())
      node.removeChild(node.firstChild);
    node.appendChild(node.ownerDocument.createTextNode(value));
  }
}

// Hide node with specified id
function hideNode(id)
{
    var style = document.getElementById(id).getAttribute("style");
    document.getElementById(id).setAttribute("style", "display:none;" + style);
}

/*
 * Find <img> or <object> which uses an imagemap.
 * If more then one object is found we can't determine which one
 * was clicked.
 *
 * This code has to be changed once bug 1882 is fixed.
 * Once bug 72527 is fixed this code should use the .images collection.
 */
function getImageForMap(map)
{
  var mapuri = "#" + map.getAttribute("name");
  var multipleFound = false;
  var img;

  var list = getHTMLElements(map.ownerDocument, "img");
  for (var i=0; i < list.length; i++) {
    if (list.item(i).getAttribute("usemap") == mapuri) {
      if (img) {
        multipleFound = true;
        break;
      } else {
        img = list.item(i);
        imgType = "img";
      }
    }
  }

  list = getHTMLElements(map.ownerDocument, "object");
  for (i = 0; i < list.length; i++) {
    if (list.item(i).getAttribute("usemap") == mapuri) {
      if (img) {
        multipleFound = true;
        break;
      } else {
        img = list.item(i);
        imgType = "object";
      }
    }
  }

  if (multipleFound)
    img = null;

  return img;
}

function getHTMLElements(node, name)
{
  if (htmlMode)
    return node.getElementsByTagName(name);
  return node.getElementsByTagNameNS(XHTMLNS, name);
}

// name should be in lower case
function isHTMLElement(node, name)
{
  if (node.nodeType != Node.ELEMENT_NODE)
    return false;

  if (htmlMode)
    return !name || node.localName.toLowerCase() == name;

  return (!name || node.localName == name) && node.namespaceURI == XHTMLNS;
}

// This function coded according to the spec at:
// http://www.bath.ac.uk/~py8ieh/internet/discussion/metadata.txt
function convertLanguageCode(abbr)
{
  if (!abbr) return "";
  var result;
  var language = "";
  var region;
  var is_region_set = false;
  var tokens = abbr.split("-");

  if (tokens[0] === "x" || tokens[0] === "i")
  {
    // x and i prefixes mean unofficial ones. So we upper-case the first
    // word and leave the rest.
    tokens.shift();

    if (tokens[0])
    {
      // Upper-case first letter
      language = tokens[0].substr(0, 1).toUpperCase() + tokens[0].substr(1);
      tokens.shift();

      if (tokens[0])
      {
        // Add on the rest as space-separated strings inside the brackets
        region = tokens.join(" ");
        is_region_set = true;
      }
    }
  }
  else
  {
    // Otherwise we treat the first as a lang, the second as a region
    // and the rest as strings.
    try
    {
      language = gLangBundle.getString(tokens[0].toLowerCase());
    }
    catch (e) 
    {
      // Language not present in lang bundle
      language = tokens[0]; 
    }

    tokens.shift();

    if (tokens[0])
    {
      try
      {
        // We don't add it on to the result immediately
        // because we want to get the spacing right.
        region = gRegionBundle.getString(tokens[0].toLowerCase());

        tokens.shift();

        if (tokens[0])
        {
          // Add on the rest as space-separated strings inside the brackets
          region += " " + tokens.join(" ");
        }
      }
      catch (e) 
      {
        // Region not present in region bundle
        region = tokens.join(" ");
      }

      is_region_set = true;
    }
  }

  if (is_region_set) {
    result = gMetadataBundle.getFormattedString("languageRegionFormat",
                                                [language, region]);
  } else
    result = language;

  return result;
}

// Returns the size of the URL in bytes; must be cached and therefore an HTTP or FTP URL
function getSize(url) {
  try
  {
    var cacheEntryDescriptor = httpCacheSession.openCacheEntry(url, Components.interfaces.nsICache.ACCESS_READ, false);
    if(cacheEntryDescriptor)
      return cacheEntryDescriptor.dataSize;
  }
  catch(ex) {}
  try
  {
    cacheEntryDescriptor = ftpCacheSession.openCacheEntry(url, Components.interfaces.nsICache.ACCESS_READ, false);
    if (cacheEntryDescriptor)
      return cacheEntryDescriptor.dataSize;
  }
  catch(ex) {}
  return -1;
}

function setAlt(elem) {
  var altText = document.getElementById("image-alt-text");
  if (elem.hasAttribute("alt")) {
    if (elem.alt != "") {
      altText.value = elem.alt;
      altText.setAttribute("style","font-style:inherit");
    } else {
      altText.value = gMetadataBundle.getString("altTextBlank");
      altText.setAttribute("style","font-style:italic");
    }
  } else {
    altText.value = gMetadataBundle.getString("altTextMissing");
    altText.setAttribute("style","font-style:italic");
  }
}
