// Copyright 2008 ProjectWizards GmbH. All rights reserved.

var WRLSnapshotURIKey						= "uri";

var WRLCleartextToManyRelationshipType		= "many";
var WRLCleartextToOneRelationshipType		= "one";
var WRLCleartextDateAttributeType			= "date";
var WRLCleartextInteger16AttributeType		= "integer16";
var WRLCleartextInteger32AttributeType		= "integer32";
var WRLCleartextInteger64AttributeType		= "integer64";
var WRLCleartextDecimalAttributeType		= "decimal";
var WRLCleartextDoubleAttributeType			= "double";
var WRLCleartextFloatAttributeType			= "float";
var WRLCleartextUndefinedAttributeType		= "undefined";
var WRLCleartextNullifyDeleteRule			= "nullify";
var WRLCleartextCascadeDeleteRule			= "cascade";

var WRLEntityClassNameKey					= "className";
var WRLEntityJSClassNameKey					= "jsClassName";
var WRLObserverKey							= "observer";
var WRLContextKey							= "context";
var WRLEntityKey							= "entity";
var WRLAjaxContextChangedByServerNotification	= "ManagedObjectContextChangedByServer";
var WRLSynchronizeErrorKey					= "error";
var NSLocalizedDescriptionKey				= "NSLocalizedDescriptionKey";
var NSLocalizedRecoverySuggestionErrorKey	= "NSLocalizedRecoverySuggestionErrorKey";

var WRLSynchronizeChangedKey				= "changed";
var WRLSynchronizeDeletedKey				= "deleted";
var WRLSynchronizeAddedKey					= "added";

var WRLJSONNullString						= "null";
var WRLJSONTrueString						= "true";
var WRLJSONValidatedString					= "validated";

var WRLEntityFormValueKey					= "entity";
var WRLPredicateFormValueKey				= "predicate";
var WRLURIsFormValueKey						= "objectURIs";
var WRLRelationshipFormValueKey				= "relationshipKey";
var WRLRecoveryOptionFormValueKey			= "recoveryOption";
var WRLKeyFormValueKey						= "key";
var WRLValueFormValueKey					= "value";
var WRLActionNameFormValueKey				= "action";
var WRLLocalizedRecoveryOptions				= "localizedRecoveryOptions";

var WRLFetchObjectsWithURIsActionName 		= "fetchObjectsWithURIs";
var WRLFetchModelActionName 				= "fetchModel";
var WRLForgetObjectsActionName 				= "forgetObjects";
var WRLValidateActionName 					= "validate";
var WRLRecoverFromValidationErrorActionName	= "recoverFromValidationError";
var WRLSaveActionName 						= "save";
var WRLUndoActionName 						= "undo";
var WRLRedoActionName 						= "redo";
var WRLFetchDirectActionName	   			= "fetch";
var WRLSynchronizeDirectActionName 			= "synchronize";

var Base = function() {
	if (arguments.length) {
		if (this == window) {
			Base.prototype.extend.call(arguments[0], arguments.callee.prototype);
		} else {
			this.extend(arguments[0]);
		}
	}
};
Base.version = "1.0.2";
Base.prototype = {
extend: function(source, value) {
	var extend = Base.prototype.extend;
	if (arguments.length == 2) {
		var ancestor = this[source];
		if ((ancestor instanceof Function) && (value instanceof Function) &&
			ancestor.valueOf() != value.valueOf() && /\bbase\b/.test(value)) {
			var method = value;
			value = function() {
				var previous = this.base;
				this.base = ancestor;
				var returnValue = method.apply(this, arguments);
				this.base = previous;
				return returnValue;
			};
			value.valueOf = function() {
				return method;
			};
			value.toString = function() {
				return String(method);
			};
		}
		return this[source] = value;
	} else if (source) {
		var _prototype = {toSource: null};
		var _protected = ["toString", "valueOf"];
		if (Base._prototyping) _protected[2] = "constructor";
		for (var i = 0; (name = _protected[i]); i++) {
			if (source[name] != _prototype[name]) {
				extend.call(this, name, source[name]);
			}
		}
		for (var name in source) {
			if (!_prototype[name]) {
				extend.call(this, name, source[name]);
			}
		}
	}
	return this;
},
base: function() {
}
};

Base.extend = function(_instance, _static) {
	var extend = Base.prototype.extend;
	if (!_instance) _instance = {};
	Base._prototyping = true;
	var _prototype = new this;
	extend.call(_prototype, _instance);
	var constructor = _prototype.constructor;
	_prototype.constructor = this;
	delete Base._prototyping;
	var klass = function() {
		if (!Base._prototyping) constructor.apply(this, arguments);
		this.constructor = klass;
	};
	klass.prototype = _prototype;
	klass.extend = this.extend;
	klass.implement = this.implement;
	klass.toString = function() {
		return String(constructor);
	};
	extend.call(klass, _static);
	var object = constructor ? klass : _prototype;
	if (object.init instanceof Function) object.init();
	return object;
};

Base.implement = function(_interface) {
	if (_interface instanceof Function) _interface = _interface.prototype;
	this.prototype.extend(_interface);
};

var WRLFetchRequest = Base.extend({
	constructor: function(entityName, predicate)
	{
		this.base();
		this.entityName = entityName;
		this.predicate = predicate;
	}

});


var WRLObject = Base.extend({
	constructor: function()
	{
	},

	willChangeValueForKey: function(key)
	{	
	},

	didChangeValueForKey: function(key)
	{
		if(this.observedKeys)
		{
			var keyObservers = this.observedKeys[key];
			if(keyObservers && keyObservers.length)
			{
				var value = this.valueForKey(key);
				for(var i=0; i<keyObservers.length; i++)
				{
					var info = keyObservers[i];
					info.observer.observerValueForKeyOfObject.call(info.observer, value, key, this, info.context);
				}
			}
		}
	},

	valueForKey: function(key)
	{
		var value = this[key];
		if(typeof value == "function")	
			value = value.call(this);
		return value;
	},

	valueForKeyPath: function(keyPath)
	{
		var index = keyPath.indexOf(".");
		if(index == -1)
			return this.valueForKey(keyPath);
		else
		{
			var firstKey = keyPath.substring(0, index);
			var value = this.valueForKey(firstKey);
			return value ? value.valueForKeyPath(keyPath.substring(index+1)) : null;
		}
	},

	setValueForKey: function(value, key)
	{
		this.willChangeValueForKey(key);
		this[key] = value;
		this.didChangeValueForKey(key);
	},

	setValueForKeyPath: function(value, keyPath)
	{
		this.willChangeValueForKey(key);
		var index = keyPath.indexOf(".");
		if(index == -1)
			this.setValueForKey(value, keyPath);
		else
		{
			var firstKey = keyPath.substring(0, index);
			var value = this.valueForKey(firstKey);
			if(value)
				value.setValueForKeyPath(value, keyPath.substring(index+1));
		}
		this.didChangeValueForKey(key);
	},

	addObserverForKey: function(observer, key, context)
	{
		if(!this.observedKeys)
			this.observedKeys = {};
		var keyObservers = this.observedKeys[key];
		if(!keyObservers)
		{
			keyObservers = [];
			this.observedKeys[key] = keyObservers;
		}
		var info = new WRLObject();
		info.setValueForKey(observer, WRLObserverKey);
		info.setValueForKey(context, WRLContextKey);
		if(keyObservers.valueForKey(WRLObserverKey).indexOf(observer) == -1)
			keyObservers.push(info);
	},

	removeObserverForKey: function(observer, key)
	{
		if(this.observedKeys)
		{
			var keyObservers = this.observedKeys[key];
			for(var i=0; i<keyObservers.length; i++)
			{
				var info = keyObservers[i];
				if(info.observer == observer)
				{
					this.observedKeys[key] = keyObservers.without(info);
					break;
				}
			}
		}
	},


	addObserverForKeyPath: function(observer, keyPath, context)
	{
		var index = keyPath.indexOf(".");
		if(index == -1)
			this.addObserverForKey(observer, keyPath, context);
		else
		{
			var firstKey = keyPath.substring(0, index);
			var value = this.valueForKey(firstKey);
			if(value)
				value.addObserverForKey(observer, keyPath.substring(index+1), context);
		}
	},

	removeObserverForKeyPath: function(observer, keyPath)
	{
		var index = keyPath.indexOf(".");
		if(index == -1)
			this.removeObserverForKey(observer, keyPath);
		else
		{
			var firstKey = keyPath.substring(0, index);
			var value = this.valueForKey(firstKey);
			if(value)
				value.removeObserverForKey(observer, keyPath.substring(index+1));
		}
	}


});

Array.prototype.valueForKey = function(key)
{
	var result = new Array();
	for(var i=0; i<this.length; i++)
		result[i] = this[i].valueForKey(key);
	return result;
}

Array.prototype.valueForKeyPath = function(key)
{
	var result = new Array();
	for(var i=0; i<this.length; i++)
		result[i] = this[i].valueForKeyPath(key);
	return result;
}


var WRLManagedObject = WRLObject.extend({
	constructor: function(context, objectID, properties)
	{
		this.base();
		this.objectID = objectID;
		this.createEmptyProperties();
		this.context = context;
		this.isFault = !properties;
		this.setProperties(properties);
		this.changedPropertiesAndTheirSnapshotValues = {};
		this.modificationDate = new Date();
	},

	storeID: function()
	{
		if(this.objectID && this.objectID.uri)
			return this.objectID.uri.split("/")[0];
		return null;
	},

	sortValueForKey: function(key)
	{
		return this.valueForKey(key);
	},

	createEmptyProperties: function()
	{
		var propertiesDict = this.objectID.entity.properties;
		this.properties = {};
		for(key in propertiesDict)
		{
			var description = propertiesDict[key];
			var type = description.type;
			if(type == WRLCleartextToManyRelationshipType)
				this.properties[key] = [];
		}
	},

	descriptionForProperty: function(property)
	{
		return this.objectID.entity.properties[property];
	},

	className: function()
	{
		if(this.objectID && this.objectID.entity && this.objectID.entity.className)
			return this.objectID.entity.className;
		return "WRLManagedObject";
	},

	entityName: function()
	{
		if(this.objectID && this.objectID.entity && this.objectID.entity.name)
			return this.objectID.entity.name;
		return "";
	},

	willAccessValueForKey: function(key)
	{
		if(this.isFault) {
			this.context.fetchObjectWithURI(this.objectID.uri);
		}
	},

	didChangeValueForKey: function(key)
	{
		this.base(key);
		this.modificationDate = new Date();
		this.context.modificationDate = this.modificationDate;
	},

	valueForKey: function(key)
	{
		this.willAccessValueForKey(key);
		var value = this[key];
		if(typeof value == "function")
			value = value.call(this);
		else if(!value)
			value = this.properties[key];
		if(value instanceof Array && value.length>0 && value[0].isFault) {
			value = this.context.fetchRelationshipInObject(key, this);				//ak warum wurde der Rückgabewert nicht dem value zugewiesen?
		}
		return value;
	},

	isAddedObject: function() {
		return this.context.isAddedObject(this);
	},

	removeObjectFromRelationship: function(object, relationName)
	{
		var description = this.descriptionForProperty(relationName);
		if(description.type == WRLCleartextToManyRelationshipType)
		{
			var collection = this.valueForKey(relationName);
			var newCollection = collection.without(object);
			this.setValueForKey(newCollection, relationName, true);
		}
		else
			this.setValueForKey(null, relationName, true);	
	},

	addObjectToRelationship: function(object, relationName)
	{
		var description = this.descriptionForProperty(relationName);
		if(description.type == WRLCleartextToManyRelationshipType)
		{
			var collection = this.valueForKey(relationName);
			if(collection.indexOf(object) == -1)
			{
				var newCollection = collection.clone();
				newCollection.push(object);
				this.setValueForKey(newCollection, relationName, true);
			}
		}
		else
			this.setValueForKey(object, relationName, true);		
	},

	setValueForKey: function(value, key, dontResolveInverse)
	{
		this.willChangeValueForKey(key);
		if(!this.changedPropertiesAndTheirSnapshotValues[key])
		{
			this.changedPropertiesAndTheirSnapshotValues[key] = this.valueForKey(key);
			if(!this.isAddedObject())
				this.context.addChangedObject(this);
		}
		var description = this.descriptionForProperty(key);
		if(!dontResolveInverse && description && (description.type == WRLCleartextToOneRelationshipType || description.type == WRLCleartextToManyRelationshipType))
		{
			var inverseKey = description.inverse;
			if(inverseKey)
			{	
				if(description.type == WRLCleartextToManyRelationshipType)
				{
					var unchangedObjects = []; 
					var relatedObjects = this.valueForKey(key);
					if(relatedObjects)
					{
						for(var i=0; i<relatedObjects.length; i++)
						{
							var relatedObject = relatedObjects[i];
							if(value.indexOf(relatedObject) == -1)
								relatedObject.removeObjectFromRelationship(this, inverseKey);
							else
								unchangedObjects.push(relatedObject);
						}
					}
					if(value)
					{
						for(var k=0; k<value.length; k++)
						{
							var relatedObject = value[k];
							if(unchangedObjects.indexOf(relatedObject) == -1)
								relatedObject.addObjectToRelationship(this, inverseKey);
						}
					}
				}
				else	// to one relationship
				{
					var relatedObject = this.valueForKey(key);
					if(relatedObject)
						relatedObject.removeObjectFromRelationship(this, inverseKey);
					if(value)
						value.addObjectToRelationship(this, inverseKey)
				}
			}
		}
		var setter = "set-"+key;
		setter = setter.camelize();
		var property = this[setter];
		if(typeof property == "function")
			property.call(this, value);
		else if(!property)
			this.properties[key] = value;
		this.didChangeValueForKey(key);
	},

	createValueForPropertyFromJSON: function(key, value)
	{
		var model = this.context.objectModel();
		var entity = this.objectID.entity;
		var type = entity.properties[key].type;
		if(type == WRLCleartextToOneRelationshipType)
			value = value ? this.context.registeredObjectWithURIString(value, true) : null;			
		else if(type == WRLCleartextToManyRelationshipType){
			if(value && value.length){
				var newValue = [];
				for(var i=0; i<value.length; i++)
					newValue.push(this.context.registeredObjectWithURIString(value[i], true));
				value = newValue;
			}
		}
		else if(type == WRLCleartextDateAttributeType){
			var milliseconds = 1000*value;
			value = value ? new Date(milliseconds) : null;
		}else if(type == WRLCleartextInteger16AttributeType || type == WRLCleartextInteger32AttributeType || type == WRLCleartextInteger64AttributeType || type == WRLCleartextDoubleAttributeType || type == WRLCleartextFloatAttributeType || type == WRLCleartextDecimalAttributeType)
			value = new Number(value);
		else if(type == WRLCleartextUndefinedAttributeType){
			var prop = entity.properties[key];
			var customClass = prop[WRLEntityClassNameKey];
			if(!customClass)
				customClass = prop[WRLEntityJSClassNameKey];
			value = (customClass && value) ? new window[customClass](value) : null;
		}
		return value;
	},

	setProperties: function(properties, useSetValueForKey, alwaysOverwrite)
	{
		if(properties)
			this.isFault = false;
		var model = this.context.objectModel();
		var entity = this.objectID.entity;
		for(var key in properties){
			var value = properties[key];		
			var type = entity.properties[key].type;
			if(type == WRLCleartextToOneRelationshipType){
				value = value ? this.context.registeredObjectWithURIString(value, true) : null;			
			}else if(type == WRLCleartextToManyRelationshipType){
				if(value && value.length){
					var newValue = [];
					for(var i=0; i<value.length; i++)
						newValue.push(this.context.registeredObjectWithURIString(value[i], true));
					value = newValue;
				}
			}
			else if(type == WRLCleartextDateAttributeType){
				var milliseconds = 1000*value;
				value = value ? new Date(milliseconds) : null;
			}else if(type == WRLCleartextInteger16AttributeType || type == WRLCleartextInteger32AttributeType || type == WRLCleartextInteger64AttributeType || type == WRLCleartextDoubleAttributeType || type == WRLCleartextFloatAttributeType || type == WRLCleartextDecimalAttributeType)
				value = new Number(value);
			else if(type == WRLCleartextUndefinedAttributeType){
				var prop = entity.properties[key];
				var customClass = prop[WRLEntityClassNameKey];
				if(!customClass)
					customClass = prop[WRLEntityJSClassNameKey];
				if(customClass && value){
					if(window[customClass])
						value = new window[customClass](value);
					else{
	//					console.log("missing javascript class: '%s'", customClass);
					}
				}else
					value = null;
			}
			if(alwaysOverwrite || this.valueForKey(key) != value){
				if(useSetValueForKey)
					this.setValueForKey(value, key);
				else
				{
					this.willChangeValueForKey(key);
					this.properties[key] = value;
					this.didChangeValueForKey(key);
				}
			}
		}
	},

	changedProperties: function()
	{
		var result = null;
		for(var key in this.changedPropertiesAndTheirSnapshotValues)
		{
			var description = this.descriptionForProperty(key);
			var type = description.type;
			var value = this.valueForKey(key);
			if(type == WRLCleartextToManyRelationshipType || type == WRLCleartextToOneRelationshipType)
				value = this.valueForKeyPath(key+".objectID.uri");
			if(!result)
				result = {};
			result[key] = value;
		}
		return result;
	},

	clearChangedProperties: function()
	{
		this.changedPropertiesAndTheirSnapshotValues = {};
	},

	validateValueForKey: function(value, key) 
	{
		var result;
		var opt = {};
		var propertyDescription = this.objectID.entity.properties[key];
		if(value)
		{
			if(propertyDescription.type == WRLCleartextToOneRelationshipType)
				value = value.objectID.uri;
			else if(propertyDescription.type == WRLCleartextToManyRelationshipType)
			{
				var newValue = [];
				for(var i=0; i<value.length; i++) 
				{
					newValue.push(value[i].objectID.uri);
				}
				value = newValue;
			}
		}
		var jsonValue = Object.toJSON(value);
		opt.parameters = {action:WRLValidateActionName, key:key, value:jsonValue};
		opt.parameters[WRLSnapshotURIKey] = this.objectID.uri;
		opt.asynchronous = false;
		var request = new Ajax.Request(this.context.requestURL, opt);
		try 
		{
			result = eval("("+request.transport.responseText+")");
			if(result.value)
				result.value = this.createValueForPropertyFromJSON(key, result.value);
		} catch (e) 
		{
			result = {validated:false, value:null, error:("The object could not be validated because of an connection error: "+e)};
		}
		result.key = key;
		return result;
	},

	validateValueForKeyPath: function(value, keyPath)
	{
		var index = keyPath.indexOf(".");
		if(index == -1)
			return this.validateValueForKey(value, keyPath);
		else
		{
			var firstKey = keyPath.substring(0, index);
			var value = this.valueForKey(firstKey);
			return value ? value.validateValueForKeyPath(value, keyPath.substring(index+1)) : {validated:false, error:'A value could not be validated because of an invalid key.'};
		}
	},
		
	recoverFromValidationError: function(recoveryOption, info)
	{
		var opt = {};
		opt.parameters = {action:WRLRecoverFromValidationErrorActionName, recoveryOption:recoveryOption};
		opt.asynchronous = false;
		var request = new Ajax.Request(this.context.requestURL, opt);		
		var syncResult = this.context.synchronize();
		NotificationCenter.defaultNotificationCenter().sendMessage(WRLAjaxContextChangedByServerNotification, {context:this.context, result:syncResult});
	},

	uri: function() {
		return this.objectID.uri;
	},
    
    dispose: function()
    {
        this.isInvalid = true;
    }

});


// WRLAjaxContext

var ajaxContexts = {};

var WRLAjaxContext = WRLObject.extend({
	constructor: function(serverContextID, requestURL)
	{
		this.base();		
		this.serverContextID = serverContextID;
		this.requestURL = requestURL;
		this.registeredObjects = {};
		this.deletedObjectsURIs = [];
		this.changedObjects = [];
		this.addedObjects = {};
		this.model = null;
		this.uniqueIDCounter = 0;
		this.fetchRelationships = true;
		ajaxContexts[serverContextID] = this;

		this.firstPartOfUniqueID = "";
		var unwanted = ["/", "."];
		var array = this.requestURL.toArray();
		for(var i=0; i<array.length; i++)
		{
			var char = array[i];
			if(unwanted.indexOf(char) == -1)
				this.firstPartOfUniqueID += char;
		}
	},

	entityWithName: function(entityName)
	{
		return this.model[entityName];
	},

	addChangedObject: function(object)
	{
		if(this.changedObjects.indexOf(object) == -1)
			this.changedObjects.push(object);
	},

	propertiesOfEntityInModel: function(entity, model)
	{
		var properties = {};
		if(entity)
		{
			var props = entity.properties;
			if(props)
				for(var propertyName in props)
					properties[propertyName] = props[propertyName];
			if(entity.superentity)
			{
				var superProps = this.propertiesOfEntityInModel(model[entity.superentity], model);
				for(var propertyName in superProps)
					properties[propertyName] = superProps[propertyName];		
			}
		}
		return properties;
	},

	parseModelFromJSONString: function(jsonString)
	{
		try
		{
			var model = eval("("+jsonString+")");
		}
		catch(e)
		{
	//		console.log("error: %s JSON: %s", e, jsonString);
		}
		
		for(var entityName in model)
		{
			var entity = model[entityName];
			entity.properties = this.propertiesOfEntityInModel(entity, model);
			entity.name = entityName;
		}
		return model;
	},

	setObjectModelString: function(value)
	{
		this.model = this.parseModelFromJSONString(value);
	},

	fetchModel: function()
	{
		var opt = {};
		opt.parameters = WRLActionNameFormValueKey+"="+WRLFetchModelActionName;
		opt.asynchronous = false;
		var request = new Ajax.Request(this.requestURL, opt);
		this.model = this.parseModelFromJSONString(request.transport.responseText);
	},

	objectModel: function()
	{
		if(!this.model)
			this.fetchModel();
		return this.model;
	},

	registeredObjectsWithEntity: function(entity, keyPath, value)
	{	
		var result = [];
		for(var key in this.registeredObjects)
		{
			var object = this.registeredObjects[key]; 
			if(object.objectID.entity.name == entity)
			{
				if(!keyPath || !value || object.valueForKeyPath(keyPath) == value)
					result.push(object);
			}
		}
		return result;	
	},

	registeredObjectWithURIString: function(objectURIString, createIfNone)
	{
		var object = this.registeredObjects[objectURIString];
		if(!object && createIfNone)
		{
			var idWithoutPK = objectURIString.substring(0, objectURIString.lastIndexOf("/"));
			var entityName = idWithoutPK.substring(idWithoutPK.lastIndexOf("/")+1);
			var model = this.objectModel();
			var entity = model[entityName];	
			var objectID = new WRLObject();
			objectID[WRLSnapshotURIKey] = objectURIString;
			objectID[WRLEntityKey] = entity;
			var className = entity.className && window[entity.className] ? entity.className : "WRLManagedObject";
			var object = new window[className](this, objectID, null);
			this.registeredObjects[objectURIString] = object;
		}
		return object;
	},

	executeFetchRequest: function(request, onSuccess)
	{	
		var opt = {};
		opt.parameters = WRLActionNameFormValueKey+"="+WRLFetchDirectActionName+"&"+WRLEntityFormValueKey+"=" + request.entityName;
		if(request.predicate)
			opt.parameters += "&"+WRLPredicateFormValueKey+"=" + request.predicate;
		if(onSuccess)
		{
			opt.context = this;
			opt.onSuccess = function(transport)
			{
				if(onSuccess)
				{
					var objects = transport.request.options.context.parsedObjectsFromJSONString(transport.responseText);
					onSuccess(objects);
				}
			}
			new Ajax.Request(this.requestURL, opt);
		}
		else
		{
			opt.asynchronous = false;
			var request = new Ajax.Request(this.requestURL, opt);
			return this.parsedObjectsFromJSONString(request.transport.responseText);
		}
	},

	fetchRelationshipInObject: function(relationship, object)
	{
		if(this.fetchRelationships) {
			var opt = {};
			opt.parameters = WRLActionNameFormValueKey+"="+WRLFetchDirectActionName+"&"+WRLSnapshotURIKey+"=" + object.objectID.uri + "&"+WRLRelationshipFormValueKey+"=" + relationship;
			opt.asynchronous = false;
			var request = new Ajax.Request(this.requestURL, opt);
			if(request.transport.responseText && request.transport.responseText.length && request.success())
				return this.parsedObjectsFromJSONString(request.transport.responseText);
			else 
				return null;
		}
	},

	fetchObjectWithURI: function(uri)
	{
		var objects = this.fetchObjectsWithURIs([uri]);
		return objects.length ? objects[0] : null;
	},

	fetchObjectsWithURIs: function(uris)
	{
		var result = [];
		var opt = {};
		opt.parameters = {action:WRLFetchObjectsWithURIsActionName, objectURIs:uris};
		opt.asynchronous = false;
		var request = new Ajax.Request(this.requestURL, opt);
		try {
			var objects = eval("("+request.transport.responseText+")");			
			for(var uri in objects) {
				var properties = objects[uri];
				delete properties.uri;
				var object = this.registeredObjectWithURIString(uri, true);
				if(object){
					object.setProperties(properties, false, true);
					result.push(object);
				}
			}
		}catch (e) {}
		return result;
	},

	insertNewObjectForEntityForName: function(entityName, storeID) // TODO: store ID mit übergeben (z.B. vom Projekt holen in MEWeb) -> storeID/entityName/ct12398712837
	{
		var object = null;
		var entity = this.objectModel()[entityName];	
		if(entity)
		{
			var uri = storeID+"/"+entityName+"/ct"+this.firstPartOfUniqueID + "-" + this.uniqueIDCounter++;
			var objectID = new WRLObject();
			objectID[WRLSnapshotURIKey] = uri;
			objectID[WRLEntityKey] = entity;
			var className = entity.className ? entity.className : "WRLManagedObject";
			object = new window[className](this, objectID, null);
			object.isFault = false;
			this.registeredObjects[uri] = object;
			this.addedObjects[uri] = entityName;
		}
		return object;
	},

	forgetObjects: function(objects)
	{
		this.registerChangedObjects = false;
		if(objects)
		{
			var objectURIs = [];
			for(var i=0; i<objects.length; i++)
			{
				var object = objects[i];
				var uri = object.objectID.uri;
                object.dispose();
				delete this.registeredObjects[uri];
				objectURIs.push(uri);
			}
			if(objectURIs.length)
			{
				var opt = {};
				opt.parameters = WRLActionNameFormValueKey+"="+WRLForgetObjectsActionName+"&"+WRLURIsFormValueKey+"="+objectURIs.toJSON();
				opt.asynchronous = false;
				new Ajax.Request(this.requestURL, opt);
			}
		}
	},

	deleteObject: function(object)
	{
		this.registerChangedObjects = true;
		this.deleteObjectWithURI(object.objectID.uri);
	},

	deleteObjects: function(objects)
	{
		if(objects)
			for(var i=0; i<objects.length; i++)
				this.deleteObject(objects[i]);
	},

	deleteObjectWithURI: function(URI)
	{
		var object = this.registeredObjects[URI];
		if(object)
		{
			var propertiesDescriptions = object.objectID.entity.properties;
			for(var propertyName in propertiesDescriptions)
			{
				var propertyDescription = propertiesDescriptions[propertyName];
				if(propertyDescription.type == WRLCleartextToManyRelationshipType)			
				{
					var inverseKey = propertyDescription.inverse;
					var deleteRule = propertyDescription.deleteRule;
					var collection = object.valueForKey(propertyName);
					if(collection)
					{
						for(var k=0; k<collection.length; k++)
						{
							var inverseObject = collection[k];
							if(deleteRule == WRLCleartextNullifyDeleteRule)
								inverseObject.removeObjectFromRelationship(object, inverseKey);
							else if(deleteRule == WRLCleartextCascadeDeleteRule)
								this.deleteObjectWithURI(inverseObject.objectID.uri);
						}
					}
				}
				else if(propertyDescription.type == WRLCleartextToOneRelationshipType)
				{
					var inverseKey = propertyDescription.inverse;
					var deleteRule = propertyDescription.deleteRule;
					var destinationObject = object.valueForKey(propertyName);
					if(destinationObject && deleteRule == WRLCleartextNullifyDeleteRule)					
					{
						if(destinationObject.objectID.entity.properties[inverseKey].type == WRLCleartextToManyRelationshipType)
						{
							var collection = destinationObject.valueForKey(inverseKey);
							destinationObject.setValueForKey(collection.without(object), inverseKey);
						}
						else
							destinationObject.setValueForKey(null, inverseKey);
					}	
					else if(deleteRule == WRLCleartextCascadeDeleteRule)
						this.deleteObjectWithURI(destinationObject.objectID.uri);
				}
			}
			delete this.registeredObjects[URI];
			if(this.addedObjects[URI])
				delete this.addedObjects[URI];
			if(this.registerChangedObjects)
				this.deletedObjectsURIs.push(URI);
				
			// remove the object from the list of changed objects
			if(this.changedObjects.indexOf(object) != -1)
				this.changedObjects = this.changedObjects.without(object);
		}
	},

	deleteObjectsWithURIs: function(URIs)
	{
		for(var i=0; i<URIs.length; i++)
			this.deleteObjectWithURI(URIs[i]);
	},

	refetchAllObjects: function() 
	{
		this.synchronize();
		var allObjects = Object.values(this.registeredObjects);
		var allURIs = Object.keys(this.registeredObjects);
		this.forgetObjects(allObjects);
		this.fetchModel();
		this.fetchObjectsWithURIs(allURIs);
	},

	handleSynchronizeResponse: function(response)
	{
		this.registerChangedObjects = false;
		if(response.updated)
			this.parsedObjectsFromResponse(response.updated);
		if(response.deleted)
			this.deleteObjectsWithURIs(response.deleted);
	},

	jsonStringFromObjects: function(objects)
	{
		var result = null;
		for(var i=0; i<objects.length; i++)
		{
			var object = objects[i];
			var changedProperties = object.changedProperties();
			if(!result)
				result = {};
			result[object.objectID.uri] = Object.clone(changedProperties);
			object.clearChangedProperties();
		}
		return result ? Object.toJSON(result) : null;
	},

	flushManipulatedObjectCaches: function()
	{
		this.deletedObjectsURIs = [];
		this.changedObjects = [];
		this.addedObjects = {};	
	},

	getAddedObjects: function()
	{
		var objects = [];
		for(var uri in this.addedObjects)
		{
			var object = this.registeredObjects[uri];
			if(object)
				objects.push(object);
		}
		return objects;
	},

	synchronize: function(onSuccess)
	{	
		var opt = {};

		opt.parameters = {};
		opt.parameters[WRLActionNameFormValueKey] = WRLSynchronizeDirectActionName;

		if(Object.keys(this.addedObjects).length)
			opt.parameters[WRLSynchronizeAddedKey] = this.jsonStringFromObjects(this.getAddedObjects());
		if(this.deletedObjectsURIs.length)
			opt.parameters[WRLSynchronizeDeletedKey] = this.deletedObjectsURIs.toJSON();
		var changed = this.jsonStringFromObjects(this.changedObjects);
		if(changed)
			opt.parameters[WRLSynchronizeChangedKey] = changed;

		opt.context = this;
		this.flushManipulatedObjectCaches();
		if(onSuccess)
		{
			opt.onSuccess = function(transport)
			{
				var result = eval("("+transport.responseText+")");
				this.context.handleSynchronizeResponse(result);
				onSuccess(result);
			}
			new Ajax.Request(this.requestURL, opt);
		}
		else
		{
			opt.asynchronous = false;
			var request = new Ajax.Request(this.requestURL, opt);
			try{
				var result = request.transport.responseText.evalJSON();
				this.handleSynchronizeResponse(result);
				return result;
			}catch(e){}
			return null;
		}
	},

	synchronizeAndSave: function(save, onSuccess)
	{
		this.synchronize(onSuccess);
		if(save)
			this.save();
	},

	parsedObjectsFromJSONString: function(jsonString)
	{
		var result = this.parsedObjectsFromResponse(jsonString.evalJSON());
		return result;
	},


	setInitialObjectsFromJSONString: function(jsonString)
	{
		this.fetchRelationships	= false;
		this.parsedObjectsFromJSONString(jsonString);
		this.fetchRelationships = true;
	},

	exchangeURI: function(oldURI, newURI)
	{
		var object = this.registeredObjects[oldURI];
		if(object)
		{
			object.objectID.uri = newURI;
			delete this.registeredObjects[oldURI];
			this.registeredObjects[newURI] = object;
		}
	},

	parsedObjectsFromResponse: function(responseObjects)
	{
		var objects = [];
		for(var i=0; i<responseObjects.length; i++)
		{
			var responseObject = responseObjects[i];
			var oldURI = responseObject.oldURI;		
			if(oldURI)
			{
				this.exchangeURI(oldURI, responseObject.uri);
				delete responseObject.oldURI;
			}
		}
		for(var i=0; i<responseObjects.length; i++)
		{
			var responseObject = responseObjects[i];
			var uri = responseObject.uri;
			delete responseObject.uri;
			var object = this.registeredObjectWithURIString(uri, true);
			if(object && responseObject.isDeleted)
				delete this.registeredObjects[uri];
			else if(object)
			{	
				object.setProperties(responseObject);
				objects.push(object);
			}
		}
		return objects;
	},
	
	save: function() {
		var error = null;
		var request = new Ajax.Request(this.requestURL, {parameters:{action:WRLSaveActionName}, asynchronous:false});
		try{
			var result = eval("("+request.transport.responseText+")");
			error = result[WRLSynchronizeErrorKey];
		}catch(e){
			return "The save operation couldn't be completed, because of a wrong responseText.";
		}		 
		return error;
	},

	undo: function() {
		new Ajax.Request(this.requestURL, {parameters:{action:WRLUndoActionName}, asynchronous:false});
		this.synchronize();
	},
	
	redo: function() {
		new Ajax.Request(this.requestURL, {parameters:{action:WRLRedoActionName}, asynchronous:false});
		this.synchronize();
	},
	
	isAddedObject: function(object) {
		return this.addedObjects[object.uri()];
	}
});


function synchronizeWithServerContext(serverContextID)
{
	var context = ajaxContexts[serverContextID];
	if(context)
	{
		var result = context.synchronize();
		NotificationCenter.defaultNotificationCenter().sendMessage(WRLAjaxContextChangedByServerNotification, {context:context, result:result});
	}
}

Date.prototype.toJSON = function() 
{
  return '"' + (this.getTime()/1000) + '"';
};


var NotificationCenter = Base.extend({
	constructor: function()
	{
		this.dict = {};
	},
	
	addObserverForMessage: function(observer, msg, method)
	{
		var observerDescription = {observer:observer, method:method};
		var observerList = this.dict[msg];
		if(!observerList)
			this.dict[msg] = [observerDescription];
		else
			observerList.push(observerDescription);
	},

	removeObserverForMessage: function(observer, msg)
	{
		var observerList = this.dict[msg];
		if(!observerList)
			return;
		var newList = [];
		for(var i=0; i<this.dict.length;i++)
		{
			var observerDescription = this.dict[i];
			if(observerDescription.observer != observer)
				newList.push(observerDescription);
		}
		this.dict[msg] = newList;
	},

	sendMessage: function(msg, context)
	{
		var observerList = this.dict[msg];
		if(!observerList)
			return;
		for(var i=0; i<observerList.length;i++)
		{
			var observerDescription = observerList[i];
			var observer = observerDescription.observer;
			var method = observerDescription.method;
			observer[method].call(observer, msg, context);
		}
	}

},{ // Class interface

	_defaultNotificationCenter: null,

	defaultNotificationCenter: function()
	{
		if(!NotificationCenter._defaultNotificationCenter)
			NotificationCenter._defaultNotificationCenter = new NotificationCenter();
		return NotificationCenter._defaultNotificationCenter;
	}

});
