
spackle; // (spackle.js is required.)
transitions; // (transitions.js is required.)

function Nav(element) {
	this.element = element;

	this.items = [];

	this.expand_duration = Nav.DEFAULT_EXPAND_DURATION;

	this.expanded_item = null;

	this.load = null;
}

// (Overridable by setting nav.item_expand_duration)
Nav.DEFAULT_EXPAND_DURATION = 200;

// (See constructor for Nav.Item later below.)
Nav.prototype.add_item = function(label, href
                                  /* optional child items */) {
	var item = new Nav.Item(label, href);

	for (var i = 2; i < arguments.length; ++i)
		item.add(arguments[i]);

	this.add(item);
}
Nav.prototype.add = function(item) {
	item.parent_nav = this;
	item.parent_item = null;

	this.items.push(item);

	if (this.items.length == 1)
		this.items[0].show_child_items();

	this.element.appendChild(item.element);
	if (item.child_items_element)
		this.element.appendChild(item.child_items_element);
}

Nav.prototype._notify_expanding = function(item) {
	if (this.expanded_item == item)
		return;

	if (this.expanded_item)
		this.expanded_item.collapse();

	this.expanded_item = item;
}

// A Nav.Item's associated element (<div>) has CSS class "nav-item". 
// If a child item, it is placed beneath a <div> that has CSS class
// "nav-child-items", and that is placed at the same depth as (not beneath)
// the parent item, e.g.:
//    <div class="nav-item">Parent</div>
//    <div class="nav-child-items">
//       <div class="nav-item">Child</div>
//    </div>
// This is so that they may be styled completely separately (without needing
// hacks involving e.g. :first-child or :first-line).
Nav.Item = function(label, href
                    /* optional child items */) {
	this.parent_nav = null;
	this.parent_item = null;

	this.label = label;

	this.href = href;

	this.element = document.createElement("div");
	this.element.className = "nav-item";

	this.element.appendChild(document.createTextNode(label));

	var that = this;
	this.element.onclick = function() {
		that._clicked();
	};

	this.child_items = [];

	// Only created once there are child items.
	this.child_items_element = null;

	for (var i = 2; i < arguments.length; ++i) {
		var child_item = arguments[i];

		this.add(child_item);
	}

	this.expanded_child_item = null;

	this.expand_interval = 0;
	this.collapse_interval = 0;
}

Nav.Item.prototype.add_item = function(label, href
                                       /* optional child items */) {
	var child_item = new Nav.Item(label, href);

	for (var i = 2; i < arguments.length; ++i)
		child_item.add(arguments[i]);

	this.add(child_item);
}
Nav.Item.prototype.add = function(child_item) {
	child_item.parent_nav = null;
	child_item.parent_item = this;

	this.child_items.push(child_item);

	if (!this.child_items_element) {
		this.child_items_element = spackle(document.createElement("div"));
		this.child_items_element.className = "nav-child-items";

		var normal_display_style = window.getComputedStyle(
			this.child_items_element).display;
		if (normal_display_style == "none")
			normal_display_style = "";
		this.child_items_element_normal_display_style = normal_display_style;
		// Not displayed initially.
		this.child_items_element.style.display = "none";

		this.child_items_element_normal_size = null;
			// (Not known until element is added to DOM.)

		if (this.element.parentNode)
			this.element.parentNode.appendChild(this.child_items_element);
	}
	this.child_items_element.appendChild(child_item.element);
	if (child_item.child_items_element)
		this.child_items_element.appendChild(child_item.child_items_element);
}

// (Child items aren't displayed initially.)
Nav.Item.prototype.show_child_items = function() {
	if (this.child_items_element) {
		this.child_items_element.style.display =
			this.child_items_element_normal_display_style;
	}
}
Nav.Item.prototype.hide_child_items = function() {
	if (this.child_items_element)
		this.child_items_element.style.display = "none";
}

Nav.Item.prototype.nav = function() {
	return this.toplevel_item().parent_nav;
}
Nav.Item.prototype.toplevel_item = function() {
	var result = this;
	while (result.parent_item)
		result = result.parent_item;
	return result;
}

Nav.Item.prototype._clicked = function() {
	this.expand();

	this.nav().load(this.href);
}

Nav.Item.prototype._notify_expanding = function(child_item) {
	if (this.expanded_child_item == child_item)
		return;

	if (this.expanded_child_item)
		this.expanded_child_item.collapse();

	this.expanded_child_item = child_item;
}

Nav.Item.prototype.expand = function() {
	if (!this.child_items_element) {
		// Notify immediate parent so it can e.g. collapse other items at same depth.
		if (this.parent_nav)
			this.parent_nav._notify_expanding(this);
		else if (this.parent_item)
			this.parent_item._notify_expanding(this);
		// (Otherwise the above is done later in this function.)

		return;
	}

	// Already expanding or expanded.
	if (this.expand_interval)
		return;
	if ((this.parent_nav &&
	     this.parent_nav.expanded_item == this) ||
	    (this.parent_item &&
	     this.parent_item.expanded_item == this))
		return;

	// Stop collapsing.
	if (this.collapse_interval) {
		clearInterval(this.collapse_interval);
		this.collapse_interval = 0;
	}

	var expand_duration = this.nav().expand_duration;

	this.child_items_element.style.overflow = "hidden";
	this.show_child_items();
	if (!this.child_items_element_normal_size)
		this.child_items_element_normal_size = this.child_items_element.size();
	this.child_items_element.set_height(0);

	var height = 0, target_height = this.child_items_element_normal_size.height;
	var increment = target_height / (expand_duration / 20.);
	var that = this;
	this.expand_interval = setInterval(function() {
		height += increment;
		if (height > target_height)
			height = target_height;

		that.child_items_element.set_height(Math.round(height));

		if (height == target_height) {
			clearInterval(that.expand_interval);
			that.expand_interval = 0;
		}
	}, 20);

	// Notify immediate parent so it can e.g. collapse other items at same depth.
	if (this.parent_nav)
		this.parent_nav._notify_expanding(this);
	else if (this.parent_item)
		this.parent_item._notify_expanding(this);
}
Nav.Item.prototype.collapse = function() {
	if (!this.child_items_element)
		return;

	// Already collapsing.
	if (this.collapse_interval)
		return;

	// Stop expanding.
	if (this.expand_interval) {
		clearInterval(this.expand_interval);
		this.expand_interval = 0;
	}

	var expand_duration = this.nav().expand_duration;

	var height = this.child_items_element_normal_size.height;
	var decrement = height / (expand_duration / 20.);
	var that = this;
	this.collapse_interval = setInterval(function() {
		height -= decrement;
		if (height < 0)
			height = 0;

		that.child_items_element.set_height(Math.round(height));

		if (height == 0) {
			clearInterval(that.collapse_interval);
			that.collapse_interval = 0;
		}
	}, 20);
}

