//John Resig snippet
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  this.Class = function(){};
  Class.extend = function(prop) {
    var _super = this.prototype;
    initializing = true;
    var prototype = new this();
    initializing = false;
    for (var name in prop) {
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;
            this._super = _super[name];
            var ret = fn.apply(this, arguments);       
            this._super = tmp;
            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }
    function Class() {
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }
    Class.prototype = prototype;
    Class.constructor = Class;
    Class.extend = arguments.callee;
    return Class;
  };
})();

var InSite = Class.extend({
	init: function() {
		this.version = '0.2';
		this.interactors = new Array();
		this.elements = new Array();
		this.unInitInteractors = new Array();
		this.unInitElements = new Array();
		this.properties = new Array();
		this.delayed = new Array();
		
		this.currentHash = window.location.hash;
		this.internHash = window.location.hash;

		jQuery.extend(jQuery.expr[':'], { 
		     contains: "$(a).text() == m[3]" 
		});
	},
	
	prepare: function(skipHash) {
		var isel = this;

		this.update();
		
		//Find and go to current hash
		if (!skipHash && window.location.hash.substr(1)) {
			this.goTo(window.location.hash.substr(1));
		}
		//Set current hash, and start check-er
		setInterval(function() { isel.checkHash(); }, 100);
		return this;
	},
	
	//Get element / create
	element: function(elementId, parameters) {
		if (elementId && !this.elements[elementId]) {
			this.elements[elementId] = new IsElement(this, elementId);
			this.unInitElements.push(elementId);
		}
		return this.elements[elementId];
	},

	//Get interactor
	interactor: function(key, elementStr, param) {
		if (!this.interactors[key]) {
			//Split (potential) multiple elements
			var elements = elementStr.split(",");
			var elObjects = new Array();
			for (var eNo in elements) {
				elObjects.push( this.element(elements[eNo]) );
			}

			if (!param || param.type == 'link') {
				this.interactors[key] = new LinkInteractor(key, this, elementStr, param);				
			} else if (param.type == 'local') {
				this.interactors[key] = new LocalInteractor(key, this, elementStr, param);
			} else if (param.type == 'url'){
				this.interactors[key] = new UrlInteractor(key, this, elementStr, param);
			} else if (param.type == 'ajax') {
				this.interactors[key] = new AjaxInteractor(key, this, elementStr, param);
			} else {
				this.interactors[key] = new FormInteractor(key, this, elementStr, param);
			}
				
			this.unInitInteractors.push(key);
		} 

		return this.interactors[key];
	},
	
	//Init uninitiated elements & interactors
	update: function(container) {
		//Init all elements
		for(var i in this.unInitElements) {
			this.elements[this.unInitElements[i]].prepare();
		}
		this.unInitElements.splice(0, this.unInitElements.length);
		
		//Loop all interactors, initiate
		for(var i = 0; i< this.unInitInteractors.length; i++) {
			this.interactors[this.unInitInteractors[i]].prepare(container);
			if (this.interactors[this.unInitInteractors[i]].initiated > 0) {
				this.unInitInteractors.splice(i--,1);
			}
		}

		//Delayed actions
		if (this.delayed.attach && this.delayed.attach != 'undefined')
		for(var i=0; i < this.delayed.attach.length; i++) {
			is.attach(this.delayed.attach[i]);
		}
		this.delayed.attach = new Array();
	},

	//(part of-)Page is rendered
	rendered: function(container) {
		
	},
	
	goTo: function(hash){
		var tri = this.splitLocation(window.location);
		var url = tri['url'] + '?' + this.mergeQuerystrings(tri['qs'], tri['hash']);
	
		var elementQS = this.getElementQS(hash);
		var viewElements = new Array();
		
		for (var i in this.elements) {
			if (this.elements[i].currentQS == elementQS[this.elements[i].cleanId()]) continue;
			viewElements.push(this.elements[i].id);
		}
		if (viewElements.length <= 0) return this;

		//Find changed elements
		var interactor = new UrlInteractor('_gotoInteractor', this, viewElements.join(','), {method: 'get', url: url})
//					.property('reset', 1)
					.execute();
	},
	
	splitLocation: function(loc) {
		var tri = new Array(3);
		loc = loc || '';
		loc = loc.toString();
		var hInd = loc.indexOf('#');
		var qsInd = loc.indexOf('?');

		tri['url'] = tri['qs'] = tri['hash'] = '';
		if (hInd > 0 && qsInd >= 0) {
			tri['url'] = loc.substr(0,qsInd);
			tri['qs'] = loc.substr(qsInd+1, (hInd-qsInd)-1);
			tri['hash'] = loc.substr(hInd+1);
		} else if (hInd >= 0) {
			tri['url'] = loc.substr(0,hInd);
			tri['hash'] = loc.substr(hInd+1);
		} else if (qsInd >= 0) {
			tri['url'] = loc.substr(0, qsInd);
			tri['qs'] = loc.substr(qsInd+1);
		} else {
			tri['url'] = loc;
		}
		return tri;
	},
	
	mergeLocation: function(tri) {
		var location = tri['url'];
		if (tri['qs']) location += '?' + tri['qs'];
		if (tri['hash']) location += '#' + tri['hash'];
		return location;
	},
	
	getUniqueQuerystring: function(q1, q2) {
		if (q1.charAt(0) == '#' || q1.charAt(0) == '?') q1 = q1.substr(1);
		if (q2.charAt(0) == '#' || q2.charAt(0) == '?') q2 = q2.substr(1);

		var result = new Array();
		var a_q1 = q1.split('&');
		var a_q2 = q2.split('&');
		var h_q2 = new Array();
		for(var i in a_q2) {
			h_q2[a_q2[i]] = 1;
		}
		for(var i in a_q1) {
			if (!h_q2[a_q1[i]]) result.push(a_q1[i]);
		}
		return result.join('&');
	},
	
	/*
	 * Merge querystrings q1 and q2. Variables under element "exclude" will be excluded from q1.
	 */
	mergeQuerystrings: function(q1, q2, exclude) {
		if (q1.charAt(0) == '#' || q1.charAt(0) == '?') q1 = q1.substr(1);
		if (q2.charAt(0) == '#' || q2.charAt(0) == '?') q2 = q2.substr(1);

		var a_q1 = q1.split('&');
		var a_q2 = q2.split('&');
		var h_q2 = new Array();
		var var_q2 = new Array();
		for(var i in a_q2) {
			var tmpStr = a_q2[i].split(/[\=]/, 1);
			h_q2[a_q2[i]] = 1;
			var_q2[tmpStr] = 1;
		}
		for(var i in a_q1) {
			var elementId = a_q1[i].split(/[\.\=]/, 1);
			var varName = a_q1[i].split(/[\=]/, 1);
			if (!h_q2[a_q1[i]] && elementId != exclude && elementId && !var_q2[varName]) {
				a_q2.push(a_q1[i]);
			}
		}
		for(var i in a_q2) {
			if (a_q2[i] == '') a_q2.splice(i--, 1);
		}
		return a_q2.join('&');
	},
	
	generateId: function(qs, elementId) {
		var qsItems = qs.split("&").sort();
		
		//Find properties given to current element
		var prop = new Array();
		for (var i=0; i<qsItems.length; i++) {
			//Skip variable if value is 0 or empty
			var eindex = qsItems[i].search(/[\=]/);
			var value = qsItems[i].substr(eindex+1);
			if (eindex && eindex >= (qsItems[i].length-1) || value == '0') continue;

			var index = qsItems[i].search(/[\.\=]/);
			if (index) {
				var el = qsItems[i].substr(0,index);
				if (el == elementId && qsItems[i].length > index) prop.push( qsItems[i].substr(index+1) );
			}
		}

		//Get ID from properties given to current element
		var id = prop.join('_');

		//Generate "virtual" id from multiple properties
		if (id.indexOf('\=') > 0 || id.length <= 0) {
			id = '_isId_' + elementId + '_' + id;
			id = id.replace(/[\=\.]/g,'');
		}
		return id;
	},
	
	checkHash: function() {
		if (this.currentHash != window.location.hash) {
			this.currentHash = window.location.hash;
			this.goTo(window.location.hash.substr(1));
		}
	},
	
	getElementQS: function(qs) {
		var eQs = new Array();
		
		var a_qs = qs.split('&').sort();
		for(var i in a_qs) {
			if (a_qs[i] == 'is') continue;
			var thisElement = a_qs[i].split(/[\.\=]/, 1);
			if (eQs[thisElement]) eQs[thisElement] += '&';
			else eQs[thisElement] = '';
			eQs[thisElement] += a_qs[i];
		}
		return eQs;
	},
	
	/*
	 * Some common non-related helpers
	 */
	
	/*
	 * Set default values to text fields
	 */
	defaultValue: function(object, string, color) {
		//Do not set if field isn't empty
		if ($(object).val() != '') return;
		
		$(object).data('startColor', $(object).css('color'));
		if (color) $(object).css('color', color);
		$(object).val(string);

		$(object).bind('focus', function(e) {
			if ($(this).val() == string) {
				$(this).val('');
				if (color) $(this).css('color', $(this).data('startColor'));
			}
		});
		$(object).bind('blur', function(e) {
			if ($(this).val() == '') {
				$(this).val(string);
				if (color) $(this).css('color', color);
			}
		});
		$(object).parents('form:first').each(function (e) {
			$(this).submit( function(f) {
				if ($(object).val() == string) $(object).val('');
			});
		});
		return this;
	},
	
	/*
	 * Pre-load images
	 */
	preloadImg: function(images) {
		$.each(images,function(e) {
			$(new Image()).attr('src',this);
		});
		return this;
	},
	
	property: function(p, v) {
		if (typeof(v) == 'undefined') return this.properties[p];
		this.properties[p] = v;
		return this;
	},

	stripEntities: function(s) {
		s= s.replace(/\&amp;/g,'&');
		return s;
	},
	
	attach: function(args) {
		var x = args['areaX'] || [0,1];
		var y = args['areaY'] || [0,1];
		var src = args['src'];
		var target = args['dst'];

		//Return if already attached
		is.attachList = is.attachList || new Array();
		if (is.attachList[src + target]) return;

		var $s = $('#' + src);
		var $t = $('#' + target);

//alert(src + ' - ' + target + '\n' + $s.length + ' - ' + $t.length);
		if ($s.length < 1 || $t.length < 1) {
			is.delayed.attach = is.delayed.attach || new Array();
			is.delayed.attach.push(args);
			return;
		}


		//Bind
		var pWidth = x[1] - x[0];
		var pHeight = y[1] - y[0];
		var offset = $t.offset();

		var left = x[0]*100 + '%';
		var top = y[0]*100 + '%';
		var width = pWidth * 100 + '%';
		var height = pHeight * 100 + '%';

		var overlay = $(document.createElement('div'))
				.addClass(args['class'])
				.width(width)
				.height(height)
				.css({
					position: 'absolute',
					top: top,
					left: left,
					cursor: 'pointer',
					zIndex: args['zIndex'] || 101
				});
		$t.prepend(overlay);
		
		overlay.bind('click', function(e) {
			$s.click();
			if (args['followLink']) {
				location.href = $s.attr('href');
			}
		}).hover(function() {
			$s.addClass('hover');
		}
		,function() {
			$s.removeClass('hover');
		});

		args['attached'] = 1;

		//Set this as attached
		is.attachList[src + target] = 1;

		return this;
	}
});

var IsElement = Class.extend({

	init: function(isObj, id) {
		this.isObj = isObj;
		this.id = id;		
		this.loadCount = new Array();		//Keep number of loading elements
		this.prop = new Array();
		this.position = 0;
		this.idInteractor = new Array();
		this.interactors = new Array();
	},
	
	//Run only when element exists. Therefore it is separated from init. 
	prepare: function(noCreate) {
		if (this.prepared > 0) return this;
		
		if (this.id.charAt(0) == '#') this.$element = $(this.id);
		else this.$element = $('#_isElement_' + this.id);
		
		//Set "current" id
		var queryString;
		if (this.property('skipHash')) queryString = this.isObj.internHash.substr(1);
		else queryString = this.isObj.splitLocation(window.location).qs;
		this.currId = this.currId || this.isObj.generateId(queryString, this.cleanId());

		//Create switch-object
		this.switcher = this.switcher || new LayerSwitch(this.prop.duration || 250);
		//Activate first element
		this.switcher.setCurrent(this.currId).prepare(this);

		if (!noCreate) this.create();

		this.prepared = 1;
		if (!this.$element.attr('id')) return null;

		return this;
	},

	addInteractor: function(key, interactor) {
		this.interactors[key] = interactor;	
	},

	reset: function(interaction) {
		var that = this;
		this.prepared = 0;
		this.created = 0;
		
		this.switcher.reset(interaction, function() {
			that.currId = interaction.id;
			that.prepare();

			for (var i in that.interactors) {
				that.interactors[i].prepare();
			}
			that.isObj.prepare(1);
			that.postAction(interaction);
		});
		
		return this;
	},
	
	create: function(i) {
		this.switcher.create(i);
		this.created = 1;
		for (var i in this.insertCache) {
			this.insert(this.insertCache[i]);
		}
		this.switcher.postInsert();
	},

	cleanId: function() {
		if(this.id.charAt(0) == '#') return this.id.substr(1);
		return this.id;
	},

	/*
	 * Act according to a given interaction (array of options)
	 */
	act: function(interaction) {
		
		//Is this a reset?
		if (interaction.prop['reset']) {
//alert(interaction.id + '\n' + interaction.data);
			this.reset(interaction);
			this.loaded();
			return;
		}
		
		//Check if we should insert data
		//If 'reload' is set, or if it doesn't exist
		if((interaction.prop && interaction.prop['reload']) || $('#' + interaction.id).length <= 0) {
			this.insert(interaction);
		}
		
		//Check if we should show the data
		if (!(interaction.prop && interaction.prop['noShow'])) {
			this.show(interaction);
		}
	},

	/*
	 * Show loading effect
	 */
	loading: function() {
		var p = this.prop['loader'] || this.isObj.property('defaultLoader');
		if (p) p.load(this.$element);
	},

	/*
	 * End loading effect
	 */
	loaded: function() {
		var p = this.prop['loader'] || this.isObj.property('defaultLoader');
		if (p) p.done(this.$element);
	},

	/*
	 * Pre and Post actions. Executed before and after an interaction is done.
	 */
	preAction: function(callerInteraction) {
		if (this.prop['pre']) this.prop['pre'].pre(callerInteraction);
		if (this.prop['prepost']) this.prop['prepost'].pre(callerInteraction);
	},
	postAction: function(callerInteraction) {
		if (this.prop['post']) this.prop['post'].post(callerInteraction);
		if (this.prop['prepost']) this.prop['prepost'].post(callerInteraction);
	},
	
	/*
	 * Insert data
	 */
	insert: function(interaction) {
		var that = this;
		
		//Insert later if element is not prepared
		if (!this.created) {
			this.insertCache = this.insertCache || new Array();
			this.insertCache.push(interaction);
			return this;
		}
		
		if (interaction.charAt) {	//Insert static element
			var id = interaction.substr(1);
			this.switcher.insert({id: id, prop: {}});

			return this;
		}
		
		//Insert data using the structure required by our switcher
		this.switcher.insert(interaction);

		//Init loading-variable
		this.loadCount[interaction.id] = 0;

		//Count number of unloaded images
		var $images = $('#' + interaction.id).find('img');
		$images.each( function() {
			if (!this.complete && this.readyState != 'complete') {
				that.loadCount[interaction.id]++;
				$(this).load(function() {
					that.loadCount[interaction.id]--;
				});
			}
		});
		//Update site content (links etc)
		//MOVED TO SWITCHER
		//this.isObj.update(interaction.element);

		return this;
	},

	setCurrent: function(eId) {
		this.currId = eId.substr(1);
		return this;
	},
	
	/*
	 * Wait until all elements are loaded, then show
	 */
	show: function(interaction) {
		var isel = this;

		if (isel.loadCount[interaction.id] > 0) {
			setTimeout(function(){ 
				isel.show(interaction); 
			}, 100);
			if (!interaction.started) {
				setTimeout(function() {
					isel.loadCount[interaction.id] = 0;
					//Error handling
				}, 8000);
			}
			interaction.started = 1;
			return isel;
		}

		//Done loading
		this.loaded();

		//Update interactor DOM elements
		var currId = this.currId;
		var newId = interaction.id;
		for(var i in this.idInteractor[currId]) if(this.idInteractor[currId][i]) this.idInteractor[currId][i].deActivate(currId);
		for(var i in this.idInteractor[newId]) this.idInteractor[newId][i].activate(newId);
		
		//Switch to new element
		if (newId != currId || interaction.prop['reload']) isel.switcher.show(interaction);
		else isel.switcher.done(interaction);
		
//Should be called when it is REALLY done (Not after show call has been executed)
//		isel.switcher.done(interaction);
		
		//Set new id as current
		this.currId = newId;
		return isel;
	},
	
	/*
	 * Set switcher-element.
	 * "Override" creates a new switcher even if one is already set.
	 */
	setSwitcher: function(sw, overr) {
		if (this.switcher && !overr) return this;
		this.switcher = sw;
		return this;
	},
	//Legacy! Remove later
	addSwitcher: function(sw, overr) { return this.setSwitcher(sw, overr); },
	
	setProperty: function(p, v) {
		if (typeof(v) == 'undefined') return this.prop[p];
		this.prop[p] = v;
		return this;
	},
	property: function(p, v) { return this.setProperty(p,v); },
	
	/*
	 * Register an interactor as a handler for this element.
	 * Required to give the DOM feedback when it's element becomes active/inactive.
	 */
	registerInteractor: function(interactor, query) {
		//Split the location into URL, QS and HASH. Generate ID from the querystring
		var tri = this.isObj.splitLocation(query);
		var id = this.isObj.generateId(tri['qs'], this.cleanId());
		
		//Create an array with the DOM handlers for this ID.
		if (!this.idInteractor[id]) this.idInteractor[id] = new Array();
		this.idInteractor[id].push(interactor);
		
		return id;
	},
	
	/*
	 * Update a DOM handlers state
	 */
	updateInteractor: function(id, state) {
		if (this.idInteractor[id]) {
			//Execute activate/deactivate action for each element
			if (state == 'active')
				for(var i in this.idInteractor[id]) this.idInteractor[id][i].activate(id);
			else
				for(var i in this.idInteractor[id]) this.idInteractor[id][i].deactivate(id);
		}
	}
});


var Interactor = Class.extend({

	//Constructor
	init: function(key, isObj, elementStr, param) {
		//Create element-objects from comma separated list
		this.el = new Array();
		if (elementStr != '') {
			var elementStrings = elementStr.split(',');
			for(var i in elementStrings) {
				this.el.push(isObj.element(elementStrings[i]));
				//Register this interactor
				isObj.element(elementStrings[i]).addInteractor(key, this);
			}
		}
		
		this.key = key;
		this.isObj = isObj;
		this.elementStr = elementStr;
		this.interactions = 0;
		this.param = param;
		this.prop = new Array();
		this.dom = new Array();
		this.running = 0;
	},

	//Interactor-object needs to exist before this is run. (unless this is a "virtual" interactor)
	prepare: function(container) {
		var that = this;

		var $items;
		if (container) $items = container.$element.find(this.key);	
		else $items = $(this.key);


		$items.each( function() {
			if (!$(this).data('_isInitiated')) {
				//Set position-data
				
				that.bind(this, that);
				$(this).data('_isInitiated', 1);
			}
		});
		return this;
	},

	/*
	 * Dummy function. Overridden in sub-classes
	 */
	bind: function(element) {
	},

	/*
	 * Send a post-action
	 */
	executePost: function(query, data) {
		var intr = this;

		//Remove &amp; etc
		query = is.stripEntities(query);

		this.property('reload', 1);
		
		var tri = intr.isObj.splitLocation(query);
		var newQuery = intr.generateQuery(tri);

		var interactions = intr.getInteractions(tri);

		//Display loading action
		intr.loadAction();
		//Send post
		var post;
		if (typeof(data) == 'object') post = $(data).serialize();
		else post = data;
		jQuery.post(newQuery, post, function(data) {
			//Display loading action
			intr.loadAction();
			intr.show(interactions, data);
		});

		return false;
	},

	executeFormGet: function(query, data) {

		query = is.stripEntities(query);
		var tri = this.isObj.splitLocation(query);
		var newQS = $(data).serialize(); 
		var url = tri['url'] + '?' + newQS;

//alert(url);
		return this.executeGet(url);
	},

	executeGet: function(query) {
		var intr = this;
		this.running = 1;

		//Remove &amp; etc
		query = is.stripEntities(query);

		//Split the query and generate a query by merging the querystring (?) and the hash (#).
		var tri = intr.isObj.splitLocation(query);
		var newQuery = intr.generateQuery(tri);
		
		//Get the interactions (one for each associated element). The new query is not used,
		//since it may contain merged data from other interactions (from the hash)
		var interactions = intr.getInteractions(tri);

		//Check how many of the elements that have been loaded previously
		var loaded = 0;
		for(var i in interactions) {
			if (!intr.prop['reload'] && $('#' + interactions[i].id).length > 0) loaded++;
		}

		if (loaded >= interactions.length && !intr.prop['reload']) {
			intr.show(interactions, '');
		} else {
			//Display loading action
			intr.loadAction();
			jQuery.get(newQuery, null, function(data) {
				intr.show(interactions, data);
			});
		}
		return false;
	},

	/*
	 * Show the result
	 * Element-object will know if data is updated, or if it already exists.
	 * 
	 */
	show: function(interactions, dataFull) {
		var intr = this;
		this.running = 1;
		
		//Split data into elements
		var rawData = dataFull.split('||');

		var data = new Array();
		for(var rd in rawData) {
			//Separate ID from content
			var sep = rawData[rd].indexOf(':');
			var id = rawData[rd].substr(0,sep);
			var content = rawData[rd].substr(sep+1);
			//Remove escape char (\)
			data[id] = content.replace(/\\(.{1})/g, '$1');
		}
		
		//For each element, create an interaction
		for(var eNo in intr.el) {
			var e = intr.el[eNo];
			//Add the data
			interactions[e.id].data = data[e.id];

			//Execute pre-action
			intr.preAction(interactions[e.id]);
			//Send interaction to designated element
			e.act(interactions[e.id]);

		}
	},

	/*
	 * Create all interaction arrays (one for each affected element)
	 */
	getInteractions: function(tri) {
		var intr = this;

		var ia = new Array(intr.el.length);
		for(var eNo in intr.el) {
			var e = intr.el[eNo];
			//TODO: Fiks id her!
			//Fra hash?
			
			var id = intr.isObj.generateId(tri['qs'], e.cleanId());
			ia[e.id] = {
				url: tri['url'],
				hash: tri['hash'],
				qs: tri['qs'],
				id: id,
				element: e,
				prop: intr.prop,
				interactor: intr
			};
		}
		return ia;
	},
	
	/*
	 * Generates a full query 
	 */
	generateQuery: function(tri) {
		var intr = this;

		//Base URL, use current if not given. I.e. a "?foo=bar" link.
		var query = tri['url'] || intr.isObj.splitLocation(window.location.href).url;
		query += '?' + tri['qs'];
		//Add hashes to query string
		if (tri['qs']) query += '&';
		query += '_is._view=' + intr.elementStr;
		//TODO: Add hash support 
		return query;
	},

	/*
	 * Pre and Post actions. Executed before and after this interaction is shown.
	 */
	preAction: function(callerInteraction) {
		if (this.prop['pre']) this.prop['pre'].pre(callerInteraction);
		if (this.prop['prepost']) this.prop['prepost'].pre(callerInteraction);
		//Elements pre actions
		for(var i in this.el) {
			this.el[i].preAction();
		}
	},
	postAction: function(callerInteraction) {
		if (this.prop['post']) this.prop['post'].post(callerInteraction);
		if (this.prop['prepost']) this.prop['prepost'].post(callerInteraction);
		//Elements post actions
		for(var i in this.el) {
			this.el[i].postAction();
		}
	},

	/*
	 * "Loading" action, to be executed before interaction is executed
	 */
	loadAction: function(interaction) {
		for (var eNo in this.el) {
			this.el[eNo].loading(interaction);
		}
	},
	
	/*
	 * 
	 */
	activate: function(id) {
		if (!this.prop['activator']) return;
		if (this.dom[id]) {
			for (var i in this.dom[id]) this.prop['activator'].activate(this.dom[id]);
		}
		return this;
	},
	
	/*
	 * 
	 */
	deActivate: function(id) {
		if (!this.prop['activator']) return;
		if (this.dom[id]) {
			for (var i in this.dom[id]) this.prop['activator'].deActivate(this.dom[id]);
		}
		return this;
	},
	
	/*
	 * An interaction is done. Check if we should update window location
	 */
	done: function(interaction) {
		var intr = this;
		intr.interactions--;

		//Only update if hash is different from current. (avoid breaking back-button)
		//TODO: Check if some property is set, to avoid setting hash.
		if (intr.interactions <= 0) {
			//Remove elements from hash that are in QS
			var wlocTri = intr.isObj.splitLocation(window.location);
			var uniqueQs = intr.isObj.getUniqueQuerystring(interaction.qs, wlocTri.qs);
			//Generate the hash we should use
			var hash = '#' + (intr.isObj.mergeQuerystrings(window.location.hash, uniqueQs, interaction.element.cleanId()) || 'is');
			
			if (window.location.hash != hash && !interaction.element.property('skipHash')) {
				window.location.assign(hash);
				intr.isObj.currentHash = hash;
			}
			intr.isObj.internHash = hash;
			
			//Update current QS
			var currentQS = intr.isObj.getElementQS(hash.substr(1));
			for(var i in intr.el) {
				intr.el[i].currentQS = currentQS[intr.el[i].id];
			}
		}

		//Execute post-action
		intr.postAction(interaction);

		//Update site-state
		this.isObj.update(interaction.element);

		this.running = 0;
	},
	
	registerDom: function(dom, query) {
		var tri = this.isObj.splitLocation(query);
		var newQuery = this.generateQuery(tri);

		for(var i in this.el) {
			var id = this.el[i].registerInteractor(this, newQuery);
			if (!this.dom[id]) this.dom[id] = new Array();
			this.dom[id].push(dom);
		}
		return this;
	},
	
	setProperty: function(p, v) {
		if (typeof(v) == 'undefined') return this.prop[p];
		this.prop[p] = v;
		return this;
	},
	property: function(p, v) { return this.setProperty(p,v); }
});

var LinkInteractor = Interactor.extend({

	bind: function(domElement, interactor) {
		var intr = this;
		$(domElement).click(function(e) { return intr.execute(this); });

		//Register this DOM as a handler for each element
		var query = $(domElement).attr('href') || window.location.href;
		this.registerDom(domElement, query);
		return this;
	},

	execute: function(link) {
		var intr = this;
		var query = $(link).attr('href') || window.location.href;

		intr.executeGet(query);
		return false;
	}
});

var FormInteractor = Interactor.extend({
	bind: function(domElement) {
		var intr = this;
		$(domElement).bind('submit', function() { return intr.execute(this); });
		
		//Register this DOM as a handler for each element
		var query = $(domElement).attr('action') || window.location.href;
		this.registerDom(domElement, query);
		return this;
	},

	execute: function(form) {
		var query = $(form).attr('action');
		if ($(form).attr('method') == 'post') this.executePost(query, form);
		else this.executeFormGet(query, form);
		return false;
	}
});

var UrlInteractor = Interactor.extend({
	execute: function() {
		if(this.param.method == 'post') {
			this.property('reload', 1);
			this.executePost(this.param.url, this.param.data);
		} else { 
			this.executeGet(this.param.url);
		}
		return false;
	},
	
	prepare: function() { return this; }
});

var IsSwitch = Class.extend({
	init: function(time) {
		this.time = time || 500;
		if (this.time == 'NULL') this.time = 0;
		this.ready = 1;
	},
	
	prepare: function(eObj) {
		if (this.initiated) return true;
		this.eObj = eObj;
		this.$element = this.eObj.$element;
		this.initiated = 1;
		return true;		
	},

	postInsert: function() {
		return this;
	},
	
	setCurrent: function(eId) {
		this.$currentElement = $('#' + eId);
		return this;
	},
	
	done: function(interaction) {
		if (!interaction.interactor) return;
		interaction.interactor.done(interaction);
	},
	
	reset: function(interaction, callback) {
		var that = this;
		var duration = interaction.prop['duration'] || this.time;
		this.$element.fadeOut(duration / 2, function() {
			that.$element.html(interaction.data);
			that.$element.fadeIn(duration / 2);
			if (callback) callback();
			that.done(interaction);
		});
		return this;
	}
});


var LayerSwitch = IsSwitch.extend({

	switcher: 'LayerSwitch',

	prepareElement: function(element) {
		var width;
		if (this.$currentElement && this.$currentElement.length > 0) {
			width = this.$currentElement.width();
		} else {
			width = this.$scrollPane.width();
		}

		element.css({ display: 'block', position: 'absolute'}).hide();
		return this;
	},
	
	prepareContainer: function(element) {
		if (this.$currentElement && this.$currentElement.length > 0) {
			this.$currentElement.show();
		}
		return this;
	},
	finishContainer: function(element) {
		this.ready = 1;
		return this;
	},

	postCreate: function(element) {
		return this;
	},
	
	preInsert: function() {
		return this;
	},

	postInsert: function() {
		return this.showEffect();
	},
	
	insert: function(interaction) {
		var that = this;

		if (!this.created) this.create(interaction);

		if ($('#' + interaction.id).length > 0) {
			$('#' + interaction.id).addClass('_isLayer');
			this.prepareElement($('#' + interaction.id));
			if(interaction.prop['reload']) $('#' + interaction.id).html(interaction.data);
			this.prepareContainer($('#' + interaction.id));
			this.finishContainer($('#' + interaction.id));
			return this;
		}

		this.ready = 0;

		//Find position of element to insert
		var $position;
		var currentPosition = this.$currentElement.data('position');
		var nextPosition = interaction.prop['position'];

		if (interaction.prop['direction'] != 'left' && (!nextPosition || nextPosition > currentPosition)) {
			interaction.prop['direction'] = 'right';
			if (nextPosition) {
				for(var i = nextPosition-1; i>currentPosition && !$position; i--) {
					if (this.eObj.slidePos[i]) {
						$position = $('#' + this.eObj.slidePos[i]);
					}
				}
			}
			nextPosition = nextPosition || currentPosition+1;
		} else {
			interaction.prop['direction'] = 'left';
			if (nextPosition) {
				for(var i = nextPosition+1; i<currentPosition && !$position; i++) {
					if (this.eObj.slidePos[i]) {
						$position = $('#' + this.eObj.slidePos[i]);
					}
				}
			}
			nextPosition = nextPosition || currentPosition-1;
		}

		$position = $position || this.$currentElement
		this.eObj.slidePos[nextPosition] = interaction.id;
		
		this.prepareContainer(interaction);
		this.preInsert(interaction);
		if (interaction.prop['direction'] == 'left') {
			$position.before('<div class="_isLayer" id="' + interaction.id + '">' + interaction.data + '</div>');
			this.prepareElement($('#' + interaction.id));
		} else {
			$position.after('<div class="_isLayer" id="' + interaction.id + '">' + interaction.data + '</div>');
			this.prepareElement($('#' + interaction.id));
		}
		this.finishContainer(interaction);

		//Set position-data for element
		$('#' + interaction.id).data('position', nextPosition);
		
		return this;
	},
	
	create: function(interaction) {
		//Merge current url + current hash
		var scrollContainerId = '_isContainer_' + this.eObj.cleanId();
		var scrollPaneId = '_isPane_' + this.eObj.cleanId();
		
		var startId = this.eObj.currId;
		if (interaction && interaction.id) startId = interaction.id;

		this.$element.wrapInner('<div class="_isPane" id="' + scrollPaneId + '">' +
					'<div class="_isContainer" id="' + scrollContainerId + '">' +
	  				'</div></div>');
		this.$scrollContainer = $('#' + scrollContainerId);
	  	this.$scrollPane = $('#' + scrollPaneId);
	  	
	  	//Create first element unless it exists
	  	if ( $('#' + startId).length <= 0) {
	  		this.$scrollContainer.wrapInner('<div class="_isLayer" id="' + startId + '"></div>');
	  		this.prepareElement($('#' + startId));
			this.prepareContainer();
	  		$('#' + startId).show();
	  	}

	  	this.$scrollPane.css({
			position: 'relative'
		});

	  	this.$currentElement = $('#' + startId);

		//Find position of first element. Can be found by looking for interactors controlling the ID,
		//and checking if it has a position assigned
		var currPos = 0;
		if (this.eObj.idInteractor[startId]) {
			for(var i in this.eObj.idInteractor[startId]) {
				var interactor = this.eObj.idInteractor[startId][i];
				if (interactor && interactor.prop['position']) currPos = interactor.prop['position']; 
			}
		}
		this.$currentElement.data('position', currPos);
		
		//Create position-array and set the first element
		this.eObj.slidePos = new Array();
		this.eObj.slidePos[currPos] = startId;
		
		this.created = 1;

		this.postCreate();

		return this;
	},
	
	show: function(interaction) {
		if (!this.created)  this.create(interaction);

		var that = this;
		
		if (!this.ready) {
			setTimeout(function() { that.show(interaction); } , 5);
			return;
		}

		return this.showEffect(interaction);
	},
	
	showEffect: function(interaction) {
		var that = this;

		if (!interaction) {
			interaction = { id: this.$currentElement.attr('id'), prop: { duration: 1 } };
		}

		var prevElement = this.$currentElement;
		this.$currentElement = $('#' + interaction.id);
		if (prevElement.attr('id') == interaction.id) prevElement = null;

		var duration = interaction.prop['duration'] || this.time;

		//Move divs to correct position 
		if (prevElement && prevElement.length > 0) {
			prevElement.css({ 	position: 'absolute',
						top: 0,
						left: 0,
					 	width: prevElement.width(),
						zIndex: 1
					}).fadeOut(duration, function() {
						$(this).css({
							zIndex: 0,
							width: 'auto'
						});
					});
		}
		this.$currentElement.css('position', 'relative').show();

		//Launch "done" actions
		that.done(interaction);

		return this;
	}
});

var SlideSwitch = LayerSwitch.extend({

	switcher: 'SlideSwitch',
	
	prepareElement: function(element) {
		element.data('width', element.width());
		element.data('height', element.height());
		element.css({'float': 'left'}).hide();
		this.$scrollPane.css({	'width': 'auto', 'height': 'auto'});
	},

	postCreate: function() {
	},

	preInsert: function() {
		this.$scrollPane.css({ 	'width': 	this.$scrollPane.width(),
					'height':	this.$scrollPane.height(),
					'overflow':	'hidden'
		});
	},

	prepareContainer: function(interaction) {
		this.$scrollContainer.css('overflow', 'hidden');
		this.$scrollPane.css('overflow', 'hidden');
	},

	finishContainer: function(interaction) {
		this.ready = 1;
	},
	
	showEffect: function(interaction) {
		var that = this;

		if (!interaction) {
			return;
			interaction = { id: this.$currentElement.attr('id'), prop: { duration: 1 } };
		}
		this.$prevElement = this.$currentElement;
		this.$currentElement = $('#' + interaction.id);

		//Vertical or horizontal scroll
		var paneWidth = this.$currentElement.data('width');
		if (interaction.prop['axis'] != 'y') {
			if (this.$prevElement) paneWidth += this.$prevElement.data('width')*2;
		} else {
		}
		this.$scrollContainer.css({width: paneWidth});

		this.$scrollPane.css({	'width': this.$scrollPane.width(),
					'height': this.$scrollPane.height()
		});

		this.$currentElement.show();

		if (this.$currentElement.data('position') < this.$prevElement.data('position')) { 
			if (interaction.prop['axis'] == 'y') this.$scrollContainer.css('marginTop', '-' + this.$currentElement.data('height') + 'px');
			else this.$scrollContainer.css('marginLeft', '-' + this.$currentElement.data('width') + 'px');
		}
		
		var posX = this.$currentElement.offset().left - this.$element.offset().left;
		var posY = this.$currentElement.offset().top - this.$element.offset().top;
		
		//Easing
		var animOptions = new Array();
		if (jQuery.easing) {
			animOptions['easing'] = interaction.prop['easing'] || 'jswing';
		}
		animOptions['duration'] = interaction.prop['duration'] || this.time;

		this.$scrollPane.animate({'height': this.$currentElement.height(), 'width': this.$currentElement.width()}, 
				animOptions['duration'], 
				function() {
					that.$scrollPane.css('overflow', 'hidden'); //WebKit fix
				}
		);
		this.$scrollContainer.animate({marginLeft: '-=' +posX, marginTop: '-=' + posY}, animOptions['duration'], null, function() {
			$(this).css({height: 'auto', width: 'auto', 'marginLeft': 0, 'marginTop': 0});
			that.$scrollPane.css({ height: 'auto', width: 'auto'} );
			if (that.$prevElement) {
				that.$prevElement.hide();
			}	
		});

		setTimeout(function() {
			that.done(interaction);
		}, animOptions['duration']);
		
		return this;
	}
});

var RepSwitch = IsSwitch.extend({

	create: function(interaction) {
		if (this.created) return true;
		
		var cleanId = this.eObj.cleanId();
		
		this.$element.wrapInner( '<div id="' + cleanId + '_wrapper">' +
					 '<div id="' + cleanId + '_content"></div></div>');
		this.$wrapper = $('#' + cleanId + '_wrapper');
		this.$content = $('#' + cleanId + '_content');
		this.$content.after('<div id="' + cleanId + '_tmpContainer"></div>');
		this.$tmpContainer = $('#' + cleanId + '_tmpContainer').hide();

		//Switcher-compatibility
		this.$currentElement = this.$content;

		this.created = 1;
		return this;
	},
	
	insert: function(interaction) {
		if (!this.created) this.create(interaction);
	
		//Fill temp-container with data
		this.newData = interaction.data;

		return this;
	},
	
	show: function(interaction) {
		var that = this;
		if (!this.created) this.create(interaction);
		
		//Set size of wrapper
		this.$wrapper.css({
			overflow: 'hidden',
			height: this.$wrapper.height(),
			width: this.$wrapper.width(),
			position: 'relative',
			zIndex: 0
		});

		//Show temp-container in front of content
		this.$content.css({
			position: 'absolute'
		});
		this.$tmpContainer.html(this.$content.html());
		this.$tmpContainer.css({
			position: 'absolute',
			zIndex: 2
		}).show();

		//Show new content behind temp-container
		this.$content.html(this.newData); 
		
		//Transform wrapper to new content
		this.$wrapper.animate({
			width: this.$content.width(),
			height: this.$content.height()
		}, this.time);
		
		//Fade out content
		this.$tmpContainer.fadeOut(this.time, function() {
			//Replace data, hide tmpContainer
			that.$tmpContainer.html('&nbsp;');

			//Reset wrapper & content
			that.$wrapper.css({
				height: 'auto',
				width: 'auto'
			});

			that.$content.css('position', 'relative');

			that.done(interaction);
		});

		return this;
	},
	
	done: function(interaction) {
		var that = this;
		this._super(interaction);
	}
	
});


var DefaultActivator = Class.extend({
	init: function() {},
	
	activate: function(domElement) {
		$(domElement).addClass('active');
	},
	
	deActivate: function(domElement) {
		$(domElement).removeClass('active');
	}
});

var CursorLoader = Class.extend({
	
	init: function(url) {
		var that = this;

		if ($('#isCursorLoader').length <= 0) {
			var image = new Image();
			image.src = url;
			image.id = 'isCursorLoader';
			$('body').append(image);
			$('#isCursorLoader').hide();
		}
		this.icon = $('#isCursorLoader');
		this.mouseX = 0;
		this.mouseY = 0;

		$().mousemove(function(e) {
			that.mouseMoveEvent(e);
		});

		return this;
	},

	load: function(element) {
		var that = this;

		this.icon.css('z-index', 100);

		this.icon.fadeIn('fast').css({
			position: 'absolute',
			top: this.mouseY + 17 + 'px',	
			left: this.mouseX + 15 + 'px'
		});

		this.mouseMoveEvent = function(e) {
			that.icon.css({
				top: (e.pageY + 17) + 'px', 
				left: (e.pageX + 15) + 'px'
			});
		};
		
		
		return this;
	},

	done: function(element) {
		this.icon.fadeOut('fast');
		this.mouseMoveEvent = function(e) {
			this.mouseX = e.pageX;
			this.mouseY = e.pageY;
			return;
		};
		return this;
	},

	mouseMoveEvent: function(e) {
		this.mouseX = e.pageX;
		this.mouseY = e.pageY;
		return;
	}
});


