function Relations() {
	this.items = {};
}

Relations.prototype = {
	constructor: Relations,
	
	/**
	 * Add an element to the dictionnary under the given name.
	 * 
	 * @param {String} id: identifier for the item
	 * @param {Object} element: item to register
	 * 
	 * @returns {Connector}: inserted element wrapped with a Connector
	 */
	add: function(id, element) {
		return this.items[id] = new Connector(element, id, this);
	},
	
	remove: function(id) {
		
	},
	
	/**
	 * Gets the wanted item
	 * 
	 * @param {String} id: item name
	 * 
	 * @returns {Connector}: the wanted item, wrapped in a selector
	 */
	get: function(id) {
		return this.items[id];
	},
	
	/**
	 * Executes all the registered actions for a given element and a given event.
	 * 
	 * First, if the required item is frozen, nothing is executed.
	 * Then, it checks every observer to see if they are locked, otherwise, their 
	 * action is performed.
	 *  
	 * @param {String} id: id of the element activated
	 * @param {String} event: name of the event activated
	 * @param eventParameters: parameters passed by the event raised
	 * 
	 * @returns: null
	 */
	execute: function(id, event, eventParameters) {
		var item = this.get(id);
		if (!item.isFrozen()) {
			var functions = item.listenedEvents[event];
			for(var i =0; i<functions.length; ++i) {
				var observer = this.get(functions[i][0]);
				if (!observer.isLocked()) {
					functions[i][1](observer.element, eventParameters);
				}
			}
		}
	},
	
	/**
	 * Executes an action on each selected item
	 * 
	 * The selection is made through an selector string or directly a regular
	 * expression. Then, on each selected item, the given action is performed
	 * passed the item as this in the action.
	 * 
	 * @param {String/RegExp} selector: selection test to get items
	 * @param {Function} action: action to perform
	 * @param {Boolean} getDetails: retrieve all information captured 
	 * 	by special expressions
	 * 
	 * @returns: {Integer} nombre d'éléments trouvés par la recherche
	 */
	each: function(selector, action, getDetails) {
		if (undefined==getDetails) {
			getDetails = false;
		}
		
		var expr = this.buildExpression(selector, getDetails);
		var compteur = 0;
		if (false==getDetails) {
			for (var itemName in this.items) {
				if (expr.test(itemName)) {
					action.call(this.items[itemName], this.items[itemName].element);
					++compteur;
				}
			}
		}
		else {
			var details = null;
			for (var itemName in this.items) {
				details = itemName.match(expr);
				if (null!=details) {
					action.call(this.items[itemName], this.items[itemName].element, details);
					++compteur;
				}
			}
		}
		
		return compteur;
	},
	
	/**
	 * Build a selector from a string
	 * 
	 * If a string is passed, it is turned as a RegExp.
	 * If an RegExp instance is passed, it is returned directly.
	 * 
	 * @param string selector: string to turn as selector
	 * @param RegExp selector: regular expression to use
	 * 
	 * @returns {RegExp}: selector
	 */
	buildExpression: function(selector, getDetails) {
		if (RegExp==selector) {
			return selector;
		}
		
		var expression = selector;
		expression = expression.replace(/(\(|\))/g, "\\$1");
		
		if (false==getDetails) {
			expression = expression.replace(/\[\[digit\]\]/g, "[0-9]+");
			expression = expression.replace(/\[\[letter\]\]/g, "[a-zA-Z]+");
			expression = expression.replace(/\./g, "\\.");
			expression = expression.replace(/\?/g, ".");
			expression = expression.replace(/\*/g, ".+");
		}
		else {
			expression = expression.replace(/\[\[digit\]\]/g, "([0-9]+)");
			expression = expression.replace(/\[\[letter\]\]/g, "([a-zA-Z]+)");
			expression = expression.replace(/\./g, "\\.");
			expression = expression.replace(/\?/g, "(.)");
			expression = expression.replace(/\*/g, "(.+)");
		}
		
		return RegExp("^" + expression + "$");
	}
};

function Connector(object, id, manager) {
	this.manager = manager;
	this.element = object;
	this.id = id;
	this.listenedEvents = {};
	this.locked = false;
	this.frozen = false;
}

Connector.prototype = {
	constructor: Connector,
	
	object: function() { return this.element; },
	
	bind: function(event, id, action) {
		if (undefined==action) {
			this.bind(event, this.id, id);
		}
		else {
			if (undefined!=this.listenedEvents[event]) {
				this.listenedEvents[event].push([id, action]);
			}
			else {
				var object = this;
				this.listenedEvents[event] = [[id, action]];
				this.element.on(event, function() {
					object.manager.execute(object.id, event, arguments);
				}) ;
			}
		}
		
		return this;
	},
	
	bindTo: function(event, id, action) {
		this.manager.get(id).bind(event, this.id, action);
		
		return this;
	},
	
	unbind: function(id, event) {
		return this;
	},
	
	click: function(id, action) {
		return this.bind("click", id, action);
	},
	
	dblclick: function(id, action) {
		return this.bind("dblclick", id, action);
	},
	
	mouseover: function(id, action) {
		return this.bind("mouseover", id, action);
	},
	
	mouseout: function(id, action) {
		return this.bind("mouseout", id, action);
	},
	
	lock: function() {	this.locked = true;	},
	
	unlock: function() {	this.locked = false;	},
	
	isLocked: function() {	return true==this.locked; },
	
	freeze: function() {	this.frozen = true;	},
	
	unfreeze: function() {	this.frozen = false;	},
	
	isFrozen: function() {	return true==this.frozen; }
};
