/***************************************************************************
 * siteshader.js
 ***************************************************************************/
(function($)
{
/***************************************************************************
 * Constants
 ***************************************************************************/
$.APP_NS        = 'http://www.basicmatrix.com/schema/ss';
$.APP_NS_PREFIX = 'ss:';

/***************************************************************************
 * function htmlspecialchars
 ***************************************************************************/
$.htmlspecialchars = function(text)
{
	var chars        = Array('&','<','>','"');
	var replacements = Array('&amp;', '&lt;', '&gt;', '&quot;');
	for (var i = 0; i < chars.length; ++i)
	{
		var re = new RegExp('\\'+chars[i],'gi');
		if (re.test(text))
		{	text = text.replace(re, replacements[i]);	}
	}
	return text;
}

$.jqueryspecialchars = function(text)
{
	var chars        = Array('#',';','&',',','.','+','*','~','\'',':','"',
	                         '!','^','$','[',']','(',')','=','>' ,'|','/');
	var replacements = Array('\\#','\\;','\\&','\\,','\\.','\\+','\\*','\\~','\\\'','\\:','\\"',
	                         '\\!','\\^','\\$','\\[','\\]','\\(','\\)','\\=','\\>' ,'\\|','\\/');
	for (var i = 0; i < chars.length; ++i)
	{
		var re = new RegExp('\\'+chars[i],'gi');
		if (re.test(text))
		{	text = text.replace(re, replacements[i]);	}
	}
	return text;
}

// Source: http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
$.parseURL = function(url)
{
	var a  =  document.createElement('a');
	a.href = url;
	return {
		source: url,
		protocol: a.protocol.replace(':',''),
		host: a.hostname,
		port: a.port,
		query: a.search,
		params: (function(){
			var ret = {},
				seg = a.search.replace(/^\?/,'').split('&'),
				len = seg.length, i = 0, s;
			for (;i<len;i++) {
				if (!seg[i]) { continue; }
				s = seg[i].split('=');
				ret[s[0]] = s[1];
			}
			return ret;
		})(),
		file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
		hash: a.hash.replace('#',''),
		path: a.pathname.replace(/^([^\/])/,'/$1'),
		relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
		segments: a.pathname.replace(/^\//,'').split('/')
	};
}

/***************************************************************************
 * class ObjectDefinition
 ***************************************************************************/
ObjectDefinition = function(options)
{
// TODO: Call a base class constructor?
	if (options)
	{	$.extend(this.config,options);	}
};
ObjectDefinition.prototype =
{
// Class members:
	document : null
};

/***************************************************************************
 * class ObjectDatabase
 ***************************************************************************/
ObjectDatabase = function(options)
{
// TODO: Call a base class constructor?
	if (options)
	{	$.extend(this.config,options);	}
};

ObjectDatabase.prototype =
{
// Class members:
	config         : {},
	objDefinitions : {},

/***************************************************************************
 * function Init
 ***************************************************************************/
	Init : function(opts)
	{
	// Preload the object definition (names only).
		this.GetDefinitions();
	},

/***************************************************************************
 * function GetDefinitions
 ***************************************************************************/
	GetDefinitions : function(opts)
	{
	// Backup "this" so the callback methods can access it.
		dbObj = this;

	// Request a list of object definitions.
		$.ajax(
		{
			'type'     : 'GET',
			'url'      : 'rpc/object_definition.xml',
			'data'     : null,
			'dataType' : 'xml',
			'success'  : function(docObj,textStatus)
			{
			// Create stubs for the object definitions that we know about
			// but have not actually loaded, yet.
				
				$('ss\\:object',docObj).each(function()
				{
					var objDefName = $(this).attr('name');
					if (objDefName in dbObj.objDefinitions == false)
					{	dbObj.objDefinitions[objDefName] = false;	}
				});

			// The object definition list is now loaded.
				if (opts && opts.success)
				{	opts.success(dbObj.objDefinitions);	}
				return true;
			},
			'error' : function(xhttp,textStatus,errorThrown)
			{
			// The object definition list failed to load.
				if (opts && opts.error)
				{	opts.error(null);	}
				return false;
			}
		});

	//
		return null;
	},

/***************************************************************************
 * function GetDefinition
 ***************************************************************************/
	GetDefinition : function(opts)
	{
	// Parameter validation:
		var objDefName = opts['definition'];
		if (objDefName == undefined ||
			objDefName == '')
		{
		// TODO: Log this?
			return false;
		}

	// Backup "this" so the callback methods can access it.
		dbObj = this;

	// Do we already have this object definition?
		if (objDefName in this.objDefinitions)
		{
		// Gather information.
			var objDefObj = this.objDefinitions[objDefName];
			if (objDefObj instanceof ObjectDefinition)
			{
			// Pass the object definition to the callback.
				if (opts && opts.success)
				{	opts.success(objDefObj);	}

			// Return it, too.
				return objDefObj;
			}
		}

	// Request the object definition.
		$.ajax(
		{
			'type'     : 'GET',
			'url'      : 'rpc/object_definition.xml',
			'data'     : { 'definition' : objDefName },
			'dataType' : 'xml',
			'success'  : function(data,textStatus)
			{
			// The object definition is a document.
			// TODO: Construct an object?
				objDefObj = new ObjectDefinition();
				objDefObj.document = data;

			// Add the object definition to the database.
				dbObj.objDefinitions[objDefName] = objDefObj;

			// Inform the callback that the object definition is now available.
				if (opts && opts.success)
				{	opts.success(objDefObj,textStatus);	}
				return true;
			},
			'error' : function(xhttp,textStatus,errorThrown)
			{
			// The object definition failed to load. Inform the callback.
				if (opts && opts.error)
				{	opts.error(xhttp,textStatus,errorThrown);	}
				return false;
			}
		});

	// The object definition could not be returned, yet.
		return null;
	},

/***************************************************************************
 * function Find
 ***************************************************************************/
	Find : function(opts)
	{
	// Backup "this" so the callback methods can access it.
		dbObj = this;

	// Gather information.
		var args =
		{
			'objClass' : ('objClass' in opts ? opts['objClass'] : null),
			'objConds' : ('objConds' in opts ? opts['objConds'] : null),
			'start'    : ('start'    in opts ? opts['start'   ] : null),
			'limit'    : ('limit'    in opts ? opts['limit'   ] : null),
		};
		var callbackArgs = ('successArgs' in opts ? opts['successArgs'] : null);

	// Request a list of object definitions.
		$.ajax(
		{
			'type'     : 'GET',
			'url'      : 'rpc/object/find.xml',
			'data'     : args,
			'dataType' : 'xml',
			'success'  : function(docObj,textStatus)
			{
			// Gather the returned objects.
				var objects = [];
				$('ss\\:object',docObj).each(function()
				{
				// Convert the XML attributes to object properties.
					var obj = {};
					$(this.attributes).each(function()
					{	obj[this.name] = this.value;	});
				// Add the object to the collection.
					objects[objects.length] = obj;
				});

			// The objects are now loaded.
				if (opts && opts.success)
				{	opts.success(objects,callbackArgs);	}
				return true;
			},
			'error' : function(xhttp,textStatus,errorThrown)
			{
			// The object query failed.
				if (opts && opts.error)
				{	opts.error(null);	}
				return false;
			}
		});

	//
		return null;
	}
};

/***************************************************************************
 * jsTree datastores:
 ***************************************************************************/
$.extend($.jstree.datastores,
{
	'custom-object_definitions' : function()
	{
		return {
			get : function(obj,treeObj,opts)
			{
			// TODO: ?
				return null;
			},
			parse : function(data,treeObj,opts,callback)
			{
				if (data instanceof ObjectDefinition)
				{
				// Gather information.
					var docObj = data.document;

				// Parameter validation:
					if ( ! docObj)
					{	if (callback)
						{	callback.call(null,false);	}
						return '';
					}

				// Generate jsTree node content.
					var data = '';

				// Discover <ss:object/> nodes.
					$('ss\\:object',docObj).each(function()
					{
					// Discover <ss:source/> nodes.
						$('ss\\:source',this).each(function()
						{
						// Gather information.
							var sourcePath   = $(this).attr('relPath');
							var module       = $(this).attr('module');
							var bSystemPath  = parseInt($(this).attr('bSystemPath' ));
							var bProfilePath = parseInt($(this).attr('bProfilePath'));
							var bModulePath  = parseInt($(this).attr('bModulePath' ));

						// Create a source node.
							data += '<li id="source">' + // TODO: Locale?
							          '<a href="#"><ins>&nbsp;</ins>';
							if (bModulePath)
							{
								if (bSystemPath)
								{	data += module + ' (System Module)';	}
								else if (bProfilePath)
								{	data += module + ' (Profile Module)';	}
							}
							else
							{
								if (bSystemPath)
								{	data += 'System';	}
								else if (bProfilePath)
								{	data += 'Profile';	}
							}
							data += '</a>' +
							          '<ul>';

						// This is an object definition.
						// Gather information.
						// TODO: Quoting?
							var objDefName = $(this).attr('name');

						// Discover <ss:property/> nodes.
							data += '<li id="object::'+objDefName+'.properties" ' +
							           'rel="properties">' + // TODO: Locale?
							          '<a href="#"><ins>&nbsp;</ins>Properties</a>';
							var propNodes = $(// TODO: ? 'ss\\:properties > ' +
							                  'ss\\:property',this);
							if (propNodes.size())
							{
								data += '<ul>';
								propNodes.each(function()
								{
								// Generate a jsTree node for the property.
								// TODO: Quoting?
									var propName = $(this).attr('name');
									data += '<li id="object::'+objDefName+'.properties['+propName+']" ' +
									           'rel="property" ' +
									      'property="'+propName+'">' + // TODO: Locale?
									          '<a href="#"><ins>&nbsp;</ins>'+propName+'</a>' +
									        '</li>';
								});
								data += '</ul>';
							}
							data += '</li>';
							// Discover <ss:namespace/> nodes.
							data += '<li>' + // TODO: Locale?
							          '<a href="#"><ins>&nbsp;</ins>Namespaces</a>';
							var nsNodes = $(// TODO: ? 'ss\\:namespaces > ' +
							                'ss\\:namespace',this);
							if (nsNodes.size())
							{
								data += '<ul>';
								nsNodes.each(function()
								{
								// Generate a jsTree node for the namespace.
								// TODO: Quoting?
									var nsName = $(this).attr('name');
									if (nsName == undefined)
									{	nsName  = 'default';	}
									data += '<li id="object::'+objDefName+'.namespaces['+nsName+']" ' +
									           'rel="namespace" ' +
									     'namespace="'+nsName+'">' + // TODO: Locale?
									         '<a href="#"><ins>&nbsp;</ins>'+nsName+'</a>' +
									        '</li>';
								});
								data += '</ul>';
							}
							data += '</li>';

						// Close the "source" node.
							data += '</ul>' +
							      '</li>';
						});
					});
				}
				else if (data instanceof Object)
				{
				// Gather information.
					var objDefs = data;

				// Generate jsTree node content.
					var data = '';

				// Associative array (object list).
					for (var name in objDefs)
					{
					// Create an unexpanded nodes.
						data += '<li id="object::'+name+'" definition="'+name+'" rel="object" class="closed">' +
						          '<a href="#"><ins>&nbsp;</ins>'+name+'</a>' +
						          '<ul></ul>' +
						        '</li>';
					}
				}
				else
				{
				// TODO: ?
				}

				if (callback)
				{	callback.call(null,data);	}
				return data;
			},
			load : function(data,tree,opts,callback)
			{
				if (opts.static)
				{	callback.call(null,opts.static);	}
				else
				{
					var objDefName = data['definition'];
					if (objDefName == undefined)
					{
						$._OBJ_DB.GetDefinitions(
						{
							'success' : function(objDefs)
							{
								callback.call(null,objDefs);
							},
							'error' : function(xhttp,textStatus,errorThrown)
							{
								callback.call(null,false);
								tree.error(errorThrown + ' ' + textStatus); 
							}
						});
					}
					else
					{
						$._OBJ_DB.GetDefinition(
						{
							'definition' : data['definition'],
							'success'    : function(objDefObj,textStatus)
							{
								callback.call(null,objDefObj);
							},
							'error' : function(xhttp,textStatus,errorThrown)
							{
								callback.call(null,false);
								tree.error(errorThrown + ' ' + textStatus); 
							}
						});
					}
				}
			}
		};
	}
});

// Initialize the Object Definition database.
$._OBJ_DB = new ObjectDatabase();

})(jQuery);

