/*
 * Copyright (c) 2012-2013 Kaarle Ritvanen
 * See LICENSE file for license details
 */

$(function() {
    $("#login").submit(function() {

	var statusBar = (function() {
	    function set(status, msg, mode) {
		$("#status").prop("class", status);
		$("#status p").text(msg);
		$("#logout").prop("class", mode ? "hidden" : null);
		$("#status div").prop("class", mode == "txn" ? null : "hidden");
		$("#commit").prop("disabled", status == "invalid");
	    }

	    function setError(msg, mode) { set("invalid", msg, mode); };

	    return {
		reset: function() { set(null, ""); },
		setError: setError,
		validationReady: function(txnValid) {
		    if (txnValid)
			set("changed", "You have uncommitted changes", "txn");
		    else setError("Some values need checking", "txn");
		}
	    }
	})();

	$.ajax("/login", {
	    type: "POST",
	    data: JSON.stringify({
		username: $("#username").val(), password: $("#password").val()
	    })
	}).done(function(data, status, xhr) {

	    function split(path) {
		var res = [];
		while (path && path != "/") {
		    var comp = path.match(/^\/([^\\\/]|\\.)+/)[0];
		    res.push(comp.substring(1));
		    path = path.substring(comp.length);
		}
		return res;
	    }

	    function escape(name) {
		if (!_.isString(name)) return String(name);
		name = name.replace(/([\\\/])/g, "\\$1");
		if (isNaN(Number(name))) return name;
		return "\\" + name;
	    }

	    function join() {
		var arg = _.toArray(arguments);
		if (arg.length == 1) return arg[0];

		var path = arg.shift();
		var name = arg.shift();

		arg.unshift((path == "/" ? "" : path) + "/" + escape(name));

		return join.apply(undefined, arg);
	    }

	    function isSubordinate(p1, p2, real) {
		p1 = split(p1);
		p2 = split(p2);
		if (real && p1.length <= p2.length) return false;
		for (var i = 0; i < p2.length; i++)
		    if (p1[i] != p2[i]) return false;
		return true;
	    }

	    function isList(meta) {
		return _.contains(["list", "set"], meta.type);
	    }

	    function isTreeNode(meta) {
		return isList(meta) ||
		    _.contains(["collection", "model"], meta.type);
	    }

	    
	    var txnMgr = (function(token, saveRequired) {
		var txn, changed, invalid;
		
		function reset() {
		    txn = null;
		    changed = {};
		    invalid = {};
		}
		reset();

		function isValid() { return !(_.size(invalid)); }

		function request(url, options) {
		    options = options || {};
		    options.headers = {"X-ACF-Auth-Token": token};
		    if (txn) options.headers["X-ACF-Transaction-ID"] = txn;
		    return $.ajax(url, options);
		}

		function abort() {
		    request("/transaction", {type: "DELETE"});
		    reset();
		}
		
		function objRequest(path, options) {
		    return request("/config" + path, options);
		}
		
		function query(path) {
		    var def = $.Deferred();

		    objRequest(path).done(function(data) {
			if (isTreeNode(data.meta) && !_.size(data.data))
			    data.data = isList(data.meta) ? [] : {};

			function index(name) {
			    return _.isArray(data.data) ? name - 1 : name;
			}

			data.get = function(name, valid) {
			    var p = join(path, name);
			    if (!valid && p in invalid) return invalid[p][0];

			    if (data.meta.type == "set") {
				return _.contains(
				    data.data, name
				) ? name : null;
			    }

			    return data.data[index(name)];
			};

			data.status = function(name) {
			    var p = join(path, name);
			    function scan(objs) {
				return _.size(_.filter(
				    _.keys(objs), function(obj) {
					return isSubordinate(obj, p);
				    }
				));
			    }

			    if (scan(invalid)) return "invalid";
			    if (scan(changed)) return "changed";
			    return null;
			}

			data.set = function(name, newValue) {
			    var def = $.Deferred();
			    function reject(xhr) { def.reject(xhr); }

			    var mpath = join(path, name);

			    var value = data.get(name, true);
			    if (value == undefined) value = null;

			    var tn = _.isObject(newValue);
			    var npv = tn ? mpath : newValue;

			    function resolve() {
				if (mpath in invalid &&
				    invalid[mpath][1] == def) {
				    
				    var del = invalid[mpath][0] == null;
				    delete invalid[mpath];
				    
				    if (del)
					_.each(
					    _.keys(invalid),
					    function(p) {
						if (isSubordinate(p, mpath, true))
						    delete invalid[p];
					    }
					);
				}
				
				def.resolve(isValid());
			    }

			    function validate() {
				var del = newValue == null;
				var set = data.meta.type == "set";

				var options;
				if (!del)
				    options = {
					type: set ? "POST" : "PUT",
					data: JSON.stringify(newValue)
				    };
				else if (data.get(name, true) != null)
				    options = {type: "DELETE"};

				if (!options) {
				    if (data.meta.type == "model" &&
					_.findWhere(
					    data.meta.fields, {name: name}
					).required)
					def.reject("Required value not set");
				    else resolve();
				    return;
				}
				
 				objRequest(
				    set && !del ? path : mpath, options
				).done(function() {
				    if (!(mpath in changed))
					changed[mpath] = value;
				    if (!tn && newValue == changed[mpath])
					delete changed[mpath];
				    
				    if (npv == null)
					_.each(
					    _.keys(changed),
					    function(p) {
						if (isSubordinate(p, mpath, true))
						    delete changed[p];
					    }
					);

				    if (!set) data.data[index(name)] = npv;
				    else if (del)
					data.data.splice(
					    data.data.indexOf(name), 1
					);
				    else data.data.push(name);

				    if (tn && !set)
					query(mpath).done(function(data) {

					    if (mpath in invalid &&
						data.meta.type == "model")
						_.each(
						    data.meta.fields,
						    function(field) {
							var mmpath = join(
							    mpath, field.name
							);
							if (field.required &&
							    !(mmpath in invalid) &&
							    data.get(
								field.name
							    ) == null)
							    invalid[mmpath] = [
								null
							    ];
						    });
					    resolve();
					}).fail(reject);
				    
				    else resolve();
				    
				}).fail(reject);
			    }

			    var prevTask;
			    if (mpath in invalid) prevTask = invalid[mpath][1];

			    invalid[mpath] = [npv, def];

			    if (prevTask) prevTask.always(validate);
			    else validate();

			    return def;
			};

			data.add = function(name) {
			    return data.set(name, name);
			};

			data.delete = function(name) {
			    var def = $.Deferred();
			    function resolve(txnValid) { def.resolve(txnValid); }
			    function reject() { def.reject(); }

			    var tasks = _.filter(
				_.pluck(_.values(invalid), 1),
				function(d) { return d.state() == "pending"; }
			    );

			    if (tasks.length)
				tasks[0].always(function() {
				    data.delete(name).done(resolve).fail(reject);
				});

			    else {
				var length = data.data.length;

				data.set(name, null).done(function(txnValid) {
				    if (isTreeNode(data.meta) &&
					data.meta.type != "set") {

					delete changed[join(path, name)];
					changed[path] = path;

					if (data.meta.type == "list")
					    for (var i = name; i < length; i++) {
						var opath = join(path, i + 1);
						var npath = join(path, i);
						
						_.each(
						    [changed, invalid],
						    function(map) {
							_.each(
							    _.keys(map),
							    function(p) {
								if (isSubordinate(
								    p, opath
								)) {
								    map[npath +
									p.substring(
									    opath.length
									)] = map[p];
								    delete map[p];
								}
							    });
						    }
						);
					    }
				    }
				    resolve(txnValid);

				}).fail(reject);
			    }

			    return def;
			};

			def.resolve(data);
		    }).fail(function() { def.reject(); });
		    
		    return def;
		}

		return {
		    start: function() {
			var def = $.Deferred();
			if (txn && isValid() && !(_.size(changed))) abort();

			if (txn)
			    def.resolve();

			else request("/transaction", {type: "POST"})
			    .done(function(data, status, xhr) {
				txn = xhr.getResponseHeader(
				    "X-ACF-Transaction-ID"
				);
				def.resolve();
			    })
			    .fail(function() { def.reject(); });

			return def;
		    },

		    commit: function() {
			var def = $.Deferred();
			function reject(xhr) { def.reject(xhr); }
			request(
			    "/transaction", {type: "PUT"}
			).done(function() {
			    reset();
			    if (saveRequired)
				request(
				    "/save", {type: "POST"}
				).done(function() {
				    def.resolve();
				}).fail(reject);
			    else def.resolve();
			}).fail(reject);
			return def;
		    },

		    abort: abort,

		    query: query,

		    logout: function() {
			return request("/login", {type: "DELETE"});
		    }
		};
	    })(
		xhr.getResponseHeader("X-ACF-Auth-Token"),
		xhr.getResponseHeader("X-ACF-Save-Required") == "1"
	    );
	    


	    function formatError(msg, xhr) {
		msg += " " + xhr.statusCode().status;
		if (xhr.responseText) msg += ': ' + xhr.responseText;
		return msg;
	    }


	    function href() {
		return $("<a>").attr({href: "javascript:void(0);"});
	    }

	    function makeRow(el) {
		if (el.is("td")) return $("<tr>").html(el);
		return el;
	    }


	    var Widget = {
		init: function(data, name, meta, level, editable, removable) {
		    this.level = level;

		    var value = data.get(name);
		    var status = data.status(name);

		    if (!editable) {
			var el = this.staticRender(value);
			this.setElStatus(el, status);
			return el;
		    }

		    this.makeEl();

		    var self = this;
		    this.requestData(value, meta).done(function(value, meta) {
			self.render(value, meta);
			self.setStatus(status);
		    });

		    var el = this.wrap();

		    if (removable) {
			var link = href().click(function() {
			    data.delete(name).done(function(txnValid) {
				statusBar.validationReady(txnValid)
				renderObject();
			    })
			}).text("Delete");
			el = makeRow(el);
			if (el.is("tr")) link = $("<td>").html(link);
			el.append(link);
		    }

		    return el;
		},

		setElStatus: function(el, status) {
		    el.prop("class", status);
		},

		makeEl: function() { this.el = this.createEl();	},

		requestData: function(value, meta) {
		    return $.Deferred().resolve(value, meta);
		},

		wrap: function() { return this.el; },

		setStatus: function(status) {
		    this.setElStatus(this.el, status);
		}
	    }


	    var Field = Object.create(Widget);

	    Field.init = function(
		data, name, meta, level, editable, removable
	    ) {
		var el = _.bind(Widget.init, this)(
		    data, name, meta, level, editable, removable
		);

		if (editable) {
		    var self = this;

		    function change() {
			self.msg.text("[checking]");
			statusBar.setError("Validating changes", "validate");
			
			data.set(name, self.get()).done(function(txnValid) {
			    self.msg.empty()
			    self.setStatus(data.status(name));
			    statusBar.validationReady(txnValid);
			    
			}).fail(function(xhr) {
			    if (_.isString(xhr)) self.msg.text(xhr);
			    
			    else if (xhr.statusCode().status == 422)
				self.msg.html(
				    _.reduce(
					_.map(
					    $.parseJSON(xhr.responseText),
					    _.escape
					),
					function(a, b) {
					    return a + "<br/>" + b;
					}
				    )
				);
			    
			    else self.msg.text(formatError("Error", xhr));
			    
			    self.setStatus("invalid");
			    statusBar.validationReady(false);
			});
		    }
		    
		    this.field.change(change);
		    if (data.status(name) == "invalid") change();
		}
		
		return el;
	    };

	    Field.staticRender = function(value) {
		return $("<div>").text(value);
	    };

	    Field.makeEl = function() {
		_.bind(Widget.makeEl, this)();
		if (!this.field) this.field = this.el;
	    };

	    Field.createEl = function() { return $("<input>"); };

	    Field.render = function(value, meta) {
		this.field.attr({type: "text", value: value});
	    };

	    Field.wrap = function() {
		var td = $("<td>");
		this.msg = $("<div>");
		td.append(this.msg);
		td.append(this.el);
		return td;
	    };

	    Field.get = function() { return this.field.val() || null; };


	    var ComboBox = Object.create(Field);

	    ComboBox.createEl = function() { return $("<select>"); };

	    ComboBox.render = function(value, meta) {
		var el = this.field;

		function opt(value, ui_value, selected) {
		    el.append($("<option>").attr(
			{value: value, selected: selected}
		    ).text(ui_value));
		}

		if (!meta.required)
		    opt("", "(none)", value == null)

		_.each(
		    _.zip(meta.choice, meta["ui-choice"]),
		    function(choice) {
			opt(choice[0], choice[1], value == choice[0]);
		    }
		);
	    }

	    var CheckBox = Object.create(Field);
	    CheckBox.staticRender = function(value) {
		return $("<div>").text(value ? "Yes" : "No");
	    };

	    CheckBox.setElStatus = function(el, status) {
		Field.setElStatus(el.parent(), status);
	    };

	    CheckBox.render = function(value, meta) {
		this.field.attr({type: "checkbox", checked: value});
	    };

	    CheckBox.get = function() { return this.field.is(":checked"); };


	    var Link = Object.create(Widget);

	    Link.staticRender = function(value, el) {
		el = el || href();
		if (value) {
		    el.click(function() {
			$.bbq.pushState("#" + value);
		    }).text("Show");
		}
		return el;
	    };

	    Link.createEl = function() { return href(); };

	    Link.render = function(value, meta) {
		this.staticRender(value, this.el)
	    };

	    Link.wrap = function() { return $("<td>").html(this.el); };

	    Link.get = function() { return {}; };


	    Inline = Object.create(Link);

	    Inline.init = function(
		data, name, meta, level, editable, removable
	    ) {
		return _.bind(Link.init, this)(
		    data,
		    name,
		    meta,
		    Math.min(6, level + 1),
		    editable,
		    removable
		);
	    };

	    Inline.setStatus = function(status) {};

	    Inline.requestData = function(value, meta) {
		var def = $.Deferred();
		txnMgr.query(value).done(function(data) {
		    def.resolve(data, meta);
		});
		return def;
	    };

	    Inline.render = function(data, meta) {
		this.data = data;
		var self = this;

		if (data.meta.type == "model")
		    _.each(data.meta.fields, function(field) {
			self.renderField(
			    field.name,
			    field,
			    field["ui-name"],
			    true,
			    false
			);
		    });
		
		else _.each(data.data, function(value, name) {
		    if (data.meta.type == "set") name = data.data[name];
		    else if (_.isArray(data.data)) name++;
		    self.renderCollectionMember(name, data.meta);
		});		
	    };

	    Inline.renderField = function(
		name, meta, label, editable, removable
	    ) {
		this.appendWidget(
		    Object.create(this.widget(meta)).init(
			this.data,
			name,
			meta,
			this.level,
			editable,
			removable
		    ),
		    label
		);
	    }
		
	    Inline.renderCollectionMember = function(name, meta) {
		var set = meta.type == "set";
		this.renderField(
		    name,
		    meta.members,
		    meta["ui-member"] + " " + name,
		    !set,
		    !set
		);
	    };

	    Inline.widget = function(meta) { return widgets[meta.widget]; };

	    Inline.wrap = function() { return this.el; };


	    var Horizontal = Object.create(Inline);

	    Horizontal.createEl = function() {
		this.previous = null;
		return $("<tr>").html($("<td>").prop(
		    "class", "placeholder"
		));
	    };

	    Horizontal.appendWidget = function(el, label) {
		if (!el.is("td")) return false;
		if (this.previous) this.previous.after(el);
		else {
		    var ph = this.el.find(".placeholder");
		    ph.after(el);
		    ph.remove();
		}
		this.previous = el;
		return true;
	    };


	    var HeaderHorizontal = Object.create(Horizontal);

	    HeaderHorizontal.init = function(
		data, name, meta, level, editable, removable
	    ) {
		this.header = $("<tr>");
		var table = $("<table>").html(this.header);
		table.append(
		    _.bind(Horizontal.init, this)(
			data, name, meta, level, editable, removable
		    )
		);
		return table;
	    };

	    HeaderHorizontal.appendWidget = function(el, label) {
		if (_.bind(Horizontal.appendWidget, this)(el, label))
		    this.header.append($("<th>").text(label));
	    };


	    var HeaderInline = Object.create(Inline);

	    HeaderInline.createEl = function() { return $("<div>"); };

	    HeaderInline.render = function(data, meta) {
		this.el.append(
		    $("<h" + this.level + ">").text(data.meta["ui-name"])
		);
	    };


	    var Vertical = Object.create(HeaderInline);

	    Vertical.wrap = function() { return $("<div>").html(this.el); };

	    Vertical.render = function(data, meta) {
		_.bind(HeaderInline.render, this)(data, meta);

		if (!isTreeNode(data.meta))
		    return this.el.append(JSON.stringify(data));
		
		this.div = $("<div>");
		this.el.append(this.div);

		_.bind(Inline.render, this)(data, meta);

		var self = this;
		
		if (_.contains(["collection", "list"], data.meta.type)) {
		    var keys = _.clone(_.keys(data.data));
		    
		    var button = $("<input>").attr(
			{type: "submit", value: "Insert"}
		    ).click(function() {
			
			var getter;
			
			function insert() {
			    var name = getter();
			    
			    if (_.contains(keys, name)) {
				button.prop("class", null);
				return;
			    }
			    keys.push(name);
			    
			    data.set(
				name, isTreeNode(data.meta.members) ? {} : null
			    ).done(function(txnValid) {
				self.renderCollectionMember(name, data.meta);
				button.prop("class", null);
				statusBar.validationReady(txnValid);
			    });
			}
			
			button.prop("class", "hidden");

			if (data.meta.type == "collection") {
			    var field = $("<input>").attr({type: "text"});
			    var row = $("<tr>").html($("<td>").html(field));
			    getter = function() {
				var res = field.val();
				row.remove();
				return res;
			    }
			    field.change(insert);
			    self.appendRow(row);
			}
			else {
			    getter = function() {
				return data.data.length + 1;
			    };
			    insert();
			}
		    });
		    this.el.append($("<p>").html(button));
		}
	    };

	    Vertical.appendWidget = function(el, label) {
		var self = this;
		el = makeRow(el);

		if (el.is("tr")) {
		    el.prepend($("<td>").text(label));
		    this.appendRow(el);
		}
		else if (el.is("table")) {
		    var td;
		    el.find("tr").each(function(index, row) {
			td = $("<td>");
			$(row).prepend(td);
			self.appendRow(row);
		    });
		    td.text(label);
		}
		else {
		    this.table = null;
		    this.div.append(el);
		}
	    };

	    Vertical.appendRow = function(row) {
		if (!this.table) {
		    this.table = $("<table>");
		    this.div.append(this.table);
		}
		this.table.append(row);
	    };


	    var CheckBoxes = Object.create(HeaderInline);

	    CheckBoxes.requestData = function(value, meta) {
		var def = $.Deferred();
		Inline.requestData(value, meta).done(function(data, meta) {
		    txnMgr.query(data.meta.members.scope).done(function(sdata) {
			meta.primitive = sdata.meta.type == "set";
			meta.choice = meta.primitive ?
			    _.values(sdata.data) : _.keys(sdata.data);
			def.resolve(data, meta);
		    });
		});
		return def;
	    };

	    CheckBoxes.render = function(data, meta) {
		_.bind(HeaderInline.render, this)(data, meta);

		var table = $("<table>");
		this.el.append(table);

		var self = this;

		_.each(meta.choice, function(choice) {
		    var ec = meta.primitive ? escape(choice) : choice;

		    var cbox = $("<input>").attr({
			type: "checkbox",
			checked: _.contains(data.data, ec)
		    });

		    var row = $("<tr>");
		    row.append($("<td>").html(cbox));

		    var item = $("<td>");
		    if (meta.primitive) item.text(choice);
		    else item.html(
			Link.staticRender(
			    join(data.meta.members.scope, choice)
			).text(choice)
		    );
		    row.append(item);

		    function setStatus() {
			self.setElStatus(row, data.status(ec));
		    }
		    setStatus();

		    cbox.change(function() {
			(
			    cbox.is(":checked") ?
				data.add(ec) :
				data.delete(ec)
			).done(function(txnValid) {
			    setStatus();
			    statusBar.validationReady(txnValid);
			});
		    });

		    table.append(row);
		});
	    };


	    var Reference = Object.create(ComboBox);

	    Reference.init = function(
		data, name, meta, level, editable, removable
	    ) {
		this.field = ComboBox.createEl();
		return _.bind(ComboBox.init, this)(
		    data, name, meta, level, editable, removable
		);
	    };

	    Reference.staticRender = function(value) {
		return Link.staticRender(value).text(value);
	    };

	    Reference.setElStatus = function(el, status) {
		ComboBox.setElStatus(el.find("select"), status);
	    };

	    Reference.createEl = function() { return $("<div>"); };

	    Reference.requestData = function(value, meta) {
		var def = $.Deferred();
		txnMgr.query(meta.scope).done(function(data) {
		    meta.choice = _.values(data.data);
		    meta["ui-choice"] = _.map(meta.choice, function(path) {
			return split(path).pop();
		    });
		    def.resolve(value, meta);
		});
		return def;
	    };

	    Reference.render = function(value, meta) {
		_.bind(ComboBox.render, this)(value, meta);

		this.el.html(this.field);
		this.el.append(" ");

		var link = $("<div>");
		var update = _.bind(function() {
		    link.html(Link.staticRender(this.get()));
		}, this);		
		this.el.append(link);

		this.field.change(update);
		update();
	    };


	    var widgets = {
		boolean: CheckBox,
		checkboxes: CheckBoxes,
		combobox: ComboBox,
		field: Field,
		inline: Vertical,
		link: Link,
		reference: Reference
	    };


	    var Tabular = Object.create(Vertical);

	    Tabular.render = function(data, meta) {
		this.header = true;
		_.bind(Vertical.render, this)(data, meta);
	    };

	    Tabular.widget = function(meta) {
		if (!isTreeNode(meta)) return Vertical.widget(meta);
		if (!this.header) return Horizontal;
		this.header = false;
		return HeaderHorizontal;
	    };


	    var Stacked = Object.create(Vertical);

	    Stacked.widget = function(meta) {
		return isTreeNode(meta) ? Vertical : Vertical.widget(meta);
	    };


	    var layouts = {stacked: Stacked, tabular: Tabular};


	    function renderObject(path, data) {
		var path = path || $.param.fragment();

		return (
		    data ? $.Deferred().resolve(data) : txnMgr.query(path)
		).done(function(data) {
		    var layout = data.meta.widget;
		    var form = Object.create(layout ? layouts[layout] : Vertical);

		    form.createEl = function() {
			return $("#content").empty();
		    };
		    form.wrap = function() { return this.el; };
		    form.requestData = function(value, meta) {
			return $.Deferred().resolve(data, meta);
		    };

		    var name = split(path).pop();

		    form.init(
			{
			    get: function(name) { return path; },
			    status: function(name) { return null; }
			},
			name,
			null,
			0,
			true,
			false
		    );
		});
	    };


	    function redirect(path) { $.bbq.pushState("#" + path); }

	    function render() {
		var path = $.param.fragment();

		function renderMenu(target, path, current, selectFirst) {
		    var def = $.Deferred();

		    txnMgr.query(path).done(function(data) {
			if (data.meta.type != "model" || _.filter(
			    data.meta.fields, function(field) {
				return !isTreeNode(field);
			    }
			).length) {
			    def.reject(data);
			    return;
			}

			var first;

			_.each(data.meta.fields, function(field) {
			    if (!data.data[field.name]) return;
			    if (!first) {
				first = field.name;
				if (!current && selectFirst) current = first;
			    }

			    var el = $("<li>");
			    var link = Link.staticRender(data.get(field.name));
			    link.text(field["ui-name"]);
			    el.prop("class", data.status(field.name));
			    if (field.name == current)
				el.addClass("current");
			    el.append(link);
			    target.append(el);
			});

			def.resolve(first);
		    });

		    return def;
		}


		txnMgr.start().done(function() {
		    var comps = split(path);
		    renderMenu($("#modules").empty(), "/", comps[0], false);
		    var tabs = $("#tabs").empty();

		    if (path == "/") return;
		    var topLevel = comps.length == 1;

		    renderMenu(tabs, "/" + comps[0], comps[1], true)
			.done(function(first) {
			    renderObject(
				topLevel ? join(path, first) : path
			    ).fail(function() {
				comps.pop();
				comps.unshift("/");
				redirect(join.apply(undefined, comps));
			    });
			})
			.fail(function(data) {
			    renderObject(path, topLevel ? data : null);
			});
		});
	    }

	    
	    function clearState() {
		statusBar.reset();
		render();
	    }
	    
	    $("#commit").click(function() {
		txnMgr.commit().done(clearState).fail(function(xhr) {
		    statusBar.setError(
			formatError("Commit failed", xhr), "txn"
		    );
		})
	    });
	    $("#revert").click(function() {
		txnMgr.abort();
		clearState();
	    });
	    
	    $("#logout").click(function() {
		txnMgr.logout().done(function() {
		    $("body").html($("<p>").text("Logged out"));
		});
	    });
	    
	    statusBar.reset();
	    $("#content").empty();

	    $(window).bind("hashchange", render);
	    redirect("/");

	}).fail(function() {
	    statusBar.setError("Login failed", "login");
	});

	return false;
    });

    $("#username").focus();
});
