function TreeModel () {

	// inherit from observable
	this.inheritFrom = Observable;
	this.inheritFrom();

	this.children 	= new Array ();

	/**
	 * factory method for items
	 */
	var that = this;
	function createItem( itemType) {
		switch (itemType) {
		case 'DEV' 	: return new DeviceModel( that ); break;
		default		: return new TreeItemModel ( that ); break;
		}
	}

	function deleteRemoteItem(child) {
		// well... we do a REAL delete. so be careful, ask for confirmation!
		//check preconditions
		if (	!child.parent == that 		||  // must be a child of this 
				 child.viewType == 'FAC'		// cannot be a facility
			) {
			return null;
		}
		
		// prepare data
		var postData = {
				'item[viewType]'	: child.viewType,
				'item[id]'			: child.id
		}
		// add request to open requests and send it.
		$.ajax({
			dataType: 'text', // we generate objects from JSON with a parser, not with eval. (security!)
			cache: false,
			type: 'POST',
			url: url_for('modComponents/deviceTreeDelete'),
			data: postData,
			beforeSend: function (xhr) {
				kgCore.openRequests.add(this, 	// the ajax-request object
					xhr,						// the XmlHttpRequest Object, may be used for cancelling the request.
					that,						// the actual tree
					function (tree, data)		// the callback
					{
						switch (data.status) {
							case 'success' :
								tree.removeChild(child);
								break;
							case 'fail' :
								// TODO implement failure notice
								break;
							default:
								break;
						}
					}
				);
			},
			success: function (data, status) {
				kgCore.openRequests.finalize(this, data, status);
			},
			complete: function(xhr, status) {
				kgCore.openRequests.finalize(this, null, status);
			}
			
		});
	}
	
	function createRemoteItem(childType) {
		// check preconditions.
		if (	!(childType == 'SEC' || childType == 'DEV') 		|| 
				!(that.viewType == 'SEC' || that.viewType == 'FAC') 
			)
		{
			return null;
		}
		
		// prepare data
		var postData = {
				'item[viewType]'	: that.viewType,
				'item[id]'			: that.id,
				'childType'			: childType
		};
		
		// add request to open requests and send it.
		$.ajax({
			dataType: 'text', // we generate objects from JSON with a parser, not with eval. (security!)
			cache: false,
			type: 'POST',
			url: url_for('modComponents/deviceTreeCreate'),
			data: postData,
			beforeSend: function (xhr) {
				kgCore.openRequests.add(this, 	// the ajax-request object
					xhr,						// the XmlHttpRequest Object, may be used for cancelling the request.
					that,						// the actual tree
					function (tree, data)		// the callback
					{
						switch (data.status) {
							case 'success' :
								tree.addChild(data.item);
								break;
							case 'fail' :
								// TODO implement failure notice
								break;
							default :
								break;
						}
					}
				);
			},
			success: function (data, status) {
				kgCore.openRequests.finalize(this, data, status);
			},
			complete: function(xhr, status) {
				kgCore.openRequests.finalize(this, null, status);
			}
			
		});
	}

	/**
	 * method for adding a child
	 */ 
	function addNewChild (type, noNotify) {
		var newChild = createItem(type);
		if (type) newChild.viewType = type;
		that.children.push ( newChild );
		if (!noNotify) that.notify('add', newChild);
		return newChild;
	};

	// public method for really creating a new section child in the DB.
	this.createChild = function(type) {
		createRemoteItem(type);
	}
	
	/**
	 * JSON importer
	 */
	this.addChild = function (jsonItem) {

		// template is neccessary, for actual creation use createChild
		if (!jsonItem || !jsonItem.item) return null;

		// create child, but do not notify yet
		var newChild = addNewChild(jsonItem.viewType, true);
		// set fields of the actual child instance. 
		for (field in jsonItem.item) {
			newChild[field] = jsonItem.item[field];
		}
		
		if (jsonItem.children != null) {
	
			// delegate children to child :)
			for (var i = 0; i < jsonItem.children.length; i++) {
				newChild.addChild(jsonItem.children[i]);
			}
		}
		this.notify('add', newChild);
		
		return newChild;
	};
	
	/**
	 * this method removes a child by deleting it on the server.
	 */
	this.deleteChild = function ( child ) 
	{
		deleteRemoteItem(child);
	};
	
	/**
	 * public function for removing a child
	 */
	this.removeChild = function ( child ) {
		var success = false;
		for (var i = 0; i < this.children.length; i++) {
			if ( child == this.children[i] ) {
				this.children.splice ( i, 1 );
				this.notify('remove', child);
				success = true;
				break;
			}
		}
		// not found
		if (!success) return false;
		// found
		return child;
	};

	/**
	 * public function for appending an existent child
	 */
	this.addExistentChild = function ( child ) {
		this.children.push(child);
		this.notify('add', child);
		return child;
	};

	/**
	 * Give a json item and get the js object instance
	 */
	this.findChild = function (jsonItem) {
		var result = null;
		for (var i = 0; i < this.children.length; i++) {
			var match = true;
			for (field in jsonItem.item) {
				if (typeof(jsonItem.item[field]) != 'object' && this.children[i][field] != jsonItem.item[field]) {
					match = false;
					break;
				}
			}
			if (match) {
				result = this.children[i];
				break;
			} else {
				result = this.children[i].findChild(jsonItem);
				if (result != null) break;
			}
		}
		return result;
	};
	
}

/**
 * Tree-Item. Inherits from a tree, i.e. every tree item is also a tree. Additionally each item knows its parent.
 */
function TreeItemModel(parentTree) {

	//inherit from tree
	this.inheritFrom = TreeModel;
	this.inheritFrom();
	
	// initialization
	this.parent = parentTree;
	
	// setters/getters
	this.setField = function (name, value) {
		// we're not allowed to override children
		if (name == 'children') return;
		// do nothing but notify if we're not actually changing.
		if (this[name] == value) {
			this.notify(name, value);
			return;
		}
		// we notify the old value, the new one can be read from the model.
		var oldVal = this[name]; 
		this[name] = value;
		this.notify(name, oldVal);
	};
	
	this.getField = function (name) {
		// return if present
		if (this[name]) return this[name];
		// special cases go here.
		return undefined;
	};

	function objectComparator(object1, object2)
	{
		var result = true;
		for (var field in object1)
		{
			if (typeof(object2[field] == 'undefined')) return false;
			if (typeof(object1[field]) == "object") {
				if (typeof(object2[field] != 'object')) return false;
				result = result && objectComparator(object1[field], object2[field]);
			} else {
				result = result && object1[field] == object2[field];
			}
		}
		return result;
	}
	
	this.syncWith = function (jsonItem) {
		if ( jsonItem.item.id != this.id ) return false;
		for (var field in jsonItem.item) {
			if (typeof(this[field]) == 'undefined' || 
				(typeof(jsonItem.item[field]) == 'object' && !objectComparator(jsonItem.item[field], this[field])) ||
				this[field] != jsonItem.item[field]) {
				this.setField(field, jsonItem.item[field]);
			}
		}
	}
	
	// lazy readings loader.
	this.getChartSettings = function (type, onReadyCallback, callbackParameter) {
		// immetiatelly call back if the we got the data already, which is at most 10 minutes old
		if (this.chartSettings 
				&& this.chartSettings[type]
				&& this.chartSettings[type]['timestamp'] > (new Date()).getTime() - 600000) {
			onReadyCallback(callbackParameter, this.chartSettings[type], this);
			return;
		}
		// create the holder, if not already present
		if (!this.chartSettings) this.chartSettings = new Array();

		/* now it gets weired. we have to call this method from the very same object, so if
		 * callbackParameter is not "this", we call us again. 
		 */ 
		if (callbackParameter != this) {
			function privateCallback(mySelf, data) {
				// here we can cache the data and call the original callback.
				if (data.status == 'success') {
					mySelf.chartSettings[type] = data;
					mySelf.chartSettings[type]['timestamp'] = (new Date()).getTime();
				}
				onReadyCallback(callbackParameter, data, mySelf);
			}
			this.getChartSettings(type, privateCallback, this);
			return;
		}
		/* if we've arrived here, we really need to get current data. so prepare an ansychronous request and
		 * call the callback (our own) to cache the data.
		 */
		var postData = {
			chartType: type,
			viewType: this['viewType'],
			id: this['id']	
		};
		var that = this;
		
		// fire the request
		$.ajax({
			dataType: 'text', // we parse JSON with a parser, not with eval.
			cache: false,
			type: 'POST',
			url: url_for('modComponents/chartRead'),
			data: postData,
			success: function (data, status) {
				// sanitize data
				data = data.replace(/\r\n/g, '').replace(/>(\s)*</g, '><');
				// simple response validation
				if (data.substr(0,10) != '<settings>') {
					onReadyCallback(callbackParameter, {status: 'fail', message: __('kg.js.invalid-data')});
				} else {
					// call back.
					onReadyCallback(callbackParameter, {status: 'success', data: data});
				}
			},
			complete: function(xhr, status) {
				if (xhr.status == 200) return; // success was called.
				onReadyCallback(callbackParameter, {status: 'fail', message: __('kg.js.xhr-fail %error%', {'%error%': status} ) });
				
			}
		});
	};
	var that = this;
	function getCacheExpirer(key) {
		return function() {
			var now = new Date();
			if (that.svgChartSettings[key] && that.svgChartSettings[key]['timestamp'] > now.getTime() - 300000) {
				that.clearCache('cache', key);
			}
		};
	}
	
	// lazy readings loader.
	this.getSvgChartSettings = function (onReadyCallback, callbackParameter, params) {
		// immetiatelly call back if the we got the data already, which is at most 5 mintutes old
		var stringParams = JSON.stringify(params);
		if (this.svgChartSettings 
				&& this.svgChartSettings[stringParams]
				&& this.svgChartSettings[stringParams]['timestamp'] > (new Date()).getTime() - 300000) {
			onReadyCallback(callbackParameter, this.svgChartSettings[stringParams], this);
			return;
		}
		// create the holder, if not already present
		if (!this.svgChartSettings) this.svgChartSettings = new Array();
		
		// TODO check if another one is already loading this data

		/* now it gets weired. we have to call this method from the very same object, so if
		 * callbackParameter is not "this", we call us again. 
		 */ 
		if (callbackParameter != this) {
			function privateCallback(mySelf, data) {
				// here we can cache the data and call the original callback.
				if (data.status == 'success') {
					mySelf.svgChartSettings[stringParams] = data;
					mySelf.svgChartSettings[stringParams]['timestamp'] = (new Date()).getTime();
				}
				// let our data expire after the cache time.
				window.setTimeout(getCacheExpirer(stringParams), 300000);
				onReadyCallback(callbackParameter, data, mySelf);
			}
			this.getSvgChartSettings(privateCallback, this, params);
			return;
		}
		/* if we've arrived here, we really need to get current data. so prepare an ansychronous request and
		 * call the callback (our own) to cache the data.
		 */
		var postData = {
			viewType: this['viewType'],
			id: this['id']
		};
		// copy params
		for (var field in params) {
			postData[field] = params[field];
		}
		var that = this;
		// fire the request
		$.ajax({
			dataType: 'text', // we parse JSON with a parser, not with eval.
			cache: false,
			type: 'POST',
			url: url_for('modComponents/svgChartRead'),
			data: postData,
			success: function (data, status) {
				// simple response validation
				try {
					data = JSON.parse(data);
					onReadyCallback(callbackParameter, {status: 'success', data: data});
				} catch (e) {
					onReadyCallback(callbackParameter, {status: 'fail', message: __('kg.js.invalid-data')});
				}
			},
			complete: function(xhr, status) {
				if (xhr.status == 200) return; // success was called.
				onReadyCallback(callbackParameter, {status: 'fail', message: __('kg.js.xhr-fail %error%', {'%error%': status} ) });				
			}
		});
	};
	
	this.clearCache = function (action, key) {
		//console.info('Cache for ', this.name, ' cleared.');
		switch (action) {
		case 'data':
			// propagate data upwards
			this.chartSettings = null;
			this.svgChartSettings = null;
			if (typeof(this.parent.clearCache) != "undefined") this.parent.clearCache(action);
			break;
		case 'price':
			// propagate price downwards
			this.chartSettings = null;
			this.svgChartSettings = null;
			for (key in this.children) {
				if (typeof(this.children[key].clearCache) != "undefined") this.children[key].clearCache(action);
			}
			break;
		case 'cache':
			// don't propagate, cache is so local.
			this.chartSettings = null;
			this.svgChartSettings[key] = null;
		}
		this.notify(action, key);
	};
	
	/**
	 * public function for moving an existent child off its current parent to this one
	 */
	this.moveChild = function ( child ) {

		// do not remove/add within the same model.
		if (child.parent == this) return;
		
		// determine new parent section.
		var parentId = null;
		if (this.viewType == 'SEC') parentId = this.id;
		// try to do remote.
		var postData = {
			'item[viewType]'	: child.viewType,
			'item[id]'	  		: child.id,
			'item[section_id]'	: parentId
		};

		$.ajax({
			dataType: 'text', // we parse JSON with a parser, not with eval.
			cache: false,
			type: 'POST',
			url: url_for('modMyKiwigrid/moveDevice'),
			data: postData,
			success: function (data, status) {
				// simple response validation
				try {
					data = JSON.parse(data);
					if (data == true) {
						var removedItem = child.parent.removeChild(child);
						if (removedItem != null) {
							child.setField('parent', that)
							that.addExistentChild(child);
						}
					}
				} catch (e) {
					console.error('Exception occured: ', e);
				}
			}
		});
	};
	
}

/*
 * subclass treeItemModel for device specific model
 */
function DeviceModel (parentTree) {
	// inheritance
	this.inheritFrom = TreeItemModel;
	this.inheritFrom(parentTree);

	// bastard overrides
	this.addItem = undefined;
	this.removeItem = undefined;
	
	// init one field.
	this.state = "new";

	var that = this;
	
	function remoteHelper(postData, callback)
	{
		$.ajax({
			dataType: 'text', // we parse JSON with a parser, not with eval.
			cache: false,
			type: 'POST',
			url: url_for('modComponents/deviceValue'),
			data: postData,
			success: function (data, status) {
				// simple response validation
				try {
					data = JSON.parse(data);
					callback({status: 'success', data: data});
				} catch (e) {
					callback({status: 'fail', message: __('kg.js.invalid-data')});
				}
			},
			complete: function(xhr, status) {
				if (xhr.status == 200) return; // success was called.
				callback({status: 'fail', message: __('kg.js.xhr-fail %error%', {'%error%': status} ) });				
			}
		});
	}

	// lazy life value setter
	this.setRemote = function(key, value, callback) {
		var postData = {
				viewType: this['viewType'],
				id: this['id'],
				key: key,
				value: value
			};
		remoteHelper(postData, callback);
	}

	// lazy life value reader
	this.getRemote = function(key, callback, since)
	{
		var postData = {
			viewType: this['viewType'],
			id: this['id'],	
			key: key
		};
		if (typeof(since) != 'undefined' && since != null) postData['since'] = since;
		remoteHelper(postData, callback);
	};
}
