// 2008.09.26, Dejan  Added optional mode to mappingFields for append mapping

var CREATELOCK_OK = 0;
var CREATELOCK_CONFLICT  = 1;
var CREATELOCK_ERROR = 3;

function CWiki (nId, sTitle, sVisibility) {
	this.mId = nId;
	this.mTitle = sTitle;
	this.mVisibility = sVisibility;
	this.mAllowAnonymWrite = false;
}

function CWikiPage (nId, sTitle, sText) {
	this.mId = nId;
	this.mTitle = sTitle;
	this.mText = sText;
	this.mNewPage = false;
	this.mEdited = false;
}

//---------------------------------------------------------------------------------------------
CWikiPage.prototype.createServerLock = function (pIDwiki) {
		this.mLockStart = -1;

		// Query server for a lock on this page
		var sTmp = WIKI_SERVER_URL + 'wikiLocks.asp?Cmd=Add&ObjectType=Page&IdWiki=' + pIDwiki + '&idPage=' + this.mId;
		var oLockReq = new CSynchRequest (sTmp, 'JSON', 'GET', null);
		if (oLockReq.oRequest.status != 200) {
			// there was an error
			alert ('CWikiPage.createServerLock.100 - Cannot post edit lock: ' + oLockReq.oRequest.status + '; ' + sTmp);
			return (CREATELOCK_ERROR);
		}
		
		if (oLockReq.oRequest.responseText == null) {
			// there was a parsing error
			alet ('CWikiPage.createServerLock.110 - There was an error parsing server response for edit lock');
			return (CREATELOCK_ERROR);
		}
		
		try { 
			eval (oLockReq.oRequest.responseText);
		} catch (eLoc) {
			alert ('CWikiPage.createServerLock.120 Error eval server response; ' + eLoc.number + '; ' + eLoc.description + '; r[' + oLockReq.oRequest.responseText + ']');
			return (CREATELOCK_ERROR);
		}

		if (gTextusLockData.status == 0) {
			// Lock succesfully created
			this.mLockStart = gTextusLockData.startTime;
			this.mLockIDauthor = gTextusLockData.IDauthor;
			return (CREATELOCK_OK);
		}
		
		if (gTextusLockData.status == 401) {
			// Can't create a lock, someone else locked it, store the details
			this.mLockedByOthers = true;
			this.mLockStart = gTextusLockData.startTime;
			this.mLockRemainsMin = gTextusLockData.remainsMin;
			this.mLockIDauthor = gTextusLockData.IDauthor;
			this.mLockAuthorName = gTextusLockData.authorName;
			return (CREATELOCK_CONFLICT);
		}

		// Server contains an error
		alert ('CWikiPage.createServerLock.140 - server error code: ' + gTextusLockData.status + ' (' + unescape(gTextusLockData.errorDesc) + ')');
		return (CREATELOCK_ERROR);
}

//---------------------------------------------------------------------------------------------
CWikiPage.prototype.removeServerLock = function (pIDwiki) {
		// Request from server to delete a lock set by current user on this wiki page
		var sTmp = WIKI_SERVER_URL + 'wikiLocks.asp?Cmd=Remove&ObjectType=Page&IdWiki=' + pIDwiki + '&idPage=' + this.mId;
		var oLockReq = new CSynchRequest (sTmp, 'JSON', 'GET', null);
		if (oLockReq.oRequest.status != 200) {
			// there was an error
			alert ('CWikiPage.removeServerLock.100 - Cannot post edit lock: ' + oLockReq.oRequest.status + '; ' + sTmp);
			return (CREATELOCK_ERROR);
		}
		
		if (oLockReq.oRequest.responseText == null) {
			// there was a parsing error
			if (gDebug) alert ('CWikiPage.removeServerLock.110 - There was an error parsing server response.');
			return (CREATELOCK_ERROR);
		}
		
		try { 
			eval (oLockReq.oRequest.responseText);
		} catch (eLoc) {
			alert ('CWikiPage.removeServerLock.120 Error eval server response; ' + eLoc.number + '; ' + eLoc.description + '; r[' + oLockReq.oRequest.responseText + ']');
			return (CREATELOCK_ERROR);
		}

		if (gTextusLockData.status == 0) {
			// Lock succesfully removed, or wasn't there at all
			this.mLockStart = -1;
			this.mLockIDauthor = -1;
			return (CREATELOCK_OK);
		}

		// Server contains an error
		if (gDebug) alert ('CWikiPage.removeServerLock.140 - server error code: ' + gTextusLockData.status + ' (' + unescape(gTextusLockData.errorDesc) + ')');
		return (CREATELOCK_ERROR);
}





//---------------------------------------------------------------------------------------------
function CUser () {
	this.mId = -1;
	this.mGroupId = -1;
	this.mUname = '';
	this.mUpwd = '';
	this.mUserFullName = '';
	this.mKeepAlive = false;	// flag - is KeepAlive (periodic pining the server) active

	//---------------------------------------------------------------------
	// This method gets some user session data from the textus server.
	// This data is necessary for calling textus server legacy methods,
	// Which require these to be explicitely passed as parameters.
	// We may also use this data to re-establish a session?
	//---------------------------------------------------------------------
	this.getServerSession = function  () {
		var eLoc;

		// Make server query
		var sURL = WIKI_SERVER_URL + 'GetSession.asp';

		var oSynchRequest = new CSynchRequest (sURL, 'XML', 'GET', null);
		if (oSynchRequest.oRequest.status != 200) {
			// there was an error
			alert ('CUser.getServerSession.100 Error - server request returned code ' + oSynchRequest.oRequest.status + ' for ' + sURL);
			return (false);
		}

		// Analyse results
//		alert ('ResponseText=' + oSynchRequest.oRequest.responseText);

		try {
//		eval ('gTextusSessionData = { "user": [ { id: 2, defaultGroup: 2, userFullName: "Dejan%20Dincic", lastVar: false } ] } ;');
			eval (oSynchRequest.oRequest.responseText);
		} catch (eLoc) {
			alert ('Error eval: ' + eLoc);
		}
//	alert ('Session.id='+gTextusSessionData.user[0].id);


		// Update object properties, which should be used in the future
		this.mId = gTextusSessionData.user[0].id;
		this.mGroupId = gTextusSessionData.user[0].defaultGroup;
		this.mUname = gTextusSessionData.user[0].userName;
		this.mUpwd = gTextusSessionData.user[0].userPass;
		this.mUserFullName = unescape(gTextusSessionData.user[0].userFullName);
	}


}	// CUser

//*** WEB PAGE HELPER CLASSES ***/

//-----------------------------------------------------------------------------
// This is a simple object to store toolbar options status (enabled/disabled)
// It contains an array of menu options index by option ID (html id string).
//-----------------------------------------------------------------------------
function CToolBar (sName) {
	this.mName = sName;
	this.mOptions = new Array ();

	//-------------------------------------------------------------------------
	this.addOption = function (sOptionName, bInitState, oDOMElement) {
		var iCurrent = this.mOptions.length;
		this.mOptions[sOptionName] = new Object ();
		this.mOptions[sOptionName].mOptionName = sOptionName;
		this.mOptions[sOptionName].mState = bInitState;
		this.mOptions[sOptionName].mElement = oDOMElement;
	}

	this.enable = function (sOptionName, bState) {
		this.mOptions[sOptionName].mState = bState;
		if (bState && (this.mOptions[sOptionName].mElement != null)) {
			this.mOptions[sOptionName].mElement.style.color = MENU_OPTION_COLOR_ENABLED;
		} else
			this.mOptions[sOptionName].mElement.style.color = MENU_OPTION_COLOR_DISABLED;
	}

}	// CToolBar


//-----------------------------------------------------------------------------
// This class takes a javascript array and an html template to render html/DOM
// output for each element of the array and appened it to the given container
// element on the page. This helps separate html design from data and code, 
// because we can design simple html templates for rendering lists.
//----------------------------------------------------------------------------
function CDhtmlList (arrList) {
	this.mArray = arrList;
	this.mContainer = null;
	this.mTemplate = null;
	this.mFieldMappings = new Array ();

	//-------------------------------------------------------------------------
	// Sets the mContainer to a DOM element specified
	//-------------------------------------------------------------------------
	this.setContainer = function (oDocument, sIdContainer) {

		// Find the page element to act as a container for display of wiki lines
		this.mContainer = oDocument.getElementById (sIdContainer);
		if (this.mContainer == null) {
			alert ('CDhtmlList.setContainer.100 error - cannot find the container [' + sIdContainer + ']');
			return (false);
		}
		showPageElem (sIdContainer, true);
		return (true);
	}

	//-------------------------------------------------------------------------
	// Delete any previous content from a page container 
	//-------------------------------------------------------------------------
	this.emptyContainer = function () {
		if (this.mContainer != null)
			this.mContainer.innerHTML = '';
	}

	//-------------------------------------------------------------------------
	// Sets the mLineTemplate to a DOM element specified
	//-------------------------------------------------------------------------
	this.setLineTemplate = function (document, sIdTemplate) {

		// Find the template line for each wiki to be listed
		this.mTemplate = document.getElementById(sIdTemplate);
		if (this.mTemplate == null) {
			alert ("CDhtmlList.setLineTemplate.100 error - cannot find the template");
			return (false);
		}
		return (true);
	}

	//-------------------------------------------------------------------------
	// Store a mapping of source-array-field to a DOM-field to a local array.
	// Parameters:
	//	sArrayField - the name of the field in the source javascript array, for
	//					example for mArray[x].title, it should be 'title'
	//	sTemplateField - the name of the field in the DOM template block for 
	//					rending this list onto the page. For example for a
	//					<DIV id="myTitle"> it is 'myTitle'
	//	sTemplateFieldProperty		- the type of the DOM property that should be set by this
	//					mapping, by the array field: 'innerHTML', 'href',...
	//	sPrefix		- a string prefix can be added to a value from the array
	//					when it's rendered into a DOM element for display
	//	sMode		- 'assign' (default, if not specified) or 'append'; this
	//					determines whether to assign or append the mapping
	//					to the template field property specified above.
	//	These mappings are used by .addLine method to copy values from the
	//		source javascript array to the DOM elements for adding to the page
	//-------------------------------------------------------------------------
	this.addFieldMapping = function (sArrayField, sTemplateField, sTemplateFieldProperty, sPrefix, sAssignMode, sCond, sCondValue, sTrueValue) {
		if (sAssignMode != null)
			if (typeof(sAssignMode) != 'string' || (sAssignMode.toUpperCase() != 'APPEND' && sAssignMode.toUpperCase() != 'OVERWRITE')) {
				alert (typeof(sAssignMode));
				alert ('Invalid mapping assign mode [' + sAssignMode + ']');
				return;
			}

		if (sCond != null)
			if (typeof(sCond) != 'string' || (sCond.toUpperCase() != 'EQ' && sCond.toUpperCase() != 'NE')) {
				alert ('Invalid mapping condition string [' + Cond + ']');
				return;
			}

		var iCurrent = this.mFieldMappings.length;
		this.mFieldMappings[iCurrent] = new Object ();
		this.mFieldMappings[iCurrent].sArrayField = sArrayField;
		this.mFieldMappings[iCurrent].sTemplateField = sTemplateField;
		this.mFieldMappings[iCurrent].sTemplateFieldProperty = sTemplateFieldProperty;
		this.mFieldMappings[iCurrent].sPrefix = sPrefix;
		this.mFieldMappings[iCurrent].sMode = sAssignMode;
		this.mFieldMappings[iCurrent].sCond = sCond;
		this.mFieldMappings[iCurrent].sCondValue = sCondValue;
		this.mFieldMappings[iCurrent].sTrueValue = sTrueValue;
	}


	//-------------------------------------------------------------------------
	// Renders mArray into a mContainer DOM element by cloning line templates
	// and setting template field values with array elements
	//-------------------------------------------------------------------------
	this.render = function () {

		if (this.mArray == null) {
			alert ('CDhtmlList.render.100 error - no input array');
			return (false);
		}
		
		if ((this.mArray.length == 0) || 
			(this.mArray[0] == null)) { // request worked, but the result array is empty
			this.mContainer.innerHTML = 'The list is empty';
			return (true);
		}


		var i, iLen;
		iLen = this.mArray.length;

		for (i=0; i < iLen; i++ ) {
			this.addLine (i);
		}

		return (true);
	}

	//-------------------------------------------------------------------------
	// 
	//-------------------------------------------------------------------------
	this.addLine = function (i) {
		var j, lMappings, oField, eLoc, sCond;

		// Clone the given display template for this array element (i)
		var oLine = this.mTemplate.cloneNode (true);
		oLine.style.display = 'block';

		// Each cloned block gets a unique ID by adding the '-xxxx' random number string
		// to the ID of the template. Example: id 'oneWiki' becomes 'oneWiki-xxx'
		var nId = Math.round(Math.random()*1000);
		oLine.id = oLine.id + '-' + nId.toString();
		try {
			this.mContainer.appendChild (oLine);
		} catch (eLoc) {
			alert ('CDhtmlList.addLine.100 ' + eLoc);
			return (false);
		}

		// Update all the fields specified for this template with array values
		lMappings = this.mFieldMappings.length;

		for (j=0; j<lMappings; j++) {
			oField = getChildElementById (oLine, this.mFieldMappings[j].sTemplateField);
			if (oField == null) {
				alert ('CDhtmlList.addLine.140 error - template field ' + this.mFieldMappings[j].sTemplateField + ' cannot be found');
				return (false);
			}

			// The following line was commented out, because if we don't change the ID of the cloned field
			// we can use the same output field for multiple assignments (for innerHTML and href for example);
			// The side effect is that generated list contains a number of fields with the same ID, but that
			// does not seem to create problems for now.
//			oField.id = oField.id + '-' + nId.toString();

			sFld = this.mFieldMappings[j].sArrayField;

			// Is this a conditional mapping?
			if (this.mFieldMappings[j].sCond != null) {
				// Check the condition, skip assignment if not true
				sCond = this.mFieldMappings[j].sCond.toUpperCase();

				if (sCond == 'EQ') {
					if (this.mArray[i][sFld] != this.mFieldMappings[j].sCondValue)
						continue;
				} else if (this.mFieldMappings[j].sCond.toUpperCase() == 'NE') {
					if (this.mArray[i][sFld] == this.mFieldMappings[j].sCondValue)
						continue;
				} else {
					// Unknow condition (not EQ or NE), do not render
					if (gDebug) alert ('Unknow mapping condition [' + sCond + ']');
					continue;
				}

				// Assign the defined value since condition evaluates TRUE
				this.setFieldProperty (
						oField,
						this.mFieldMappings[j].sTemplateFieldProperty,
						this.mFieldMappings[j].sTrueValue,
						this.mFieldMappings[j].sPrefix);

			}	// conditional mapping

			else {
				// Not conditional mapping
				// Value assignment
				if (this.mFieldMappings[j].sTemplateFieldProperty == 'href')
					// Use prefix with href
					if (this.mFieldMappings[j].sMode != null && this.mFieldMappings[j].sMode == 'append')
						oField[this.mFieldMappings[j].sTemplateFieldProperty] += this.mFieldMappings[j].sPrefix + this.mArray[i][sFld];
					else
						oField[this.mFieldMappings[j].sTemplateFieldProperty] = this.mFieldMappings[j].sPrefix + this.mArray[i][sFld];

				else if (this.mFieldMappings[j].sTemplateFieldProperty.substr (0, 2) == 'on') {
					// It's an event handler. Place the src fld value in brackets for a function call
					oField[this.mFieldMappings[j].sTemplateFieldProperty] = this.mFieldMappings[j].sPrefix + '(' + this.mArray[i][sFld] + ')';
				}
				else
				if (this.mFieldMappings[j].sPrefix != null && this.mFieldMappings[j].sPrefix != '')
					oField[this.mFieldMappings[j].sTemplateFieldProperty] = this.mFieldMappings[j].sPrefix  + unescape(this.mArray[i][sFld]);
				else
					oField[this.mFieldMappings[j].sTemplateFieldProperty] = unescape(this.mArray[i][sFld]);
			}	// uncoditional mapping
		}


		return (true);

	}	// .addLine

}	// CDhtmlList

//---------------------------------------------------------------------------------------------
CDhtmlList.prototype.setFieldProperty = function (oFld, sProperty, sValue, sPrefix) {
	var sTmpValue = sValue;
	// Check if Prefix needs to be added
	if (sPrefix != null)	sTmpValue += sPrefix;
	// Handle second level property when part of .style.xxxx	TODO - change to handle any in the future
	if (sProperty.substr(0, 6) == 'style.') {
		var sSecProperty = sProperty.substr (6);
		oFld['style'][sSecProperty]  = sTmpValue;
	}
	else
		oFld[sProperty] = sTmpValue;
}



//*** SERVER REQUEST CLASSES - CBRequest ***/


//-----------------------------------------------------------------------------------------------
// This object launches an asynchronous request to load some JSON data. When data is loaded, it 
// is parsed and then an external handler function is invoked.
//-----------------------------------------------------------------------------------------------
function CAsynchRequest (sUrl, fnHandler, sResponseType, sPostType, sData, fnErrHandler) {
	this.sUrl = sUrl;
	this.fnHandler = fnHandler;
	this.oRequest = null;

	//-----------------------------------------------------------------------------------------------
	this.makeAsynchRequest = function (sUrl, fnHandler, sType, sPostType, sData, fnErrHandler) {

        var eLoc, oRequest = false;

        if (window.XMLHttpRequest) { // Mozilla, Safari,...
            oRequest = new XMLHttpRequest();
            if (oRequest.overrideMimeType) {
                oRequest.overrideMimeType('text/xml');
            }
        } else if (window.ActiveXObject) { // IE
            try {
                oRequest = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (eLoc) {
                try {
                    oRequest = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (eLoc) {}
            }
        }

        if (!oRequest) {
            alert('Giving up :( Cannot create an XMLHTTP instance');
            return null;
        }
		if (sType.toUpperCase() == 'JSON')
	        oRequest.onreadystatechange = function () { ehandleJSONData (oRequest, fnHandler, fnErrHandler); };
		else
		if (sType.toUpperCase() == 'TEXT')
	        oRequest.onreadystatechange = function () { ehandleTextData (oRequest, fnHandler); };
		else {
			alert ('makeAsynchRequest error: unknow request type [' + sType);
			return (null);
		}

		if (sPostType == 'POST') {
	        oRequest.open('POST', sUrl, true);
			oRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
		}
		else
	        oRequest.open('GET', sUrl, true);
        oRequest.send(sData);
		return oRequest;
    }

	this.oRequest = this.makeAsynchRequest (sUrl, fnHandler, sResponseType, sPostType, sData, fnErrHandler);
}


//-----------------------------------------------------------------------------------
// Standard event handler for loading JSON data: parse the data and invoke an
// external function to process it. This method should logically be part of the
// CJSONRequest class, but the callback (onreadystatechange) does not work that way.
//-----------------------------------------------------------------------------------
function ehandleJSONData (pRequest, pHandler, pErrHandler) {
	var eLoc;

	if (pRequest.readyState == 4) 
		if (pRequest.status == 200) {
			// Process the result
			try {
				// Note about eval javascript function: if the loaded json script contains a variable 
				// declaration (like var gTest), this variable will only be visible within this script file and this method
				// To make it globally visible, remove the "var" declaration from the script.
				eval (pRequest.responseText);
			} catch (eLoc) {
				if (document.all)
					alert ("ehandleJSONData.100: Error eval " + eLoc.number + " " + eLoc.description + pRequest.responseText);
				else
					alert ('ehandleJSONData.100: Error eval; ' + eLoc.name + ': ' + eLoc.message + ' ' + pRequest.responseText);
				return;
			}
		
			// Activate external handler, which must know what data is loaded
			// into which global variable and act accordingly.
			pHandler();
		} else {
			// Server error ocurred, if error handler provided - call it
			if (pErrHandler)
				pErrHandler(pRequest);
		}
}

//-----------------------------------------------------------------------------------------------
// This object launches an asynchronous request to load some JSON data. When data is loaded, it 
// is parsed and then an external handler function is invoked.
//	- sResponseType: 'TEXT', 'JSON' or 'XML'
//	- sPostType: 'GET' or 'POST'
//-----------------------------------------------------------------------------------------------
function CSynchRequest (sUrl, sResponseType, sPostType, sData) {
	this.sUrl = sUrl;
	this.oRequest = null;

	//-----------------------------------------------------------------------------------------------
	this.makeSynchRequest = function (sUrl, sType, sPostType, sData) {

        var eLoc, oRequest = false;

        if (window.XMLHttpRequest) { // Mozilla, Safari,...
            oRequest = new XMLHttpRequest();
            if (oRequest.overrideMimeType && (sType.toUpperCase() == 'XML')) {
                oRequest.overrideMimeType('text/xml');
            }
        } else if (window.ActiveXObject) { // IE
            try {
                oRequest = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (eLoc) {
                try {
                    oRequest = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (eLoc) {}
            }
        }

        if (!oRequest) {
            alert('CSynchRequest.100 Giving up :( Cannot create an XMLHTTP instance');
            return null;
        }
	
		if (sPostType == 'POST') {
	        oRequest.open('POST', sUrl, false);
			oRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
		}
		else
	        oRequest.open('GET', sUrl, false);
        oRequest.send(sData);		// TODO catch errors for this operation
		return oRequest;
    }

	//-----------------------------------------------------------------------------------------------
	// MAIN
	//-----------------------------------------------------------------------------------------------
	this.oRequest = this.makeSynchRequest (sUrl, sResponseType, sPostType, sData);
	// If Return value is XMLHttpRequest it makes references to this CSynchRequest object impossible
	// return (this.oRequest);
}


//----------------------------------------------------------------------------
// General Utility Functions
//----------------------------------------------------------------------------


//----------------------------------------------------------------------------
// Returns parameter value from the URL - client version; it does not support
//	urls containing '&' (more than parameter) as values for the parameters
//----------------------------------------------------------------------------
function getParam(sVars, sName) {
	var aVal, aVars = sVars.substr(1).split ('&');	// creates an array of strings separated by &
	for (var i=0; i<aVars.length; i++) {
		//aVal = aVars[i].split('=',2);	// split only up to first = sign, in case the value contains = sign too; split ('=', 1) does not seem to work well;
		//if (aVal[0].toUpperCase() == sName.toUpperCase())
		//	return aVal[1];
		var iOff = aVars[i].indexOf('=');
		if (iOff == -1)		// no '=' sign was found, not a valid varible
			continue;
		var aName = aVars[i].substring (0, iOff);
		var aValue =aVars[i].substr (iOff+1);
		if (aName.toUpperCase() == sName.toUpperCase())
			return aValue;
	}
	return null;
}

//-----------------------------------------------------------------------------------
function getChildElementById (oElem, sId) {
	var oChild, oGrandChild;

	for (var i=0; i<oElem.childNodes.length; i++)
	{
		oChild = oElem.childNodes[i];
		if (oChild.id)
			if (oChild.id.toUpperCase() == sId.toUpperCase())
				return (oChild);
		if ((oGrandChild=getChildElementById(oChild, sId)) != null)
			return (oGrandChild);
	}
	return null;
}

