//MooTools More, <http://mootools.net/more>. Copyright (c) 2006-2008 Valerio Proietti, <http://mad4milk.net>, MIT Style License.
/*
Script: Fx.Slide.js
	Effect to slide an element in and out of view.

License:
	MIT-style license.
*/

Fx.Slide = new Class({

	Extends: Fx,

	options: {
		mode: 'vertical'
	},

	initialize: function(element, options){
		this.addEvent('complete', function(){
			this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
			if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
		}, true);
		this.element = this.subject = $(element);
		this.parent(options);
		var wrapper = this.element.retrieve('wrapper');
		this.wrapper = wrapper || new Element('div', {
			styles: $extend(this.element.getStyles('margin', 'position'), {'overflow': 'hidden'})
		}).wraps(this.element);
		this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
		this.now = [];
		this.open = true;
	},

	vertical: function(){
		this.margin = 'margin-top';
		this.layout = 'height';
		this.offset = this.element.offsetHeight;
	},

	horizontal: function(){
		this.margin = 'margin-left';
		this.layout = 'width';
		this.offset = this.element.offsetWidth;
	},

	set: function(now){
		this.element.setStyle(this.margin, now[0]);
		this.wrapper.setStyle(this.layout, now[1]);
		return this;
	},

	compute: function(from, to, delta){
		var now = [];
		var x = 2;
		x.times(function(i){
			now[i] = Fx.compute(from[i], to[i], delta);
		});
		return now;
	},

	start: function(how, mode){
		if (!this.check(arguments.callee, how, mode)) return this;
		this[mode || this.options.mode]();
		var margin = this.element.getStyle(this.margin).toInt();
		var layout = this.wrapper.getStyle(this.layout).toInt();
		var caseIn = [[margin, layout], [0, this.offset]];
		var caseOut = [[margin, layout], [-this.offset, 0]];
		var start;
		switch (how){
			case 'in': start = caseIn; break;
			case 'out': start = caseOut; break;
			case 'toggle': start = (this.wrapper['offset' + this.layout.capitalize()] == 0) ? caseIn : caseOut;
		}
		return this.parent(start[0], start[1]);
	},

	slideIn: function(mode){
		return this.start('in', mode);
	},

	slideOut: function(mode){
		return this.start('out', mode);
	},

	hide: function(mode){
		this[mode || this.options.mode]();
		this.open = false;
		return this.set([-this.offset, 0]);
	},

	show: function(mode){
		this[mode || this.options.mode]();
		this.open = true;
		return this.set([0, this.offset]);
	},

	toggle: function(mode){
		return this.start('toggle', mode);
	}

});

Element.Properties.slide = {

	set: function(options){
		var slide = this.retrieve('slide');
		if (slide) slide.cancel();
		return this.eliminate('slide').store('slide:options', $extend({link: 'cancel'}, options));
	},
	
	get: function(options){
		if (options || !this.retrieve('slide')){
			if (options || !this.retrieve('slide:options')) this.set('slide', options);
			this.store('slide', new Fx.Slide(this, this.retrieve('slide:options')));
		}
		return this.retrieve('slide');
	}

};

Element.implement({

	slide: function(how, mode){
		how = how || 'toggle';
		var slide = this.get('slide'), toggle;
		switch (how){
			case 'hide': slide.hide(mode); break;
			case 'show': slide.show(mode); break;
			case 'toggle':
				var flag = this.retrieve('slide:flag', slide.open);
				slide[(flag) ? 'slideOut' : 'slideIn'](mode);
				this.store('slide:flag', !flag);
				toggle = true;
			break;
			default: slide.start(how, mode);
		}
		if (!toggle) this.eliminate('slide:flag');
		return this;
	}

});


/*
Script: Tips.js
	Class for creating nice tips that follow the mouse cursor when hovering an element.

License:
	MIT-style license.
*/

var Tips = new Class({

	Implements: [Events, Options],

	options: {
		onShow: function(tip){
			tip.setStyle('visibility', 'visible');
		},
		onHide: function(tip){
			tip.setStyle('visibility', 'hidden');
		},
		showDelay: 100,
		hideDelay: 100,
		className: null,
		offsets: {x: 16, y: 16},
		fixed: false
	},

	initialize: function(){
		var params = Array.link(arguments, {options: Object.type, elements: $defined});
		this.setOptions(params.options || null);
		
		this.tip = new Element('div').inject(document.body);
		
		if (this.options.className) this.tip.addClass(this.options.className);
		
		var top = new Element('div', {'class': 'tip-top'}).inject(this.tip);
		this.container = new Element('div', {'class': 'tip'}).inject(this.tip);
		var bottom = new Element('div', {'class': 'tip-bottom'}).inject(this.tip);

		this.tip.setStyles({position: 'absolute', top: 0, left: 0, visibility: 'hidden'});
		
		if (params.elements) this.attach(params.elements);
	},
	
	attach: function(elements){
		$$(elements).each(function(element){
			var title = element.retrieve('tip:title', element.get('title'));
			var text = element.retrieve('tip:text', element.get('rel') || element.get('href'));
			var enter = element.retrieve('tip:enter', this.elementEnter.bindWithEvent(this, element));
			var leave = element.retrieve('tip:leave', this.elementLeave.bindWithEvent(this, element));
			element.addEvents({mouseenter: enter, mouseleave: leave});
			if (!this.options.fixed){
				var move = element.retrieve('tip:move', this.elementMove.bindWithEvent(this, element));
				element.addEvent('mousemove', move);
			}
			element.store('tip:native', element.get('title'));
			element.erase('title');
		}, this);
		return this;
	},
	
	detach: function(elements){
		$$(elements).each(function(element){
			element.removeEvent('mouseenter', element.retrieve('tip:enter') || $empty);
			element.removeEvent('mouseleave', element.retrieve('tip:leave') || $empty);
			element.removeEvent('mousemove', element.retrieve('tip:move') || $empty);
			element.eliminate('tip:enter').eliminate('tip:leave').eliminate('tip:move');
			var original = element.retrieve('tip:native');
			if (original) element.set('title', original);
		});
		return this;
	},
	
	elementEnter: function(event, element){
		
		$A(this.container.childNodes).each(Element.dispose);
		
		var title = element.retrieve('tip:title');
		
		if (title){
			this.titleElement = new Element('div', {'class': 'tip-title'}).inject(this.container);
			this.fill(this.titleElement, title);
		}
		
		var text = element.retrieve('tip:text');
		if (text){
			this.textElement = new Element('div', {'class': 'tip-text'}).inject(this.container);
			this.fill(this.textElement, text);
		}
		
		this.timer = $clear(this.timer);
		this.timer = this.show.delay(this.options.showDelay, this);

		this.position((!this.options.fixed) ? event : {page: element.getPosition()});
	},
	
	elementLeave: function(event){
		$clear(this.timer);
		this.timer = this.hide.delay(this.options.hideDelay, this);
	},
	
	elementMove: function(event){
		this.position(event);
	},
	
	position: function(event){
		var size = window.getSize(), scroll = window.getScroll();
		var tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight};
		var props = {x: 'left', y: 'top'};
		for (var z in props){
			var pos = event.page[z] + this.options.offsets[z];
			if ((pos + tip[z] - scroll[z]) > size[z]) pos = event.page[z] - this.options.offsets[z] - tip[z];
			this.tip.setStyle(props[z], pos);
		}
	},
	
	fill: function(element, contents){
		(typeof contents == 'string') ? element.set('html', contents) : element.adopt(contents);
	},

	show: function(){
		this.fireEvent('show', this.tip);
	},

	hide: function(){
		this.fireEvent('hide', this.tip);
	}

});

/*
---

script: Chain.Wait.js

description: value, Adds a method to inject pauses between chained events.

license: MIT-style license.

authors:
- Aaron Newton

requires: 
- core:1.2.4/Chain 
- core:1.2.4/Element
- core:1.2.4/Fx
- /MooTools.More

provides: [Chain.Wait]

...
*/

(function() {

    var wait = {
        wait: function(duration) {
            return this.chain(function() {
                this.callChain.delay($pick(duration, 500), this);
            } .bind(this));
        }
    };

    Chain.implement(wait);

    if (window.Fx) {
        Fx.implement(wait);
        ['Css', 'Tween', 'Elements'].each(function(cls) {
            if (Fx[cls]) Fx[cls].implement(wait);
        });
    }

    Element.implement({
        chains: function(effects) {
            $splat($pick(effects, ['tween', 'morph', 'reveal'])).each(function(effect) {
                effect = this.get(effect);
                if (!effect) return;
                effect.setOptions({
                    link: 'chain'
                });
            }, this);
            return this;
        },
        pauseFx: function(duration, effect) {
            this.chains(effect).get($pick(effect, 'tween')).wait(duration);
            return this;
        }
    });

})();

/*
---

script: Element.Measure.js

description: Extends the Element native object to include methods useful in measuring dimensions.

credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element.Style
- core:1.2.4/Element.Dimensions
- /MooTools.More

provides: [Element.Measure]

...
*/

Element.implement({

    measure: function(fn) {
        var vis = function(el) {
            return !!(!el || el.offsetHeight || el.offsetWidth);
        };
        if (vis(this)) return fn.apply(this);
        var parent = this.getParent(),
			restorers = [],
			toMeasure = [];
        while (!vis(parent) && parent != document.body) {
            toMeasure.push(parent.expose());
            parent = parent.getParent();
        }
        var restore = this.expose();
        var result = fn.apply(this);
        restore();
        toMeasure.each(function(restore) {
            restore();
        });
        return result;
    },

    expose: function() {
        if (this.getStyle('display') != 'none') return $empty;
        var before = this.style.cssText;
        this.setStyles({
            display: 'block',
            position: 'absolute',
            visibility: 'hidden'
        });
        return function() {
            this.style.cssText = before;
        } .bind(this);
    },

    getDimensions: function(options) {
        options = $merge({ computeSize: false }, options);
        var dim = {};
        var getSize = function(el, options) {
            return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
        };
        var parent = this.getParent('body');
        if (parent && this.getStyle('display') == 'none') {
            dim = this.measure(function() {
                return getSize(this, options);
            });
        } else if (parent) {
            try { //safari sometimes crashes here, so catch it
                dim = getSize(this, options);
            } catch (e) { }
        } else {
            dim = { x: 0, y: 0 };
        }
        return $chk(dim.x) ? $extend(dim, { width: dim.x, height: dim.y }) : $extend(dim, { x: dim.width, y: dim.height });
    },

    getComputedSize: function(options) {
        options = $merge({
            styles: ['padding', 'border'],
            plains: {
                height: ['top', 'bottom'],
                width: ['left', 'right']
            },
            mode: 'both'
        }, options);
        var size = { width: 0, height: 0 };
        switch (options.mode) {
            case 'vertical':
                delete size.width;
                delete options.plains.width;
                break;
            case 'horizontal':
                delete size.height;
                delete options.plains.height;
                break;
        }
        var getStyles = [];
        //this function might be useful in other places; perhaps it should be outside this function?
        $each(options.plains, function(plain, key) {
            plain.each(function(edge) {
                options.styles.each(function(style) {
                    getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
                });
            });
        });
        var styles = {};
        getStyles.each(function(style) { styles[style] = this.getComputedStyle(style); }, this);
        var subtracted = [];
        $each(options.plains, function(plain, key) { //keys: width, height, plains: ['left', 'right'], ['top','bottom']
            var capitalized = key.capitalize();
            size['total' + capitalized] = size['computed' + capitalized] = 0;
            plain.each(function(edge) { //top, left, right, bottom
                size['computed' + edge.capitalize()] = 0;
                getStyles.each(function(style, i) { //padding, border, etc.
                    //'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
                    if (style.test(edge)) {
                        styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
                        size['total' + capitalized] = size['total' + capitalized] + styles[style];
                        size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
                    }
                    //if width != width (so, padding-left, for instance), then subtract that from the total
                    if (style.test(edge) && key != style &&
						(style.test('border') || style.test('padding')) && !subtracted.contains(style)) {
                        subtracted.push(style);
                        size['computed' + capitalized] = size['computed' + capitalized] - styles[style];
                    }
                });
            });
        });

        ['Width', 'Height'].each(function(value) {
            var lower = value.toLowerCase();
            if (!$chk(size[lower])) return;

            size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
            size['total' + value] = size[lower] + size['total' + value];
            delete size['computed' + value];
        }, this);

        return $extend(styles, size);
    }

});

/*
---

script: Fx.Elements.js

description: Effect to change any number of CSS properties of any number of Elements.

license: MIT-style license

authors:
- Valerio Proietti

requires:
- core:1.2.4/Fx.CSS
- /MooTools.More

provides: [Fx.Elements]

...
*/

Fx.Elements = new Class({

    Extends: Fx.CSS,

    initialize: function(elements, options) {
        this.elements = this.subject = $$(elements);
        this.parent(options);
    },

    compute: function(from, to, delta) {
        var now = {};
        for (var i in from) {
            var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
            for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
        }
        return now;
    },

    set: function(now) {
        for (var i in now) {
            var iNow = now[i];
            for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
        }
        return this;
    },

    start: function(obj) {
        if (!this.check(obj)) return this;
        var from = {}, to = {};
        for (var i in obj) {
            var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
            for (var p in iProps) {
                var parsed = this.prepare(this.elements[i], p, iProps[p]);
                iFrom[p] = parsed.from;
                iTo[p] = parsed.to;
            }
        }
        return this.parent(from, to);
    }

});

/*
---

script: Fx.Sort.js

description: Defines Fx.Sort, a class that reorders lists with a transition.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element.Dimensions
- /Fx.Elements
- /Element.Measure

provides: [Fx.Sort]

...
*/

Fx.Sort = new Class({

    Extends: Fx.Elements,

    options: {
        mode: 'vertical'
    },

    initialize: function(elements, options) {
        this.parent(elements, options);
        this.elements.each(function(el) {
            if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
        });
        this.setDefaultOrder();
    },

    setDefaultOrder: function() {
        this.currentOrder = this.elements.map(function(el, index) {
            return index;
        });
    },

    sort: function(newOrder) {
        if ($type(newOrder) != 'array') return false;
        var top = 0,
			left = 0,
			next = {},
			zero = {},
			vert = this.options.mode == 'vertical';
        var current = this.elements.map(function(el, index) {
            var size = el.getComputedSize({ styles: ['border', 'padding', 'margin'] });
            var val;
            if (vert) {
                val = {
                    top: top,
                    margin: size['margin-top'],
                    height: size.totalHeight
                };
                top += val.height - size['margin-top'];
            } else {
                val = {
                    left: left,
                    margin: size['margin-left'],
                    width: size.totalWidth
                };
                left += val.width;
            }
            var plain = vert ? 'top' : 'left';
            zero[index] = {};
            var start = el.getStyle(plain).toInt();
            zero[index][plain] = start || 0;
            return val;
        }, this);
        this.set(zero);
        newOrder = newOrder.map(function(i) { return i.toInt(); });
        if (newOrder.length != this.elements.length) {
            this.currentOrder.each(function(index) {
                if (!newOrder.contains(index)) newOrder.push(index);
            });
            if (newOrder.length > this.elements.length)
                newOrder.splice(this.elements.length - 1, newOrder.length - this.elements.length);
        }
        var margin = top = left = 0;
        newOrder.each(function(item, index) {
            var newPos = {};
            if (vert) {
                newPos.top = top - current[item].top - margin;
                top += current[item].height;
            } else {
                newPos.left = left - current[item].left;
                left += current[item].width;
            }
            margin = margin + current[item].margin;
            next[item] = newPos;
        }, this);
        var mapped = {};
        $A(newOrder).sort().each(function(index) {
            mapped[index] = next[index];
        });
        this.start(mapped);
        this.currentOrder = newOrder;
        return this;
    },

    rearrangeDOM: function(newOrder) {
        newOrder = newOrder || this.currentOrder;
        var parent = this.elements[0].getParent();
        var rearranged = [];
        this.elements.setStyle('opacity', 0);
        //move each element and store the new default order
        newOrder.each(function(index) {
            rearranged.push(this.elements[index].inject(parent).setStyles({
                top: 0,
                left: 0
            }));
        }, this);
        this.elements.setStyle('opacity', 1);
        this.elements = $$(rearranged);
        this.setDefaultOrder();
        return this;
    },

    getDefaultOrder: function() {
        return this.elements.map(function(el, index) {
            return index;
        });
    },

    forward: function() {
        return this.sort(this.getDefaultOrder());
    },

    backward: function() {
        return this.sort(this.getDefaultOrder().reverse());
    },

    reverse: function() {
        return this.sort(this.currentOrder.reverse());
    },

    sortByElements: function(elements) {
        return this.sort(elements.map(function(el) {
            return this.elements.indexOf(el);
        }, this));
    },

    swap: function(one, two) {
        if ($type(one) == 'element') one = this.elements.indexOf(one);
        if ($type(two) == 'element') two = this.elements.indexOf(two);

        var newOrder = $A(this.currentOrder);
        newOrder[this.currentOrder.indexOf(one)] = two;
        newOrder[this.currentOrder.indexOf(two)] = one;
        return this.sort(newOrder);
    }

});