<commit_before>/**********************************************************************
 *
 * NIPAP JavaScript functions
 *
 *********************************************************************/

/**
 * Global variables...
 */
var schema_id = 0;

/*
 * The prefix_list variable is used to keep a copy of all the prefixes
 * currently in the displayed list. Before adding, they are given a new
 * attribute: children. It is used to save information regarding
 * whether the prefix has children or not. These values are allowed:
 *  -2: We have no clue
 *  -1: At least one, but might be more (used for parent prefixes which
 *	  was received when a prefix further down was requested including
 *	  parents)
 *   0: No children
 *  >0: Has children
 */
var prefix_list = Object();
var pool_list = new Object();
var indent_head = Object();

// Object for storing statistics
var stats = Object();

// current options - mostly used for prefix addition on
// the prefix add and pool edit pages.
var cur_opts = new Object();

/*
 * Holy shit, this is an ugly hack...
 * As the prefix search results sometimes need to link to an edit-prefix-page
 * and sometimes need to link to a select-prefix function, we keep the current
 * state in a global variable...
 */
var prefix_link_type = 'edit';

// Max prefix lengths for different address families
var max_prefix_length = [32, 128];

var current_query = {
	'query_string': '',
	'parents_depth': 0,
	'children_depth': 0
};
var query_id = 0;
var newest_query = 0;

var offset = 0;
var outstanding_nextpage = 0;
var end_of_result = 0;

var search_key_timeout = 0;

// store current container for inserting prefixes
var container = null;

/**
 * A general log function
 *
 * This will try to post a message to the javascript console available in
 * Chrome / Firefox, if that's not available, it'll try opera and thereafter
 * give up.
 */
function log(msg) {
	try {
		console.log(msg);
	} catch (error) {
		try {
			window.opera.postError(msg)
		} catch (error) {
			// no console available
		}
	}
}

/**
 * Display a notice popup
 *
 * @param title string Title to display above error message.
 * @param msg Message to display to user.
 */
function showDialogNotice(title, msg) {

	var dialog = $('<div>' + msg + '</div>')
		.dialog({
			autoOpen: false,
			resizable: false,
			show: 'fade',
			hide: 'fade',
			open: function() {
				$('.ui-widget-overlay').hide().fadeIn('fast');
				$(this).parents(".ui-dialog:first").find(".ui-dialog-titlebar").addClass("ui-dialog-question");
				$(this).parents(".ui-dialog:first").find(".ui-dialog-titlebar-close").remove();
				$(this).parents(".ui-dialog:first").find(":button:eq(0)").addClass("button button_green");
			},
			modal: true,
			title: title,
			buttons: [
				{
					style: 'margin: 10px; width: 50px;',
					text: "OK",
					click: function() { $(this).dialog("close"); }
				}
			]
		});

	dialog.dialog('open');

	return false;
}

/*
 * Show general confirm dialog
 */
function showDialogYesNo(title, msg, target) {
	var dialog = $('<div>' + msg + '</div>')
		.dialog({
			autoOpen: false,
			resizable: false,
			show: 'fade',
			hide: 'fade',
			open: function() {
				$('.ui-widget-overlay').hide().fadeIn('fast');
				$(this).parents(".ui-dialog:first").find(".ui-dialog-titlebar").addClass("ui-dialog-question");
				$(this).parents(".ui-dialog:first").find(".ui-dialog-titlebar-close").remove();
				// XXX: this is a bloody hack to override the jquery ui CSS
				//		classes, or rather remove them and have our standard
				//		button look instead
				$(this).parents(".ui-dialog:first").find(":button").removeClass("ui-state-default");
				$(this).parents(".ui-dialog:first").find(":button").removeClass("ui-state-focus");
				$(this).parents(".ui-dialog:first").find(":button:eq(0)").addClass("button button_red");
				$(this).parents(".ui-dialog:first").find(":button:eq(1)").addClass("button button_green");
			},
			modal: true,
			title: title,
			buttons: [
				{
					style: 'margin: 10px; width: 50px;',
					text: "Yes",
					click: target,
				},
				{
					style: 'margin: 10px; width: 50px;',
					text: "No",
					click: function() { $(this).dialog("close"); }
				}
			]
		});

	dialog.dialog('open');

	return dialog;
}


/*
 * Show general dialog
 */
function showDialog(id, title, content, width) {
	// figure out default width
	if (width == undefined) {
		width = 400;
	} else {
		width = parseInt(width);
	}

	var c = '<div>' + content + '</div>';

	var dialog = $(c)
		.dialog({
			autoOpen: false,
			resizable: false,
			show: 'fade',
			hide: 'fade',
			open: function() {
				$('.ui-widget-overlay').hide().fadeIn('fast');
			},
			modal: true,
			width: width,
			title: title
		});

	dialog.dialog('open');

	return dialog;
}


/*
 * Display the search help "pop-up".
 */
function displaySearchHelp() {

	var c = '';
	c += '<div class="dialog_text" style="padding: 15px;">' +
		"Searching is the primary method of navigating the many thousand of prefixes that NIPAP is built to handle. It's very similar to how popular search engines, such as Google, Yahoo or Bing, are used." +
		"<h4>Matching text</h4>Just as with any search engine, you can enter a word or multiple words to match the text information associated with a prefix, that is the description or comment field. Each word is treated as a search 'term' and all search terms are joined together by the boolean operator AND. That means that searching for <i>'<b>foo bar</b>'</i> will be interpreted as a search for <i>'<b>foo</b>'</i> and <i>'<b>bar</b>'</i>. Any match must contain both the word <i>'<b>foo</b>'</i> and the word <i>'<b>bar</b>'</i>, though not necessarily in that order. <i>'<b>bar foo</b>'</i> will match, just as <i>'<b>foo</b> test test <b>bar</b>'</i> will match." +
		   "<h4>IP addresses / prefixes</h4>Searching for IP addresses are specially treated and will only match in the IP address column. Simply enter a complete IPv4 or IPv6 address and it will be automatically interpreted as an IP address.<br/><br/>It is possible to match entire prefixes and all their content by searching for a prefix in CIDR notation (e.g. 192.168.1.0/24). Searching for 192.168.1.x or 192.168.1 (as some would expect to match everything in 192.168.1.0/24) will not work as they will not be interpreted as prefixes but as text." +
		   "<h3>Examples</h3>" +
		   "To find what the IP address 192.168.1.1 is used for:<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;192.168.1.1" +
		   "<br/><br/>To list all addresses inside 172.16.0.0/24:<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;172.16.0.0/24" +
		   "<br/><br/>To match prefixes with 'TEST-ROUTER-1' in description or comment and that are somewhere in the network 10.0.0.0/8:<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;10.0.0.0/8 TEST-ROUTER-1" +
		   '<br/><br/></div>';

	d = showDialog('search_help', 'Searching', c, 800);

	return false;
}

/*
 * Error handler for ajax-errors.
 *
 * Function designed to be passed as error callback to i.e. getJSON().
 */
function ajaxErrorHandler(e, jqXHR, ajaxSettings, thrownError) {

	showDialogNotice('Server error', thrownError);

}


/**********************************************************************
 *
 * Prefix list functions
 *
 *********************************************************************/

/*
 * Toggles a collapse group
 */
function toggleGroup(id) {

	var col = $('#collapse' + id);

	if (col.css('display') == 'none') {
		expandGroup(id);
	} else {
		collapseGroup(id);
	}

}

/*
 * Expands a collapse group
 */
function expandGroup(id) {

	var col = $('#collapse' + id);
	var exp = $('#prefix_exp' + id);

	col.slideDown();
	if (prefix_list[id].children < 0) {
		// there might be additional children, display a +!
		exp.html('+');
	} else {
		exp.html('&ndash;');
	}

}

/*
 * Collapse a collapse group
 */
function collapseGroup(id) {

	var col = $('#collapse' + id);
	var exp = $('#prefix_exp' + id);

	col.slideUp();
	exp.html('+');

}

/*
 * Clear all state related to prefix search
 */
function clearPrefixSearch() {
	$('#prefix_list').empty();
	$('#search_interpret_container').empty();
	$('#search_result_help').show();
	$('#nextpage').hide();
	query_id += 1;

	current_query = {
		'query_string': '',
		'parents_depth': 0,
		'children_depth': 0
	};
}

/*
 * Wrapper for pacing our searches somewhat
 *
 * 200ms works out to be a pretty good delay for a fast typer
 */
function prefixSearchKey() {
	clearTimeout(search_key_timeout);
	search_key_timeout = setTimeout("performPrefixSearch()", 200);
}

/*
 * Perform a search operation
 */
function performPrefixSearch(explicit) {
	if (explicit != true) {
		explicit = false;
	}

	// Skip search if query string empty
	if (jQuery.trim($('#query_string').val()).length < 1) {
		clearPrefixSearch();
		return true;
	}
	end_of_result = 0;

	// Keep track of search timing
	stats.query_sent = new Date().getTime();

	var search_q = {
		'query_id': query_id,
		'query_string': $('#query_string').val(),
		'schema': schema_id,
		'parents_depth': optToDepth($('input[name="search_opt_parent"]:checked').val()),
		'children_depth': optToDepth($('input[name="search_opt_child"]:checked').val()),
		'include_all_parents': 'true',
		'include_all_children': 'false',
		'max_result': 50,
		'offset': 0
	}

	// Skip search if it's equal to the currently displayed search
	if (
		(search_q.query_string == current_query.query_string &&
		 search_q.parents_depth == current_query.parents_depth &&
		 search_q.children_depth == current_query.children_depth)
		 && explicit == false) {
		return true;
	}


	current_query = search_q;
	query_id += 1;
	offset = 0;

	$('#prefix_list').empty();
	$('#search_result_help').hide();

	showLoadingIndicator($('#prefix_list'));
	$.getJSON("/xhr/smart_search_prefix", current_query, receivePrefixList);

	// add search options to URL
	setSearchPrefixURI(explicit);

}


/*
 * Extract search options and add to URI
 */
function setSearchPrefixURI(explicit) {

	var url = $.url();
	var url_str = "";

	url_str = url.attr('protocol') + '://' + url.attr('host');

	if (url.attr('port') != null) {
		url_str += ":"+url.attr('port');
	}

	url_str += url.attr('path');

	if (url.attr('query') != null) {
		url_str += '?' + url.attr('query');
	}

	url_str += '#query_string=' +
		encodeURIComponent($('#query_string').val()) +
		'&search_opt_parent=' +
		encodeURIComponent($('input[name="search_opt_parent"]:checked').val()) +
		'&search_opt_child=' +
		encodeURIComponent($('input[name="search_opt_child"]:checked').val()) +
		'&explicit=' + encodeURIComponent(explicit);

    log(url_str);

	window.location.href = url_str;

}


/*
 * Called when next page of results is requested by the user.
 */
function performPrefixNextPage () {
	if (outstanding_nextpage == 1 || end_of_result == 1 ||
		jQuery.trim($('#query_string').val()).length < 1) {
		return;
	}
	outstanding_nextpage = 1;

	offset += 49;

	current_query.query_id = query_id;
	current_query.offset = offset;

	query_id += 1;

	$.getJSON("/xhr/smart_search_prefix", current_query, receivePrefixListNextPage);

}


/*
 * Add a prefix to the prefix list.
 *
 * A prefix can be placed in two ways, either in a container or relative to
 * another prefix. If a container is given, the prefix is simply added to it. If
 * relative placement is asked for, the prefix can instead be placed before or
 * after an already existing prefix.
 *
 * The 'relative' option can either be null, when the prefix is to be placed in
 * a parent container, or an object with two attributes: 'reference', which is the
 * DOM entry to base the placement on, and 'orientation' which can be the
 * strings 'before' or 'after'.
 */
function showPrefix(prefix, parent_container, relative) {

	// add main prefix container
	if (relative == null) {
		parent_container.append('<div id="prefix_entry' + prefix.id + '" data-prefix-id="' + prefix.id + '">');
	} else {
		if (relative.orientation == 'before') {
			relative.reference.before('<div id="prefix_entry' + prefix.id + '">');
		} else {
			relative.reference.after('<div id="prefix_entry' + prefix.id + '">');
		}
	}

	var prefix_entry = $('#prefix_entry' + prefix.id);
	prefix_entry.data('prefix_id', prefix.id);
	prefix_entry.addClass('prefix_entry');
	prefix_entry.append('<div id="prefix_row' + prefix.id + '">');
	var prefix_row = $('#prefix_row' + prefix.id );
	if (prefix.match == true) {
		prefix_row.addClass("row_match");
	} else {
		prefix_row.addClass("row_collateral");
	}

	// add indent and prefix container
	prefix_row.append('<div id="prefix_ind_pref' + prefix.id + '">');
	var prefix_ind_pref = $('#prefix_ind_pref' + prefix.id);
	prefix_ind_pref.addClass('prefix_column');
	prefix_ind_pref.addClass('prefix_ind_pref');

	// add indent
	prefix_ind_pref.append('<div id="prefix_indent' + prefix.id + '">');
	var prefix_indent = $('#prefix_indent' + prefix.id);
	prefix_indent.addClass("prefix_indent");
	prefix_indent.html('<div id="prefix_exp' + prefix.id + '"></div>');
	var prefix_exp = $('#prefix_exp' + prefix.id);
	prefix_exp.addClass("prefix_exp");

	// If the prefixes has children  (or we do not know), add expand button
	if (prefix.children == 0 || hasMaxPreflen(prefix)) {

		// the prefix_indent container must contain _something_
		prefix_exp.html('&nbsp;');

	} else {

		// add expand button
		prefix_exp.html('+');
		$('#prefix_exp' + prefix.id).click(function (e) {
			collapseClick(prefix.id);
			e.stopPropagation();
		});

		// If we are sure that the children has been fetched, the group will
		// already be fully expanded and a minus sign should be shown
		if (prefix.children == 1) {
			prefix_exp.html("-");
		}

	}

	prefix_indent.width(prefix_indent.width() + 30 * prefix.indent);

	// add prefix
	prefix_ind_pref.append('<div id="prefix_prefix' + prefix.id + '">');
	var prefix_prefix = $('#prefix_prefix' + prefix.id);
	prefix_prefix.addClass('prefix_column');
	prefix_prefix.addClass('prefix_prefix');

	// Different actions for different list types...
	// First: select a prefix in the list
	if (prefix_link_type == 'select') {

		prefix_prefix.html('<a href="javascript:void(0);" onClick="selectPrefix(' +
			prefix.id + '); return false;">' + prefix.display_prefix + '</a>');

	// Add prefix to pool
	} else if (prefix_link_type == 'add_to_pool') {

		prefix_prefix.html('<a href="/pool/add_prefix/' + pool_id + '?prefix=' +
			prefix.id + '&schema=' + schema_id + '" onClick="addToPool(' + prefix.id +
			'); return false;">' + prefix.display_prefix + '</a>');

	// Or edit prefix
	} else {
		prefix_prefix.html(prefix.display_prefix);
	}

	// add button
	prefix_row.append('<div id="prefix_button_col' + prefix.id + '">')
	var prefix_button_col = $('#prefix_button_col' + prefix.id);
	prefix_button_col.addClass('prefix_column');
	prefix_button_col.addClass('prefix_button_col');
	prefix_button_col.append('<div id="prefix_button' + prefix.id + '" data-prefix-id="' + prefix.id + '">');

	var prefix_button = $('#prefix_button' + prefix.id);
	prefix_button.addClass('minibutton');
	prefix_button.addClass('prefix_button');
	prefix_button.html("<div class='prefix_button_icon' class='prefix_button_icon'>&nbsp;</div>");
	prefix_button.click(prefix, function(e) {
		showPrefixMenu(e.currentTarget.getAttribute('data-prefix-id'));
		e.preventDefault();
		e.stopPropagation();
	});

	// Add prefix type
	prefix_row.append('<div id="prefix_type' + prefix.id + '">');
	var prefix_type = $('#prefix_type' + prefix.id);
	prefix_type.addClass('prefix_column');
	prefix_type.addClass('prefix_type');
	prefix_type.append('<div id="prefix_type_icon' + prefix.id + '">');
	var prefix_type_icon = $('#prefix_type_icon' + prefix.id);
	prefix_type_icon.addClass('prefix_type_icon');
	prefix_type_icon.addClass('prefix_type_' + prefix.type);

	// Add tooltip to prefix type icon
	prefix_type_icon.addClass('tooltip');
	prefix_type_icon.attr('title', prefix.type[0].toUpperCase() + prefix.type.slice(1));
	prefix_type_icon.html(prefix.type[0].toUpperCase());

	// Add order number
	prefix_row.append('<div id="prefix_order_id' + prefix.id + '">');
	var prefix_order_id = $('#prefix_order_id' + prefix.id);
	prefix_order_id.addClass('prefix_column');
	prefix_order_id.addClass('prefix_order_id');
	if (prefix.order_id == null || prefix.order_id == '') {
		prefix_order_id.html("&nbsp;");
	} else {
		prefix_order_id.html(prefix.order_id);
	}

	// Add node
	prefix_row.append('<div id="prefix_node' + prefix.id + '">');<commit_msg>Prefix menu only displayed on prefix search page<commit_after>/**********************************************************************
 *
 * NIPAP JavaScript functions
 *
 *********************************************************************/

/**
 * Global variables...
 */
var schema_id = 0;

/*
 * The prefix_list variable is used to keep a copy of all the prefixes
 * currently in the displayed list. Before adding, they are given a new
 * attribute: children. It is used to save information regarding
 * whether the prefix has children or not. These values are allowed:
 *  -2: We have no clue
 *  -1: At least one, but might be more (used for parent prefixes which
 *	  was received when a prefix further down was requested including
 *	  parents)
 *   0: No children
 *  >0: Has children
 */
var prefix_list = Object();
var pool_list = new Object();
var indent_head = Object();

// Object for storing statistics
var stats = Object();

// current options - mostly used for prefix addition on
// the prefix add and pool edit pages.
var cur_opts = new Object();

/*
 * Holy shit, this is an ugly hack...
 * As the prefix search results sometimes need to link to an edit-prefix-page
 * and sometimes need to link to a select-prefix function, we keep the current
 * state in a global variable...
 */
var prefix_link_type = 'edit';

// Max prefix lengths for different address families
var max_prefix_length = [32, 128];

var current_query = {
	'query_string': '',
	'parents_depth': 0,
	'children_depth': 0
};
var query_id = 0;
var newest_query = 0;

var offset = 0;
var outstanding_nextpage = 0;
var end_of_result = 0;

var search_key_timeout = 0;

// store current container for inserting prefixes
var container = null;

/**
 * A general log function
 *
 * This will try to post a message to the javascript console available in
 * Chrome / Firefox, if that's not available, it'll try opera and thereafter
 * give up.
 */
function log(msg) {
	try {
		console.log(msg);
	} catch (error) {
		try {
			window.opera.postError(msg)
		} catch (error) {
			// no console available
		}
	}
}

/**
 * Display a notice popup
 *
 * @param title string Title to display above error message.
 * @param msg Message to display to user.
 */
function showDialogNotice(title, msg) {

	var dialog = $('<div>' + msg + '</div>')
		.dialog({
			autoOpen: false,
			resizable: false,
			show: 'fade',
			hide: 'fade',
			open: function() {
				$('.ui-widget-overlay').hide().fadeIn('fast');
				$(this).parents(".ui-dialog:first").find(".ui-dialog-titlebar").addClass("ui-dialog-question");
				$(this).parents(".ui-dialog:first").find(".ui-dialog-titlebar-close").remove();
				$(this).parents(".ui-dialog:first").find(":button:eq(0)").addClass("button button_green");
			},
			modal: true,
			title: title,
			buttons: [
				{
					style: 'margin: 10px; width: 50px;',
					text: "OK",
					click: function() { $(this).dialog("close"); }
				}
			]
		});

	dialog.dialog('open');

	return false;
}

/*
 * Show general confirm dialog
 */
function showDialogYesNo(title, msg, target) {
	var dialog = $('<div>' + msg + '</div>')
		.dialog({
			autoOpen: false,
			resizable: false,
			show: 'fade',
			hide: 'fade',
			open: function() {
				$('.ui-widget-overlay').hide().fadeIn('fast');
				$(this).parents(".ui-dialog:first").find(".ui-dialog-titlebar").addClass("ui-dialog-question");
				$(this).parents(".ui-dialog:first").find(".ui-dialog-titlebar-close").remove();
				// XXX: this is a bloody hack to override the jquery ui CSS
				//		classes, or rather remove them and have our standard
				//		button look instead
				$(this).parents(".ui-dialog:first").find(":button").removeClass("ui-state-default");
				$(this).parents(".ui-dialog:first").find(":button").removeClass("ui-state-focus");
				$(this).parents(".ui-dialog:first").find(":button:eq(0)").addClass("button button_red");
				$(this).parents(".ui-dialog:first").find(":button:eq(1)").addClass("button button_green");
			},
			modal: true,
			title: title,
			buttons: [
				{
					style: 'margin: 10px; width: 50px;',
					text: "Yes",
					click: target,
				},
				{
					style: 'margin: 10px; width: 50px;',
					text: "No",
					click: function() { $(this).dialog("close"); }
				}
			]
		});

	dialog.dialog('open');

	return dialog;
}


/*
 * Show general dialog
 */
function showDialog(id, title, content, width) {
	// figure out default width
	if (width == undefined) {
		width = 400;
	} else {
		width = parseInt(width);
	}

	var c = '<div>' + content + '</div>';

	var dialog = $(c)
		.dialog({
			autoOpen: false,
			resizable: false,
			show: 'fade',
			hide: 'fade',
			open: function() {
				$('.ui-widget-overlay').hide().fadeIn('fast');
			},
			modal: true,
			width: width,
			title: title
		});

	dialog.dialog('open');

	return dialog;
}


/*
 * Display the search help "pop-up".
 */
function displaySearchHelp() {

	var c = '';
	c += '<div class="dialog_text" style="padding: 15px;">' +
		"Searching is the primary method of navigating the many thousand of prefixes that NIPAP is built to handle. It's very similar to how popular search engines, such as Google, Yahoo or Bing, are used." +
		"<h4>Matching text</h4>Just as with any search engine, you can enter a word or multiple words to match the text information associated with a prefix, that is the description or comment field. Each word is treated as a search 'term' and all search terms are joined together by the boolean operator AND. That means that searching for <i>'<b>foo bar</b>'</i> will be interpreted as a search for <i>'<b>foo</b>'</i> and <i>'<b>bar</b>'</i>. Any match must contain both the word <i>'<b>foo</b>'</i> and the word <i>'<b>bar</b>'</i>, though not necessarily in that order. <i>'<b>bar foo</b>'</i> will match, just as <i>'<b>foo</b> test test <b>bar</b>'</i> will match." +
		   "<h4>IP addresses / prefixes</h4>Searching for IP addresses are specially treated and will only match in the IP address column. Simply enter a complete IPv4 or IPv6 address and it will be automatically interpreted as an IP address.<br/><br/>It is possible to match entire prefixes and all their content by searching for a prefix in CIDR notation (e.g. 192.168.1.0/24). Searching for 192.168.1.x or 192.168.1 (as some would expect to match everything in 192.168.1.0/24) will not work as they will not be interpreted as prefixes but as text." +
		   "<h3>Examples</h3>" +
		   "To find what the IP address 192.168.1.1 is used for:<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;192.168.1.1" +
		   "<br/><br/>To list all addresses inside 172.16.0.0/24:<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;172.16.0.0/24" +
		   "<br/><br/>To match prefixes with 'TEST-ROUTER-1' in description or comment and that are somewhere in the network 10.0.0.0/8:<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;10.0.0.0/8 TEST-ROUTER-1" +
		   '<br/><br/></div>';

	d = showDialog('search_help', 'Searching', c, 800);

	return false;
}

/*
 * Error handler for ajax-errors.
 *
 * Function designed to be passed as error callback to i.e. getJSON().
 */
function ajaxErrorHandler(e, jqXHR, ajaxSettings, thrownError) {

	showDialogNotice('Server error', thrownError);

}


/**********************************************************************
 *
 * Prefix list functions
 *
 *********************************************************************/

/*
 * Toggles a collapse group
 */
function toggleGroup(id) {

	var col = $('#collapse' + id);

	if (col.css('display') == 'none') {
		expandGroup(id);
	} else {
		collapseGroup(id);
	}

}

/*
 * Expands a collapse group
 */
function expandGroup(id) {

	var col = $('#collapse' + id);
	var exp = $('#prefix_exp' + id);

	col.slideDown();
	if (prefix_list[id].children < 0) {
		// there might be additional children, display a +!
		exp.html('+');
	} else {
		exp.html('&ndash;');
	}

}

/*
 * Collapse a collapse group
 */
function collapseGroup(id) {

	var col = $('#collapse' + id);
	var exp = $('#prefix_exp' + id);

	col.slideUp();
	exp.html('+');

}

/*
 * Clear all state related to prefix search
 */
function clearPrefixSearch() {
	$('#prefix_list').empty();
	$('#search_interpret_container').empty();
	$('#search_result_help').show();
	$('#nextpage').hide();
	query_id += 1;

	current_query = {
		'query_string': '',
		'parents_depth': 0,
		'children_depth': 0
	};
}

/*
 * Wrapper for pacing our searches somewhat
 *
 * 200ms works out to be a pretty good delay for a fast typer
 */
function prefixSearchKey() {
	clearTimeout(search_key_timeout);
	search_key_timeout = setTimeout("performPrefixSearch()", 200);
}

/*
 * Perform a search operation
 */
function performPrefixSearch(explicit) {
	if (explicit != true) {
		explicit = false;
	}

	// Skip search if query string empty
	if (jQuery.trim($('#query_string').val()).length < 1) {
		clearPrefixSearch();
		return true;
	}
	end_of_result = 0;

	// Keep track of search timing
	stats.query_sent = new Date().getTime();

	var search_q = {
		'query_id': query_id,
		'query_string': $('#query_string').val(),
		'schema': schema_id,
		'parents_depth': optToDepth($('input[name="search_opt_parent"]:checked').val()),
		'children_depth': optToDepth($('input[name="search_opt_child"]:checked').val()),
		'include_all_parents': 'true',
		'include_all_children': 'false',
		'max_result': 50,
		'offset': 0
	}

	// Skip search if it's equal to the currently displayed search
	if (
		(search_q.query_string == current_query.query_string &&
		 search_q.parents_depth == current_query.parents_depth &&
		 search_q.children_depth == current_query.children_depth)
		 && explicit == false) {
		return true;
	}


	current_query = search_q;
	query_id += 1;
	offset = 0;

	$('#prefix_list').empty();
	$('#search_result_help').hide();

	showLoadingIndicator($('#prefix_list'));
	$.getJSON("/xhr/smart_search_prefix", current_query, receivePrefixList);

	// add search options to URL
	setSearchPrefixURI(explicit);

}


/*
 * Extract search options and add to URI
 */
function setSearchPrefixURI(explicit) {

	var url = $.url();
	var url_str = "";

	url_str = url.attr('protocol') + '://' + url.attr('host');

	if (url.attr('port') != null) {
		url_str += ":"+url.attr('port');
	}

	url_str += url.attr('path');

	if (url.attr('query') != null) {
		url_str += '?' + url.attr('query');
	}

	url_str += '#query_string=' +
		encodeURIComponent($('#query_string').val()) +
		'&search_opt_parent=' +
		encodeURIComponent($('input[name="search_opt_parent"]:checked').val()) +
		'&search_opt_child=' +
		encodeURIComponent($('input[name="search_opt_child"]:checked').val()) +
		'&explicit=' + encodeURIComponent(explicit);

    log(url_str);

	window.location.href = url_str;

}


/*
 * Called when next page of results is requested by the user.
 */
function performPrefixNextPage () {
	if (outstanding_nextpage == 1 || end_of_result == 1 ||
		jQuery.trim($('#query_string').val()).length < 1) {
		return;
	}
	outstanding_nextpage = 1;

	offset += 49;

	current_query.query_id = query_id;
	current_query.offset = offset;

	query_id += 1;

	$.getJSON("/xhr/smart_search_prefix", current_query, receivePrefixListNextPage);

}


/*
 * Add a prefix to the prefix list.
 *
 * A prefix can be placed in two ways, either in a container or relative to
 * another prefix. If a container is given, the prefix is simply added to it. If
 * relative placement is asked for, the prefix can instead be placed before or
 * after an already existing prefix.
 *
 * The 'relative' option can either be null, when the prefix is to be placed in
 * a parent container, or an object with two attributes: 'reference', which is the
 * DOM entry to base the placement on, and 'orientation' which can be the
 * strings 'before' or 'after'.
 */
function showPrefix(prefix, parent_container, relative) {

	// add main prefix container
	if (relative == null) {
		parent_container.append('<div id="prefix_entry' + prefix.id + '" data-prefix-id="' + prefix.id + '">');
	} else {
		if (relative.orientation == 'before') {
			relative.reference.before('<div id="prefix_entry' + prefix.id + '">');
		} else {
			relative.reference.after('<div id="prefix_entry' + prefix.id + '">');
		}
	}

	var prefix_entry = $('#prefix_entry' + prefix.id);
	prefix_entry.data('prefix_id', prefix.id);
	prefix_entry.addClass('prefix_entry');
	prefix_entry.append('<div id="prefix_row' + prefix.id + '">');
	var prefix_row = $('#prefix_row' + prefix.id );
	if (prefix.match == true) {
		prefix_row.addClass("row_match");
	} else {
		prefix_row.addClass("row_collateral");
	}

	// add indent and prefix container
	prefix_row.append('<div id="prefix_ind_pref' + prefix.id + '">');
	var prefix_ind_pref = $('#prefix_ind_pref' + prefix.id);
	prefix_ind_pref.addClass('prefix_column');
	prefix_ind_pref.addClass('prefix_ind_pref');

	// add indent
	prefix_ind_pref.append('<div id="prefix_indent' + prefix.id + '">');
	var prefix_indent = $('#prefix_indent' + prefix.id);
	prefix_indent.addClass("prefix_indent");
	prefix_indent.html('<div id="prefix_exp' + prefix.id + '"></div>');
	var prefix_exp = $('#prefix_exp' + prefix.id);
	prefix_exp.addClass("prefix_exp");

	// If the prefixes has children  (or we do not know), add expand button
	if (prefix.children == 0 || hasMaxPreflen(prefix)) {

		// the prefix_indent container must contain _something_
		prefix_exp.html('&nbsp;');

	} else {

		// add expand button
		prefix_exp.html('+');
		$('#prefix_exp' + prefix.id).click(function (e) {
			collapseClick(prefix.id);
			e.stopPropagation();
		});

		// If we are sure that the children has been fetched, the group will
		// already be fully expanded and a minus sign should be shown
		if (prefix.children == 1) {
			prefix_exp.html("-");
		}

	}

	prefix_indent.width(prefix_indent.width() + 30 * prefix.indent);

	// add prefix
	prefix_ind_pref.append('<div id="prefix_prefix' + prefix.id + '">');
	var prefix_prefix = $('#prefix_prefix' + prefix.id);
	prefix_prefix.addClass('prefix_column');
	prefix_prefix.addClass('prefix_prefix');

	// Different actions for different list types...
	// First: select a prefix in the list
	if (prefix_link_type == 'select') {

		prefix_prefix.html('<a href="javascript:void(0);" onClick="selectPrefix(' +
			prefix.id + '); return false;">' + prefix.display_prefix + '</a>');

	// Add prefix to pool
	} else if (prefix_link_type == 'add_to_pool') {

		prefix_prefix.html('<a href="/pool/add_prefix/' + pool_id + '?prefix=' +
			prefix.id + '&schema=' + schema_id + '" onClick="addToPool(' + prefix.id +
			'); return false;">' + prefix.display_prefix + '</a>');

	// Or edit prefix
	} else {
		prefix_prefix.html(prefix.display_prefix);
	}

	// add button if list not used to select prefix to add to pool or select
	// prefix to allocate from
	if (prefix_link_type != 'select' && prefix_link_type != 'add_to_pool') {
		prefix_row.append('<div id="prefix_button_col' + prefix.id + '">');
		var prefix_button_col = $('#prefix_button_col' + prefix.id);
		prefix_button_col.addClass('prefix_column');
		prefix_button_col.addClass('prefix_button_col');
		prefix_button_col.append('<div id="prefix_button' + prefix.id + '" data-prefix-id="' + prefix.id + '">');

		var prefix_button = $('#prefix_button' + prefix.id);
		prefix_button.addClass('minibutton');
		prefix_button.addClass('prefix_button');
		prefix_button.html("<div class='prefix_button_icon' class='prefix_button_icon'>&nbsp;</div>");
		prefix_button.click(prefix, function(e) {
			showPrefixMenu(e.currentTarget.getAttribute('data-prefix-id'));
			e.preventDefault();
			e.stopPropagation();
		});
	}

	// Add prefix type
	prefix_row.append('<div id="prefix_type' + prefix.id + '">');
	var prefix_type = $('#prefix_type' + prefix.id);
	prefix_type.addClass('prefix_column');
	prefix_type.addClass('prefix_type');
	prefix_type.append('<div id="prefix_type_icon' + prefix.id + '">');
	var prefix_type_icon = $('#prefix_type_icon' + prefix.id);
	prefix_type_icon.addClass('prefix_type_icon');
	prefix_type_icon.addClass('prefix_type_' + prefix.type);

	// Add tooltip to prefix type icon
	prefix_type_icon.addClass('tooltip');
	prefix_type_icon.attr('title', prefix.type[0].toUpperCase() + prefix.type.slice(1));
	prefix_type_icon.html(prefix.type[0].toUpperCase());

	// Add order number
	prefix_row.append('<div id="prefix_order_id' + prefix.id + '">');
	var prefix_order_id = $('#prefix_order_id' + prefix.id);
	prefix_order_id.addClass('prefix_column');
	prefix_order_id.addClass('prefix_order_id');
	if (prefix.order_id == null || prefix.order_id == '') {
		prefix_order_id.html("&nbsp;");
	} else {
		prefix_order_id.html(prefix.order_id);
	}

	// Add node
	prefix_row.append('<div id="prefix_node' + prefix.id + '">');
{'fraction_non_alphanumeric': 0.1794646642039605, 'fraction_numerical': 0.00987101071882759, 'mean_word_length': 4.167735878579252, 'pattern_counts': {'":': 12, '<': 167, '<?xml version=': 0, '>': 163, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 16, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6182118', 'n_tokens_mistral': 11519, 'n_tokens_neox': 10574, 'n_words': 2855}