/////////////////////////////////////////////////////////////////
//	Copyright © 2004-2008 by Symmetrix Software, Inc.
//				Brookfield, Wisconsin
//				ALL RIGHTS RESERVED
  

///////////////////////// DOM Node Types //////////////////////////
var NODE_ELEMENT 									= 1;
var NODE_ATTRIBUTE								= 2;
var NODE_TEXT											= 3;
var NODE_CDATA_SECTION						= 4;
var NODE_ENTITY_REFERENCE					= 5;
var NODE_ENTITY										= 6;
var NODE_PROCESSING_INSTRUCTION		= 7;
var NODE_COMMENT									= 8;
var NODE_DOCUMENT									= 9;
var NODE_DOCUMENT_TYPE						= 10;
var NODE_DOCUMENT_FRAGMENT				= 11;
var NODE_NOTATION									= 12;

///////////////////////// PLXModel "Class" Definition //////////////////////////
function PLXModel() {}						// Requesit constructor...

// Class "member functions"
PLXModel.prototype.toString = function()
{
	return "PLXModel";
}

PLXModel.prototype.init = function()
{
    this.onInitStart();

    // The document must contain an xml island that specifies the initial state 
    // of the model including the properties that we must watch and the actions
    // to take when those properties change.  There are also parameters and other
    // goodies we need.  This xml island has the unique id "plxState", so let's
    // go get it.
    var xmlIsland = document.getElementById("plxState");
    if (!xmlIsland)
        throw new Error("Unable to locate model state in source document.");

    // xmlIsland refers to an HTML tag that contains xml formatted data.  We want
    // to use the xml DOM on this data, so we need to convert it to an instance
    // of XMLDocument that we will store in our _plxState member.
    this._plxState = null;

    // MSIE implements xml data islands directly through the XMLDocument member of
    // elements that are XML.  So this is easy in this case....
    if (xmlIsland.XMLDocument)
        this._plxState = xmlIsland.XMLDocument;

    // Firefox implements a DOM parser object that can parse a string.
    // The string is pulled from the inner HTML of the xml tag to create the 
    // xml document for the state....
    else if (window.DOMParser)
    {
        var parser = new DOMParser();
        this._plxState = parser.parseFromString(xmlIsland.innerHTML, "text/xml");
        
        // Save a little memory?
        xmlIsland.innerHTML = "";
    }

    // For browsers that do not support either of those methods, 
    // access to the xml island using the xml DOM is not
    // so easy.  We are reduced moving the HTML nodes from the HTML DOM to
    // to an XMLDocument object we create.
    // NOTE: This used to work for Firefox, but as version 3.5 (approx) it 
    //  didn't and the DOMParser solution was implemented.
    else
    {
        // Create the empty XMLDocument....
        this._plxState = document.implementation.createDocument("", "", null);

        // Create a root document element that corresponds to what we have in the
        // MSIE case.
        this._plxState.appendChild(this._plxState.createElement("plxstate"));
        var xmlRoot = this._plxState.documentElement;

        // Get access to the (single) child of the xml island that contains the
        // stuff we want to move.
        var htmlRoot = xmlIsland.getElementsByTagName("plxstate")[0];

        // While this HTML element has children, move the child from the HTML
        // domain into the XML domain.....
        while (htmlRoot.firstChild)
            xmlRoot.appendChild(htmlRoot.removeChild(htmlRoot.firstChild));
    }


    // Basic information....
    this._CID = this.getParam("CID");
    this._serverAgentURL = this.getParam("ServerAgentURL");

    // Need to run the html tags in the body to index the elements with id attributes.
    // This is MUCH faster than using getElementById() (in IE anyway).
    this.notifyServer("uiInit1");
    this.indexElems();


    // Add some null property change requests for some properties that we want track the value of,
    // but the model doesn't need to react to.  NOTE: application code may want
    // to react to these and they may also add their own onPCs 
    this.onPC("model.message", "");
    this.onPC("model.status", "");
    this.onPC("model.activeOb", "");

    // Initialize all html document elements by sending notifications to all....
    this.notifyServer("uiInit2");
    this.notifyAll();

    this.onInitComplete();

}

PLXModel.prototype.onInitStart = function()
{
    uiShowBusy("Initializing....");
}


PLXModel.prototype.onInitComplete = function()
{
	uiShowBusy( null );
	this.notifyServer( "uiReady" );
}

PLXModel.prototype.notifyServer = function( ev )
{
	var eventNotifyURL = this.getParam( "EventNotifyURL" );
	if ( eventNotifyURL )
		{
		var url = eventNotifyURL + "?event=" + ev;
		
		// Read ack from the url...
		// Don't really care what it says....
		var xmlDoc = this.readServerResponse( url );
		}
}

PLXModel.prototype.indexElems = function( elem )
{
	// If top call of the index process, then create the index and start at the 
	// body of the html part of the document....
	if ( !elem )
		{
		this._elemIdx = new Object;
		elem = document.body;
		}
		
	// Skip indexing any xml data islands or their children.....
	if ( elem.nodeName.toLowerCase() == "xml" )
		return;

	// Check to see whether this element has an id and index it if so....
	var id = elem.getAttribute( "id" );
	if ( id )
		this._elemIdx[ id ] = elem;
		
	// Walk this element's child elements and index those (recursively)....
	for ( var currChild = elem.firstChild; currChild; currChild = currChild.nextSibling )
		if ( currChild.nodeType == NODE_ELEMENT )
			this.indexElems( currChild ); 
}

PLXModel.prototype.lookupElem = function( id )
{
	if ( id )
		return this._elemIdx[id];
	else
		return null; 
}

PLXModel.prototype.notifyAll = function()
{
	// Collect all of the OnPC elements in the entire state document and
	// dispatch their actions....
	var allOnPCs = this._plxState.selectNodes( "/plxstate/props/prop/onpc" );
	this.dispatchPropChanges( allOnPCs );
}

PLXModel.prototype.getRootCID = function()
{
	if ( !this._rootCID )
		{
		var idx = this._CID.indexOf( "." );
		if ( idx > 0 )
			{
			this._rootCID = this._CID.substr( 0, idx );
			this._cfgPath = this._CID.substr( idx + 1 )
			}
		else
			{
			this._rootCID = this._CID;
			this._cfgPath = "";
			}
		}
		
	return this._rootCID;
}

PLXModel.prototype.getCfgPath = function()
{
	if ( this._cfgPath == undefined || this._cfgPath == null )
		this.getRootCID();
		
	return this._cfgPath;
}

PLXModel.prototype.XTransaction = function( args )
{
	// Check for bogus input....
	if ( this._CID && this._CID != "" &&
			 this._serverAgentURL && this._serverAgentURL != "" )
		{
		// Construct an url...
		var url = this._serverAgentURL + "?cid=" + this._CID + "&" + args;
		
		// Read xml from the url...
		var xmlDoc = this.readServerResponse( url );
		
		// Apply updates specified by the xml....
		this.applyModelUpdates( xmlDoc );
		
		// Check for error clearing any message gizmo first...
		uiDisplayError( null );
		
		var serverStatus = this.getPV( "model.status" );
		if ( serverStatus != "success" )
			{
  		    if ( serverStatus == "timeout" )
   				{
   				var redirectURL = this.getErrorURL( xmlDoc );
   				if ( redirectURL )
   					window.location = redirectURL;
   				else
   					uiDisplayError( "Configuration has timed out." );
   				}		
			else
				uiDisplayError( this.getPV( "model.message", "No message specified..." ) );
   			}
		}
		
	else
		throw new Error( "Missing CID or server agent url ('" + this._CID + "','" + this._serverAgentURL + "')" );
}

PLXModel.prototype.getErrorURL = function( xmlDoc )
{
	var redirectAttr = xmlDoc.selectSingleNode( "/update/model/@redirect" );
	if ( redirectAttr )
		return redirectAttr.nodeValue;
	else
		return null;
}

PLXModel.prototype.loadXml = function (url) {
    var xmlDoc;

    //IE ActiveX
    try {
        xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
    }
    catch (e1) {
        //Mozilla/Firefox, Opera (Also WebKit)
        try {
            xmlDoc = document.implementation.createDocument("", "", null);
        }
        catch (e2) {
            throw new Error("PLXModel.loadXml - Cannot instantiate XMLDOM object. Error=" + e2.message);
        }
    }
    try {
        xmlDoc.async = false;
        xmlDoc.load(url);
        return xmlDoc;
    }
    catch (e3) {
        //webkit (safari, chrome) ajax fallback
        try {
            var breadcrumb = "new";
            var xhttpReq = new XMLHttpRequest();
            if (xhttpReq == null || xhttpReq == undefined)
                throw new Error("xmlHttpRequest is null.");
            xhttpReq.open("GET", url, false);
            xhttpReq.setRequestHeader("Content-Type", "text/xml");

            breadcrumb += " GET";
            xhttpReq.send(null);
            breadcrumb += " send"
            xmlDoc = xhttpReq.responseXML;
            //xmlDoc = xhttpReq.responseText;  //return string
            if (!xmlDoc)
                throw new Error("PLX.loadXml - failed to load xml using XMLHttpRequest approach");
            return xmlDoc;
        }
        catch (e4) {
            throw new Error("PLX.loadXml -  Error: " + e4.message);
        }
    }

}


PLXModel.prototype.readServerResponse = function( url )
{
	var xmlDoc;
	
//	// if the official DOM Level 2 method is available, ....
//	if ( document.implementation && document.implementation.createDocument )
//		xmlDoc = document.implementation.createDocument("", "", null);

//	// Otherwise, try proprietary Micro$oft way...
//	else if (window.ActiveXObject)
//		xmlDoc = new ActiveXObject("Microsoft.XMLDOM");

//	// Otherwise, we're sol....
//	else
//		throw new Error( "Your browser is not worthy." );

//	// Make us wait until all the xml comes down the wire....
//	xmlDoc.async = false;

	// Read the xml....
	uiShowBusy("Communicating....");
	xmlDoc = g_myModel.loadXml(url);
	//var wasLoaded = xmlDoc.load( url );
	uiShowBusy( null );

	if (xmlDoc)
		return xmlDoc;
		
	else
		{
		var shortMsg = "XML data from '" + url + "' failed to load.";
		var parseErrorOb = xmlDoc.parseError;
		if ( parseErrorOb )
			throw new Error( formatArgs( "%0 %1.  Source = \"%2\"", [shortMsg,parseErrorOb.reason, parseErrorOb.srcText] )); 
		else
			throw new Error( shortMsg );
		}
}	

PLXModel.prototype.applyModelUpdates = function( xmlDoc )
{
	// Find the node that is the root of the model.
	// Assumed to be a direct child of the document root.....
	var modelRootNode;
	var currNode = xmlDoc.documentElement.firstChild;
	while ( currNode && !modelRootNode )
		{
		if ( currNode.nodeType == NODE_ELEMENT && currNode.nodeName == "model" )
			modelRootNode = currNode;
		else
			currNode = currNode.nextSibling;
		} 
	
	// If we found it, then update our internal object properties
	// from this nodes and, recursively, its descendents.
	if ( modelRootNode )
		{
		uiShowBusy( "Updating...." );
		this.applyObjectUpdates( modelRootNode );
		uiShowBusy( null );
		}
	else
		throw new Error( "Model's root node not found."  );
		
}

PLXModel.prototype.applyObjectUpdates = function( xmlElem )
{
	// WHAT???
	if ( !xmlElem )
		return;

	var idx;
	var objID = xmlElem.tagName;

	// Update object properties from xml element's attributes...
	var allMyAttribs = xmlElem.attributes;
	if ( allMyAttribs )
		{
		idx = 0;
		var currAttrib;
		while ( currAttrib = allMyAttribs[idx++] )
			this.setPV( objID + "." + currAttrib.name, currAttrib.value );
		}
			
	// Recurse this element's children to do the same.....
	var allMyChildNodes = xmlElem.childNodes;
	for ( idx = 0; idx < allMyChildNodes.length; idx++ )
		{
		if ( allMyChildNodes[idx].nodeType == NODE_ELEMENT )
			this.applyObjectUpdates( allMyChildNodes[idx] );
		}
}

PLXModel.prototype.getParam = function( name, defVal )
{
	// Select the text node of the param element with the specified name....
	var node = this._plxState.selectSingleNode( "/plxstate/params/p[@_n='" + name + "']/@_v" );
	
	// If found then return the text or the default value if empty.
	if ( node )
		{
		var value = node.nodeValue;
		return value && value != "" ? value : defVal;
		}
	
	// Not found, return the default.
	else
		return defVal;
}

PLXModel.prototype.getPropElem = function( propID )
{
	// Selects the "prop" element with the specified id.
	return this._plxState.selectSingleNode( "/plxstate/props/prop[@id='" + propID + "']" )
}

PLXModel.prototype.getPV = function( propID, defVal )
{
	var propElem = this.getPropElem( propID );
	if ( !propElem )
		return defVal;
		
	if ( propElem.getAttributeNode( "_v" ) )
		return propElem.getAttribute( "_v" );
	else
		return defVal;
}

PLXModel.prototype.setPV = function( propID, propValue, noNotify )
{
	// Property defined?  If not, we're done.
	var propElem = this.getPropElem( propID );
	if ( !propElem )
		return;
		
	// If the specified property value is different, then set the property
	// value and dispatch the actions.
	var propChanged = propElem.getAttribute( "_v" ) != propValue;
	if ( propChanged )
		{
		propElem.setAttribute( "_v", propValue );

		if ( !noNotify )
			this.dispatchPropChanges( propElem.getElementsByTagName( "onpc" ), propValue );
		}
}

PLXModel.prototype.dispatchPropChanges = function( onPCElems, propValue )
{
	var staleOnes = new Array;

	// Walk the on property change elements provided....	
	for ( var idx = 0; idx < onPCElems.length; idx++ )
		{
		var currOnPC = onPCElems[idx];
		
		// get the action handler for this guy....
		var handler = this.parseHandler( currOnPC.getAttribute( "_a" ) );
		
		// If there is one, then ....
		if ( handler )
			{
			// get the id of the element to receive the action....
			var tarElem = null;
			var tarElemID = currOnPC.getAttribute( "_e" );
			
			// Get the element itself.  It's OK if tarElem is null.
			if ( tarElemID )
				tarElem = this.lookupElem( tarElemID );

			// If the caller specified the new property value, then we are good to go...
			if ( propValue )
				handler( tarElem, propValue );
				
			// Otherwise we need to get the value ourselves....
			else
				{
				var pv = currOnPC.parentNode.getAttribute( "_v" );
				handler( tarElem, pv );
				}
			}
			
		// If this notification is only supposed to happen once, then add it
		// to the list of stale onPC elements for later removal....
		if ( propValue && currOnPC.getAttributeNode( "_once" ) )
			staleOnes.push( currOnPC );
		}
	
	// Loose any stale onPC elements....
	while ( staleOnes.length )
		{
		var staleOnPC = staleOnes.pop();
		staleOnPC.parentNode.removeChild( staleOnPC );
		}
}

PLXModel.prototype.getAppOb = function( indexID, objID, createIfNeeded )
{
	objID = objID.toLowerCase();
	indexID = indexID.toLowerCase();
	
	var objIndex = this.getAppObIndex( indexID, createIfNeeded );
	var theObj = objIndex[ objID ];

	if ( !theObj && createIfNeeded )
		{
		theObj = new Object;
		theObj._id = objID;
		objIndex[ objID ] = theObj;
		}
		
	return theObj;
	
}

PLXModel.prototype.getAppObIndex = function( indexID, createIfNeeded )
{
	indexID = indexID.toLowerCase();

	if ( !this._appObIndices )
		this._appObIndices = new Object;
		
	var theIndex = this._appObIndices[ indexID ];
	if ( !theIndex && createIfNeeded )
		{
		theIndex = new Object;
		this._appObIndices[ indexID ] = theIndex;
		}
		
	return theIndex
}

PLXModel.prototype.getElementById = function( plxID )
{
	// can we wanswer?
	if ( !this._plxState )
		return null;
		
	// select the mapping element with the specified ID.  
	var mapping = this._plxState.selectSingleNode( "/plxstate/idmappings/map[@id='" + plxID + "']" );
	if ( !mapping )
		return null;
		
	// The "_e" attribute is the html element id of the actual desired element.
	return this._elemIdx[ mapping.getAttribute( "_e" ) ];
}

PLXModel.prototype.onPC = function ( propID, propValue, targetElemID, handlerTxt, notifyOnce )
{
	// If the specified property is not defined, then create one.
	var propElem = this.getPropElem( propID );
	if ( !propElem )
		{
		propElem = this._plxState.createElement( "prop" );
		var propList = this._plxState.selectSingleNode( "/plxstate/props" );
		propList.appendChild( propElem );
		propElem.setAttribute( "id", propID );
		propElem.setAttribute( "_v", propValue );
		}
		
	// If an action handler is specified, then create an onPC child element that 
	// becomes it.
	if ( handlerTxt  )
		{
		var handlerElem = this._plxState.createElement( "onpc" );
		propElem.appendChild( handlerElem );
		
		handlerElem.setAttribute( "_a", handlerTxt );
		if ( targetElemID )
			handlerElem.setAttribute( "_e", targetElemID );

		if ( notifyOnce )
			handlerElem.setAttribute( "_once", notifyOnce );
		}
};


///////////////////////// HANDLER INFORMATION /////////////////
//	Handler prototypes....
//		_e	- HTML element that is a target for the action
//		_v	- The new value of the object property. (string)
//
///////////////////////////////////////////////////////////////
PLXModel.prototype.parseHandler = function(handlerTxt)
{
    // Must have something to parse....
    if (!handlerTxt || handlerTxt == "")
        return null;

    // If no handlers defined, then define the pre-defined ones.....
    if (!this._hndlrs)
    {
        this._hndlrs = new Object;
        this._hndlrs["hide"] = function(_e, _v) { _e.style.display = _v == "y" ? "none" : ""; };
        this._hndlrs["hideb"] = function(_e, _v) { _e.style.display = _v == "y" || _v == "" ? "none" : ""; };
        this._hndlrs["hideIfMT"] = function(_e, _v) { _e.style.display = _v == "" ? "none" : ""; };
        this._hndlrs["hideIf0"] = function(_e, _v) { _e.style.display = _v == "0" ? "none" : ""; };
        this._hndlrs["show"] = function(_e, _v) { _e.style.display = _v == "y" ? "" : "none"; };
        this._hndlrs["showb"] = function(_e, _v) { _e.style.display = _v == "y" || _v == "" ? "" : "none"; };
        this._hndlrs["showIfMT"] = function(_e, _v) { _e.style.display = _v == "" ? "" : "none"; };
        this._hndlrs["showIf0"] = function(_e, _v) { _e.style.display = _v == "0" ? "" : "none"; };
        this._hndlrs["cloak"] = function(_e, _v) { _e.style.visibility = _v == "y" ? "hidden" : "visible"; };
        this._hndlrs["cloakb"] = function(_e, _v) { _e.style.visibility = _v == "y" || _v == "" ? "hidden" : "visible"; };
        this._hndlrs["cloakIfMT"] = function(_e, _v) { _e.style.visibility = _v == "" ? "hidden" : "visible"; };
        this._hndlrs["uncloak"] = function(_e, _v) { _e.style.visibility = _v == "y" ? "visible" : "hidden"; };
        this._hndlrs["uncloakb"] = function(_e, _v) { _e.style.visibility = _v == "y" || _v == "" ? "visible" : "hidden"; };
        this._hndlrs["uncloakIfMT"] = function(_e, _v) { _e.style.visibility = _v == "" ? "visible" : "hidden"; };
        this._hndlrs["setText"] = function(_e, _v) { _e.innerHTML = _v };
        this._hndlrs["setTextNBSP"] = function(_e, _v) { _e.innerHTML = _v && _v != "" ? _v : "&#160;"; };
        this._hndlrs["setValue"] = function(_e, _v) { _e.defaultValue = _v; _e.value = _v; };
        this._hndlrs["check"] = function(_e, _v) { _e.checked = _v == "y"; };
        this._hndlrs["disableElem"] = function(_e, _v) { _e.disabled = _v != 'y' ? true : false; };
        this._hndlrs["enable"] = uiEnable;
        this._hndlrs["select"] = uiSelect;
        this._hndlrs["setConfig"] = function(_e, _v)
                                        {
                                            _e.src = g_myModel.getParam(_v == 'y' ? 'IconEnumConfigured' : 'IconEnumUnConfigured');
                                            _e.title = _v == 'y' ? 'This selection is configured.  Click to change configuration...' :
																	'This selection is NOT configured.  You must click to complete the configuration...';
                                        }
    }

    // Create a new handler if we've never seen this one before.....
    var theHandler = this._hndlrs[handlerTxt];
    if (!theHandler)
    {
        theHandler = new Function("_e,_v", handlerTxt);
        this._hndlrs[handlerTxt] = theHandler;
    }

    return theHandler;
}



