/**
 * Nicopedia library for JavaScript.
 */

var Publisher = Class.create();
Object.extend(Publisher.prototype, {

	initialize: function () {
		this._inputs  = [];
		this._filters = [];
		this._outputs = [];
	},

	registerInput: function (input) {
		this._inputs.push(input);
		return this;
	},

	registerFilter: function (filter) {
		this._filters.push(filter);
		return this;
	},

	registerOutput: function (output) {
		this._outputs.push(output);
		return this;
	},

	execute: function (items) {
		this._input(items, function (items) {
			items = this._filter(items);
			this._output(items);
			this.clear();
		}.bind(this));
		return this;
	},

	clear: function () {
		this._inputs  = [];
		this._filters = [];
		this._outputs = [];
	},

	_input: function (items, afterLoaded) {
		var self = this;
		var loaded = function () {
			var allLoaded = items.all(function (it) { return it.isReady() });
			if (allLoaded) afterLoaded(items);
		};
		this._inputs.each(function (input) {
			input.call(self, items, loaded);
		});
		loaded();
	},

	_filter: function (items) {
		var self = this;
		this._filters.each(function (filter) {
			var _items = filter.call(self, items);
			if (_items !== undefined) items = _items;
		});
		return items;
	},

	_output: function (items) {
		var self = this;
		this._outputs.each(function (output) {
			output.call(self, items);
		});
	}

});

var Nicopedia = Class.create();
Nicopedia.URL = 'http://dic.nicovideo.jp/';
Nicopedia.API_URL = 'http://api.nicodic.jp/';
Nicopedia.inMaintenance = false;
Object.extend(Nicopedia.prototype, {

	initialize: function (articles, category) {
		if (Nicopedia.inMaintenance) {
			throw new Error('dic.nicovideo.jp is under maintenance');
		}

		this.publisher = new Publisher();
		this.delayExecution = false;
		this.executing = false;
		this.ready = false;

		articles = articles || [];
		articles = articles instanceof Array ? articles : [articles];
		this.articles = articles.map(function (a) {
			return (typeof a == "string") ? Nicopedia.Article.get(a, category) : a;
		});
	},

	execute: function () {
		if (!this.delayExecution || this.ready) {
			this.publisher.execute(this.articles);
		} else {
			this.executing = true;
		}
		return this;
	},

	getReady: function () {
		this.ready = true;
		if (this.executing) {
			this.executing = false;
			this.execute();
		}
	},

	/* Inputs */

	loadWritten: function () {
		this.publisher.registerInput(function (articles, loader) {
			articles
				.findAll(function (a) { return a.written === undefined; })
				.invoke("loadWritten", loader);
		});
		return this;
	},

	loadSummary: function () {
		this.publisher.registerInput(function (articles, loader) {
			articles
				.findAll(function (a) { return a.summary === undefined; })
				.invoke("loadSummary", loader);
		});
		return this;
	},

	/* Filters */

	writtenOnly: function () {
		this.publisher.registerFilter(function (articles) {
			return articles.findAll(function (a) { return a.written; });
		});
		return this;
	},

	/* Outputs */

	onReady: function (callback) {
		var self = this;
		this.publisher.registerOutput(function (articles) {
			callback.call(self, articles);
		});
		return this;
	},

	withArticles: function (callback) {
		var self = this;
		this.publisher.registerOutput(function (articles) {
			callback.call(self, articles);
		});
		return this;
	},

	eachArticle: function (callback) {
		var self = this;
		this.publisher.registerOutput(function (articles) {
			articles.each(function (a, i) { callback.call(self, a, i) });
		});
		return this;
	}

});

Nicopedia.Article = Class.create();
Object.extend(Nicopedia.Article.prototype, {

	initialize: function (title, category, written, viewTitle, summary) {
		this.title = title;
		this.category = category || "a";
		this.written = written;
		this.viewTitle = viewTitle;
		this.summary = summary;
		this._busy = 0;
		this._callbacks = {};
	},

	isReady: function () {
		return (this._busy == 0);
	},

	getURL: function () {
		return Nicopedia.URL + this.category + "/" + encodeURIComponent(this.title);
	},

	loadWritten: function (callback) {
		if (this.written !== undefined) {
			callback(this);
			return;
		}

		this._busy++;
		var jsonpCallback = (function (data) {
			if (data instanceof Array) data = data[0];
			this.written = (data != 0);
			this._busy--;
			callback(this);
		}).bind(this);

		if (this.category == "a") {
			new Nicopedia.JSONP(["e", jsonpCallback, this.title]);
		} else {
			new Nicopedia.JSONP(["page.exist", jsonpCallback, this.category, this.title]);
		}
	},

	loadSummary: function (callback) {
		if (this.summary !== undefined) {
			callback(this);
			return;
		}

		this._busy++;
		var jsonpCallback = (function (data) {
			this.written = (data !== null);
			this.viewTitle = data ? data.viewTitle : "";
			this.summary = data ? data.summary : "";
			this._busy--;
			callback(this);
		}).bind(this);

		new Nicopedia.JSONP(["page.summary", jsonpCallback, this.category, this.title]);
	}

});

Nicopedia.Article.get = function (name, category) {
	if (!this._cache) this._cache = {};
	var key = (category || "a") + "/" + name;
	var it = this._cache[key];
	if (!it) {
		it = this._cache[key] = new this(name, category);
	}
	return it;
}

Nicopedia.searchByTitle = function (query) {
	var pedia = new Nicopedia();
	pedia.delayExecution = true;

	var jsonpCallback = (function (data) {
		if (data && data.pages && data.pages.length > 0) {
			pedia.total = data.found_rows || 0;
			pedia.articles = data.pages.map(function (a) {
				var article = Nicopedia.Article.get(a.title, a.category);
				article.written = true;
				article.viewTitle = a.view_title;
				article.summary = a.summary;
				return article;
			});
		}
		pedia.getReady();
	}).bind(this);

	new Nicopedia.JSONP(["page.search.title", jsonpCallback, query]);
	return pedia;
}

Nicopedia.listCreated = function () {
	var pedia = new Nicopedia();
	pedia.delayExecution = true;

	var jsonpCallback = (function (data) {
		if (data && data.pages && data.pages.length > 0) {
			pedia.articles = data.pages.map(function (a) {
				var article = Nicopedia.Article.get(a.title, a.category);
				article.written = true;
				article.viewTitle = a.view_title;
				article.summary = a.summary;
				return article;
			});
		}
		pedia.getReady();
	}).bind(this);

	new Nicopedia.JSONP(["page.created", jsonpCallback]);
	return pedia;
}


Nicopedia.JSONP = Class.create();
Nicopedia.JSONP.counter = 0;
Object.extend(Nicopedia.JSONP.prototype, {

	CALLBACK_PREFIX: '__nicopedia_api',

	initialize: function (parameters, cancelFire) {
		this.url = Nicopedia.API_URL;
		this.parameters = parameters || [];
		if (this.parameters.length >= 1 && /^https?:\/\//.test(this.parameters[0])) {
			this.url = this.parameters.shift();
		}
		if (!cancelFire) this.fire();
	},

	fire: function () {
		var path = this.parameters.map(function (p, i) {
			if (typeof p == "function") {
				var cbname = this.buildCallbackName(i);
				window[cbname] = function () {
					p.apply(this, arguments);
					window[cbname] = undefined;
				}
				return cbname;
			} else {
				return encodeURIComponent(p);
			}
		}.bind(this)).join("/");

		this.loadScript(this.url + path);
	},

	buildCallbackName: function (i) {
		var cbname = [this.CALLBACK_PREFIX, i].concat(
			this.parameters.map(function (p) {
				return (typeof p == "function") ? "fn" : p;
			})
		).join("_");
		return encodeURIComponent(cbname).replace(/[^a-zA-Z0-9]/g, "_");
	},

	loadScript: function (url) {
		var elemScript = document.createElement('script');
		elemScript.setAttribute('type', 'text/javascript');
		elemScript.setAttribute('charset', 'utf-8');
		elemScript.setAttribute('src', url);
		elemScript.setAttribute('defer', 'defer');
		elemScript.setAttribute('async', 'async');

		var elemHead = document.getElementsByTagName('head').item(0);
		elemHead.appendChild(elemScript);
	}

});

Nicopedia.SELECTOR = ".nicopedia";
Nicopedia.WRITTEN_LINK = {
	insertion: Insertion.After,
	template: '<a href="#{link}" class="nicopedia_tag written">[!]</a>',
	styles: {},
	title: null
};
Nicopedia.NONEXIST_LINK = {
	insertion: Insertion.After,
	template: '<a href="#{link}" class="nicopedia_tag nonexist">[?]</a>',
	styles: {},
	title: null
};
Nicopedia.decorateLinks = function (options) {
	if (Nicopedia.inMaintenance) {
		return;
	}

	options = Object.extend({
		selector: Nicopedia.SELECTOR,
		writtenLink: Nicopedia.WRITTEN_LINK,
		nonexistLink: Nicopedia.NONEXIST_LINK
	}, options || {});

	var elems = $$(options.selector).reject(function (el) {
		return el.hasClassName("__nicopedia_loaded");
	});
	if (elems.length == 0) return;

	var words = elems.map(function (el) {
		return el.innerHTML.stripTags().strip().unescapeHTML();
	});

	var pedia = new Nicopedia(words);
	pedia.loadWritten();
	pedia.eachArticle(function (article, i) {
		var elem = elems[i];
		var link = options[article.written ? "writtenLink" : "nonexistLink"];
		if (!link) return;

		var context = {
			link: article.getURL(),
			title: article.title.escapeHTML().replace(/"/g, '&quot;')
		};

		if (link.insertion && link.template) {
			var template = link.template;
			if (typeof template == "string") {
				template = new Template(template);
			}
			new link.insertion(elem, template.evaluate(context));
		}
		if (link.styles) {
			elem.setStyle(link.styles);
		}
		if (link.title) {
			var template = link.title;
			if (typeof template == "string") {
				template = new Template(template);
			}
			elem.setAttribute("title", template.evaluate(context));
		}

		elem.addClassName("__nicopedia_loaded");
	});
	pedia.execute();
};

(function (f) {
	if (typeof Nico != "undefined" && typeof Nico.onReady == "function") {
		Nico.onReady(f);
	} else {
		Event.observe(window, "load", f);
	}
})(function () {
	if (Nicopedia.inMaintenance) return;
	if (typeof window.disableNicopediaAutoDecoration == "undefined") {
		if (typeof Cookie != "undefined" && Cookie.get("nodic")) return;
		Nicopedia.decorateLinks();
	}
});
