+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+// To the extent possible under law, the person who associated CC0
+// with this work has waived all copyright and related or neighboring
+// rights to this work.
+
+"use strict";
+
+Array.prototype.firstWhere = function (callback, thisArg) {
+ for (var i = 0; i < this.length; ++i)
+ if (callback.call(thisArg, this[i]))
+ return this[i];
+};
+
+Array.prototype.contains = function (o) {
+ return this.indexOf(o) >= 0;
+};
+
+Array.prototype.lacks = function (o) {
+ return !this.contains(o);
+};
+
+Array.prototype.containsSubset = function (o) {
+ return o.every(this.contains, this);
+};
+
+Array.prototype.pushUnique = function (o) {
+ if (this.lacks(o))
+ this.push(o);
+};
+
+var BLOCKS = {
+ helmets: ["hats", "accessories"],
+ wetsuits: ["tops", "bottoms", "socks", "dresses"],
+ dresses: ["tops", "bottoms"]
+};
+Object.keys(ACNL_WEARABLES).forEach(function (type) {
+ BLOCKS[type] = (BLOCKS[type] || []).concat(type);
+});
+Object.keys(BLOCKS).forEach(function (k) {
+ BLOCKS[k].forEach(function (v) {
+ BLOCKS[v].pushUnique(k);
+ });
+});
+
+function choice (os) {
+ return os[Math.floor(os.length * Math.random())];
+}
+
+function category (item) {
+ return Object.keys(ACNL_WEARABLES).firstWhere(function (type) {
+ return ACNL_WEARABLES[type].contains(item);
+ });
+}
+
+function notLewd (outfit) {
+ var categories = outfit.map(category);
+ return (categories.contains("wetsuits")
+ || categories.contains("dresses")
+ || categories.containsSubset(["tops", "bottoms"]));
+}
+
+function buildArray (a, l) {
+ return (a || []).concat(l);
+}
+
+function impossibilities (categories) {
+ return categories.map(function (c) { return BLOCKS[c]; }
+ ).reduce(buildArray, []);
+}
+
+function possibilities (outfit) {
+ return Object.keys(ACNL_WEARABLES
+ ).filter(Array.prototype.lacks, impossibilities(outfit.map(category))
+ ).map(function (k) { return ACNL_WEARABLES[k] }
+ ).reduce(buildArray, []);
+}
+
+function isFinished(outfit) {
+ return notLewd(outfit) && Math.random() > 0.5;
+}
+
+function nextItem (outfit) {
+ return !isFinished(outfit) && choice(possibilities(outfit));
+}
+
+function outfit () {
+ var ps = [];
+ var next;
+ while (next = nextItem(ps))
+ ps.push(next);
+ return ps;
+}
+
+function article (item) {
+ if (item[0].toUpperCase() === item[0] && item.indexOf("'s") >= 0)
+ return "";
+ else if (item.substr(-1) === "s" && item.substr(-2) !== "ss"
+ && item.substr(-6) !== "cosmos") // :(
+ return "";
+ else if ("aeiouAEIO".indexOf(item[0]) >= 0
+ || ("FHLMNRSX".indexOf(item[0]) >= 0
+ && item.split(" ")[0].toUpperCase() == item.split(" ")[0]))
+ return "an ";
+ else
+ return "a ";
+}
+
+function dblink (name) {
+ return ["<a target=\"_blank\" href=\"http://moridb.com/items/search?q=",
+ encodeURIComponent(name),
+ "\">", name.replace("&", "&"), "</a>"].join("");
+}
+
+function litanize (names, link) {
+ names = names.map(function (name) {
+ return article(name) + (link ? dblink(name) : name);
+ });
+ switch (names.length) {
+ case 1: break;
+ case 2:
+ names[names.length - 1] = (choice(["and ", "with "])
+ + names[names.length - 1]);
+ break;
+ default:
+ if (Math.random() > 0.5 && names.length >= 4) {
+ names[names.length - 2] = "and " + names[names.length - 2];
+ names[names.length - 1] = "with " + names[names.length - 1];
+ } else {
+ names[names.length - 1] = "and " + names[names.length - 1];
+ }
+ break;
+ }
+
+ return names.join(names.length == 2 ? " " : ", ");
+}
+
+function litanizeElement (eid, tid) {
+ var o = outfit();
+ document.getElementById(eid).innerHTML = litanize(o, true) + "?";
+ // TODO: This often generates > 140 characters. Do I care?
+ // Maybe pick shorter prefixes if it would help.
+ var t = document.getElementById(tid);
+ t.href = t.href.replace(
+ /&text=[^&]+/, "&text=" + encodeURIComponent(
+ choice(["Going out in ",
+ "Labelle suggested ",
+ "I'll wear "]) + litanize(o, false) + "."));
+}