

// Ah namespace
var Ah = {};

// namespace for stored collections etc
Ah.Storage = {};

/**
 * Mixin utility class for storing events that need to be detached later 
 * (needed for event functions that have been bound - as these create anonymous functions)
 * 
 * use by adding it to Impliments array of class to mix it into
 */
Ah.DetachableEvents = new Class({
	
    /**
     * hash to store the detacheable events
     * will be added as a property of the class that this is mixed into
     */
	detachableEvents: new Hash(),
	
    /**
     * detaches a stored event
     * 
     * @param {String} key - the key of the detachable event to unregister
     */
	detachEvent: function(key){
        var detach = this.detachableEvents.get(key);
    	detach.obj.removeEvent(detach.type, detach.fn);
		return this;
	},
    
    /**
     * adds an event to the hash
     * 
     * @param {String} key - the key that this will be stored under in the hash
     * @param {Object} obj - the object to add the event to
     * @param {Object} type - the event type
     * @param {Object} fn - the function to run on this event
     */
	addDetachableEvent: function(key, obj, type, fn){
		obj.addEvent(type, fn);
		this.detachableEvents.set(key, { obj:obj, type: type, fn: fn });
		return this;
	},
	
    /**
     * unregisters all of the stored events
     */
	removeDetachableEvents: function(){
		this.detachableEvents.each(function(detach, key){
			this.detach(key);
		}, this);
		this.detachableEvents.empty();
	}
		
});


// MOOTOOLS EXTENSIONS ////////////////////////////////////////////////////////////////////////

// extend element class with periodical observable tendencies
Element.implement({
    
    previousValue: null,
    
    observe: function(callback, interval, args){
        var interval = interval || 500;
        this.previousValue = this.get('value');
        (function(args){
			var value = this.get('value');
            if(value != this.previousValue && value != ''){ 
                callback.attempt(args);
                this.previousValue = value;
            }
        }).periodical(interval, this, args);

    }
});


// extend element class with some convenience methods
Element.implement({
    
    show: function(){
        this.setStyle('display', 'block');
    },
    
    hide: function(){
        this.setStyle('display', 'none');
    },
    
    visible: function(){
        this.setStyle('visibility', 'visible');
    },
    
    invisible: function(){
        this.setStyle('visibility', 'hidden');
    }
        
});



/**
* Adds traversable functionality to Hash
* 
* Unfortunately could not be extended from Hash, as all props and methods become iterable 
* rather than just the key: value pairs stored in the hash 
*/
/*
Hash.implement({
	
	// held in an object so that dont get added to keys and iterated over
	trav: { pointer: 0, rotate: true },
	
	// Set the rotate property
	setRotate: function(value){
		this.trav.rotate = value;
		return this;
	},
	
	// Returns the current key
	key: function(){
		return this.getKeys()[this.trav.pointer];
	},
	
	// Returns the current value
	current: function(){
		return this.get(this.key());
	},
	
	//  Gets the first key
	firstKey: function(){
		return this.getKeys()[0];
	},
	
	//  Gets the last key
	lastKey: function(){
		return this.getKeys()[this.getLength() - 1];
	},
	
	//  Gets the next key
	nextKey: function(){
		return  this.getKeys()[this.trav.pointer + 1];
	},
	
	//  Gets the previous key
	previousKey: function(){
		return this.getKeys()[this.trav.pointer - 1];
	},
	
	// Traverse and return the hash for chaining... ///////////////////////////////
	
	f: function(){
		if(this.trav.pointer != 0){
			this.trav.pointer = 0;
			this.onChange();
		}
		return this;
	},
	
	l: function(){
		var len = this.getLength() - 1;
		var p = this.trav.pointer;
		this.trav.pointer = (len >= 0) ? len : 0;
		if(this.trav.pointer != p) this.onChange();
		return this;
	},
	
	n: function(){
		var key = this.nextKey();
		if ($defined(key)) {
			this.trav.pointer++;
			this.onChange();
		}else if(this.trav.rotate){
			this.f();
		}
		return this;
	},
	
	p: function(){
		var key = this.previousKey();
		if ($defined(key)) {
			this.trav.pointer--;
			this.onChange();
		}else if(this.trav.rotate){
			this.l();
		}
		return this;
	},
	
	// Traverse and return current value... /////////////////////////////////
	
	first: function(){
		this.f();
		return this.current();
	},
	
	last: function(){
		this.l();
		return this.current();
	},
	
	next: function(){
		this.n();
		return this.current();
	},
	
	previous: function(){
		this.p();
		return this.current();
	},
	
	// Publish an onChange event
	onChange: function(){
		this.fireEvent('onChange', {key: this.key(), value: this.current()});
	}

});


Hash.implement( new Events );
*/

/**
 * class that stores items in a hash but acts as though it is traversable
 */
Ah.Collection = new Class({
	
	Implements: [Events, Ah.DetachableEvents],
	
	name: 'Collection',
    items: null,
	pointer: null,
    
    options: {
        rotate: true // if will go back to first if next called when on last item etc
    },
    
    initialize: function(items, options){
		this.pointer = 0;
		this.items = new Hash();
        this.items.merge(items);
        this.setOptions(options);
        this.fireEvent('onInitialize');
    },
	
	
	// Set the rotate property
	setRotate: function(value){
		this.options.rotate = value;
		return this;
	},
	
	// Returns the current key
	key: function(){
		return this.items.getKeys()[this.pointer];
	},
	
	// Returns the current value
	current: function(){
		return this.items.get(this.key());
	},
	
	//  Gets the first key
	firstKey: function(){
		return this.items.getKeys()[0];
	},
	
	//  Gets the last key
	lastKey: function(){
		return this.items.getKeys()[this.items.getLength() - 1];
	},
	
	//  Gets the next key
	nextKey: function(){
		return  this.items.getKeys()[this.pointer + 1];
	},
	
	//  Gets the previous key
	previousKey: function(){
		return this.items.getKeys()[this.pointer - 1];
	},
	

    /**
     * traverses to the first item
     * 
     * @return - the collection so can chain traversal
     */
	f: function(){
		if(this.pointer != 0){
			this.pointer = 0;
			this.onChange();
		}
		return this;
    },
    
    /**
     * traverses to the last item
     * 
     * @return - the collection so can chain traversal
     */
    l: function(){
		var len = this.items.getLength() - 1;
		var p = this.pointer;
		this.pointer = (len >= 0) ? len : 0;
		if(this.pointer != p) this.onChange();
		return this;
    },
    
    /**
     * traverses to the next item
     * 
     * @return - the collection so can chain traversal
     */
    n: function(){
		var key = this.nextKey();
		if ($defined(key)) {
			this.pointer++;
			this.onChange();
		}else if(this.options.rotate){
			this.f();
		}
		return this;
    },
    
    /**
     * traverses to the previous item
     * 
     * @return - the collection so can chain traversal
     */
    p: function(){
		var key = this.previousKey();
		if ($defined(key)) {
			this.pointer--;
			this.onChange();
		}else if(this.options.rotate){
			this.l();
		}
		return this;
    },
	
	/**
     * traverses to the item with specified key
     * 
     * @return - the collection so can chain traversal
     */
	t: function(key){
		var i = this.getKeys().indexOf(key);
		if (i != -1) {
			this.pointer = i;
			this.onChange();
		}
		return this;
	},
	
    /**
     * traverses to the first item and returns it
     * 
     * @return - the item 
     */
    first: function(){
		this.f();
		return this.current();
    },
    
    /**
     * traverses to the last item and returns it
     * 
     * @return - the item 
     */
    last: function(){
        this.l();
		return this.current();
    },
    
    /**
     * traverses to the next item and returns it
     * 
     * @return - the item 
     */
    next: function(){
        this.n();
		return this.current();
    },
    
    /**
     * traverses to the previous item and returns it
     * 
     * @return - the item 
     */
    previous: function(){
        this.p();
		return this.current();
    },
    
	/**
     * traverses to the item with the specified key and returns it
     * 
     * @return - the item
     */
	to: function(key){
		this.t(key);
		return this.current();
	},

    
    /**
     * change event passes the key and item in an oject: {key: key, value: item} 
     */
	onChange: function(){
		this.fireEvent('onChange', {key: this.key(), value: this.current()});
	},
    
    
    
    // shortcuts to hash methods so can use the collection like a hash //////////////////////
	
	getLength: function(){
		return this.items.getLength();
	},
	
	forEach: function(fn, bind){
		return this.items.forEach(fn, bind);
	},
	
	getClean: function(){
		return this.items.getClean();
	},
	
	has: function(value){
		return this.items.has(value);
	},
	
	keyOf: function(value){
		return this.items.keyOf(value);
	},

	hasValue: function(value){
		return this.items.hasValue(value);
	},

	extend: function(properties){
		this.items.extend(properties);
		return this;
	},

	merge: function(properties){
		this.items.merge(properties);
		return this;
	},

	remove: function(key){
		this.items.remove(key);
		return this;
	},

	get: function(key){
		return this.items.get(key);
	},

	set: function(key, value){
		this.items.set(key, value);
		return this;
	},

	empty: function(){
		this.items.empty();
		this.pointer = 0;
		return this;
	},

	include: function(key, value){
		return this.items.include(key, value);
	},

	map: function(fn, bind){
		return this.items.map(fn, bind);
	},

	filter: function(fn, bind){
		return this.items.filter(fn, bind);
	},

	every: function(fn, bind){
		return this.items.every(fn, bind);
	},

	some: function(fn, bind){
		return this.items.some(fn, bind);
	},

	getKeys: function(){
		return this.items.getKeys();
	},

	getValues: function(){
		return this.items.getValues();
	},

	toQueryString: function(){
		return this.items.toQueryString();
	},
	
	// aliases
	
	indexOf: function(value){
		return this.keyOf(value);
	},
	
	contains: function(value){
		return this.hasValue(value);
	},
	
	erase: function(key){
		return this.remove(key);
	},
	
	each: function(fn, bind){
		return this.forEach(fn, bind);
	}
	
});
