/** Class of the application logic which processes data for the view. */
var Logic = {

	// Default column set labels for fingerprint columns, cd columns and non-cd and similarity value columns
	columnSet: {fingerprint: 'FP', cd: 'CD', noncd: 'NON-CD', similarity: 'SIM'},
	// Format of MSketch input structure
	appletMolFormat: 'mrv',
	// Similarity tag name
	similarityTag: 'DissimilarityIndex',
	// Separator of query options
	separator: '!',
	// List of numeric column types
	numericColumnTypes: ['INTEGER', 'DOUBLE', 'FLOAT', 'NUMBER(10,0)'],
	// Length of sortable formula numbers
	sortableLength: 5,

	// Index of the selected table or relation tree
	slcted: null,
	// Array for relation trees
	relationTrees: [],
	// Array for table names
	tables: [],
	// Array for table meta data, e.g. Logic.tableMetaData[0] is the meta data of Logic.tables[0]
	// 0: table type number, 1: similarity type, 2: screening config
	tableMetaData: [],
	// Array for column lists, e.g. Logic.tableColumns[0] is the column list of Logic.tables[0]
	tableColumns: [],
	// Indicates whether the current table is writable or not
	isWritable: false,
	// Structure strings
	molecules: [],
	// Query options string in Cartridge-style format
	queryOptions: '',
	// Search type of the last query
	searchType: '',
	// Hit color and alignment options string
	colorAndAlignmentOptions: '',
	// Markush options
	markush: {filter: null, mode: null, homology: null, code: null, alignScaffold: null, coloring: null},
	// Search parameters in the uri
	uriParam: {srch: null, molecule: null, condition: null, option: null, conditionDetailed: []},
	// Sorting
	sorting: {column: null, asc: null},
	// Indicates whether a query molecule has been given at least once in a search or not
	lastQueryMolecule: null,

	/**
	 * Interpret url query to set Query and Options pane parameters.
	 */
	processUrl: function() {
		// Url is encoded so a lot of special characters are replaced with % plus alphanumeric characters
		var params = window.location.search.match(new RegExp('tablename=[A-Za-z0-9_.-]+|t=[0-9]+|s=[A-Za-z]+|q=[A-Za-z0-9@%_.)(\x5B-\x5E-]+|c=[A-Za-z0-9%_.)(-]+|o=['+Logic.separator+'A-Za-z0-9%_.+-]+', 'g'));
		if (params != null) {
			for (var i = 0; i < params.length; i++) {
				var param = params[i].split('=');
				switch (param[0]) {
					case 'tablename':
						var idx;
						if (View.isRelationalMode()) {
							idx = Logic.getRelationTreeIndexByName(param[1]);
						} else {
							idx = Logic.getTableIndexByName(param[1]);
						}
						Logic.slcted = (idx == null ? 0 : idx);
						break;
					case 't':
						if (View.isRelationalMode()) {
							Logic.slcted = (param[1] >= Logic.relationTrees.length ? 0 : param[1]);
						} else {
							Logic.slcted = (param[1] >= Logic.tables.length ? 0 : param[1]);
						}
						break;
					case 's':
						Logic.uriParam.srch = param[1];
						break;
					case 'q':
						Logic.uriParam.molecule = unescape(param[1]);
						break;
					case 'c':
						var conditionStr = unescape(param[1]);
						Logic.uriParam.condition = Logic.convertUrlToSql(conditionStr);
						Logic.processConditionsInUrl(conditionStr);
						break;
					case 'o':
						Logic.uriParam.option = unescape(param[1]);
						break;
				}
			}
		}
	},

	/**
	 * Interpret condition part in the url to set Query pane parameters.
	 *
	 * @param conditionString   The condition part of the url.
	 */
	processConditionsInUrl: function(conditionString) {
		var conditions = conditionString.split(' ');
		var columns = Logic.getColumnsArray();
		// State of the analyzer. Next word is 0: column name, 1: operator, 2: operand
		var state = 0;
		var column;
		for (var i = 0; i < conditions.length; i++) {
			switch (state) {
				case 0:
					if (Util.inArray(conditions[i], columns)) {
						Logic.uriParam.conditionDetailed[conditions[i]] = {operator: '', operand: []};
						column = conditions[i];
						state = 1;
					}
					break;
				case 1:
					Logic.uriParam.conditionDetailed[column].operator = conditions[i];
					state = 2;
					break;
				case 2:
					if (conditions[i] != 'and') {
						if (conditions[i].charAt(0) == "'" && conditions[i].charAt(conditions[i].length-1) == "'") {
							conditions[i] = conditions[i].substr(1, conditions[i].length-2);
						}
						Logic.uriParam.conditionDetailed[column].operand.push(conditions[i]);
					}
					if (Logic.uriParam.conditionDetailed[column].operator != 'between' || 
						Logic.uriParam.conditionDetailed[column].operand.length > 1) {
						state = 0;
					}
					break;
			}
		}
	},

	/**
	 * Process table information XML.
	 *
	 * @param data   The XML string containing the table information.
	 */
	processTableInfo: function(data) {
		var xml = XML.parse(data);
		var tables = $(xml).find('Table');
		if (tables == 0) {
			alert('No tables were found.');
		} else {
			tables.each(function(i) {
				Logic.tables[i] = $(this).find('TableName').text();
				Logic.tableColumns[i] = [];
				$(this).find('ColumnName').each(function(j) {
					var columnName = $(this).text().toLowerCase();
					var columnType = $(this).next('ColumnType').text();
					var chemicalTerms = $(this).next('ColumnType').next('ChemicalTermsExpression').text();
					var columnGroup;
					if (columnName.match(/^cd_fp[0-9]+$/) != null) {
						columnGroup = Logic.columnSet.fingerprint;
					} else if (columnName.match(/^cd_/) != null) {
						columnGroup = Logic.columnSet.cd;
					} else if (columnName == Logic.similarityTag) {
						columnGroup = Logic.columnSet.similarity;
					} else {
						columnGroup = Logic.columnSet.noncd;
					}
					Logic.tableColumns[i][j] = {name: columnName, type: columnType, group: columnGroup, chemicalTerms: chemicalTerms};
				});
				var tableType = $(this).find('TableType').text().replace(/ .+$/, '').toLowerCase();
				var fingerprint = [];
				var screening = [];
				switch (tableType) {
					case 'molecules':
						fingerprint = ['Chemical Hashed FingerPrint'];
						screening = [['Default']];
						break;
					case 'reactions':
						fingerprint = ['Reaction Fingerprint'];
						screening = [['ReactantTanimoto', 'ProductTanimoto', 'CoarseReactionTanimoto', 'MediumReactionTanimoto', 'StrictReactionTanimoto']];
						break;
					default:
						break;
				}
				Logic.tableMetaData[i] = [tableType, fingerprint, screening];
			});
		}
		if (Logic.slcted == null) {
			for (var i = 0; i < Logic.tables.length; i++) {
				if (Logic.tables[i] == Cache.cfg['guiAttributes']['defaultResults'].val) {
					Logic.slcted = i;
					return;
				}
			}
			Logic.slcted = 0;
		}
	},

	/**
	 * Process relationship information XML.
	 *
	 * @param data   The XML string containing the table information.
	 */
	processRelationInfo: function(data) {
		var xml = XML.parse(data);
		var relationTrees = $(xml).find('Relationship');
		if (relationTrees.length == 0) {
			alert('No relationship definitions were found.');
		} else {
			relationTrees.each(function(h) {
				Logic.relationTrees[h] = {name: $(this).find('TreeName').text(), tables: [], tableColumns: [], tableMetaData: []};
				$(this).find('Table').each(function(i) {
					Logic.relationTrees[h].tables[i] = $(this).find('TableName').text();
					Logic.relationTrees[h].tableColumns[i] = [];
					$(this).find('ColumnName').each(function(j) {
						var columnName = $(this).text().toLowerCase();
						var columnType = $(this).next('ColumnType').text();
						var columnGroup;
						if (columnName.match(/^cd_fp[0-9]+$/) != null) {
							columnGroup = Logic.columnSet.fingerprint;
						} else if (columnName.match(/^cd_/) != null) {
							columnGroup = Logic.columnSet.cd;
						} else {
							columnGroup = Logic.columnSet.noncd;
						}
						Logic.relationTrees[h].tableColumns[i][j] = {name: columnName, type: columnType, group: columnGroup};
					});
					var tableType = $(this).find('TableType').text().replace(/ .+$/, '').toLowerCase();
					var fingerprint = [];
					var screening = [];
					switch (tableType) {
						case 'molecules':
							fingerprint = ['Chemical Hashed FingerPrint'];
							screening = [['Default']];
							break;
						case 'reactions':
							fingerprint = ['Reaction Fingerprint'];
							screening = [['ReactantTanimoto', 'ProductTanimoto', 'CoarseReactionTanimoto', 'MediumReactionTanimoto', 'StrictReactionTanimoto']];
							break;
						default:
							break;
					}
					Logic.relationTrees[h].tableMetaData[i] = [tableType, fingerprint, screening];
				});
			});
		}
		if (Logic.slcted == null) {
			for (var i = 0; i < Logic.relationTrees.length; i++) {
				if (Logic.relationTrees[i].name == Cache.cfg['guiAttributes']['defaultResults'].val) {
					Logic.slcted = i;
					return;
				}
			}
			Logic.slcted = 0;
		}
	},

	/**
	 * Return the query molecule is set in Marvin Sketch.
	 *
	 * @return string
	 */
	getQuery: function() {
		if (Logic.molecules[View.marvin.query.applet] == null || Logic.molecules[View.marvin.query.applet].ws == null) {
			return '';
		}
		return Logic.molecules[View.marvin.query.applet].ws;
	},

	/**
	 * Return the currently selected table or relational tree name.
	 *
	 * @return string
	 */
	getCurrentName: function() {
		return (View.isRelationalMode() ? Logic.relationTrees[Logic.slcted].name : Logic.tables[Logic.slcted]);
	},

	/**
	 * Return the currently used structure table, even if we are in relational mode.
	 *
	 * @return string
	 */
	getStructureTableName: function() {
		return (View.isRelationalMode() ? Logic.relationTrees[Logic.slcted].tables[0] : Logic.tables[Logic.slcted]);
	},

	/**
	 * Return the currently used structure table type, even if we are in relational mode.
	 *
	 * @return string
	 */
	getStructureTableType: function() {
		return (View.isRelationalMode() ? Logic.relationTrees[Logic.slcted].tableMetaData[0][0] : Logic.tableMetaData[Logic.slcted][0]);
	},

	/**
	 * Return the currently used fingerprint for similarity search, even if we are in relational mode.
	 *
	 * @return string
	 */
	getFingerprint: function() {
		return (View.isRelationalMode() ? Logic.relationTrees[Logic.slcted].tableMetaData[0][1] : Logic.tableMetaData[Logic.slcted][1]);
	},

	/**
	 * Return the currently used screening configuration, even if we are in relational mode.
	 *
	 * @return string
	 */
	getScreeningConfig: function() {
		return (View.isRelationalMode() ? Logic.relationTrees[Logic.slcted].tableMetaData[0][2] : Logic.tableMetaData[Logic.slcted][2]);
	},
	/**
	 * Return the index of a table given by its name.
	 *
	 * @param tableName   Name of the table.
	 * @return integer, or null if not found.
	 */
	getTableIndexByName: function(tableName) {
		for (var i = 0; i < Logic.tables.length; i++) {
			if (Logic.tables[i] == tableName) {
				return i;
			}
		}
		return null;
	},

	/**
	 * Return the index of a relation tree given by its name.
	 *
	 * @param treeName   Name of the relation tree.
	 * @return integer, or null if not found.
	 */
	getRelationTreeIndexByName: function(treeName) {
		for (var i = 0; i < Logic.relationTrees.length; i++) {
			if (Logic.relationTrees[i].name == treeName) {
				return i;
			}
		}
		return null;
	},

	/**
	 * Return relation tree names in relational mode.
	 *
	 * @return array
	 */
	getRelationTreeNames: function() {
		var treeList = [];
		for (var i = 0; i < Logic.relationTrees.length; i++) {
			treeList.push(Logic.relationTrees[i].name);
		}
		return treeList;
	},

	/**
	 * Return the field names for a selected table or relation tree.
	 *
	 * @param structureFieldsOnly   Boolean, it matters only in relational mode.
	 * @return array of column names.
	 */
	getFieldsForSelected: function(structureFieldsOnly) {
		if (View.isRelationalMode()) {
			if (structureFieldsOnly == true && Logic.relationTrees[Logic.slcted] != null) {
				return Logic.relationTrees[Logic.slcted].tableColumns[0];
			}
			var fieldList = [];
			for (var i = 0; i < Logic.relationTrees[Logic.slcted].tableColumns.length; i++) {
				fieldList = fieldList.concat(Logic.relationTrees[Logic.slcted].tableColumns[i]);
			}
			return fieldList;
		}
		return Logic.tableColumns[Logic.slcted];
	},

	/**
	 * Gets the first table it finds in the relation tree for a given field name.
	 *
	 * return string, the name of the correspondent table, or null if not found.
	 */
	getTableForRelatedColumn: function(columnName) {
		var tableName;
		var dotLocation = columnName.indexOf('.');
		if (dotLocation != -1) {
			tableName = columnName.substr(0, dotLocation);
			return tableName;
		}
		for (var i = 0; i < Logic.relationTrees[Logic.slcted].tableColumns.length; i++) {
			for (var j = 0; j < Logic.relationTrees[Logic.slcted].tableColumns[i].length; j++) {
				if (columnName.toLowerCase() == Logic.relationTrees[Logic.slcted].tableColumns[i][j].name.toLowerCase()) {
					return Logic.relationTrees[Logic.slcted].tables[i];
				}
			}
		}
		return null;
	},

	/**
	 * Get the size of molecule images, and create a dummy image if there is not any image to check.
	 *
	 * @return object with properties of x (image width) and y (image height).
	 */
	getImageSize: function() {
		if (View.image.width == 0 || View.image.height == 0) {
			if ((typeof('image') == "function" && typeof(image.prototype) == "object") == false) {
				// Create a dummy image if there is not any image to retrieve the size
				var dummyImage = View.createDummyElement('img', 'image');
			}
			var iWidth = $('.image').css('width');
			var iHeight = $('.image').css('height');
			View.image.width = (iWidth == null ? 0 : parseInt(iWidth));
			View.image.height = (iHeight == null ? 0 : parseInt(iHeight));
			if (dummyImage != null) {
				View.removeDummyElement(dummyImage);
			}
		}
		return {x: View.image.width, y: View.image.height};
	},

	/**
	 * Get the size of the MSketch applet.
	 *
	 * @param containerId   Id of the container element. Its CSS defines the applet size inside.
	 * @return object with properties of x (applet width) and y (applet height), or false in case of wrong configuration.
	 */
	getMarvinSize: function(containerId) {
		for (property in View.marvin) {
			if (View.marvin[property].container == containerId) {
				if (View.marvin[property].width == 0 || View.marvin[property].height == 0) {
					var containerObj = document.getElementById(containerId);
					if (containerObj != null) {
						View.marvin[property].width = containerObj.clientWidth;
						View.marvin[property].height = containerObj.clientHeight;
					}
				}
				return {x: View.marvin[property].width, y: View.marvin[property].height};
			}
		}
		return false;
	},

	/**
	 * Replace pre-defined columns sets to column names. Similarity markers are not substituted.
	 *
	 * @param configColumns   The configured column list to display given in string. The column names and sets are separated by space characters from each other.
	 * @return string.
	 */
	replacePredefinedSets: function(configColumns) {
		var existingColumns = Logic.getFieldsForSelected();
		var addColumns = {fingerprint: '', cd: '', noncd: ''};
		for (var i = 0; i < existingColumns.length; i++) {
			switch (existingColumns[i]['group']) {
				case Logic.columnSet.fingerprint:
					if (!addColumns.fingerprint.match(new RegExp('(^| )'+existingColumns[i]['name']+'( |$)', 'g'))) {
							addColumns.fingerprint += existingColumns[i]['name']+' ';
					}
					break;
				case Logic.columnSet.cd:
					if (!addColumns.cd.match(new RegExp('(^| )'+existingColumns[i]['name']+'( |$)', 'g'))) {
							addColumns.cd += existingColumns[i]['name']+' ';
					}
					break;
				case Logic.columnSet.noncd:
					if (!addColumns.noncd.match(new RegExp('(^| )'+existingColumns[i]['name']+'( |$)', 'g'))) {
							addColumns.noncd += existingColumns[i]['name']+' ';
					}
					break;
			}
		}
		var oldColumnList;
		do {
			oldColumnList = configColumns;
			configColumns = configColumns.replace(new RegExp('(^| )'+Logic.columnSet.fingerprint+'( |$)', 'g'), ' '+addColumns.fingerprint);
		} while (oldColumnList != configColumns);
		do {
			oldColumnList = configColumns;
			configColumns = configColumns.replace(new RegExp('(^| )'+Logic.columnSet.cd+'( |$)', 'g'), ' '+addColumns.cd);
		} while (oldColumnList != configColumns);
		do {
			oldColumnList = configColumns;
			configColumns = configColumns.replace(new RegExp('(^| )'+Logic.columnSet.noncd+'( |$)', 'g'), ' '+addColumns.noncd);
		} while (oldColumnList != configColumns);
		if (configColumns.charAt(0) == ' ') {
			configColumns = configColumns.substr(1, configColumns.length);
		}
		if (configColumns.charAt(configColumns.length-1) == ' ') {
			configColumns = configColumns.substr(0, configColumns.length-1);
		}
		return configColumns;
	},

	/**
	 * Get columns of the currently selected table, which should be displayed on the spreadsheet.
	 *
	 * @param fromCache   If true, column list comes from memory.
	 * @return string, in which columns are separated by space characters from each other.
	 */
	getColumns: function(fromCache) {
		var configColumns;
		if (fromCache == true && Cache.columnList != null) {
			configColumns = Cache.columnList;
		} else {
			var storageObj = Logic.getCurrentName();
			configColumns = (Cache.cfg['spreadsheet'][storageObj] != null ? Cache.cfg['spreadsheet'][storageObj].val : 
			Cache.cfg['spreadsheet']['default'].val);
		}
		configColumns = Logic.replacePredefinedSets(configColumns);
		if (!fromCache || Cache.columnList == null) {
			do {
				oldColumnList = configColumns;
				configColumns = configColumns.replace(new RegExp('(^| )'+Logic.columnSet.similarity+'( |$)', 'g'), ' ');
			} while (oldColumnList != configColumns);
			if (configColumns.charAt(0) == ' ') {
				configColumns = configColumns.substr(1, configColumns.length);
			}
			if (configColumns.charAt(configColumns.length-1) == ' ') {
				configColumns = configColumns.substr(0, configColumns.length-1);
			}
		}
		return configColumns;
	},

	/**
	 * Return the columns of the currently selected table in an array.
	 *
	 * @return array of columns.
	 */
	getColumnsArray: function() {
		var columns = Logic.getColumns();
		if (columns.length > 0) {
			var colArr = columns.split(' ');
			if (View.isRelationalMode()) {
				for (var i = 0; i < colArr.length; i++) {
					if (colArr[i].indexOf('.') == -1) {
						colArr[i] = Logic.getTableForRelatedColumn(colArr[i])+'.'+colArr[i];
					}
				}
			}
			return colArr;
		}
		return [];
	},

	/**
	 * Return the indices of those columns where similarity values should be displayed.
	 *
	 * @return zero-based integer array of column indices.
	 */
	getSimilarityPositions: function() {
		var configColumns = (Cache.cfg['spreadsheet'][Logic.tables[Logic.slcted]] != null ? 
			Cache.cfg['spreadsheet'][Logic.tables[Logic.slcted]].val : Cache.cfg['spreadsheet']['default'].val);
		configColumns = Logic.replacePredefinedSets(configColumns);
		var columnArray = configColumns.split(' ');
		var simPositions = [];
		var position = 0;
		var simFound = 0;
		for (var i = 0; i < columnArray.length; i++) {
			if (columnArray[i] == Logic.columnSet.similarity) {
				simFound++;
			} else {
				position++;
				simFound = 0;
			}
			simPositions[position] = simFound;
		}
		return simPositions;
	},

	/**
	 * Get the custom fields of the table.
	 *
	 * @return array
	 */
	getCustomFields: function() {
		var existingColumns = Logic.getFieldsForSelected(true);
		var columns = [];
		for (var i = 0; i < existingColumns.length; i++) {
			if (existingColumns[i]['group'] == Logic.columnSet.noncd && 
				existingColumns[i]['chemicalTerms'] == '') {
				columns.push(existingColumns[i]['name']);
			}
		}
		return columns;
	},

	/**
	 * Get the custom field values of the Insert pane.
	 *
	 * @return array
	 */
	getCustomFieldValues: function() {
		var fieldVals = [];
		$('#insert input.insertInput').each(function(i, inputElement) {
			fieldVals.push(inputElement.value);
		});
		return fieldVals;
	},

	/**
	 * Get the edited field values.
	 *
	 * @return array
	 */
	getEditedFieldValues: function() {
		var fieldVals = {columns: [], values: []};
		var customFields = Logic.getCustomFields();
		for (var i = 0; i < customFields.length; i++) {
			var textareaObj = document.getElementById(View.prefix.editTextarea+customFields[i]);
			if (textareaObj != null && textareaObj.value != View.originalFieldValues[customFields[i]]) {
				fieldVals.columns.push(customFields[i]);
				fieldVals.values.push(XML.convertSpecChars(textareaObj.value));
			}
		}
		return fieldVals;
	},

	/**
	 * Return the column value of a row from the current page result set.
	 *
	 * @param rowNumber      Zero-based number of the row.
	 * @param columnName     Name of the column.
	 * @return string
	 */
	getRowField: function(rowNumber, columnName) {
		var rowData = Result.getRowWithRelatedData(Cache.rowsXml, null, rowNumber);
		var columnIndex = rowData.indexOf('<'+columnName+'>');
		if (columnIndex > -1) {
			return rowData.substring(columnIndex+columnName.length+2, rowData.indexOf('</'+columnName+'>'));
		}
		if (columnName != columnName.toUpperCase()) {
			columnIndex = rowData.indexOf('<'+columnName.toUpperCase()+'>');
			if (columnIndex > -1) {
				return rowData.substring(columnIndex+columnName.length+2, rowData.indexOf('</'+columnName.toUpperCase()+'>'));
			}
		}
		if (columnName != columnName.toLowerCase()) {
			columnIndex = rowData.indexOf('<'+columnName.toLowerCase()+'>');
			if (columnIndex > -1) {
				return rowData.substring(columnIndex+columnName.length+2, rowData.indexOf('</'+columnName.toLowerCase()+'>'));
			}
		}
		return '';
	},

	/**
	 * Store the drawn molecule into the memory.
	 *
	 * @param marvinId   Id of the marvin applet which molecule is being stored of.
	 * @return string of the structure representation.
	 */
	storeQueryMolecule: function(marvinId) {
		if (document.getElementById(marvinId) != null && (navigator.appName == "Microsoft Internet Explorer" || 
			document.getElementById(marvinId).getMol != null)) {
			Logic.molecules[marvinId] = {};
			Logic.molecules[marvinId].url = document.getElementById(marvinId).getMol('cxsmarts');
			Logic.molecules[marvinId].ws = (Logic.molecules[marvinId].url == '' ? '' : document.getElementById(marvinId).getMol(Logic.appletMolFormat));
		}
		return (Logic.molecules[marvinId] != null ? Logic.molecules[marvinId].ws : '');
	},

	/**
	 * Retrieve the molecule from the MView applet.
	 *
	 * @return string, or null if an error occured.
	 */
	getEditedMolecule: function() {
		if (document.getElementById('MView') != null && (navigator.appName == "Microsoft Internet Explorer" || 
			document.getElementById('MView').getM != null)) {
			return document.getElementById('MView').getM(0, Logic.appletMolFormat);
		}
		return null;
	},

	/**
	 * Return the type of a column given by its name.
	 *
	 * @param columnName   Name of the column.
	 * @return string
	 */
	getColumnType: function(columnName) {
		var columnType = '';
		var existingColumns = Logic.getFieldsForSelected(true);
		for (var j = 0; j < existingColumns.length; j++) {
			if (existingColumns[j]['name'] == columnName) {
				columnType = existingColumns[j]['type'];
				break;
			}
		}
		return columnType;
	},

	/**
	 * Read from the config whether a table / relation tree is writable or not, and set the global flag depending on the result.
	 *
	 * @param storeName   Name of the table or relation tree.
	 */
	setWritableFlag: function(storeName) {
		if (Cache.cfg['spreadsheet'][storeName] != null) {
			if (Cache.cfg['spreadsheet'][storeName].attrs['writable'] != null) {
				Logic.isWritable = (Cache.cfg['spreadsheet'][storeName].attrs['writable'] == 'true' ? true : false);
				return;
			}
		} else if (Cache.cfg['spreadsheet']['default'].attrs['writable'] != null) {
			Logic.isWritable = (Cache.cfg['spreadsheet']['default'].attrs['writable'] == 'true' ? true : false);
			return;
		}
		Logic.isWritable = false;
	},

	/**
	 * Get string generated by Option pane settings.
	 *
	 * @param isSimilarity   True, if similarity specific options should be visualized, false otherwise.
	 * @return string
	 */
	getOptions: function(isSimilarity) {
		var result = '';
		if (isSimilarity == true) {
			var screeningConfig = $('#screeningConfig').val();
			if ($('#similarityType').val() != 'none' && screeningConfig != 'Default') {
				result += Logic.separator+'dissimilarityMetric:'+screeningConfig;
			}
			result += Logic.separator+'simThreshold:'+$('#similarityThreshold').val();
		}
		result += Logic.separator+'maxHitCount:'+$('#maxHits').val()+
			Logic.separator+'maxTime:'+(parseInt($('#maxTime').val())*1000*60)+
			Logic.separator+'returnNonHits:'+($('#inverseResultSet').attr('checked') == true ? 'y':'n');
		$('#advanced input').each(function() {
			if ($(this).attr('checked') == true) {
				var inputName = $(this).attr('name');
				result += Logic.separator+inputName+':'+$(this).val();
			}	
		});
		if ($('#chemTermsArea').val() != '') {
			result += Logic.separator+'ctFilter:'+XML.convertSpecChars($('#chemTermsArea').val());
		}
		return result;
	},

	/**
	 * Get where condition string generated by Query pane settings.
	 *
	 * @return string, or null if there are no conditions to be set for columns.
	 */
	getSqlCondition: function() {
		var condition = '';
		var settings = Logic.getQuerySettings();
		if (settings.columnNames.length < 1) {
			return null;
		}
		var first = true;
		for (var i = 0; i < settings.columnNames.length; i++) {
			if (settings.columnNames[i] != null) {
				var columnNameParts = settings.columnNames[i].split('.');
				var schema = "";
				var field;
				if (columnNameParts.length > 1) {
					schema = columnNameParts[0] + '.';
					field = columnNameParts[1];
				} else {
					field = columnNameParts[0];
				}
				if (Cache.cfg['guiAttributes']['formulaComparison'].val == 'chemical' && field.toLowerCase() == 'cd_formula' && 
					Util.inArray(settings.operators[i], ['<', '>', 'between']) && 
					(!View.isRelationalMode() || Logic.getTableForRelatedColumn('cd_sortable_formula') != null)) {

					settings.columnNames[i] = schema + (field == field.toUpperCase() ? 'CD_SORTABLE_FORMULA' : 'cd_sortable_formula');
					for (j in settings.args[i]) {
						if (!isNaN(j)) {
							settings.args[i][j] = Logic.convertFormulaToSortable(settings.args[i][j]);
						}
					}
				}
				if (View.isRelationalMode() && !settings.columnNames[i].match(/\./)) {
					settings.columnNames[i] = Logic.getTableForRelatedColumn(settings.columnNames[i])+'.'+settings.columnNames[i];
				}
				if (!first) {
					condition += ' and '+settings.columnNames[i]+' ';
				} else {
					condition += settings.columnNames[i]+' ';
					first = false;
				}
				var columnType = Logic.getColumnType(settings.columnNames[i]);
				var quote = (Util.inArray(columnType, Logic.numericColumnTypes)  ? "" : "'");
				switch (settings.operators[i]) {
					case 'between':
						condition += "between "+quote+settings.args[i][0]+quote+" and "+quote+settings.args[i][1]+quote;
						break;
					case 'starts with':
						condition += "like '"+settings.args[i][0]+"%'";
						break;
					case 'ends with':
						condition += "like '%"+settings.args[i][0]+"'";
						break;
					case 'contains':
						condition += "like '%"+settings.args[i][0]+"%'";
						break;
					case 'is':
						condition += "= '"+settings.args[i][0]+"%'";
						break;
					case 'before':
						condition += "< '"+settings.args[i][0]+"'";
						break;
					case 'after':
						condition += "> '"+settings.args[i][0]+"'";
						break;
					default:
						condition += settings.operators[i]+" "+quote+settings.args[i][0]+quote;
						break;
				}
			}
		}
		return condition;
	},

	/**
	 * Get where condition string generated by Query pane settings.
	 *
	 * @return string, or null if there are no conditions to be set for columns.
	 */
	getUrlCondition: function() {
		var condition = '';
		var settings = Logic.getQuerySettings();
		if (settings.columnNames.length < 1) {
			return null;
		}
		var first = true;
		for (var i = 0; i < settings.columnNames.length; i++) {
			if (settings.columnNames[i] != null) {
				if (View.isRelationalMode() && !settings.columnNames[i].match(/\./)) {
					settings.columnNames[i] = Logic.getTableForRelatedColumn(settings.columnNames[i])+'.'+settings.columnNames[i];
				}
				if (!first) {
					condition += ' and '+settings.columnNames[i]+' ';
				} else {
					condition += settings.columnNames[i]+' ';
					first = false;
				}
				var columnType = Logic.getColumnType(settings.columnNames[i]);
				var quote = (Util.inArray(columnType, Logic.numericColumnTypes) ? "" : "'");
				switch (settings.operators[i]) {
					case 'between':
						condition += "between "+quote+settings.args[i][0]+quote+" and "+quote+settings.args[i][1]+quote;
						break;
					case 'starts with':
					case 'ends with':
					case 'contains':
						condition += settings.operators[i].replace(/ /, '_')+" '"+settings.args[i][0]+"'";
						break;
					default:
						condition += settings.operators[i]+" "+quote+settings.args[i][0]+quote;
						break;
				}
			}
		}
		return condition;
	},

	/**
	 * Check the Query pane element configuraton, and store the values in an object.
	 *
	 * @return object, which has three arrays, one for column names, one for the operators and one for operands.
	 */
	getQuerySettings: function() {
		var settings = {columnNames: [], operators: [], args: []};
		var columns = Logic.getColumnsArray();
		$('#query select.querySelect').each(function(i) {
			var selectValue = $(this).val().replace(/_/g, ' ');
			settings.args[i] = [];
			if (selectValue != 'none') {
				var selectNum = $(this).attr('id').substr(View.prefix.columnCondition.length);
				settings.columnNames[i] = columns[selectNum];
				settings.operators[i] = selectValue;
				$('div#'+View.prefix.queryInput+selectNum+' input.queryArg').each(function(j) {
					settings.args[i][j] = $(this).val();
				});
			}
		});
		return settings;
	},

	/**
	 * Convert conditions can be received in the url to a sql condition which can be used in WHERE section.
	 *
	 * @param urlCondition   Condition in the url.
	 * @return string
	 */
	convertUrlToSql: function(urlCondition) {
		urlCondition = urlCondition.replace(/is/g, '=').replace(/before/g, '<').replace(/after/g, '>');
		urlCondition = urlCondition.replace(/starts_with '([\S]+)'/g, "like '$1%'").replace(/ends_with '([\S]+)'/g, "like '%$1'").replace(/contains '([\S]+)'/g, "like '%$1%'");
		return urlCondition;
	},

	/**
	 * Make a Cartridge-style query string according to the current query configuration, which can be used 
	 * as a web service paramerer, and store this value in the memory.
	 *
	 * @return string
	 */
	makeQueryData: function() {
		if (Logic.uriParam.srch == null) {
			Logic.searchType = View.getSearchType();
		} else {
			Logic.searchType = Logic.uriParam.srch;
			Logic.uriParam.srch = null;
		}
		var result ='sep='+Logic.separator+' t:'+Logic.searchType;
		if (Logic.uriParam.option == null) {
			result += Logic.getOptions(Logic.searchType == 't');
		} else {
			result += Logic.uriParam.option;
			Logic.uriParam.option = null;
		}
		var orderBy = null;
		if (Logic.sorting.column != null) {
			orderBy = ' order by '+Logic.sorting.column+(Logic.sorting.asc ? '' : ' desc');
		}
		var jchemTable = (View.isRelationalMode() ? Logic.relationTrees[Logic.slcted].tables[0]: Logic.tables[Logic.slcted]);
		if (Logic.uriParam.condition == null) {
			var where = Logic.getSqlCondition();
			result += XML.convertSpecChars(Logic.separator+'filterQuery:select cd_id from '+jchemTable+(where == null ? '' : ' where '+where)+(orderBy == null ? '' : orderBy));
		} else {
			result += XML.convertSpecChars(Logic.separator+'filterQuery:select cd_id from '+jchemTable+' where '+Logic.uriParam.condition);
			Logic.uriParam.condition = null;
		}
		Logic.queryOptions = result;
		Logic.makeColoringData();
		return Logic.queryOptions;
	},

	/**
	 * Make a Cartridge-style hit coloring string according to the current coloring configuration, which can be used 
	 * as a web service paramerer, and store this value in the memory. 
	 *
	 * @param hitColoring     Hit coloring option string. If null, it is set to 'y'.
	 * @param alignmentMode   Alignment option string. If null, it is set to 'rotate'.
	 * @return string
	 */
	makeColoringData: function(hitColoring, alignmentMode) {
		if (hitColoring == null) {
			hitColoring = 'y';
		}
		if (alignmentMode == null) {
			alignmentMode = (document.getElementById('alignment') == null ? 'rotate' : $('#alignment').val());
		}
		Logic.colorAndAlignmentOptions = (hitColoring == 'y' ? 'coloring:'+hitColoring+' alignmentMode:'+alignmentMode+' hitColor:'+Cache.cfg['guiAttributes']['hitColor'].val : '');
		return Logic.colorAndAlignmentOptions;
	},

	/**
	 * Set sorting for a column. If the same column is selected twice in-a-row, the ascending order will change.
	 *
	 * @param columnName   Name of the field to be sorted.
	 */
	orderByColumn: function(columnName) {
		var columnNameParts = columnName.split('.');
		var schema = "";
		var field;
		if (columnNameParts.length > 1) {
			schema = columnNameParts[0] + '.';
			field = columnNameParts[1];
		} else {
			field = columnNameParts[0];
		}
		if (Cache.cfg['guiAttributes']['formulaComparison'].val == 'chemical' && field.toLowerCase() == 'cd_formula' && 
			(!View.isRelationalMode() || Logic.getTableForRelatedColumn('cd_sortable_formula') != null)) {

			columnName = schema + (field == field.toUpperCase() ? 'CD_SORTABLE_FORMULA' : 'cd_sortable_formula');
		}		
		if (Logic.sorting.column == null || Logic.sorting.column != columnName) {
			Logic.sorting = {column: columnName, asc: true};
		} else {
			Logic.sorting = {column: columnName, asc: !Logic.sorting.asc};
		}
	},

	/**
	 * Convert a CD_FORMULA string to CD_SORTABLE_FORMULA format.
	 *
	 * @param formula   The cd_formula formatted string.
	 * @return cd_sortable_formula formatted string.
	 */
	convertFormulaToSortable: function(formula) {
		var sortableFormula = '';
		var atoms = formula.split(/[0-9]+/);
		var numbers = formula.split(/[^0-9]+/);
		for (var i = 0; i < atoms.length-1; i++) {
			sortableFormula += atoms[i];
			var zeros = '';
			for (var j = 0; j < Logic.sortableLength - numbers[i+1].length; j++) {
				zeros += '0';
			} 
			sortableFormula += zeros + numbers[i+1];
		}
		return sortableFormula;
	}
};
