//-----------------------------------------------------------------------------
//	Copyright (C) 2007, 2009 Humboldt-Universitaet zu Berlin
//
//	This library is free software; you can redistribute it and/or
//	modify it under the terms of the GNU Lesser General Public
//	License as published by the Free Software Foundation; either
//	version 2.1 of the License, or (at your option) any later version.
//
//	This library is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//	Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public
//	License along with this library; if not, write to the Free Software
//	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//-----------------------------------------------------------------------------

/**
 * @file XmlLog.js
 * @author Ronald Kluth
 * @date created at 2007/04/04
 * @brief A javascript which handles parsing and display of ODEMx XML output
 * @sa XmlWriter
 * @since 2.0
 */

//----------------------------------------------------------------------globals

// 'var xmlListFile' and 'var xmlLogScriptIncluded' are defined before this
// script is included in HTML

// provide a means to check for the inclusion of this script, the value of
// this variable is checked in the HTML document after this script was
// supposed to be included, an error message is displayed if it is 'false'
xmlLogScriptIncluded = true;

// the filter object which stores several hashes of record properties
// that can be filtered in the Log display
var filter = new LogFilter();

// the log handler is responsible for handling the log files
// and which one of them is currently displayed
var logHandler = new LogFileHandler();

//-----------------------------------------------------initialization functions

// start log display after parsing the first Log file
// and initialization of the necessary menus
function init() {
log('init()');

//alert( "script loaded" );

	parseXmlLog( xmlListFile );
	initMenus();
	showFilteredData();
}

// use log handler info to fill menu boxes for filtering the log display
var menusInitialized = false;
function initMenus() {
log('initMenus()');

	// period selection and log only need to be initialized once
	if ( ! menusInitialized ) {

		// fill an HTML select menu with the file values
		var html = '<select ' +
				   'onchange="' +
				   'changeFile( this.options[this.selectedIndex].value )">';

		// iterate over the whole xml snapshot
		for ( var j = 0; j < logHandler.file.length; ++j ) {
			// create a new select option with value = logHandler.file index
			html += '<option value="'+ j +'">';
			html += logHandler.file[j].start + "-";
			html += logHandler.file[j].end
			html += '</option>';
		}

		// add an otion for all files
		html += '<option value="all">all</option>';

		html += '</select>';

		// populate the select menu
		$('periodSelect').innerHTML = html;

		// initialize the log window
		$('logToggle').onclick = function () { toggle( 'log' ); };

		$('filterReset').onclick = function () { filter.reset(); };
	}

	// ----------------------------------------------------------------
	// this part will be executed again when the displayed file changes
	// new filter records might have been added when parsing a new file

	// populate the filter panels
	$('mtnDisplay').innerHTML = hashToHTML( filter.recordTexts, 'mtnEntry', "recordText" );
	$('mtiDisplay').innerHTML = hashToHTML( filter.recordChannels, 'mtiEntry', "recordChannel" );
	$('mtsDisplay').innerHTML = hashToHTML( filter.recordScopes, 'mtsEntry', "recordScope" );
	$('slDisplay').innerHTML = hashToHTML( filter.senderLabels, 'slEntry', "senderLabel" );
	$('stDisplay').innerHTML = hashToHTML( filter.senderTypes, 'stEntry', "senderType" );

	// add onclick events to all record filters in xxxDisplays
	// which call toggleFilter() for the selection
	addClickHandler( "mtnEntry", "recordText" );
	addClickHandler( "mtiEntry", "recordChannel" );
	addClickHandler( "mtsEntry", "recordScope" );
	addClickHandler( "slEntry", "senderLabel" );
	addClickHandler( "stEntry", "senderType" );

	menusInitialized = true;
}

//----------------------------------------------------------------------parsing

// parsing requires the xml list file which contains information about all
// XML files generated during the simulation: file name, start time, end time
function parseXmlLog( listFile ) {
log('parseXmlLog()');

	showInfo( "Retrieving simulation data..." );

	// read the xml list file, returns XMLDocument object
	var xmlFiles = loadXml( listFile );

	// get all 'file' details from XML list file, returns XPathResult object
	var files = xmlFiles.evaluate( "//file",
								   xmlFiles,
								   null,
								   XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
								   null );

	var name, start, end;

	// fill the logHandler with the file information:
	// file name, start time, end time
	for ( var i = 0; i < files.snapshotLength; ++i ) {

		name = files.snapshotItem(i).getAttribute('name');
		start = files.snapshotItem(i).getAttribute('starttime');
		end = files.snapshotItem(i).getAttribute('endtime');

		// construct a log file object and add it to the logHandler
		// the record tree is left out here because it takes too long
		// to parse all files at once -> see: changeFile()
		logHandler.file.push( new File ( name, start, end ) );
	}

	// set the handler to display the first log file by default
	if ( logHandler.file[0].name )
		logHandler.currentFileIndex = 0;
	else
		alert( "Error: XML start file not initialized" )

	// --------------------------------------------------
	// only the first file is parsed 'onload' of the page

	log( logHandler.file[0].name );

	// get the log record tree from the current file
	loadLogRecords( 0 );
}


// fill the log handler record tree from file stored at the given index
function loadLogRecords( fileIndex ) {
log('loadLogRecords( ' + fileIndex + ' )')

	// if the file has not been parsed yet, do it now
	if ( logHandler.file[ fileIndex ].allRecords == 0 ) {

		// get the record tree from the current file
		var xmlTree = loadXml( logHandler.file[ fileIndex ].name );

		// get all record nodes from the XML tree
		var tree = xmlTree.evaluate( "//record",
									  xmlTree,
									  null,
									  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
									  null );

		// set log handler record tree
		logHandler.file[ fileIndex ].allRecords = buildRecordArrayAndFilter( tree );

		// the filter displays must be rechecked, possibly new handlers added
		initMenus();
	}
}

//--------------------------------------------------------------html production

// this function takes an XPathResult XML snapshot,
// which in this case is the record tree of an XML file,
// and builds the RecordArray and the possible filters
function buildRecordArrayAndFilter( xpathRecordTree ) {
log('buildRecordArrayAndFilter()');

	// result empty, display message
	if ( xpathRecordTree.snapshotLength == 0 ) {
		showInfo( "Nothing to display." );
		return 0;
	}

	// the return Array which gets filled with Record()s
	var recordArray = new Array();

	// variables needed to describe a record without details
	var recordTime, recordChannel, recordText, recordScope;
	var senderType, senderLabel;

	// iterate over the whole XML record snapshot,
	// get complete record info and add fields to filter
	for ( var i = 0; i < xpathRecordTree.snapshotLength; ++i )
	{
		recordTime = xpathRecordTree.snapshotItem(i).getElementsByTagName('time')[0].textContent;
		// time is not filtered

		recordChannel = xpathRecordTree.snapshotItem(i).getElementsByTagName('channel')[0].textContent;
		filter.add( "recordChannel", recordChannel );

		recordText = xpathRecordTree.snapshotItem(i).getElementsByTagName('text')[0].textContent;
		filter.add( "recordText", recordText );

		// scope might not exist; must get node list and check
		var scopeNodes = xpathRecordTree.snapshotItem(i).getElementsByTagName('scope');
		if( scopeNodes.length > 0 )
		{
			recordScope = scopeNodes[0].textContent.replace( /</g, '&lt;' ).replace( />/g, '&gt;' );
			filter.add( "recordScope", recordScope );
		}

		senderLabel = xpathRecordTree.snapshotItem(i).getElementsByTagName('senderlabel')[0].textContent;
		filter.add( "senderLabel", senderLabel );

		senderType = xpathRecordTree.snapshotItem(i).getElementsByTagName('sendertype')[0]
					.textContent.replace( /</g, '&lt;' ).replace( />/g, '&gt;' );
		filter.add( "senderType", senderType );

		// store the retrieved information in a record
		var currentRecord = new Record( recordTime, recordChannel, recordText,
										recordScope, senderLabel, senderType );

		// print details belonging to this event
		var details = xpathRecordTree.snapshotItem(i).getElementsByTagName('detail');

		// temporary local vars
		var detailName;
		var detailValue;

		// add details to the xpathRecordTree tag list
		for ( var det_i = 0; det_i < details.length; ++det_i )
		{
			// get name and value
			detailName = details[det_i].getElementsByTagName('name')[0].textContent;
			detailValue = details[det_i].getElementsByTagName('value')[0].textContent;

			currentRecord.details.push( new Detail( detailName, detailValue ) );
		}

		// store the complete record
		recordArray.push( currentRecord );
	} // end of record loop

	return recordArray;
}

// this function builds HTML from the RecordArray
// of the currently active log file
function recordArrayToHTML( fileIndex ) {
log('recordArrayToHTML()');

	var newHTML = ""; 		// the output stream
	var lastSimTime = null;	// remember the records' simulation time
	var lastSender = "";	// suppress printing the same sender
	var firstRecordAtTime = false;

	// variables needed to describe a record without details
	var recordTime, recordChannel, recordText, recordScope;
	var senderType, senderLabel;

	// global logHandler.currentFileIndex is set
	// whenever the time selector is accessed
	var records = logHandler.file[ fileIndex ].allRecords;

	// iterate over the whole record array
	for ( var i = 0; i < records.length; ++i )
	{
		// get complete record info and immediately check the filter
		// if the record does not pass all checks, the loop continues immediately
		recordText = records[i].text;
		if ( !filter.pass( "recordText", recordText ) ) continue;
		recordScope = records[i].scope;
		if ( !filter.pass( "recordScope", recordScope ) ) continue;
		recordChannel = records[i].channel;
		if ( !filter.pass( "recordChannel", recordChannel ) ) continue;
		senderLabel = records[i].senderLabel;
		if ( !filter.pass( "senderLabel", senderLabel ) ) continue;
		senderType = records[i].senderType;
		if ( !filter.pass( "senderType", senderType ) ) continue;
		recordTime = records[i].time;

		// to print sender at start of time box, even if repeated
		firstRecordAtTime = false;

		// check for change in the simulation time of a record
		if ( recordTime != lastSimTime ) {

			// new simulation time: close previous time box
			if ( lastSimTime != null )
			{
				newHTML += "</table>" + // timeTable
						   "</div>";    // timeBox
			}
			// remember time of current timeBox;
			lastSimTime = recordTime;

			// build new time box containing a table
			newHTML += "<div class='timeBox' title='Simulation Time:" + recordTime + "'>" +
					   "<h3>Simulation Time: " + recordTime + "</h3>" +
					   "<table class='timeTable'>" +
					   "<tr><th class='th_sender'>Sender Label</th>" +
					   "<th class='th_event'>Record Type</th>" +
					   "<th class='th_tag'>Details</th></tr>";

			// remember the start of a new time box, so a sender will be shown
			firstRecordAtTime = true;
		}

		// build new record row
		newHTML += "<tr>";

		// check if sender label needs to be displayed or if it's still the same
		if ( senderLabel != lastSender || firstRecordAtTime ) {
			// show sender label
			newHTML += "<td class='sender' title='" + senderType + "'>" +
					   senderLabel +
					   "</td>";

			lastSender = senderLabel;
		}
		else {
			// leave senderlabel empty if still the same
			newHTML += "<td class='empty'>&nbsp;</td>";
		}
		// get event name
		newHTML += "<td class='event' " +
				"title='" + recordScope + " " + recordChannel +"'>" +
				recordText +
				"</td>";

		// print details belonging to this event
		var details = records[i].details
		if ( details.length > 0 )
		{
			// print details in the third column
			for ( var det_i = 0; det_i < details.length; ++det_i )
			{
				if ( det_i > 0 ) {
					// start row, leave first two columns empty
					newHTML += "</tr><tr><td class='empty' colspan='2'>&nbsp;</td>";
				}
				newHTML += "<td class='tag'>";

				// get name and value
				detailName = details[det_i].name;
				detailValue = details[det_i].value;

				// print details and treat comments
//				if ( detailName != "comment" ) {
					newHTML += "<div class='tagName'>" +
							   detailName +
							   "</div>" +
							   "<div class='tagValue'>" +
							   detailValue +
							   "</div>";
//				}
//				else
//				{
//					newHTML += "<div class='comment'>" +
//							   detailValue +
//							   "</div>";
//				}

				// close detail
				newHTML += "</td>";
			}
		}
		else { // no details
			newHTML += "<td>&nbsp;</td>";
		}

		// close event box and draw spacer line to next event
		newHTML += "</tr>";
		newHTML += "<tr><td class='spacer' colspan='3'></td></tr>";

	} // end of record loop

	// close last time box
	newHTML += "</table>";
	newHTML += "</div>";

	return newHTML;
}

//------------------------------------------------------------display functions

// display the record array of the currently selected file,
// called on init and on click of a filter choice
function showFilteredData() {
log('showFilteredData()');

	// in some browsers the info works, in others it doesn't
	// still, make an attempt to have this shown
	showInfo( "Retrieving simulation data..." );

	// this is where the parsed info gets displayed
	$('content').innerHTML = "";

	// the user may choose to display all info at once, or only one file
	if ( logHandler.currentFileIndex != "all" ) {

		// display the chosen file's content
		$('content').innerHTML = recordArrayToHTML( logHandler.currentFileIndex );
	}
	else {

		// display all files' content by appendinng to 'content'
		for ( var file_i = 0; file_i < logHandler.file.length; ++file_i ) {
			$('content').innerHTML += recordArrayToHTML( file_i );
		}
	}

	// in case there's nothing to display, tell the user
	if ( $('content').innerHTML.length > 0 )
		showContent();
	else
		showInfo( "Nothing to display" );

	// redraw the filter, i.e. correct the filtered entries' style
	// sometimes when loading a new file, the 'line-through' is gone
	filter.redraw();
}

// a file is parsed into a record array if it has not been loaded before,
// called when the period selector is accessed
function changeFile( newFileIndex ) {
log('changeFile( ' + newFileIndex + ' )');

	// if all files are to be shown, all trees have to be built in memory
	if ( newFileIndex == "all" ) {

		// confirm potentially long load time
		if ( confirm( "Loading data from all files may take a long time.\n\n" +
					  "Do you want to continue?" ) )
		{
			// build all record trees
			for ( var i = 0; i < logHandler.file.length; ++i ) {

				loadLogRecords( i );
			}
		}
		else { // if the action was cancelled above
			return;
		}
	}
	else { // only one file to be loaded, do it
		loadLogRecords( newFileIndex );
	}

	// remember which file is currently active
	logHandler.currentFileIndex = newFileIndex;

	// display the current file
	showFilteredData();
}

//-------------------------------------------------------------helper functions

// show an info box to tell the user what's being done
// this, however is not shown consistently on all browsers
function showInfo( infoText ) {
log('showInfo( ' + infoText + ' )');

	$('content').style.display = 'none';
	$('info').innerHTML = "<p>" + infoText + "</p>";
	$('info').style.display = 'block';
}

// display the content area, called after 'content' was re-filled
function showContent() {
log('showContent()');

	$('info').style.display = 'none';
	$('content').style.display = 'block';
}

// provides easy access to all id'ed elements of the document
function $( elementId ) {
	return document.getElementById( elementId );
}

// numeric ordering function
function ascendingOrder(a, b){ return (a-b); }

// fill an HTML table with the contents of the given filter hash
function hashToHTML( outputHash, tdClass, which ) {
log('hashToHTML( ' + tdClass + ' )');

	// use a temporary array to sort the output
	var tmp = new Array();

	// copy hash keys ( filter entries ) into array
	for ( var element in outputHash ) {
		tmp.push( element );
	}

	// sort the filter entries to be displayed
	// check for numeric values, which need a different sorting function
	if ( !isNaN( tmp[0] ) ){
		tmp.sort( ascendingOrder );
	}
	else {
		tmp.sort();
	}

	// output is stored in a 3-column-table
	var html = "<table>";
	var entries = 0;

	// get the corresponding HTML ID hash
	var ids = eval( "filter." + which + "ElementHtmlIDs;" );

	// add buttons to the table for switching the selection (all, none, invert)
	html += "<tr>" +
			"<td nowrap id='" + tdClass + "All' class='selSwitcher'>" +
			"Filter All" +
			"</td>" +
			"<td nowrap id='" + tdClass + "None' class='selSwitcher'>" +
			"Filter None" +
			"</td>" +
			"<td nowrap id='" + tdClass + "Invert' class='selSwitcher'>" +
			"Invert Selection" +
			"</td>" +
			"</tr>";

	// fill the table
	for ( var i = 0; i < tmp.length; ++i ) {

		// start row
		if ( (entries%3) == 0 )
			html += "<tr>";

		// remember IDs given to corresponding HTML elements
		ids[ tmp[i] ] = tdClass + i;

		// one entry, nowrap to avoid line wrapping in some browsers
		html += "<td nowrap id='" + tdClass + i + "' class='" + tdClass + "'>";
		html += tmp[i] + "</td>";

		// end row
		if ( (entries%3) == 2 )
			html += "</tr>";

		// counter to end row after every third entry
		++entries;
	}

	// add proper amount of empty cells
	if ( (entries%3) == 1 )
		html += "<td></td><td></td></tr>";

	if ( (entries%3) == 2 )
		html += "<td></td></tr>"

	// end table
	html += "</table>";

	return html;
}

// useful function to get all elements belonging to one class
function getElementsByClassName( className, parentElement ) {
log('getElementsByClassName( ' + className + ' )');

	// xpath expression to find objects by className
  	xpath = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";

    // apply xpath to the document node
    var query = document.evaluate(xpath, $(parentElement) || document,
      			null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

    // fill result list and return it
    var results = [];
    for (var i = 0, length = query.snapshotLength; i < length; ++i) {
      results.push(query.snapshotItem(i));
    }
    return results;
}

// all filter entries need to have an onclick event attached,
// which will toggle the filtering of the corresponding record type
function addClickHandler( className, which ) {
log("addClickHandler( " + className + ", " + which + " )");

	// add onclick handler to selection switches (all, none, invert)
	eval( "$('" + className + "All').onclick = " +
		  "function () { filter.selectAll('" + which + "'); };" );

	eval( "$('" + className + "None').onclick = " +
		  "function () { filter.selectNone('" + which + "'); };" );

	eval( "$('" + className + "Invert').onclick = " +
		  "function () { filter.invert('" + which + "'); };" );

	// get list of all HTML elements with className
	try { var list = getElementsByClassName( className ); }
	catch(e) { alert( 'Error initializing dynamic record type choice' ); }

	// init record type entries with onclick event listener
	for ( var i = 0; i < list.length; ++i ) {
		$( list[i].id ).onclick =
		function () {
			filter.toggle( which, this.id );
		};
	}
}

// add the given string to the JS log window
function log( string ) {
	// add to the top row of the log-<div>
	var tmp = string + "<br />";
	tmp += $('log').innerHTML;
	$('log').innerHTML = tmp;
}

// visibility toggle for given element
function toggle( elementId ) {
log( 'toggle( ' + elementId + ' )' );

	$( elementId ).style.display =
		( $( elementId ).style.display == 'block' ) ? 'none' : 'block';
}

//-------------------------------------------------------------------XML loader

// get XMLHttp object
function createXmlHttp() {
log('createXmlHttp()');

	// get the object the standard way, or the Microsoft way
	try {
		return( new XMLHttpRequest() );
	}
	catch(e) {
		return( new ActiveXObject('Microsoft.XMLHTTP') );
	}
}

// load a given XML file by name
function loadXml( file ) {
log('loadXml( ' + file + ' )');

	// get the XMLHttp object
	var xmlhttp = createXmlHttp();

	// if object initialized, make request for XML file contents
	if ( xmlhttp != null )
	{
		xmlhttp.open( "GET", file, false );
		xmlhttp.send( null );
	}
	else // error
	{
		alert( "Cannot load XML file, this browser does not support XMLHTTP." )
	}

	// read the xml document (implicit cast)
	return xmlhttp.responseXML;
}

//----------------------------------------------------------------------classes


// object to store the complete record type information
function Record( recTime, recChannel, recText, recScope, recSenderLabel, recSenderType ) {
	this.time = recTime;
	this.channel = recChannel;
	this.text = recText;
	this.scope = recScope;
	this.senderLabel = recSenderLabel;
	this.senderType = recSenderType;
	this.details = [] // Array containing Detail()s
}

// object to store record detail information
function Detail( detName, detValue ) {
	this.name = detName;
	this.value = detValue;
}

// the filter object in JSON notation:
// uses hash arrays { keyStringToFilter : bool } to filter out records
// choices offered: MTS, MTN, MTI, SL, ST -- analogous to LogFilter
function LogFilter() {
	return {

		// filter entries
		recordScopes	: {},
		recordTexts		: {},
		recordChannels	: {},
		senderLabels	: {},
		senderTypes		: {},

		// corresponding filter entry IDs in the HTML document
		recordScopeElementHtmlIDs	: {},
		recordTextElementHtmlIDs	: {},
		recordChannelElementHtmlIDs	: {},
		senderLabelElementHtmlIDs	: {},
		senderTypeElementHtmlIDs	: {},

		// add an Element to the filter
		add:
		function( which, newElement ) {
			// if the element exists and is being filtered, don't
			if ( ! eval( "this." + which + "s[ newElement ] == true;" ) )
				eval( "this." + which + "s[ newElement ] = false;" );
		},

		// checks whether element value is true, meaning it must be filtered
		pass:
		function( which, testElement ) {
			return !( eval( "this." + which + "s[ testElement ] == true;" ) );
		},

		// sets the value of the element to block as true
		block:
		function( which, blockedElement ) {
			eval( "this." + which + "s[ blockedElement ] = true");
		},

		// sets the value of blocked element to false, so it will pass
		unblock:
		function( which, blockedElement ) {
			eval( "this." + which + "s[ blockedElement ] = false");
		},

		// switches the filtering of an entry between true and false and
		toggle:
		function( which, entryId ) {

			// replacement of '<' and '>' needed for C++ template type names
			var entry = $(entryId).textContent.replace( /</g, '&lt;' ).replace( />/g, '&gt;' );

			log('filter.toggle( ' + which + ', ' + entry + ' )');

			// if this record name is being filtered, unblock it
			if ( ! this.pass( which, entry ) ) {
				this.unblock( which, entry );
			}
			// this record name is currently not being filtered
			else {
				this.block( which, entry );
			}

			// display the filtered content
			showFilteredData();
		},

		// set all filter entries to true
		selectAll:
		function ( which ) {
		log('filter.selectAll( ' + which + ' )');

			// get filter hash and corresponding entry ID hash
			var hash = eval( "this." + which + "s;" );

			// set all entries in filter hash to true
			for ( var entry in hash ) {

				hash[entry] = true;
			}

			// display newly filtered Log
			showFilteredData();
		},

		// set all filter entries to false
		selectNone:
		function ( which ) {
		log('filter.selectNone( ' + which + ' )');

			// get filter hash and corresponding entry ID hash
			var hash = eval( "this." + which + "s;" );

			// set all entries in filter hash to false
			for ( var entry in hash ) {

				hash[entry] = false;
			}

			// display newly filtered Log
			showFilteredData();
		},

		// set all filter entries to their negation
		invert:
		function ( which ) {
		log('filter.invert( ' + which + ' )');

			// get filter hash and corresponding entry ID hash
			var hash = eval( "this." + which + "s;" );

			// negate all entries in the given filter hash
			for ( var entry in hash ) {

				hash[entry] = !hash[entry];
			}

			// display newly filtered Log
			showFilteredData();
		},

		// redraw the style of all filter entries
		redraw:
		function() {
		log('filter.redraw()');

			// all allowed eval selections
			which = [ "recordScope", "recordText", "recordChannel", "senderLabel", "senderType" ];

			// iterate over all filter hashes
			for ( var i = 0; i < which.length; ++i ) {

				// get current filter hash and ID hash
				var hash = eval( "this." + which[i] + "s;" );
				var ids = eval( "this." + which[i] + "ElementHtmlIDs;" );

				// iterate over all elements in the current hash
				for ( var entry in hash ) {

					// get the entry's ID in the document
					var entryId = ids[ entry ];

					// set style according to filtering status
					if ( hash[entry] == false ) {
						$( entryId ).style.textDecoration = "none";
						//$( entryId ).style.color = "white";
						$( entryId ).style.backgroundColor = "firebrick";
						hash[entry] = false;
					}
					else {
						$( entryId ).style.textDecoration = "line-through";
						//$( entryId ).style.color = "red";
						$( entryId ).style.backgroundColor = "black";
						hash[entry] = true;
					}
				}
			}
		},

		// reset all filter entries to false
		reset:
		function () {
		log('filter.reset()');

			// all allowed eval selections
			which = [ "recordScope", "recordText", "recordChannel", "senderLabel", "senderType" ];

			// iterate over all filter hashes
			for ( var i = 0; i < which.length; ++i ) {

				// get current filter hash and ID hash
				var hash = eval( "this." + which[i] + "s;" );

				// iterate over all elements in the current hash
				for ( var entry in hash ) {

					hash[entry] = false;
				}
			}

			// display newly filtered log
			showFilteredData();
		}
	}; // end tmp filter object
}

// the handler object constructor for all Log file data
function LogFileHandler() {
	this.currentFileIndex = undefined;	// the currently active Log file
	this.file = new Array(); 			// of File() objects
}

// the file object constructor
function File( fileName, startTime, endTime ) {
	this.name = fileName;	// actual file name
	this.start = startTime;	// simulation time of first log record
	this.end = endTime;		// simulation time of last log record
	this.allRecords = 0;	// will hold an Array() of Record() objects,
							// set to 0 to ensure it is only computed once
}
