/**
 * Nicovideo API Client Library
 * @require jQuery
 * @copyright 2009 DWANGO Co., LTD. All rights reserved.
 */

(function () {

// concatenate two functions.
function chain(head, tail) {
	return head ? function () {
			head && head.apply(this, arguments);
			tail && tail.apply(this, arguments);
	} : tail;
}

// convert complicated hash to url query form.
function query(obj) {
	if (typeof obj == "object") {
		var params = [];
		for (var key in obj) {
			key = encodeURIComponent(key);
			query.encode(params, key, obj[key]);
		}
		return params.join("&");
	} else if (typeof obj == "string") {
		return obj;
	} else {
		throw new TypeError("non-Object passed to query()");
	}
}
query.encode = function (params, key, obj) {
	if (jQuery.isArray(obj)) {
		jQuery.each(obj, function (i, value) {
			query.encode(params, key + "[]", value);
		});
	} else if (typeof obj == "object") {
		for (var k in obj) {
			if (obj[k] !== undefined) {
				k = encodeURIComponent(k);
				query.encode(params, key + "[" + k + "]", obj[k]);
			}
		}
	} else if (obj === true) {
		params.push(key + "=1");
	} else if (obj === false) {
		params.push(key + "=0");
	} else if (obj === null) {
		params.push(key + "=");
	} else if (obj !== undefined) {
		params.push(key + "=" + encodeURIComponent(obj));
	}
}

// Prepare events
jQuery.each(
	[ "nicoApiStart", "nicoApiSuccess", "nicoApiError", "nicoApiFail",
	  "nicoApiComplete", "nicoApiStop" ],
	function (i, evt) {
		jQuery.fn[evt] = function (f) {
			return this.bind(evt, f);
		};
	});

var NicoAPI = window.NicoAPI = {
	token: null,
	active: 0,
	defaultOptions: {
		global: true
	},

	Status: {
		SUCCESS: "ok",
		FAILURE: "fail"
	},

	Error: {
		INVALID_COMMAND: "COMMANDERROR",
		INVALID_PARAMETER: "PARAMERROR",
		INVALID_TOKEN: "INVALIDTOKEN",
		EXPIRED_TOKEN: "EXPIRETOKEN",
		INVALID_SESSION: "NOAUTH",
		MAXIMUM: "MAXERROR",
		DUPLICATE: "EXIST",
		NOTEXIST: "NONEXIST",
		SECRET_USER: "SECRETUSER",
		INVALID_USER: "INVALIDUSER",
		INVALID_VIDEO: "INVALIDVIDEO",
		MAINTENANCE: "MAINTENANCE",
		INTERNAL: "INTERNAL"
	},

	/**
	 * Usage:
	 *  NicoAPI.call("/api/foo/bar", { "paramA": 1, "paramB": 2 })
	 *  	.success(function (data) {
	 *  		// API call succeed
	 *  	})
	 *  	.when(NicoAPI.Error.MAXIMUM, function () {
	 *  		alert("Maximum Error!");
	 *  	});
	 */
	call: function (path, params, options) {
		params = params || {};
		options = jQuery.extend(this.defaultOptions, options || {});

		if (this.token)
			params.token = this.token;

		if (options.global && !NicoAPI.active++)
			jQuery.event.trigger("nicoApiStart");

		var xhr, responder = new NicoAPI.Responder(options);
		xhr = responder.xhr = jQuery.ajax({
			type: "POST",
			url: path,
			data: query(params),
			dataType: "json",
			success: function (data, status) {
				if (data.status == NicoAPI.Status.SUCCESS) {
					if (responder.onSuccess)
						responder.onSuccess(data, status);
					if (options.global)
						jQuery.event.trigger("nicoApiSuccess", [xhr, data, status]);
				} else if (data.status == NicoAPI.Status.FAILURE && data.error) {
					if (responder.onFail)
						responder.onFail(data, data.error.code);
					if (options.global)
						jQuery.event.trigger("nicoApiFail", [xhr, data, data.error.code]);
				} else {
					if (responder.onError)
						responder.onError(xhr, "unknown", data);
					if (options.global)
						jQuery.event.trigger("nicoApiError", [xhr, "unknown", data]);
				}
			},
			error: function (_, status, e) {
				if (responder.onError)
					responder.onError(xhr, status, e);
				if (options.global)
					jQuery.event.trigger("nicoApiError", [xhr, status, e]);
			},
			complete: function (_, status) {
				if (responder.onComplete)
					responder.onComplete(xhr, status);
				if (options.global)
					jQuery.event.trigger("nicoApiComplete", [xhr, status]);
				if (options.global && !--NicoAPI.active)
					jQuery.event.trigger("nicoApiStop");
			}
		});

		return responder;
	},

	options: function (opts) {
		if (opts !== undefined)
			this.defaultOptions = opts || {};
		return this.defaultOptions;
	}
};

NicoAPI.Responder = function (options) {
	options = options || {};
	this.onSuccess = options.success;
	this.onError = options.error;
	this.onFail = options.fail;
	this.onComplete = options.complete;
}
NicoAPI.Responder.prototype = {
	success: function (cb) {
		this.onSuccess = chain(this.onSuccess, cb);
		return this;
	},
	error: function (cb) {
		this.onError = chain(this.onError, cb);
		return this;
	},
	fail: function (cb) {
		this.onFail = chain(this.onFail, cb);
		return this;
	},
	when: function (catchStatus, cb) {
		if (!catchStatus) throw "invalid catchStatus";
		catchStatus = catchStatus.toLowerCase();
		this.onFail = chain(this.onFail, function (data, status) {
			if (catchStatus == status.toLowerCase())
				cb(data, status);
		});
		return this;
	},
	complete: function (cb) {
		this.onComplete = chain(this.onComplete, cb);
		return this;
	}
};



function make_id_list(item_type, item_id) {
	var id_list = {};
	id_list[item_type] = jQuery.makeArray(item_id);
	return id_list;
}

NicoAPI.Deflist = {

	list: function () {
		return NicoAPI.call(
			"/api/deflist/list");
	},

	add: function (item_type, item_id, description) {
		return NicoAPI.call(
			"/api/deflist/add",
			{ "item_type": item_type,
			  "item_id": item_id,
			  "description": description });
	},

	update: function (item_type, item_id, description) {
		return NicoAPI.call(
			"/api/deflist/update",
			{ "item_type": item_type,
			  "item_id": item_id,
			  "description": description });
	},

	remove: function (item_type, item_id) {
		return this.removeMulti(make_id_list(item_type, item_id));
	},

	removeMulti: function (id_list) {
		return NicoAPI.call(
			"/api/deflist/delete",
			{ "id_list": id_list });
	},

	move: function (target_group_id, item_type, item_id) {
		return this.moveMulti(target_group_id, make_id_list(item_type, item_id));
	},

	moveMulti: function (target_group_id, id_list) {
		return NicoAPI.call(
			"/api/deflist/move",
			{ "target_group_id": target_group_id,
			  "id_list": id_list });
	},

	copy: function (target_group_id, item_type, item_id) {
		return this.copyMulti(target_group_id, make_id_list(item_type, item_id));
	},

	copyMulti: function (target_group_id, id_list) {
		return NicoAPI.call(
			"/api/deflist/copy",
			{ "target_group_id": target_group_id,
			  "id_list": id_list });
	}

};


NicoAPI.MylistGroup = {

	list: function () {
		return NicoAPI.call(
			"/api/mylistgroup/list");
	},

	get: function (group_id) {
		return NicoAPI.call(
			"/api/mylistgroup/get",
			{ "group_id": group_id });
	},

	add: function (name, description, is_public, default_sort, icon_id) {
		return NicoAPI.call(
			"/api/mylistgroup/add",
			{ "name": name,
			  "description": description,
			  "public": is_public,
			  "default_sort": default_sort,
			  "icon_id": icon_id });
	},

	update: function (group_id, name, description, is_public, default_sort, icon_id) {
		return NicoAPI.call(
			"/api/mylistgroup/update",
			{ "group_id": group_id,
			  "name": name,
			  "description": description,
			  "public": is_public,
			  "default_sort": default_sort,
			  "icon_id": icon_id });
	},

	remove: function (group_id) {
		return NicoAPI.call(
			"/api/mylistgroup/delete",
			{ "group_id": group_id });
	},

	sort: function (group_id_list) {
		return NicoAPI.call(
			"/api/mylistgroup/sort",
			{ "group_id_list": group_id_list });
	}

};


NicoAPI.Mylist = {

	list: function (group_id) {
		return NicoAPI.call(
			"/api/mylist/list",
			{ "group_id": group_id });
	},

	add: function (group_id, item_type, item_id, description) {
		return NicoAPI.call(
			"/api/mylist/add",
			{ "group_id": group_id,
			  "item_type": item_type,
			  "item_id": item_id,
			  "description": description });
	},

	update: function (group_id, item_type, item_id, description) {
		return NicoAPI.call(
			"/api/mylist/update",
			{ "group_id": group_id,
			  "item_type": item_type,
			  "item_id": item_id,
			  "description": description });
	},

	remove: function (group_id, item_type, item_id) {
		return this.removeMulti(group_id, make_id_list(item_type, item_id));
	},

	removeMulti: function (group_id, id_list) {
		return NicoAPI.call(
			"/api/mylist/delete",
			{ "group_id": group_id,
			  "id_list": id_list });
	},

	move: function (group_id, target_group_id, item_type, item_id) {
		return this.moveMulti(group_id, target_group_id,
					make_id_list(item_type, item_id));
	},

	moveMulti: function (group_id, target_group_id, id_list) {
		return NicoAPI.call(
			"/api/mylist/move",
			{ "group_id": group_id,
			  "target_group_id": target_group_id,
			  "id_list": id_list });
	},

	copy: function (group_id, target_group_id, item_type, item_id) {
		return this.copyMulti(group_id, target_group_id,
					make_id_list(item_type, item_id));
	},

	copyMulti: function (group_id, target_group_id, id_list) {
		return NicoAPI.call(
			"/api/mylist/copy",
			{ "group_id": group_id,
			  "target_group_id": target_group_id,
			  "id_list": id_list });
	}

};


NicoAPI.Watchitem = {

	list: function () {
		return NicoAPI.call(
			"/api/watchitem/list");
	},

	exist: function (item_type, item_id) {
		return NicoAPI.call(
			"/api/watchitem/exist",
			{ "item_type": item_type,
			  "item_id": item_id });
	},

	add: function (item_type, item_id) {
		return NicoAPI.call(
			"/api/watchitem/add",
			{ "item_type": item_type,
			  "item_id": item_id });
	},

	remove: function (item_type, item_id) {
		return this.removeMulti(make_id_list(item_type, item_id));
	},

	removeMulti: function (id_list) {
		return NicoAPI.call(
			"/api/watchitem/delete",
			{ "id_list": id_list });
	}

};

NicoAPI.Mymemory = {

	list: function () {
		return NicoAPI.call(
			"/api/mymemory/list");
	},

	remove: function (item_type, item_id) {
		return this.removeMulti(make_id_list(item_type, item_id));
	},

	removeMulti: function (id_list) {
		return NicoAPI.call(
			"/api/mymemory/delete",
			{ "id_list": id_list });
	}

};

NicoAPI.Friendlist = {

	list: function (options) {
		options = options || {};
		return NicoAPI.call(
			"/api/friendlist/list",
			{ "sort": options.sort,
			  "order": options.order,
			  "offset": options.offset,
			  "count": options.count })
	},

	remove: function (target_user_id) {
		return NicoAPI.call(
			"/api/friendlist/delete",
			{ "target_user_id": target_user_id });
	}

};

NicoAPI.Mylistcomment = {

	remove: function (item_type, item_id, comment_user_id) {
		return NicoAPI.call(
			"/api/mylistcomment/delete",
			{ "item_type": item_type,
			  "item_id": item_id,
			  "comment_user_id": comment_user_id });
	}

};

})();
