window.Collection = Class.create(Enumerable,{
	initialize:function(source) {
		var man = new Utils,myself = this,sys = {array:[]};
		this.set = function(key,value) {
			var index = myself.keyIndex(key);
			if (index < 0) sys.array.push({key:key,value:value});
			else sys.array[index].value = value;
		};
		this.get = function(key) {
			var index = myself.keyIndex(key);
			return (index < 0)?null:sys.array[index].value;
		};
		this.unset = function(key) {
			var index = myself.keyIndex(key);
			if (index > -1) sys.array.splice(index,1);
		};
		this.keys = function() {
			return sys.array.pluck('key');
		};
		this.values = function() {
			return sys.array.pluck('value');
		};
		this.indexOf = function(value) {
			return myself.values().indexOf(value);
		};
		this.getByIndex = function(index) {
			var value = sys.array[index];
			return Object.isUndefined(value)?{}:value;
		};
		this._each = function(fnc) {
			return sys.array._each(fnc);
		};
		this.keyIndex = function(key) {
			return myself.keys().indexOf(key);
		};
		if (man.isEnumerable(source))
			source.each(function(obj,index) {
				with (myself)
					if (Object.isHash(source) || man.isCollection(source))
						set(obj.key,obj.value);
					else set(index,obj);
			});
		else for (var key in source)
			myself.set(key,source[key]);
	}
});

window.Observable = Class.create({
	initialize:function() {
		var man = new Utils,myself = this,
			sys = {events:new Collection};
		this.observe = function(id,action) {
			var events = sys.events.get(id) || [];
			with (events) if (indexOf(action) < 0)
				push(man.toFunction(action));
			sys.events.set(id,events);
			return myself;
		};
		this.stopObserving = function(id,action) {
			var events = sys.events.get(id),
				index = $A(events).indexOf(action);
			if (index > -1) events.splice(index,1);
			return myself;
		};
		this.fire = function(id) {
			var output = [],args = $A(arguments).slice(1),
				events = $A(sys.events.get(id));
			for (var fnc,i = 0; fnc = events[i]; i++)
				output.push(man.execArray([fnc].concat(args)));
			return output;
		};
	}
});

window.Iterator = Class.create(Observable,{
	initialize:function($super,source,cyclic) {
		var man = new Utils,myself = this,sys = {};
		this.toCollection = function() {
			return sys.src;
		};
		this.gotoIndex = function(index) {
			var count = sys.src.size() || 1;
			index = man.toNumber(index);
			if (cyclic) while (index < 0 || index >= count)
				index += index < 0?count:-count;
			else index = index < 0?0:(index >= count?count-1:index);
			var prev = myself.getCurrentIndex(),offset = index-prev;
			if (void(sys.index = index) || offset)
				myself.fire('iterator:change',myself.getCurrentItem(),sys.src.getByIndex(prev),offset);
			return myself;
		};
		this.getCurrentIndex = function() {
			var index = sys.index || 0,count = sys.src.size() || 1;
			return index >= count?count-1:index;
		};
		this.setCyclic = function(value) {
			cyclic = value;
			return myself;
		};
		this.getCyclic = function() {
			return man.toBoolean(cyclic);
		};
		this.getCurrentItem = function() {
			return sys.src.getByIndex(myself.getCurrentIndex());
		};
		this.next = function() {
			return myself.gotoOffset(+1);
		};
		this.previous = function() {
			return myself.gotoOffset(-1);
		};
		this.gotoOffset = function(offset) {
			return myself.gotoIndex(myself.getCurrentIndex()+offset);
		};
		this.gotoKey = function(key) {
			var index = sys.src.keyIndex(key);
			return index < 0?myself:myself.gotoIndex(index);
		};
		if (man.isEnumerable(source)) source = new Collection(source);
		sys.src = (source instanceof Collection)?source:new Collection;
		myself.setCyclic(true);
		myself.gotoIndex();
		$super();
	}
});

window.PictureGallery = Class.create(Observable,{
	initialize:function($super,node,coverFader) {
		var man = new Utils,myself = this,sys = {iterator:new Iterator};
		this.addItem = function(id,src,memo) {
			sys.iterator.toCollection().set(id,{src:src,memo:memo});
			return myself;
		};
		this.fromJSON = function(array) {
			if (man.isEnumerable(array)) array.each(function(obj) {
				myself.addItem(obj.id,obj.src,obj.memo);
			});
			return myself;
		};
		this.setPathPrefix = function(prefix) {
			sys.root = new String(prefix);
			return myself;
		};
		this.showItem = function(id) {
			var item = sys.iterator.getCurrentItem(),action = [sys.iterator,'gotoKey',id];
			if (item && item.key != id) {
				if (hasFader()) coverFader.playForward(action);
				else man.execArray(action);
			} return myself;
		};
		this.scrollTo = function(x,y) {
			var offset = node.cumulativeOffset();
			with (document.viewport.getScrollOffsets()) {
				x = Object.isNumber(x)?offset.top+x:null || left;
				y = Object.isNumber(y)?offset.top+y:null || top;
			} window.scrollTo(x,y);
			return myself;
		};
		this.setFader = function(fader) {
			if (fader instanceof window.Fader)
				(coverFader = fader).getTransform().setLastPlay();
			return myself;
		};
		this.getCanvas = function() {
			return node;
		};
		this.getFader = function() {
			return coverFader;
		};
		var loadStart = function(current,prev,offset) {
			myself.fire('gallery:load',current.value,prev.value,offset);
			node.writeAttribute({src:sys.root+current.value.src});
		};
		var hasFader = function() {
			return coverFader instanceof window.Fader;
		};
		myself.setPathPrefix();
		$super(void(node = $(node) || {}));
		if (new String(node.tagName).toLowerCase() != 'img')
			node = new Element('img');
		sys.iterator.observe('iterator:change',loadStart);
		if (hasFader()) Event.observe(node,'load',coverFader.playBackward);
	}
});


var Utils = Class.create({
	initialize:function() {
		var myself = this;
		Math.norm = function(a,b) {
			with (Math) return abs(abs(a)-abs(b));
		}
		this.randName = function() {
			return 'r'+Math.random().toString().substr(2,8);
		};
		this.isUndefined = function(obj) {
			return typeof obj == 'undefined';
		};
		this.isElement = function(obj) {
			return Object.isElement(obj);
		};
		this.isFunction = function(obj) {
			return Object.isFunction(obj);
		};
		this.toBoolean = function(obj) {
			return new Boolean(obj).valueOf();
		};
		Object.isEvent = function(obj) {
			return obj instanceof Event;
		}
		this.toFunction = function(obj) {
			if (Object.isFunction(obj)) return obj;
			return function() {return obj};
		}
		this.toNumber = function(obj) {
			if (arguments.length > 1) obj = $A(arguments);
			//alert(obj)
			obj = myself.isEnumerable(obj)?obj:[obj];
			with (obj = obj.map(function(item) {
				return new Number(item).valueOf() || 0;
			})) return (length == 1)?shift():obj;
		};
		this.isEnumerable = function(obj) {
			return obj?myself.toBoolean(obj._each):false;
		};
		this.isCollection = function(obj) {
			return obj instanceof Collection;
		};
		this.execArray = function(fnc) {
			if (!fnc) return;
			if (myself.isFunction(fnc)) return fnc();
			for (var fnc = $A(fnc),arr = [],i = 0; i < fnc.length; arr.push('fnc['+(i++)+']'));
			if (arr.length) if (fnc[0] != null && typeof fnc[0] == 'object' && myself.isFunction(fnc[0][fnc[1]]))
				with (arr) return new Function('fnc','return '+shift()+'['+shift()+']('+join(',')+')')(fnc);
			else if (myself.isFunction(fnc.first()) || myself.isFunction(window[fnc.first()]))
				with (arr) return new Function('fnc','return '+shift()+'('+join(',')+')')(fnc);
		};
		this.getRegExp = function() {
			return new RegExp($w($A(arguments).join(' ')).join('/s*?').replace(/\//g,'\\'),'i');
		};
		this.getRegVars = function() {
			for (var vars = {},i = 1; i < 10; i++)
				vars['$'+i] = RegExp['$'+i];
			return vars;
		};
		this.max = function(list) {
			return $A(list).max(function(array) {
				return array.length;
			});
		};
		this.getBrowserVersion = function() {
			if (navigator.appVersion.match('MSIE (.*?);')) return parseFloat(RegExp.$1);
			return NaN;
		};
	}
});