var POSITION_COL = 0;
var OPTIONS_COL  = 1;
var TITLE_COL    = 2;
var AUTHOR_COL   = 3;

var user_lists = new Lists();
var user_suggest = []; // array of User objects

var options_id = null;

var edit_pos = 0;
var edit_id  = 0;

var curr_list = null;

var menu = [];  // Options menu
var smenu = []; // Lists menu when adding from search results
var timeout;
var status_timeout = null;

// Automatically get a list once the page loads
$(document).ready(init);

// Add event handlers for 'hashchange' event
if (window.addEventListener)
	window.addEventListener('hashchange', change_list_hash, false);
else if (window.attachEvent)
	window.attachEvent('onhashchange', change_list_hash);

function init()
{
	if (location.hash) {
		var list_id = location.hash.substring(1);
		get_lists(list_id);
	} else if (curr_list_id)
		get_lists(curr_list_id, "onhashchange" in window ? 1 : 0);

	$('form.searchadd').live('submit', function() {
		// Make Ajax request to add book
		var $this = $(this);
		var record_id = $this.find('input[name=record]').val();
		var list_id   = $this.find('input[name=list]').val();
		$this.find('input[name=list]').val('0');

		var ajax = new pgengler.Ajax(base_url, search_book_added, { param: record_id });

		ajax.post({
			act: 'ajax_addid',
			record: record_id,
			list: list_id
		});

		return false;
	});

	$('.add')
		.button()
		.next()
			.button({
				text: false,
				icons: {
					primary: 'ui-icon-triangle-1-s'
				}
			})
			.click(function(e) { search_lists(e, $(this)); return false; })
			.parent()
				.buttonset();
}

///////
// KEY HANDLER
///////
function key_press(event)
{
	event = event ? event : ((window.event) ? event : null);

	if (event) {
		key = (event.charCode) ? event.charCode : ((event.which) ? event.which : event.keyCode);
		if (key == 27) {
			hide_options();
			clear_move_box();
			hide_detail();
		}
	}	
}

function clear_move_box()
{
	if (!edit_pos)
		return;

	var record_id = edit_id;

	var cell = {
		element: 'td', cssclass: 'position', text: edit_pos, onclick: function() { move_to_box(record_id); }
	};

	var row = document.getElementById('row' + edit_id);
	var current_position_cell = row.getElementsByTagName('td')[0];

	row.replaceChild(create_element(cell), current_position_cell);

	edit_pos = edit_id = 0;
}

function move_to_box(record_id)
{
	clear_move_box();

	// Get table row
	var row = document.getElementById('row' + record_id);

	// Get position column
	var current_position_cell = row.getElementsByTagName('td')[0];

	edit_id  = record_id;
	edit_pos = curr_list.get_book(record_id).position;

	// Create new HTML
	var cell = {
		element: 'td', cssclass: 'position',
		children: [
			{
				element: 'form', style: 'margin: 0; padding: 0', onsubmit: function() { return move_to(record_id); },
				children: [
					{ element: 'input', type: 'text', style: 'width: 2em; margin: 0; padding: 0', name: 'position', id: 'position', value: edit_pos },
					{ element: 'input', type: 'submit', style: 'display: none' }
				]
			}
		]
	};

	row.replaceChild(create_element(cell), current_position_cell);

	var pos_box = document.getElementById('position');
	pos_box.focus();
	pos_box.select();
}

function move_to(record_id)
{
	// Get new position
	var box      = document.getElementById('position');
	var position = parseInt(box.value);

	// Remove focus from box (Todo #1405)
	box.blur();

	// Get row
	var row = document.getElementById('row' + record_id);

	// Get position cell
	var current_position_cell = row.getElementsByTagName('td')[0];

	// Create spinner
	var cell = {
		element: 'td',
		children: [
			{ element: 'img', src: image_url + 'processing.gif' }
		]
	};

	// Display spinner
	row.replaceChild(create_element(cell), current_position_cell);

	curr_list.move_book(record_id, position, 0);

	return false;
}

function get_lists(list_id, suppress_show)
{
	if (!list_id)
		list_id = curr_list_id;

	// Initialize Ajax object
	var ajax = new pgengler.Ajax(base_url, get_lists_aux);

	show_loading();

	// Make Ajax request
	ajax.get({
		act: 'ajax_getlists',
		list: list_id,
		suppress: suppress_show
	});

	return false;
}

function get_lists_aux(response)
{
	// Check for errors
	if (has_error(response))
		return;

	var root     = response.getElementsByTagName('lists')[0];
	curr_list_id = root.getAttribute('load');
	var suppress = parseInt(root.getAttribute('suppress'));

	if (user_lists)
		delete user_lists;
	user_lists = new Lists();

	// Get lists
	var lists = root.getElementsByTagName('list');
	var users = root.getElementsByTagName('contacts')[0].getElementsByTagName('contact');

	delete user_suggest;
	user_suggest = [];

	// Go through lists
	load_lists(lists);

	var list = curr_list = user_lists.get_list(curr_list_id);

	// Go through contacts
	for (var i = 0; i < users.length; i++) {
		user_suggest.push(User.from_xml(users[i]));
	}

	if (list.readonly()) {
		// Disable list editing for virtual lists
		disable_list_editing();
	} else {
		enable_list_editing();
	}

	// Fill in the lists dropdown
	populate_lists_dropdown(curr_list_id);

	// Display list
	show_list(curr_list_id, suppress);
}

function show_list(list_id, suppress)
{
	// Get list
	var list = user_lists.get_list(list_id);

	if (!list)
		return;

	// Update page URL
	window.location.href = base_url + '#' + list.id();

	// Include list name in page title
	document.title = 'Reading List - ' + list.name();

	if (suppress)
		return;

	// Get table
	var table = document.getElementById('list');
	var old_tbody = table.getElementsByTagName('tbody')[0];

	// Create non-visible tbody that we'll add the rows to
	var tbody = {
		element: 'tbody', children: [ ]
	};

	// Update table caption with new list name
	document.getElementById('listcaption').innerHTML = list.name();

	// Get books on list
	var books = list.get_books();

	var len = books ? books.length : 0;

	for (var i = 0; i < len; i++) {
		var book = books[i];

		if (!book)
			continue;

		// Display row
		var row = {
			element: 'tr', id: 'row' + book.record_id, cssclass:( book.read ? 'read' : 'unread') + (book.owned ? ' owned' : ''),
			children: [
				{ element: 'td', text: list.use_positions() ? book.position : '', cssclass: 'position', onclick: list.use_positions() ? function(record_id) { return function() { move_to_box(record_id); } }(book.record_id) : null },
				get_options_column(book, curr_list),
				{
					element: 'td',
					children: [
						{ element: 'a', href: base_url + 'detail/' + book.record_id, text: book.getTitle(), onclick: function(record_id) { return function() { show_detail(record_id); return false; } }(book.record_id) },
						book.note ? { element: 'img', src: image_url + 'note.png', alt: 'Note', title: book.note, cssclass: 'note', onclick: function(record_id) { return function() { show_note(record_id); } }(book.record_id)} : null
					]
				},
				{ element: 'td', text: book.getAuthorString() }
			]
		};

		tbody.children.push(row);
	}

	// Figure out what to show
	if (!books || books.length == 0) {
		// Show 'List is empty' message
		show_empty_row();
	} else {
		// Show list
		table.replaceChild(create_element(tbody), old_tbody);
	}

	if (list.use_positions() == 1)
		dnd_init();

	// Change list ID
	var id_elem = document.getElementById('listid');
	id_elem.value = list.id();

	curr_list_id = list.id();
	curr_list = list;

	// Scroll to top of page (Todo #2300)
	window.scrollTo(0, 0);
}

function menu_clicked(e, menuItem, record_id)
{
	if (typeof(menuItem.data) != 'object' || menuItem.data.length != 2)
		return;

	var type = menuItem.data[0];
	var thing_id = menuItem.data[1];

	if (type == 'list') {
		if (thing_id == -1) {
			add_list_with_record(record_id);
		} else {
			copy_book_to_list(record_id, thing_id);
		}
		hide_options();
	} else if (type == 'suggest') {
		suggest(record_id, thing_id);
		hide_options();
	} else if (type == 'note') {
		show_note(record_id);
		hide_options();
	}
}

function show_menu(event, record_id)
{
	// Remove existing menu
	delete menu;

	var book = curr_list.get_book(record_id);

	var options = { arrowSrc: image_url + 'arrow_right.gif', onClick: function(e, menuItem) { menu_clicked(e, menuItem, record_id); } };
	menu = new $.Menu('#options' + record_id, null, options);

	var add_note_item = new $.MenuItem({ src: book.note ? 'Edit note' : 'Add note', data: [ 'note', '' ] }, options);
	menu.addItem(add_note_item);

	var copy_to_menu = new $.MenuItem({ src: 'Copy to' }, options);
	var copy_to = [];

	var lists = user_lists.get_editable_lists();
	for (var i = 0; i < lists.length; i++) {
		// Skip the current list (Todo #1759)
		if (lists[i].id() == curr_list.id())
			continue;

		// Create new menu item
		var menu_item = new $.MenuItem({ src: lists[i].name(), data: [ 'list', lists[i].id() ] }, options);

		// If it's already on the target list, disable the menu item (Todo #1497)
		if (lists[i].get_book(book.record_id))
			menu_item.disable();
		copy_to.push(menu_item);
	}
	if (lists.length > 1)
		copy_to.push(new $.MenuItem({ }, options));
	copy_to.push(new $.MenuItem({ src: 'Create list...', data: [ 'list', -1 ] }, options));

	new $.Menu(copy_to_menu, copy_to, options);

	menu.addItem(copy_to_menu);

	var suggest_to_menu = new $.MenuItem({ src: 'Suggest to' }, options);
	var suggest_to = [];
	for (var i = 0; i < user_suggest.length; i++)
		suggest_to.push(new $.MenuItem({ src: user_suggest[i].name(), data: [ 'suggest', user_suggest[i].id() ] }, options));
	new $.Menu(suggest_to_menu, suggest_to, options);

	if (suggest_to.length == 0)
		suggest_to_menu.disable();

	menu.addItem(suggest_to_menu);

	var e = event ? event : window.event;

	menu.onClick(e);
}

function clear_status()
{
	var status = document.getElementById('addstatus');
	status.innerHTML = '';
}

function get_element_position(element)
{
	var curr_left = 0;
	var curr_top  = 0;

	if (element.offsetParent) {
		curr_left = element.offsetLeft;
		curr_top  = element.offsetTop;
		while (element = element.offsetParent) {
			curr_left += element.offsetLeft;
			curr_top  += element.offsetTop;
		}
	}
	return [curr_left, curr_top];
}

///////
// SHOW ADDITIONAL OPTIONS
///////

function hide_options(event)
{
	$.Menu.closeAll();

	return false;
}

///////
// DELETE FROM LIST
///////
function del(record_id, conf)
{
	// Get the row this item's in
	var row = document.getElementById('row' + record_id);

	// Get record info
	var record = curr_list.get_book(record_id);

	if (confirm("Are you sure you want to remove '" + record.title + "' from this list?")) {
		// Do the deletion
		var ajax = new pgengler.Ajax(base_url, del_aux);

		// Make the Ajax request
		ajax.post({
			act: 'ajax_delete',
			record: record_id,
			list: curr_list_id
		});
	}

	return false;
}

function del_aux(response)
{
	// Check for errors
	if (has_error(response))
		return;

	var root = response.getElementsByTagName('delete')[0];

	var record_id = parseInt(root.getAttribute('record'));

	// Get row
	var row = document.getElementById('row' + record_id);

	// Remove row
	$(row).fadeOut(400, function() {
		$(this).remove();

		curr_list.remove_book(record_id);

		if (curr_list.length() == 0)
			show_empty_row();
		else
			redisplay_positions();
	});
}

///////
// COPY TO OTHER LIST
///////

function copy_book_to_list(record_id, list_id, callback)
{
	// Initialize Ajax
	var ajax = new pgengler.Ajax(base_url, copy_book_to_list_aux, { param: callback });

	// Make Ajax request
	ajax.post({
		act: 'ajax_copy',
		record: record_id,
		list: list_id
	});
}

function copy_book_to_list_aux(response, callback)
{
	if (has_error(response))
		return;

	var root      = response.getElementsByTagName('copy')[0];
	var record_id = parseInt(root.getAttribute('record'));
	var list_id   = parseInt(root.getAttribute('list'));

	var status = -1;
	if (root.getElementsByTagName('double').length > 0) {
		// If no callback function is provided, display the error.
		if (!callback)
			status_error('Book is already on that list');
		// Set error code
		status = -2;
	} else if (root.getElementsByTagName('success').length > 0) {
		// Add to appropriate List object
		var list = user_lists.get_list(list_id);
		if (list)
			list.add_book(Book.from_xml(root.getElementsByTagName('record')[0]));
		status_success('Book copied');
		status = 0;
	}

	if (callback)
		callback(record_id, status);

	hide_options();
}

///////
// ADD TO LIST
///////

function search_lists(event, button)
{
	// Get record ID
	var record_id = button.closest('form').find('input[name=record]').val();

	// Get lists
	var ajax = new pgengler.Ajax(base_url, search_lists_aux, { param: { event: event, button: button } });

	ajax.get({
		act: 'ajax_search_lists',
		record: record_id
	});
}

function search_lists_aux(response, params)
{
	if (has_error(response))
		return;

	var root = response.getElementsByTagName('lists')[0];

	var record_id = parseInt(root.getAttribute('record'));

	// Remove existing menu
	delete smenu;

	// Create menu
	var options = { arrowSrc: image_url + 'arrow_right.gif', onClick: function(e, menuItem) { search_menu_clicked(e, menuItem, record_id, params.button); } };
	smenu = new $.Menu('#options', null, options);

	var lists = root.getElementsByTagName('list');

	for (var i = 0; i < lists.length; i++) {
		var list_id      = parseInt(lists[i].getAttribute('id'));
		var list_default = parseInt(lists[i].getAttribute('default'));
		var list_name    = lists[i].firstChild.nodeValue;
		if (list_default)
			list_name = '<b>' + list_name + '</b>';

		// Create new menu item
		var menu_item = new $.MenuItem({ src: list_name, data: [ 'list', list_id ] }, options);

		smenu.addItem(menu_item);
	}

	if (lists.length > 1)
		smenu.addItem(new $.MenuItem({ }, options));
	smenu.addItem(new $.MenuItem({ src: 'Create list...', data: [ 'list', -1 ] }, options));

	smenu.onClick(params.event);
}

function search_menu_clicked(e, menuItem, record_id, button)
{
	if (typeof(menuItem.data) != 'object' || menuItem.data.length != 2)
		return;

	var list_id = menuItem.data[1];

	if (list_id == -1) {
		var list_name = prompt('List name: ');
		if (list_name) {
			var ajax = new pgengler.Ajax(base_url, search_add_list, { param: button });
			ajax.post({
				act: 'addlist',
				listname: list_name
			});
		}
	} else {
		search_submit_add(list_id, button);
	}
}

function search_add_list(response, button)
{
	var root = response.getElementsByTagName('lists')[0];

	var list_id = parseInt(root.getAttribute('load'), 10);

	search_submit_add(list_id, button);
}

function search_submit_add(list_id, button)
{
	var form = button.closest('form');
	form.find('input[name=list]').val(list_id);
	$.Menu.closeAll();
	form.submit();
}

function search_book_added(response, record_id)
{
	var root = response.getElementsByTagName('add')[0];

	var list_id = parseInt(root.getAttribute('list'), 10);

	if (root.getElementsByTagName('double').length > 0) {
		// If no callback function is provided, display the error.
		status_text = 'Book is already on that list';
	} else if (root.getElementsByTagName('record').length > 0) {
		// Add to appropriate List object
		var list = user_lists.get_list(list_id);
		if (list)
			list.add_book(Book.from_xml(root.getElementsByTagName('record')[0]));
		status_text = 'Book added successfully';
	} else {
		status_text = 'An error occurred while adding the book';
	}

	// Show status
	var status = $('#result' + record_id).find('.status');
	status.removeClass('ui-state-error ui-state-highlight').text(status_text).show();

	// If success, hide status after 5 seconds
	if (status_text.match(/success/)) {
		status.addClass('ui-state-highlight');
		setTimeout(function() {
			status.hide();
		}, 5000);
	} else {
		status.addClass('ui-state-error');
	}
}

function add_book_record(record_id, field_name)
{
	// Get the list ID
	var list = -1;
	if (document.getElementById('listid'))
		list = document.getElementById('listid').value;

	// Clear the status field
	clearTimeout(timeout);
	var status = document.getElementById('addstatus');
	if (status)
		status.innerHTML = 'Processing...';
	else
		document.getElementById(field_name).innerHTML = 'Processing...';

	var ajax = new pgengler.Ajax(base_url, add_book_aux);

	// Make the Ajax request
	var params = {
		act: 'ajax_addid',
		record: record_id,
		list: list
	};
	if (field_name)
		params['field'] = field_name;

	ajax.post(params);

	return false;	
}

function add_book(isbn, field_name)
{
	// Get the entered ISBN
	if (document.getElementById('isbn'))
		isbn = document.getElementById('isbn').value;

	// Get the list ID
	var list = -1;
	if (document.getElementById('listid'))
		list = document.getElementById('listid').value;

	// Clear the status field
	clearTimeout(timeout);
	var status = document.getElementById('addstatus');
	if (status)
		status.innerHTML = 'Processing...';
	else
		document.getElementById(field_name).innerHTML = 'Processing...';

	// Disable the 'add' button, if we've got one
	var add_button = document.getElementById('add_button');
	if (add_button) {
		add_button.setAttribute('value', 'Adding...');
		add_button.disabled = true;
	}

	var ajax = new pgengler.Ajax(base_url, add_book_aux);

	// Make the Ajax request
	var params = {
		act: 'ajax_addisbn',
		isbn: isbn,
		list: list
	};
	if (field_name)
		params['field'] = field_name;

	ajax.post(params);

	// Clear the ISBN box
	if (document.getElementById('isbn'))
		document.getElementById('isbn').value = '';

	return false;	
}

function add_book_aux(response)
{
	// Check for errors
	if (has_error(response))
		return;

	// Get status field
	var status = document.getElementById('addstatus');
	var field  = 0;

	var root  = response.getElementsByTagName('add')[0];
	if (root.getElementsByTagName('field').length > 0) {
		status = document.getElementById(root.getElementsByTagName('field')[0].firstChild.nodeValue);
		field = 1;
	}

	// Enable the 'add' button, if we've got one
	var add_button = document.getElementById('add_button');
	if (add_button) {
		add_button.setAttribute('value', 'Add');
		add_button.disabled = false;
	}

	// Skip if the book is already on the list
	if (root.getElementsByTagName('double')[0]) {
		status.innerHTML = 'Book is already on this list.';
		return;
	}

	// Skip if there was an error
	if (root.getElementsByTagName('notfound')[0]) {
		status.innerHTML = "No book with that ISBN could be found. This probably means that Amazon doesn't know about it. Let Phil know."
		return;
	}

	// Get list
	var list_id = parseInt(root.getAttribute('list'));
	var list    = user_lists.get_list(list_id);

	// Figure out if we're displaying the empty text, and remove it if we are
	var emptyrow = document.getElementById('empty');
	if (emptyrow)
		emptyrow.parentNode.removeChild(emptyrow);

	var book = Book.from_xml(root.getElementsByTagName('record')[0]);
	if (list)
		list.add_book(book);

	var alpha = 0;
	if (root.getElementsByTagName('alpha')[0])
		alpha = 1;

	if (field == 0) {
		// Build new row
		var table = document.getElementById('list') || document.getElementById('fixedlist');
		var tbody = table.getElementsByTagName('tbody')[0];
		var new_row;

		if (alpha != 1) {
			// Add at end
			new_row = tbody.insertRow(-1);
		} else {
			// Figure out where it belongs
			for (var i = 0; i < tbody.rows.length; i++) {
				var row_title = tbody.rows[i].getElementsByTagName('td')[TITLE_COL].getElementsByTagName('a')[0].innerHTML.trim();
				if (book.title < row_title) {
					new_row = tbody.insertRow(i);
					break;
				}
			}
			if (!new_row) {
				new_row = tbody.insertRow(-1);
			}
		}

		// Create row for new book
		var row = {
			element: 'tr', id: 'row' + book.record_id, cssclass:( book.read ? 'read' : 'unread') + (book.owned ? ' owned' : ''),
			children: [
				{ element: 'td', text: list.use_positions() ? book.position : '', cssclass: 'position', onclick: list.use_positions() ? function() { move_to_box(book.record_id); } : null },
				get_options_column(book, curr_list),
				{
					element: 'td',
					children: [
						{ element: 'a', href: base_url + 'detail/' + book.record_id, text: book.getTitle(), onclick: function() { show_detail(book.record_id); return false; } },
						book.note ? { element: 'img', src: image_url + 'note.png', alt: 'Note', title: book.note, cssclass: 'note', onclick: function() { show_note(book.record_id); } } : null
					]
				},
				{ element: 'td', text: book.getAuthorString() }
			]
		};

		// Make new row visible
		tbody.replaceChild(create_element(row), new_row);

		if (alpha == 0)
			dnd.makeDraggable(document.getElementById('row' + book.record_id));
	}

	status.innerHTML = 'Book added successfully.';
	if (field == 0)
		timeout = setTimeout(clear_status, 5000);
}

///////
// MOVE ITEMS
///////

function moved_item(record_id, old_position, new_position, dragged)
{
	// If we're not doing a drag-and-drop move, manually move the row
	if (!dragged) {
		// Get the table
		var table = document.getElementById('list') || document.getElementById('fixedlist');

		// Get the table's body
		var tbody = table.getElementsByTagName('tbody')[0];

		// Get the current row
		var row = document.getElementById('row' + record_id);

		// Add a new row in the right spot
		var new_row = tbody.insertRow(new_position > old_position ? new_position : new_position - 1);

		// Set row-level properties
		new_row.setAttribute('id', 'row' + record_id);
		new_row.setAttribute('class', row.getAttribute('class'));
		dnd.makeDraggable(new_row);

		for (var i = 0; i < row.childNodes.length; i++)
			new_row.appendChild(row.childNodes[i].cloneNode(true));

		// Detach original row from table
		row.parentNode.removeChild(row);

		edit_pos = new_position;
		clear_move_box();
	}

	redisplay_positions();
}

///////
// Redisplay positions
///////

function redisplay_positions()
{
	var table = document.getElementById('list') || document.getElementById('fixedlist');
	var tbody = table.getElementsByTagName('tbody')[0];
	var rows  = tbody.rows;

	for (var i = 0; i < rows.length; i++) {
		var pos_col = rows[i].getElementsByTagName('td')[0];
		if (pos_col.innerHTML.trim() != '') {
			pos_col.innerHTML = i + 1;
		}
	}	
}

///////
// Toggle "read" and "owned" status
///////

function toggle_read(record_id)
{
	var ajax = new pgengler.Ajax(base_url, toggle_aux);

	// Make the Ajax request
	ajax.post({
		act: 'ajax_toggle',
		record: record_id,
		list: curr_list_id
	});

	return false;
}

function toggle_owned(record_id)
{
	var ajax = new pgengler.Ajax(base_url, toggle_aux);

	// Make the Ajax request
	ajax.post({
		act: 'ajax_owned',
		record: record_id
	});

	return false;
}

function toggle_aux(response)
{
	// Check for error
	if (has_error(response))
		return;

	var root      = response.getElementsByTagName('toggle')[0];
	var record_id = parseInt(root.getAttribute('record'));
	var read      = parseInt(root.getAttribute('read'));
	var owned     = parseInt(root.getAttribute('owned'));
	var remove    = parseInt(root.getAttribute('remove'));

	// Get the row
	var row = $('#row' + record_id);

	if (remove) {
		row.fadeOut(400, function() {
			$(this).remove();
			curr_list.remove_book(record_id);
			redisplay_positions();
			if (curr_list.length() == 0)
				show_empty_row();
		});
		return;
	}

	// Set classes for row
	row.removeClass();

	if (read)
		row.addClass('read');
	else
		row.addClass('unread');

	if (owned)
		row.addClass('owned');

	// Set alt/title text for icons

	var owned_icon = document.getElementById('owned' + record_id);
	if (owned)
		owned_icon.setAttribute('title', "I don't own this");
	else
		owned_icon.setAttribute('title', 'I own this');

	var read_icon = document.getElementById('read' + record_id);
	if (read)
		read_icon.setAttribute('title', 'Mark as unread');
	else
		read_icon.setAttribute('title', 'Mark as read');
}

///////
// Create "options" column
///////

function get_options_column(book, list)
{
	return cell = {
		element: 'td',
		children: [
			{
				element: 'a', href: '#', onclick: function() { return toggle_read(book.record_id); },
				children: [
					book.read
						? { element: 'img', src: image_url + 'read.png', id: 'read' + book.record_id, cssclass: 'spaced readicon', alt: 'Mark as unread', title: 'Mark as unread' }
						: { element: 'img', src: image_url + 'read.png', id: 'read' + book.record_id, cssclass: 'spaced readicon', alt: 'Mark as read', title: 'Mark as read' }
				]
			},
			!list.readonly()
				?
				{
					element: 'a', href: '#', onclick: function() { return del(book.record_id); },
					children: [
						{ element: 'img', src: image_url + 'delete.png', cssclass: 'spaced' }
					]
				}
				: null,
			{
				element: 'a', href: '#', onclick: function() { return toggle_owned(book.record_id); },
				children: [
					book.owned
					? { element: 'img', src: image_url + 'owned.png', id: 'owned' + book.record_id, cssclass: 'spaced ownedicon', alt: "I don't own this", title: "I don't own this" }
					: { element: 'img', src: image_url + 'owned.png', id: 'owned' + book.record_id, cssclass: 'spaced ownedicon', alt: "I own this", title: "I own this" }
				]
			},
			{ element: 'img', id: 'options' + book.record_id, cssclass: 'spaced options', src: image_url + 'options.gif', alt: 'Options', style: 'cursor: default', onclick: function(event) { show_menu(event, book.record_id); } }
		]
	};
}

///////
// Load a different list
///////

function change_list_hash()
{
	change_list(location.hash.substring(1));
}

function change_list(list_id)
{
	// Get list selection, if necessary
	var lists_box;
	if (!list_id) {
		lists_box = document.getElementById('listbox');
		list_id   = lists_box.options[lists_box.selectedIndex].value;
	}

	// Make sure target list is valid
	if (!user_lists.get_list(list_id))
		return;

	// Cancel list edit so we don't get a JS error (Todo #1179)
	cancel_list_edit();

	// Clear error field (Todo #1180)
	var status = document.getElementById('addstatus');
	status.innerHTML = '';

	// Remove focus from the dropdown so it's difficult to accidentally change
	if (lists_box)
		lists_box.blur();

	// Display new list
	show_list(list_id);

	return false;
}

function change_list_timeout(ajax, list_id)
{
	ajax.abort();

	show_list(list_id);
}

function change_list_no_ajax()
{
	// Get selection
	var lists_box = document.getElementById('listbox');
	var list_id   = lists_box.options[lists_box.selectedIndex].value;

	if (list_id != 0)
		window.location.href = base_url + 'list/' + list_id;
}

function show_loading()
{
	// First, make sure we have a table
	var table = document.getElementById('list');

	if (!table)
		return;

	var empty = {
		element: 'tbody',
		children: [
			{
				element: 'tr', id: 'loading', cssclass: 'unread',
				children: [
					{
						element: 'td', colspan: 4,
						children: [
							{ element: 'img', src: image_url + 'loading.gif' },
							{ element: 'text', text: 'Loading...' }
						]
					}
				]
			}
		]
	};

	var current_tbody = table.getElementsByTagName('tbody')[0];

	// Show 'Loading' row
	table.replaceChild(create_element(empty), current_tbody);
}

function hide_loading()
{
	var div = document.getElementById('loading');
	if (div)
		div.parentNode.removeChild(div);
}

///////
// Load Lists object from XML
///////
function load_lists(xml)
{
	if (!user_lists)
		user_lists = new Lists();

	for (var i = 0; i < xml.length; i++) {
		var list = List.from_xml(xml[i]);

		user_lists.update_list(list);

		// Get records
		var records = xml[i].getElementsByTagName('record');

		var len = records.length;

		for (var j = 0; j < len; j++) {
			list.add_book(Book.from_xml(records[j]));
		}
	}
	curr_list = user_lists.get_list(curr_list_id);
}

///////
// Suggestions
///////

// This function is called from the 'onchange' of the 'users' dropdown box
// from a book detail screen; we need to find the target user ID.
function suggest_book(record_id)
{
	var dropdown = document.getElementById('suggestto');
	var user_id  = parseInt(dropdown.options[dropdown.selectedIndex].value);

	if (user_id != -1)
		suggest(record_id, user_id);
}

function suggest(record_id, user_id)
{
	var ajax = new pgengler.Ajax(base_url, suggest_aux);

	ajax.post({
		act: 'ajax_suggest',
		record: record_id,
		user: user_id
	});
}

function suggest_aux(response)
{
	// Check for error
	if (has_error(response))
		return;

	var root = response.getElementsByTagName('suggestion')[0];

	var record_id = parseInt(root.getAttribute('record'));
	var user_id   = parseInt(root.getElementsByTagName('user_id')[0].firstChild.nodeValue);

	hide_options();
}

///////
// Add list
///////

function add_list_with_record(record_id)
{
	// Get list name
	var list_name = prompt("List name: ");

	if (!list_name)
		return;

	// Make Ajax request
	var ajax = new pgengler.Ajax(base_url, add_list_aux);
	ajax.post({
		act: 'addlist',
		listname: list_name,
		record: record_id
	});
}

function add_list()
{
	// Get parameters
	var list_name = document.getElementById('listname').value;

	if (!list_name) {
		alert('You must enter a name for the new list');
		return false;
	}

	// Make Ajax request
	var ajax = new pgengler.Ajax(base_url, add_list_aux);
	ajax.post({
		act: 'addlist',
		listname: list_name
	});

	// Don't POST form
	return false;
}

function add_list_aux(response)
{
	// Clear 'add list' box
	document.getElementById('listname').value = '';

	// Since we're switching to the new list, we can reuse that feature's processing
	get_lists_aux(response);
}

///////
// Edit list
///////

function disable_list_editing()
{
	document.getElementById('listcaption').setAttribute('onclick', '');
	document.getElementById('isbn').disabled = true;
	document.getElementById('add_button').disabled = true;
}

function enable_list_editing()
{
	document.getElementById('listcaption').setAttribute('onclick', "edit_list()");
	document.getElementById('isbn').disabled = false;
	document.getElementById('add_button').disabled = false;
}

function edit_list()
{
	var ajax = new pgengler.Ajax(base_url, edit_list_aux);
	ajax.get({
		act: 'ajax_getlists',
		list: curr_list_id
	});
}

function edit_list_aux(response)
{
	// Check for error
	if (has_error(response))
		return;

	var root = response.getElementsByTagName('lists')[0];

	var default_id = parseInt(root.getAttribute('default'));

	load_lists(root.getElementsByTagName('list'));

	// Make sure we're not trying to edit a virtual list
	if (curr_list.readonly())
		return;

	var caption = document.getElementById('listcaption');

	// Create form
	var form = {
		element: 'form', method: 'post', id: 'edit_list', style: 'display: inline', onsubmit: function() { return submit_list_edit(); },
		children: [
			{
				element: 'fieldset',
				children: [
					{ element: 'label', assoc: 'edit_list_name', text: 'Name: ' },
					{ element: 'input', type: 'text', id: 'edit_list_name', value: curr_list.name() },
					{ element: 'br' },
					{ element: 'label', assoc: 'edit_list_public', text: 'Public list? ' },
					{ element: 'input', type: 'checkbox', id: 'edit_list_public', checked: curr_list.public() == 1 },
					(curr_list.id() != default_id) ? { element: 'label', assoc: 'edit_list_delete', text: ' Delete list? ' } : null,
					(curr_list.id() != default_id) ? { element: 'input', type: 'checkbox', id: 'edit_list_delete' } : null,
					{ element: 'br' },
					{ element: 'label', assoc: 'edit_list_positions', text: 'Use positions? (If not checked, list is sorted alphabetically by title)' },
					{ element: 'input', type: 'checkbox', id: 'edit_list_positions', checked: curr_list.use_positions() },
					{ element: 'br' },
					{ element: 'label', assoc: 'edit_list_on_mark_read_nothing', text: 'When marking a book on this list as read:' },
					{ element: 'br' },
					{ element: 'input', type: 'radio', name: 'on_mark_read', id: 'edit_list_on_mark_read_nothing', checked: (curr_list.on_mark_read() == 0), onclick: function() { document.getElementById('edit_list_on_mark_read_list').disabled = true; } },
					{ element: 'label', assoc: 'edit_list_on_mark_read_nothing', text: 'Do nothing' },
					{ element: 'br' },
					{ element: 'input', type: 'radio', name: 'on_mark_read', id: 'edit_list_on_mark_read_remove', checked: (curr_list.on_mark_read() == -1), onclick: function() { document.getElementById('edit_list_on_mark_read_list').disabled = true; } },
					{ element: 'label', assoc: 'edit_list_on_mark_read_nothing', text: 'Remove' },
					{ element: 'br' },
					{ element: 'input', type: 'radio', name: 'on_mark_read', id: 'edit_list_on_mark_read_move', checked: (curr_list.on_mark_read() > 0), onclick: function() { var list = document.getElementById('edit_list_on_mark_read_list'); list.disabled = false; list.focus(); } },
					{ element: 'label', assoc: 'edit_list_on_mark_read_nothing', text: 'Move to:' },
					{
						element: 'select', id: 'edit_list_on_mark_read_list', disabled: curr_list.on_mark_read() <= 0,
						children: lists_for_dropdown(curr_list.on_mark_read())
					},
					{ element: 'br' },
					{ element: 'input', type: 'hidden', id: 'listid', value: curr_list.id() },
					{ element: 'input', type: 'submit', value: 'Save' },
					{ element: 'button', type: 'button', onclick: function() { cancel_list_edit(); }, text: 'Cancel' }
				]
			}
		]
	};

	caption.parentNode.replaceChild(create_element(form), caption);
}


function cancel_list_edit()
{
	// Check if form exists before we do anything (Todo #1186)
	var fieldset = document.getElementById('edit_list');

	if (!fieldset)
		return;

	var caption = {
		element: 'div', id: 'listcaption', onclick: function() { edit_list(); }, text: curr_list ? curr_list.name() : ''
	};

	var edit_form = document.getElementById('edit_list');
	edit_form.parentNode.replaceChild(create_element(caption), edit_form);
}

function submit_list_edit()
{
	// Get values
	var list_id     = document.getElementById('listid').value;
	var name        = document.getElementById('edit_list_name').value;
	var is_public   = document.getElementById('edit_list_public').checked == true ? 1 : 0;
	var del         = document.getElementById('edit_list_delete') ? (document.getElementById('edit_list_delete').checked == true ? 1 : 0) : 0;
	var use_pos     = document.getElementById('edit_list_positions') ? (document.getElementById('edit_list_positions').checked == true ? 1 : 0) : 0;

	var on_mark_read = 0;
	if (document.getElementById('edit_list_on_mark_read_remove') && document.getElementById('edit_list_on_mark_read_remove').checked == true)
		on_mark_read = -1;
	else if (document.getElementById('edit_list_on_mark_read_move') && document.getElementById('edit_list_on_mark_read_move').checked == true) {
		var box = document.getElementById('edit_list_on_mark_read_list');
		on_mark_read = box.options[box.selectedIndex].value;
	}

	var ajax = new pgengler.Ajax(base_url, submit_list_edit_aux);

	ajax.post({
		act: 'ajax_savelist',
		list: list_id,
		public: is_public,
		del: del,
		use_positions: use_pos,
		on_mark_read: on_mark_read,
		name: name
	});

	return false;
}

function submit_list_edit_aux(response)
{
	// Check for error
	if (has_error(response))
		return;

	var root = response.getElementsByTagName('lists')[0];

	var default_id = parseInt(root.getAttribute('default'));

	load_lists(root.getElementsByTagName('list'));

	cancel_list_edit();

	populate_lists_dropdown(curr_list_id || default_id);

	show_list(curr_list_id || default_id);		
}

///////
// Show record detail
///////
function show_detail(record_id)
{
	// Hide any existing detail popups
	hide_detail();

	// Make an Ajax request to get information for this record
	var ajax = new pgengler.Ajax(base_url, show_detail_aux);

	ajax.get({
		act: 'ajax_detail',
		record: record_id
	});
}

function show_detail_aux(response)
{
	// Check for error
	if (has_error(response))
		return;

	var root = response.getElementsByTagName('detail')[0];

	// Get values
	var record_id = parseInt(root.getAttribute('record'));
	var title     = root.getElementsByTagName('title')[0].firstChild.nodeValue;

	var associate_id = root.getElementsByTagName('associate_id')[0].firstChild.nodeValue;

	var authors = [];
	var len = root.getElementsByTagName('author').length;
	for (var i = 0; i < len; i++)
		authors.push(root.getElementsByTagName('author')[i].firstChild.nodeValue);

	var list_names = [];
	var list_ids   = [];
	len = root.getElementsByTagName('list').length;
	for (var i = 0; i < len; i++) {
		var list = root.getElementsByTagName('list')[i];
		list_names.push(list.firstChild.nodeValue);
		list_ids.push(parseInt(list.getAttribute('id')));
	}

	var tags = [];
	len = root.getElementsByTagName('tag').length;
	for (var i = 0; i < len; i++)
		tags.push(Tag.from_xml(root.getElementsByTagName('tag')[i]));

	var record_books = [];
	len = root.getElementsByTagName('book').length;
	for (var i = 0; i < len; i++)
		record_books.push(root.getElementsByTagName('book')[i]);

	delete user_suggest;
	user_suggest = [];

	len = root.getElementsByTagName('contact').length;
	for (var i = 0; i < len; i++) {
		user_suggest.push(User.from_xml(root.getElementsByTagName('contact')[i]));
	}

	// Create a <div> to display all of this in
	var display = {
		element: 'div', id: 'recordinfo',
		children: [
			{
				element: 'div', style: 'text-align: center',
				children: [
					{ element: 'h3', text: title },
					{ element: 'p', text: authors.length > 0 ? authors.join(', ') : '' }
				]
			},
			{ element: 'text', text: 'The following books are included:' }
		]
	};

	// Books and covers
	var book_list = { element: 'ul', children: [ ] };
	var cover_list = { element: 'div', id: 'covers', children: [ ] };
	for (var i = 0; i < record_books.length; i++) {
		var title   = record_books[i].getElementsByTagName('title')[0].firstChild.nodeValue;
		var isbn    = record_books[i].getElementsByTagName('isbn')[0].firstChild.nodeValue;
		var binding = '';
		if (record_books[i].getElementsByTagName('binding')[0].firstChild)
			binding = record_books[i].getElementsByTagName('binding')[0].firstChild.nodeValue;
		var amazon  = parseInt(record_books[i].getAttribute('amazon'));
		var cover   = parseInt(record_books[i].getAttribute('cover'));

		if (amazon) {
			book_list.children.push({
				element: 'li', children: [
					{ element: 'a', href: 'http://www.amazon.com/dp/' + isbn + '/?tag=' + associate_id, text: title },
					{ element: 'text', text: ' (ISBN ' + isbn + ')' },
					{ element: 'text', text: binding ? ' (' + binding + ')' : '' }
				]
			});
		} else {
			book_list.children.push(
				{ element: 'text', text: title },
				{ element: 'text', text: binding ? ' (' + binding + ')' : '' }
			);
		}

		if (cover) {
			cover_list.children.push(
				{ element: 'img', src: covers_url + isbn + '.jpg' },
				{ element: 'text', text: ' ' }
			);
		}
	}
	display.children.push(book_list);

	// Tags
	if (curr_user_id) {
		display.children.push({ element: 'text', text: 'Tags:' });

		var tags_list = { element: 'ul', id: 'tags', children: [ ] }

		for (var i in tags) {
			tags_list.children.push({
				element: 'li', text: tags[i].name(),
				children: [
					{ element: 'text', text: ' [ ' },
					{ element: 'a', href: '#', text: 'x', onclick: function(tag_id) { return function() { remove_tag(record_id, tag_id); return false; } }(tags[i].id()) },
					{ element: 'text', text: ' ]' }
				]
			});
		}

		tags_list.children.push({
			element: 'li', id: 'newtag',
			children: [
				{ element: 'a', href: '#', text: 'Add tags', onclick: function() { show_add_tags(record_id); return false; } }
			]
		});
		display.children.push(tags_list);
	}

	// Suggest
	if (user_suggest.length > 0) {
		display.children.push({ element: 'text', text: 'Suggest this book to another user: ' });

		var suggest_list = {
			element: 'select', id: 'suggestto', onchange: function() { suggest_book(record_id); },
			children: [
				{ element: 'option', value: -1, text: '<Pick user>' }
			]
		};
		for (var i = 0; i < user_suggest.length; i++) {
			var u = user_suggest[i];

			suggest_list.children.push({
				element: 'option', value: u.id(), text: u.name()
			});
		}
		display.children.push(suggest_list);
		display.children.push({ element: 'br' });
		display.children.push({ element: 'br' });
	}

	// Lists
	if (list_names.length > 0) {
		display.children.push({ element: 'text', text: 'On these lists:' });

		var lists_list = { element: 'ul', children: [ ] };
		for (var i = 0; i < list_names.length; i++) {
			lists_list.children.push({
				element: 'li',
				children: [
					{ element: 'a', href: '#', text: list_names[i], onclick: function(list_id) { return function() { switch_list(list_id); return false; } }(list_ids[i]) }
				]
			});
		}
		display.children.push(lists_list);
	}

	display.children.push(cover_list);

	jQuery.facebox(create_element(display).innerHTML);
}

function switch_list(list_id)
{
	// Close facebox
	$(document).trigger('close.facebox');

	// Change list
	change_list(list_id);
}

function hide_detail()
{
	var disp = document.getElementById('recordinfo');

	if (disp)
		disp.parentNode.removeChild(disp);
}

///////
// Notes
///////
function show_note(record_id)
{
	// Get book
	var book = curr_list.get_book(record_id);

	// Create div to display
	var display = {
		element: 'div',
		children: [
			{ element: 'h3', text: book.title },
			{ element: 'div', id: 'note', children: [ ] }
		]
	};

	if (book.note) {
		var note_area = display.children[1];
		var lines = book.note.split(/\n/);
		for (var line in lines) {
			note_area.children.push(
				{ element: 'text', text: lines[line] },
				{ element: 'br' }
			)
		}
	}

	jQuery.facebox(create_element(display).innerHTML);

	$('#note').editable(base_url, { type: 'textarea', rows: 25, data: book.note, submit: 'Save', cancel: 'Cancel', submitdata: save_note, record: record_id, submit_callback: save_note_aux });

	if (!book.note)
		$('#note').click();
}

function save_note(value, object)
{
	return {
		act: 'ajax_savenote',
		record: object.record
	}
}

function save_note_aux(result, status)
{
	/*
	   Since this call comes from the jQuery plugin and not our normal Ajax module,
	   we get a plain string instead of the normal XML object and need to parse
	   the string into such.
	*/
	var response = null;
	if (window.DOMParser)
		response = (new DOMParser()).parseFromString(result, 'text/xml');
	else {
		response = new ActiveXObject("Microsoft.XMLDOM");
		response.async = false;
		response.loadXML(result);
	}

	if (!response)
		return;

	// Check for error
	if (has_error(response))
		return;

	var root = response.getElementsByTagName('record')[0];

	var record_id = parseInt(root.getAttribute('id'));
	var read      = parseInt(root.getAttribute('read'));
	var owned     = parseInt(root.getAttribute('owned'));
	var prefix    = root.getElementsByTagName('prefix').length > 0 ? root.getElementsByTagName('prefix')[0].firstChild.nodeValue : null;
	var title     = root.getElementsByTagName('title')[0].firstChild.nodeValue;
	var note      = root.getElementsByTagName('note').length > 0 ? root.getElementsByTagName('note')[0].firstChild.nodeValue : null;

	// Update record
	curr_list.get_book(record_id).note = note;

	// Update display
	if (note) {
		// Display icon
		var row  = document.getElementById('row' + record_id);
		var cell = row.getElementsByTagName('td')[TITLE_COL];
		if (!cell.getElementsByTagName('img').length > 0) {
			cell.appendChild(create_element({
				element: 'img', src: image_url + 'note.png', alt: 'Note', title: note, cssclass: 'note', onclick: function() { show_note(record_id); }
			}));
		}
	} else {
		// Don't display icon
		var row  = document.getElementById('row' + record_id);
		var cell = row.getElementsByTagName('td')[TITLE_COL];
		if (cell.getElementsByTagName('img').length > 0) {
			var img = cell.getElementsByTagName('img')[0];
			img.parentNode.removeChild(img);
		}		
	}

	var str = '';

	if (note) {
		var lines = note.split(/\n/);
		for (var line in lines)
			str += lines[line] + '<br />';
	}

	$(document).trigger('close.facebox');

	return str;
}

///////
// Populate lists dropdown
//////
function lists_for_dropdown(selected)
{
	var lists = user_lists.get_lists();

	var len = lists.length;

	var dropdown_lists = [ ];

	for (var i = 0; i < len; i++) {
		dropdown_lists.push({
			element: 'option', value: lists[i].id(), text: lists[i].name(), selected: lists[i].id() == selected
		});
	}

	return dropdown_lists;
}

function populate_lists_dropdown(current_list_id, exclude_readonly, element)
{
	// Get lists box
	var lists_box = element ? element : document.getElementById('listbox');

	var box = {
		element: 'select', id: lists_box.id,
		children: [ ]
	};

	var readonly_lists = user_lists.get_readonly_lists();
	var editable_lists = user_lists.get_editable_lists();

	var addto;

	if (!exclude_readonly) {
		for (var i = 0; i < readonly_lists.length; i++) {
			box.children.push({
				element: 'option', value: readonly_lists[i].id(), selected: (readonly_lists[i].id() == current_list_id), text: readonly_lists[i].name()
			});
		}
		addto = {
			element: 'optgroup', label: 'My Lists', children: [ ]
		};
		box.children.push(addto);
	} else {
		addto = box;
	}

	for (var i = 0; i < editable_lists.length; i++) {
		addto.children.push({
			element: 'option', value: editable_lists[i].id(), text: editable_lists[i].name(), selected: (editable_lists[i].id() == current_list_id)
		});
	}

	lists_box.parentNode.replaceChild(create_element(box), lists_box);
}

//////////////
// Error handling
//////////////
function has_error(response)
{
	if (response.getElementsByTagName('error').length == 0)
		return false;
	var error   = response.getElementsByTagName('error')[0];
	var message = error.firstChild.nodeValue;

	// For now, just display the text in an "alert" window
	alert(message);

	return true;
}


//////////////

function show_empty_row()
{
	// Get table
	var table = document.getElementById('list');
	var tbody = table.getElementsByTagName('tbody')[0];

	var empty = {
		element: 'tbody', child: {
			element: 'tr', id: 'empty', cssclass: 'unread',
			children: [
				{ element: 'td', colspan: 4, text: 'This list is empty. Use the "add" field below to add some books to this list!' }
			]
		}
	};

	table.replaceChild(create_element(empty), tbody);
}

///////
// STATUS MESSAGE UPDATES
///////
function status_success(message)
{
	status_message(message, false);
}

function status_error(message)
{
	status_message(message, true);
}

function status_message(text, error)
{
	var message_area;
	if (text === null)
		message_area = create_element({ element: 'div', id: 'msgarea', style: 'display: none' });
	else {
		message_area = create_element({
			element: 'div', id: 'msgarea', cssclass: error ? 'error' : 'info', children: [
				{ element: 'text', text: text },
				{ element: 'text', text: ' ' },
				{ element: 'a', id: 'close', href: 'javascript:clear_status()', text: 'Close' }
			]
		});
	}
	
	// Get original message area
	var msgarea = document.getElementById('msgarea');

	if (!msgarea)
		return;

	// Show new message
	msgarea.parentNode.replaceChild(message_area, msgarea);

	// Position new message area (centered horizontally)
	if (text !== null)
		message_area.setAttribute('style', "left: " + ((document.body.clientWidth / 2) - (message_area.clientWidth / 2)) + 'px');

	// Cancel any existing timer
	if (status_timeout) {
		clearTimeout(status_timeout);
		status_timeout = null;
	}

	// If successful, remove message after five seconds
	if (!error)
		status_timeout = setTimeout(clear_status, 5 * 1000);
}

function clear_status()
{
	status_message(null, false);

	if (status_timeout) {
		clearTimeout(status_timeout);
		status_timeout = null;
	}
}

///////
// Bonus string functions
///////

String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g,"");
}
String.prototype.ltrim = function() {
	return this.replace(/^\s+/,"");
}
String.prototype.rtrim = function() {
	return this.replace(/\s+$/,"");
}

