/* 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/. */

this.EXPORTED_SYMBOLS = [ "CharsetMenu" ];

const { classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "gBundle", function() {
  const kUrl = "chrome://browser/locale/charsetMenu.properties";
  return Services.strings.createBundle(kUrl);
});
/**
 * This set contains encodings that are in the Encoding Standard, except:
 *  - XSS-dangerous encodings (except ISO-2022-JP which is assumed to be
 *    too common not to be included).
 *  - x-user-defined, which practically never makes sense as an end-user-chosen
 *    override.
 *  - Encodings that IE11 doesn't have in its correspoding menu.
 */
const kEncodings = new Set([
  // Globally relevant
  "UTF-8",
  "windows-1252",
  // Arabic
  "windows-1256",
  "ISO-8859-6",
  // Baltic
  "windows-1257",
  "ISO-8859-4",
  // "ISO-8859-13", // Hidden since not in menu in IE11
  // Central European
  "windows-1250",
  "ISO-8859-2",
  // Chinese, Simplified
  "gbk",
  "gb18030",
  // Chinese, Traditional
  "Big5",
  // Cyrillic
  "windows-1251",
  "ISO-8859-5",
  "KOI8-R",
  "KOI8-U",
  "IBM866", // Not in menu in Chromium. Maybe drop this?
  // "x-mac-cyrillic", // Not in menu in IE11 or Chromium.
  // Greek
  "windows-1253",
  "ISO-8859-7",
  // Hebrew
  "windows-1255",
  "ISO-8859-8-I",
  "ISO-8859-8",
  // Japanese
  "Shift_JIS",
  "EUC-JP",
  "ISO-2022-JP",
  // Korean
  "EUC-KR",
  // Thai
  "windows-874",
  // Turkish
  "windows-1254",
  // Vietnamese
  "windows-1258",
  // Hiding rare European encodings that aren't in the menu in IE11 and would
  // make the menu messy by sorting all over the place
  // "ISO-8859-3",
  // "ISO-8859-10",
  // "ISO-8859-14",
  // "ISO-8859-15",
  // "ISO-8859-16",
  // "macintosh"
]);

// Always at the start of the menu, in this order, followed by a separator.
const kPinned = [
  "UTF-8",
  "windows-1252"
];

this.CharsetMenu = Object.freeze({
  build: function BuildCharsetMenu(event) {
    let parent = event.target;
    if (parent.lastChild.localName != "menuseparator") {
      // Detector menu or charset menu already built
      return;
    }
    let doc = parent.ownerDocument;

    function createItem(encoding) {
      let menuItem = doc.createElement("menuitem");
      menuItem.setAttribute("type", "radio");
      menuItem.setAttribute("name", "charsetGroup");
      try {
        menuItem.setAttribute("label", gBundle.GetStringFromName(encoding));
      } catch (e) {
        // Localization error but put *something* in the menu to recover.
        menuItem.setAttribute("label", encoding);
      }
      try {
        menuItem.setAttribute("accesskey",
                              gBundle.GetStringFromName(encoding + ".key"));
      } catch (e) {
        // Some items intentionally don't have an accesskey
      }
      menuItem.setAttribute("id", "charset." + encoding);
      return menuItem;
    }

    // Clone the set in order to be able to remove the pinned encodings from
    // the cloned set.
    let encodings = new Set(kEncodings);
    for (let encoding of kPinned) {
      encodings.delete(encoding);
      parent.appendChild(createItem(encoding));
    }
    parent.appendChild(doc.createElement("menuseparator"));
    let list = [];
    for (let encoding of encodings) {
      list.push(createItem(encoding));
    }

    list.sort(function (a, b) {
      let titleA = a.getAttribute("label");
      let titleB = b.getAttribute("label");
      // Normal sorting sorts the part in parenthesis in an order that
      // happens to make the less frequently-used items first.
      let index;
      if ((index = titleA.indexOf("(")) > -1) {
        titleA = titleA.substring(0, index);
      }
      if ((index = titleB.indexOf("(")) > -1) {
        titleA = titleB.substring(0, index);
      }
      let comp = titleA.localeCompare(titleB);
      if (comp) {
        return comp;
      }
      // secondarily reverse sort by encoding name to sort "windows" or
      // "shift_jis" first. This works regardless of localization, because
      // the ids aren't localized.
      let idA = a.getAttribute("id");
      let idB = b.getAttribute("id");
      if (idA < idB) {
        return 1;
      }
      if (idB < idA) {
        return -1;
      }
      return 0;
    });

    for (let item of list) {
      parent.appendChild(item);
    }
  },
});