/** Class for performing Ajax requests and processing responses. */
var Ajax = {

	// Connection id
	connectionId: null,
	// Id of the current search
	searchId: null,
	// Stores which molecules are displayed currently
	molecules: {first: null, limit: null},
	// Indicates if applet loading is in progress or not
	appletLoading: false,
	// Stores the last molecule number that was opened via MView
	lastAppletId: -1,

	/**
	 * Retrieve all table / relationship information.
	 */
	getTableInfo: function() {
		if (View.isRelationalMode()) {
			var wservice = Cache.cfg['soapPath']['tableinfoRel'].val;
			var wmethod = Cache.cfg['soapMethod']['tableinfoRel'].val;
			var relInfo = true;
		} else {
			var wservice = Cache.cfg['soapPath']['tableinfo'].val;
			var wmethod = Cache.cfg['soapMethod']['tableinfo'].val;
			var relInfo = false;
		}
		View.setProgressWait();
		SOAP.async = false;
		SOAP.send(wservice, wmethod, 
			[Cache.cfg['db']['driver'].val, Cache.cfg['db']['url'].val, Cache.cfg['db']['username'].val, Cache.cfg['db']['password'].val, Cache.cfg['db']['propertyTable'].val], 
			function(response) {
				try {
					Ajax.connectionId = XML.parse(response).getElementsByTagName('ConnectionHandlerId').item(0).firstChild.nodeValue;
					if (relInfo) {
						Logic.processRelationInfo(response);
					} else {
						Logic.processTableInfo(response);
					}
				} catch (e) {
					alert(e.message);
				} finally {
					View.setProgress();
				}
			}, function (response) {
				Login.deleteCookies();
				Ajax.errorHandler(response);
			}
		);
	},

	/**
	 * Close the last connection.
	 */
	closeConnection: function() {
		if (Ajax.connectionId != null) {	
			SOAP.async = false;
			SOAP.send(Cache.cfg['soapPath']['close'].val, Cache.cfg['soapMethod']['close'].val, [Ajax.connectionId], function(response) {}, Ajax.errorHandler);
		}
	},

	/**
	 * Start a new search using the current query configuration.
	 *
	 * @param async          The response handling will be asynchronous if true or null, otherwise synchronous.
	 * @param firstElement   Ordinal number of the element to be jumped after the query. Default is 0.
	 */
	setQuery: function(async, firstElement) {
		if (async == null) {
			async = true;
		}
		if (firstElement == null) {
			firstElement = 0;
		}
		Ajax.molecules.first = firstElement;
		Ajax.molecules.limit = Scrolling.elemsPerPage;
		var queryColumnList = Logic.getColumns();
		var molecule = '';
		if (Logic.uriParam.molecule != null) {
			molecule = Logic.uriParam.molecule;
			Logic.uriParam.molecule = null;
		} else {
			molecule = Logic.getQuery();
		}
		Logic.lastQueryMolecule = molecule;
		if (View.isRelationalMode()) {
			var storageObj = Logic.getRelationTreeNames()[Logic.slcted];
			var wservice = Cache.cfg['soapPath']['completeRel'].val;
			var wmethod = Cache.cfg['soapMethod']['completeRel'].val;
			var wparams = [Ajax.connectionId, storageObj, XML.convertSpecChars(molecule), Logic.queryOptions, 
				firstElement, Scrolling.elemsPerPage, parseInt(Cache.cfg['guiAttributes']['relatedMaxNumber'].val), 
				'empty', queryColumnList, null];
		} else {
			var storageObj = Logic.tables[Logic.slcted];
			var wservice = Cache.cfg['soapPath']['complete'].val;
			var wmethod = Cache.cfg['soapMethod']['complete'].val;
			var wparams = [Ajax.connectionId, storageObj, XML.convertSpecChars(molecule), Logic.queryOptions, firstElement, Scrolling.elemsPerPage, 'empty', queryColumnList, null];
		}
		if (Wndw.isOpen('markush')) {
			Wndw.close('markush');
		}
		View.setProgressWait();
		SOAP.async = async;
		SOAP.send(wservice, wmethod, wparams,
			function(response) {
				try {
					Cache.columnList = queryColumnList;
					var responseObj = XML.parse(response);
					Ajax.searchId = responseObj.getElementsByTagName('SearchId').item(0).firstChild.nodeValue;
					Scrolling.noElements = responseObj.getElementsByTagName('ResultCount').item(0).firstChild.nodeValue;
					Scrolling.setVisible(Scrolling.noElements > Scrolling.elemsPerPage);
					Scrolling.replaceScrollbar(firstElement);
					// Cache the XML response
					if (View.isRelationalMode()) {
						Cache.searchFieldsXml = XML.serialize(responseObj.getElementsByTagName('FieldInfo').item(0));
						Cache.rowsXml = XML.serialize(responseObj.getElementsByTagName('Rows').item(0));
					} else {
						Cache.rowsXml = Result.insertSimilarity(XML.serialize(responseObj.getElementsByTagName('Rows').item(0)));
					}
					View.displayResult('spreadsheetResult');
					if (View.mode == 'gui') {
						Event.sorting();
					}
					Ajax.displayImages(firstElement, Scrolling.elemsPerPage);
					View.setPreviewNumbers();
					for (idx in View.dynamicWindows) {
						if (View.dynamicWindows[idx].loaded != null) {
							View.dynamicWindows[idx].loaded = [];
						}
					}
				} catch (e) {
					alert(e.message);
				} finally {
					View.setProgress();
				}
			}, Ajax.errorHandler
		);
	},

	/**
	 * Request certain structures from the result set of the current search.
	 *
	 * @param from    The ordinal number of the first molecule should be requested from search result set.
	 * @param limit   Number of requested molecules after the first one.
	 * @param async   The response handling will be asynchronous if true, otherwise synchronous.
	 */
	getMolData: function(from, limit, async) {
		if (async == null) {
			async = true;
		}
		Ajax.molecules.first = from;
		Ajax.molecules.limit = limit;
		if (View.isRelationalMode()) {
			var wservice = Cache.cfg['soapPath']['getmoldataRel'].val;
			var wmethod = Cache.cfg['soapMethod']['getmoldataRel'].val;
			var wparams = [Ajax.searchId, from, limit, parseInt(Cache.cfg['guiAttributes']['relatedMaxNumber'].val), 'empty', Logic.getColumns(true), null];
		} else if (Ajax.molecules.first == from && limit < Ajax.molecules.limit) {
			// If the user resizes the window this way, the structures would change but they were subset of the 
			// previous results, so all of them can be found in the cache.

			// Cache the changed XML response
			Cache.rowsXml = Result.removeLastRows(Cache.rowsXml, limit);
			View.displayResult('spreadsheetResult');
			if (View.mode == 'gui') {
				Event.sorting();
			}
			Ajax.displayImages(from, limit);
			return;
		} else {
			var wservice = Cache.cfg['soapPath']['getmoldata'].val;
			var wmethod = Cache.cfg['soapMethod']['getmoldata'].val;
			var wparams = [Ajax.searchId, from, limit, 'empty', Logic.getColumns(true), null];
		}
		if (Wndw.isOpen('markush')) {
			Wndw.close('markush');
		}
		View.setProgressWait();
		SOAP.async = async;
		SOAP.send(wservice, wmethod, wparams, 
			function(response) {
				try {
					if (View.isRelationalMode()) {
						var responseObj = XML.parse(response);
						Cache.searchFieldsXml = XML.serialize(responseObj.getElementsByTagName('FieldInfo').item(0));
						Cache.rowsXml = XML.serialize(responseObj.getElementsByTagName('Rows').item(0));
					} else {
						Cache.rowsXml = Result.insertSimilarity(response);
					}
					View.displayResult('spreadsheetResult');
					if (View.mode == 'gui') {
						Event.sorting();
					}
					Ajax.displayImages(from, limit);
				} catch (e) {
					alert(e.message);
				} finally {
					View.setProgress();
				}
			}, Ajax.errorHandler
		);
	},

	/**
	 * Request a currenty displayed molecule.
	 *
	 * @param rowNum   Ordinal number of the the structure on the page.
	 * @param format   Output format.
	 * @return the result molecule string in the proper format.
	 */
	getMolecule: function(rowNum, format) {
		if (View.isRelationalMode()) {
			var wservice = Cache.cfg['soapPath']['getmoldataRel'].val;
			var wmethod = Cache.cfg['soapMethod']['getmoldataRel'].val;
			var wparams = [Ajax.searchId, Ajax.molecules.first+rowNum, 1, parseInt(Cache.cfg['guiAttributes']['relatedMaxNumber'].val), format, Logic.getColumns(true), null];
		} else {
			var wservice = Cache.cfg['soapPath']['getmoldata'].val;
			var wmethod = Cache.cfg['soapMethod']['getmoldata'].val;
			var wparams = [Ajax.searchId, Ajax.molecules.first+rowNum, 1, format, Logic.getColumns(true), null];
		}
		var output = '';
		View.setProgressWait();
		SOAP.async = false;
		SOAP.send(wservice, wmethod, wparams, 
			function(response) {
				output = response;
			}, Ajax.errorHandler
		);
		View.setProgress();
		output = output.substr(output.indexOf('<Molecule>')+10);
		return output.substr(0, output.indexOf('</Molecule>'));
	},

	/**
	 * Get more similar hits for a molecule using the current query structure.
	 *
	 * @param molecule   Target molecule.
	 * @return array of MRV-s, where the different locations of the query are emphasized.
	 */
	getMoreHits: function(molecule) {
		if (Logic.getQuery() == '') {
			alert('No query was set.');
			return null;
		}
		var query = Logic.molecules[View.marvin.query.applet].ws;
		var searchType = View.getSearchType();
		switch (searchType) {
			case 's':
				searchType = 'substructure';
				break;
			case 'r':
				searchType = 'superstructure';
				break;
			case 'f':
				searchType = 'full';
				break;
			case 'ff':
				searchType = 'full fragment';
				break;
			default:
				alert('Invalid search type.');
				return null;
		}
		var output = [];
		View.setProgressWait();
		SOAP.async = false;
		SOAP.send(Cache.cfg['soapPath']['getmorehits'].val, Cache.cfg['soapMethod']['getmorehits'].val, 
			[XML.convertSpecChars(molecule), searchType, XML.convertSpecChars(query), parseInt(Cache.cfg['guiAttributes']['enumMaxNumber'].val), 
			'coloring:y alignmentMode:off hitColor:'+Cache.cfg['guiAttributes']['hitColor'].val, Cache.cfg['guiAttributes']['enumDisplayMode'].val], 
			function(response) {
				try {
					var idx = response.indexOf('<MoreHits>\r\n<Hit>\r');
					if (idx == -1) {
						idx = response.indexOf('<MoreHits>\r\n<Hit>');
						response = response.substr(idx+18);
					} else {
						response = response.substr(idx+19);
					}
					response = response.substr(0, response.indexOf('</Hit></MoreHits>'));
					output = response.split(/<\/Hit>\r?\n<Hit>\r?\n/);
				} catch(e) {
					alert(e.message);
				}
			}, Ajax.errorHandler
		);
		View.setProgress();
		return output;
	},

	/**
	 * Insert a molecule in a structure table.
	 */
	insertMolecule: function() {
		var tableName = Logic.getStructureTableName();
		View.setProgressWait();
		SOAP.async = false;
		SOAP.send(Cache.cfg['soapPath']['insert'].val, Cache.cfg['soapMethod']['insert'].val, 
			[Ajax.connectionId, tableName, XML.convertSpecChars(Logic.molecules[View.marvin.insert.applet].ws), 
				Logic.getCustomFields(), Logic.getCustomFieldValues()], 
			function(response) {}, Ajax.errorHandler
		);
		View.setProgress();
		if (Logic.molecules[View.marvin.query.applet] != null && Logic.lastQueryMolecule != Logic.molecules[View.marvin.query.applet].ws) {
			Logic.molecules[View.marvin.query.applet].ws = Logic.lastQueryMolecule;
		}
		Ajax.setQuery(true, Scrolling.elementId);
	},

	/**
	 * Update the molecule structure and custom fields belong to it.
	 *
	 * @param moleculeId   Id of the structure.
	 */
	editMolecule: function(moleculeId) {
		var molecule = Logic.getEditedMolecule();
		var changedFields = Logic.getEditedFieldValues();
		var tableName = Logic.getStructureTableName();
		View.setProgressWait();
		SOAP.async = false;
		SOAP.send(Cache.cfg['soapPath']['edit'].val, Cache.cfg['soapMethod']['edit'].val, 
			[Ajax.connectionId, tableName, moleculeId, (molecule == null ? null : XML.convertSpecChars(molecule)), 
				changedFields.columns, changedFields.values], 
			function(response) {}, Ajax.errorHandler
		);
		View.setProgress();
		if (Logic.molecules[View.marvin.query.applet] != null && Logic.lastQueryMolecule != Logic.molecules[View.marvin.query.applet].ws) {
			Logic.molecules[View.marvin.query.applet].ws = Logic.lastQueryMolecule;
		}
		Ajax.setQuery(true, Scrolling.elementId);
	},

	/**
	 * Remove a molecule given by its cd_id from a structure table.
	 *
	 * @param moleculeId   Id of the structure.
	 */
	removeMolecule: function(moleculeId) {
		var tableName = Logic.getStructureTableName();
		View.setProgressWait();
		SOAP.async = false;
		SOAP.send(Cache.cfg['soapPath']['remove'].val, Cache.cfg['soapMethod']['remove'].val, 
			[Ajax.connectionId, tableName, moleculeId], 
			function(response) {}, Ajax.errorHandler
		);
		View.setProgress();
		if (Logic.molecules[View.marvin.query.applet] != null && Logic.lastQueryMolecule != Logic.molecules[View.marvin.query.applet].ws) {
			Logic.molecules[View.marvin.query.applet].ws = Logic.lastQueryMolecule;
		}
		Ajax.setQuery(true, Scrolling.elementId);
	},

	/**
	 * Get the library size of a Markush structure.
	 *
	 * @param molecule   The Markush structure in MRV format.
	 * @return integer.
	 */
	getMarkushLibrarySize: function(molecule) {
		var size = 0;
		View.setProgressWait();
		SOAP.async = false;
		SOAP.send(Cache.cfg['soapPath']['markushLibrarySize'].val, Cache.cfg['soapMethod']['markushLibrarySize'].val, 
			[XML.convertSpecChars(molecule)],
			function(response) {
				View.setProgress();
				size = response;
			}, Ajax.errorHandler
		);
		return size;
	},

	/**
	 * Enumerate a Markush structure in a random or sequential manner.
	 *
	 * @param molecule   The Markush structure in MRV format.
	 */
	markushEnumerate: function(molecule) {	
		var output = [];
		View.setProgressWait();
		SOAP.async = false;
		SOAP.send(Cache.cfg['soapPath']['markushEnumerate'].val, Cache.cfg['soapMethod']['markushEnumerate'].val, 
			[XML.convertSpecChars(molecule), Logic.markush.mode, parseInt(Cache.cfg['guiAttributes']['enumMaxNumber'].val), Logic.markush.filter, 
			Logic.markush.homology, Logic.markush.code, Logic.markush.alignScaffold, Logic.markush.coloring, 'mrv'],
			function(response) {
				try {	
					var idx = response.indexOf('<Enumeration>\r\n<Structure>\r');
					if (idx == -1) {
						idx = response.indexOf('<Enumeration>\r\n<Structure>');
						response = response.substr(idx+27);
					} else {
						response = response.substr(idx+28);
					}
					response = response.substr(0, response.indexOf('</Structure></Enumeration>'));
					output = response.split(/<\/Structure>\r?\n<Structure>\r?\n/);
				} catch(e) {
					alert(e.message);
				} finally {
					View.setProgress();
				}
			}, Ajax.errorHandler
		);
		return output;
	},

	/**
	 * Display certain structures of the result set.
	 *
	 * @param from    The ordinal number of the first molecule should be displayed on the page.
	 * @param limit   Number of requested molecules after the first one.
	 */
	displayImages: function(from, limit) {
		var timestamp;
    	if (navigator.appName != 'Netscape') {
    		var d = new Date();
    		timestamp = d.getTime();
    	}
		for (var i = 0; i < limit; i++) {
			if (document.getElementById(View.prefix.rowNumber+i) != null) {
       			View.getRowNumber(from, i).draw(View.prefix.rowNumber+i);
     		}
    	}
    	View.drawImages(from, 0, limit, timestamp);
	},

	/**
	 * Replace an image in the spreadsheet with an MView applet.
	 *
	 * @param id     Ordinal number of the structure which will be displayed via MView.
	 * @param from   Ordinal number of the first structure in the page.
	 */
	getApplet: function(id, from) {
		Ajax.appletLoading = true;
		var timestamp;
		if (navigator.appName != 'Netscape') {
			var d = new Date();
			timestamp = d.getTime();
		}
		var lastCellId = View.prefix.image+(Ajax.lastAppletId - from);
		if (Ajax.lastAppletId != id && document.getElementById(lastCellId) != null) {
			View.drawImages(from, Ajax.lastAppletId - from, 1, timestamp);
		}
		if (View.isRelationalMode()) {
			var wservice = Cache.cfg['soapPath']['getmoldataRel'].val;
			var wmethod = Cache.cfg['soapMethod']['getmoldataRel'].val;
			var wparams = [Ajax.searchId, id, 1, parseInt(Cache.cfg['guiAttributes']['relatedMaxNumber'].val), 'mrv', Logic.getColumns(true), Logic.colorAndAlignmentOptions];
		} else {
			var wservice = Cache.cfg['soapPath']['getmoldata'].val;
			var wmethod = Cache.cfg['soapMethod']['getmoldata'].val;
			var wparams = [Ajax.searchId, id, 1, 'mrv', Logic.getColumns(true), Logic.colorAndAlignmentOptions];
		}
		SOAP.async = true;
		SOAP.send(wservice, wmethod, wparams, 
			function(response) {
				try {
					response = response.replace(/<\?xml version="1.0" \?>/g, '');
					var molecules = XML.parse(response).getElementsByTagName('cml');
					var editable = (View.edited.row == (id - from) ? 2 : 0);
					document.getElementById(View.prefix.image+(id - from)).innerHTML = 
						View.getView('@javascript-URI-encoded@'+escape('<?xml version="1.0" ?>\n'+XML.serialize(molecules[0])), 'MView', editable, null, null, View.isRGroupNeeded);
					Ajax.appletLoading = false;
				} catch(e) {
					alert(e.message);
				} finally {
					View.setProgress();
				}
			}, Ajax.errorHandler
		);
		Ajax.lastAppletId = id;
	},

	/**
	 * Request version information.
	 */
	getInfo: function() {
		View.setProgressWait();
		SOAP.async = true;
		SOAP.send(Cache.cfg['soapPath']['versioninfo'].val, Cache.cfg['soapMethod']['versioninfo'].val, [], 
			function(response) {
				try {
					var responseObj = XML.parse(response);
					var versions = responseObj.getElementsByTagName('Version');
					var jchemInfo = versions.item(0).firstChild.nodeValue;
					var vmInfo = responseObj.getElementsByTagName('Vendor').item(0).firstChild.nodeValue;
					vmInfo += ' '+responseObj.getElementsByTagName('VmName').item(0).firstChild.nodeValue;
					vmInfo += ' '+versions.item(1).firstChild.nodeValue;
					var osInfo = responseObj.getElementsByTagName('Arch').item(0).firstChild.nodeValue;
					osInfo += ' '+responseObj.getElementsByTagName('Name').item(0).firstChild.nodeValue;
					osInfo += ' '+versions.item(2).firstChild.nodeValue;
					View.setInfo(jchemInfo, vmInfo, osInfo);
				} catch(e) {
					alert(e.message);
				} finally {
					View.setProgress();
				}
			}, Ajax.errorHandler
		);
	},

	/**
	 * General error handler of Ajax responses.
	 *
	 * @param response   Response in case of error.
	 */
	errorHandler: function(response) {
		if (response != null) {
			var errorMsg = response.getElementsByTagName(SOAP.errorElementName).item(0).firstChild.nodeValue;
			if (errorMsg.match(/Cannot find connectionHandler ID/)) {
				errorMsg = 'Connection was lost.';
			} else if (errorMsg.match(/Search Id=[A-Za-z0-9\-]* was not found/)) {
				errorMsg = 'Search results were lost.';
			}
			errorMsg += '\nPress OK to reload the application.';
			if (confirm(errorMsg)) {
				window.location.reload(); 
			}
		}
		View.setProgress();
	}
};