// ------------------------------------------------
// ServiceIF.js
// Author:	jpotter@troupscreeksoftware.com
// Date:	Mar 11, 2007
// Desc:	Web Service Interface Manager
// Copyright 2007 Troups Creek Software LLC
// ------------------------------------------------
// Manages interactions with the TCS web services module (DAS.asmx).
//
// Rewritten in March to remove dependency on webservice.htc behavior
// and instead invoke web services via standard SOAP protocol in conjunction
// with the AJAX XmlHttpRequest object. Now works in both IE and Firefox!
// -----------------------------------------
// ServiceIF::ServiceIF
function ServiceIF()
{
	// data members
	this.wsNameSpace = "http://troupscreeksoftware.com/webservices/";
	this.wsPage = "./DAS.asmx";
	
	// -------------------------------------------------------------------------------------------
	//            B A S I C   O P E R A T I O N S
	// -------------------------------------------------------------------------------------------
	// Fundamental methods.
	
	// -----------------------------------------
	// ServiceIF::init
	this.init = function()
	{	// nothing to do, just let our app flow control know we our service setup is complete
		gApp.triggerFlow(eServicesOk);
	}
		
	// -----------------------------------------
	// ServiceIF::createXmlRequest
	this.createXmlRequest = function()
	{	// return a new XmlHttpRequest object based on browser capabilities
		var xReq = null;
		if(window.ActiveXObject)
		{	// Windows IE
			xReq = new ActiveXObject("Microsoft.XMLHTTP");
		}	
		else if (window.XMLHttpRequest)
		{	// Mozilla, Firefox, et al.
			xReq = new XMLHttpRequest();
		}
		else
		{
			xReq = null;
		}
		return xReq;
	}

	// -----------------------------------------
	// ServiceIF::invokeWebService
	this.invokeWebService = function(wsBody, wsCallback, wsMethodName)
	{	// make the call to the web service
		var wsRequest = this.createXmlRequest();
		wsRequest.open("POST", this.wsPage, true);
		wsRequest.onreadystatechange = function()
			{
				if(wsRequest.readyState == 4)
				{	// request is finished
					if(wsRequest.status)
					{
						if(wsRequest.status == 200)
						{	// successful reponse
							wsCallback(false, wsRequest.responseXML);
						}
						else
						{
							wsCallback(true, wsRequest.statusText);
						}
					}
					else
					{	// Mozilla bug - on network error the request status is unavailable
						wsCallback(true, "unknown network error");
					}
				}
			};

			wsRequest.setRequestHeader("Content-Type", "text/xml");
			wsRequest.setRequestHeader("SOAPAction", this.wsNameSpace + wsMethodName);
			wsRequest.send(
						'<?xml version="1.0" encoding="utf-8"?>' +
						'<soap:Envelope' +
						'   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
						'   xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' + 
						'   xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' +
						'   <soap:Body>' + wsBody + 
						'	</soap:Body>' +
						'</soap:Envelope>'
						);
	}

	// -------------------------------------------------------------------------------------------
	//            C O N F I G   S E R V I C E S
	// -------------------------------------------------------------------------------------------
	// Methods to manipulate the site configuration data.

	// -----------------------------------------
	// ServiceIF::updateConfig
	this.updateConfig = function(filename, cfgTxt)
	{	// replace the current config settings
		var wsMethodName = "UpdateConfig";
		var wsBody =	'<' + wsMethodName + ' xmlns="' + this.wsNameSpace + '">' +
						'	<filename>' + filename + '</filename>' +
						'	<config>' + cfgTxt + '</config>' +
						'</' + wsMethodName + '>';
		try
		{	// call our web service to update the site configuration data
			this.invokeWebService(wsBody, this.updateConfigCallback, wsMethodName)
		}
		catch(ex)
		{
			gMsg.displayMsg(3, "Request for web service " + wsMethodName + " failed: " + ex.message);
		}
	}

	// -------------------------------------------------------------------------------------------
	//            I T E M   S E R V I C E S
	// -------------------------------------------------------------------------------------------
	// Methods that manipulate data items.

	// -----------------------------------------
	// ServiceIF::addItem
	this.addItem = function(pathname, newItem)
	{	// service to append an item to the data set
		var wsMethodName = "AddItem";
		var wsBody =	'<' + wsMethodName + ' xmlns="' + this.wsNameSpace + '">' +
						'	<filename>' + pathname + '</filename>' +
						'	<newitem>' + newItem + '</newitem>' +
						'</' + wsMethodName + '>';
		try
		{	// call our web service to add a new item to the data
			this.invokeWebService(wsBody, this.addItemCallback, wsMethodName)
		}
		catch(ex)
		{
			gMsg.displayMsg(3, "Request for web service " + wsMethodName + " failed: " + ex.message);
		}
	}

	// -----------------------------------------
	// ServiceIF::purgeItems
	this.purgeItems = function(pathname)
	{	// purge inactive items from the database
		var wsMethodName = "PurgeInactiveItems";
		var wsBody =	'<' + wsMethodName + ' xmlns="' + this.wsNameSpace + '">' +
						'	<filename>' + pathname + '</filename>' +
						'</' + wsMethodName + '>';
		try
		{	// call our web service to remove 'inactive' items from the data
			this.invokeWebService(wsBody, this.purgeItemsCallback, wsMethodName)
		}
		catch(ex)
		{
			gMsg.displayMsg(3, "Request for web service " + wsMethodName + " failed: " + ex.message);
		}
	}

	// -----------------------------------------
	// ServiceIF::replaceItem
	this.replaceItem = function(pathname, newItem)
	{	// service to replace an existing item in the data set
		var wsMethodName = "ReplaceItem";
		var wsBody =	'<' + wsMethodName + ' xmlns="' + this.wsNameSpace + '">' +
						'	<filename>' + pathname + '</filename>' +
						'	<newitem>' + newItem + '</newitem>' +
						'</' + wsMethodName + '>';
		try
		{	// call our web service to add a new item to the data
			this.invokeWebService(wsBody, this.replaceItemCallback, wsMethodName)
		}
		catch(ex)
		{
			gMsg.displayMsg(3, "Request for web service " + wsMethodName + " failed: " + ex.message);
		}
	}

	// -------------------------------------------------------------------------------------------
	//            L O G G I N G   S E R V I C E S
	// -------------------------------------------------------------------------------------------
	// Methods that manipulate the site admin log (activity trace)

	// -----------------------------------------
	// ServiceIF::addLogEntry
	this.addLogEntry = function(domainName, reason)
	{	// service to append a log entry to the application log
		var now = new Date();
		var ref = document.referrer;
		var logFileName = gData.getDataPath() + "/" + domainName + ".log";
		if(ref == "") ref = "-";
		// build up the entry
		var logEntry = "";
		logEntry += "<host>" + location.hostname + "</host>";
		logEntry += "<user>" + "-" + "</user>";
		logEntry += "<authUser>" + "-" + "</authUser>";
		logEntry += "<date>" + now + "</date>";
		logEntry += "<request>" + location.href + "</request>";
		logEntry += "<status>" + "-" + "</status>";
		logEntry += "<bytes>" + "-" + "</bytes>";
		logEntry += "<referrer>" + ref + "</referrer>";
		logEntry += "<userAgent>" + navigator.userAgent + "</userAgent>";
		logEntry += "<action>" + reason + "</action>";
		// now make the request
		var wsMethodName = "AddLogEntry";
		var wsBody =	'<' + wsMethodName + ' xmlns="' + this.wsNameSpace + '">' +
						'	<filename>' + logFileName + '</filename>' +
						'	<entry>' + logEntry + '</entry>' +
						'</' + wsMethodName + '>';

		try
		{	// call our web service to add this data to the log file
			this.invokeWebService(wsBody, this.addLogEntryCallback, wsMethodName)
		}
		catch(ex)
		{
			gMsg.displayMsg(3, "Request for web service " + wsMethodName + " failed: " + ex.message);
		}
	}

	// -----------------------------------------
	// ServiceIF::clearLog
	this.clearLog = function(spec)
	{	// clear/reset the specified log file
		// depending on the file that was specified, set up the actual filename
		var filename = "";
		if(spec == "admin") filename = gData.getDataPath() + "/" + gConfig.domainid + ".log";
		else if(spec == "click") filename = gData.logFilename;
		if(filename != "")
		{
			var wsMethodName = "ClearLogFile";
			var wsBody =	'<' + wsMethodName + ' xmlns="' + this.wsNameSpace + '">' +
							'	<filename>' + filename + '</filename>' +
							'</' + wsMethodName + '>';
			try
			{	// call our web service to clear (delete) the specified log file
				this.invokeWebService(wsBody, this.clearLogCallback, wsMethodName)
			}
			catch(ex)
			{
				gMsg.displayMsg(3, "Request for web service " + wsMethodName + " failed: " + ex.message);
			}
		}
	}

	// -------------------------------------------------------------------------------------------
	//            V A L I D A T I O N  S E R V I C E S
	// -------------------------------------------------------------------------------------------
	// Methods that perform security validation
	// TODO: review hacked security mechanism & replace with standard

	// -----------------------------------------
	// ServiceIF::validateAdminAccess
	this.validateAdminAccess = function(pw)
	{	// make sure the administrator access request is valid
		var wsMethodName = "AuthorizeAccess";
		// format the body of the request
		var wsBody =	'<' + wsMethodName + ' xmlns="' + this.wsNameSpace + '">' +
						'	<file>' + gData.getDataPath() + "/" + gConfig.domainid + 'Key.xml</file>' +
						'	<data>' + pw + '</data>' +
						'</' + wsMethodName + '>';
		// define the callback
		var wsCallback = this.validateAdminCallback;

		try
		{	// call our web service to validate our admin dude
			this.invokeWebService(wsBody, wsCallback, wsMethodName)
		}
		catch(ex)
		{
			gMsg.displayMsg(3, "Request for web service " + wsMethodName + " failed: " + ex.message);
		}
	}
	
	// -------------------------------------------------------------------------------------------
	//            S E R V I C E   C A L L B A C K S
	// -------------------------------------------------------------------------------------------
	// Methods that fire on response to the web service request.
	// (in alphabetical order)
	//
	//	NOTE: As with event handlers, the "this" reference does not necessarily refer to the service interface 
	//  object - it may refer to the element that triggered the event... hence, the use of the global references

	// -----------------------------------------
	// ServiceIF::addItemCallback
	this.addItemCallback = function(isError, txt)
	{	// process the add item results
		if(isError)
		{
			gMsg.displayMsg(3,"addItemCallback reports service failed: " + txt);
		}
		else
		{
			var result = txt.getElementsByTagName("AddItemResult")[0].firstChild.nodeValue;
			if(result == "ok")
			{	// log it and let our app flow control know operation is complete
				gSvc.addLogEntry(gConfig.domainid, "New Item");
				gApp.triggerFlow(eNewItemOk);
			}
			else
			{
				gMsg.displayMsg(3,"addItemCallback reports add new item operation failed: " + result);
			}
		}
	}

	// -----------------------------------------
	// ServiceIF::addLogEntryCallback
	this.addLogEntryCallback = function(isError, txt)
	{	// log entry callback for our web service - handle any errors but no additional processing needed
		if(isError)
		{
			gMsg.displayMsg(3,"addLogEntryCallback reports service failed: " + txt);
		}
		else
		{
			var result = txt.getElementsByTagName("AddLogEntryResult")[0].firstChild.nodeValue;
			if(result != "ok")
			{	// only react if it didn't actually work
				gMsg.displayMsg(3,"addLogEntryCallback reports add log entry operation failed: " + result);
			}
		}
	}

	// -----------------------------------------
	// TODO: review references to edit config window
	// ServiceIF::clearLogCallback
	this.clearLogCallback = function(isError, txt)
	{	// clear log callback - just handle any error conditions
		if(isError)
		{
			gMsg.displayMsg(3,"clearLogCallback reports service failed: " + txt);
		}
		else
		{
			var result = txt.getElementsByTagName("ClearLogFileResult")[0].firstChild.nodeValue;
			if(result == "ok")
			{	// success
				gMsg.displayMsg(1,"Log successfully cleared.");
				if(gApp.editConfigWindow)
					{gApp.editConfigWindow.focus();}		// otherwise the window gets buried
			}
			else
			{	// it didn't work
				{gMsg.displayMsg(3,"clearLogCallback reports clear log operation failed: " + result);}
			}
		}
	}

	// -----------------------------------------
	// ServiceIF::purgeItemsCallback
	this.purgeItemsCallback = function(isError, txt)
	{	// purge items callback
		if(isError)
		{
			gMsg.displayMsg(3,"purgeItemsCallback reports service failed: " + txt);
		}
		else
		{
			var result = txt.getElementsByTagName("PurgeInactiveItemsResult")[0].firstChild.nodeValue;
			if(result == "ok")
			{	// log it and let our app flow control know purge is complete
				gSvc.addLogEntry(gConfig.domainid, "Purge");
				gApp.triggerFlow(ePurgeOk);
			}
			else
			{
				gMsg.displayMsg(3,"purgeItemsCallback reports purge operation failed: " + result);
			}
		}
	}

	// -----------------------------------------
	// ServiceIF::replaceItemCallback
	this.replaceItemCallback = function(isError, txt)
	{	// process the replace item results
		if(isError)
		{
			gMsg.displayMsg(3,"replaceItemCallback reports service failed: " + txt);
		}
		else
		{
			var result = txt.getElementsByTagName("ReplaceItemResult")[0].firstChild.nodeValue;
			if(result == "ok")
			{	// log it and let our app flow control know operation is complete
				gSvc.addLogEntry(gConfig.domainid, "Replace Item");
				gApp.triggerFlow(eEditItemOk);
			}
			else
			{
				gMsg.displayMsg(3,"replaceItemCallback reports replace item operation failed: " + result);
			}
		}
	}

	// -----------------------------------------
	// ServiceIF::updateConfigCallback
	this.updateConfigCallback = function(isError, txt)
	{	// process the update to the site configuration
		if(isError)
		{
			gMsg.displayMsg(3,"updateConfigCallback reports service failed: " + txt);
		}
		else
		{
			var result = txt.getElementsByTagName("UpdateConfigResult")[0].firstChild.nodeValue;
			if(result == "ok")
			{	// log it, inform the user, and let our app flow control know update is complete
				gSvc.addLogEntry(gConfig.domainid, "UpdateConfig");
				gMsg.displayMsg(1,"Update successful. Changes will be applied when the page is refreshed or reloaded.");
				gApp.triggerFlow(eConfigOk);
			}
			else
			{
				gMsg.displayMsg(3,"updateConfigCallback reports update configuration operation failed: " + result);
			}
		}
	}
	
	// -----------------------------------------
	// ServiceIF::validateAdminCallback
	this.validateAdminCallback = function(isError, txt)
	{	// admin access validation callback
		if(isError)
		{
			gMsg.displayMsg(3,"validateAdminCallback reports service failed: " + txt);
		}
		else
		{
			var result = txt.getElementsByTagName("AuthorizeAccessResult")[0].firstChild.nodeValue;
			if(result == "true")
			{	// granted - let our app flow control know we can enter admin mode
				gSvc.addLogEntry(gConfig.domainid, "Admin Accessed");
				gMsg.displayMsg(1,"Administrator access granted.");
				gApp.triggerFlow(eAdminGranted);
			}
			else
			{
				gSvc.addLogEntry(gConfig.domainid, "Admin Denied");
				gMsg.displayMsg(2,"Administrator access denied.");
			}
		}
	}
}

