// Copyright (c) 2007-2008 Chris Schneider, <http://www.chrisbk.de>
// a cow makes moo, this script too. requires mootools 1.2b or compatible.
// MIT-style licence

var CssEffects = {
	prefix: '-moofx',
	initialValues: {
		duration: '0ms',
		property: 'all',
		transition: 'sine-in-out'
	}
};

if (!Browser.Engine.trident4)
(function(){

CssEffects.Rule = new Class({
	initialize: function(selector){
		this.importances = new Hash();
		this.values = new Hash();
		
		this.selector = selector.replace(/:active|:focus|:hover/ig, '');
		this.dynamicPseudos = selector.match(/(:active|:focus|:hover)/ig) || []; // <--- !!! add at end
		
		this.specificity = (function(){
			var str = selector.replace(/:(before|after|first-letter|first-line)/, '');
			var a = str.match(/#/g); a = a ? a.length : 0;
			var b = str.match(/\[|\:|\./g); b = b ? b.length : 0;
			var c = str.match(/( |\+|>)[a-z]+/ig); c = c ? c.length : 0;
			if (str.match(/^[a-z]+/i)) c++;
			return 100 * a + 10 * b + c;
		})();
		
		this.use();
	},
	
	addDeclaration: function(property, value, importance){
		if (!property) return this.addShortHand(value, importance);	
		if (!this.importances.has(property) || this.importances.get(property) <= importance) {
			if (value.contains(',')) value = value.split(/\s*,\s*/);
			this.values.set(property, value);
			this.importances.set(property, importance);
		}
		return this;
	},
	
	addShortHand: function(shortHand, importance){
		shortHand.match(/([^\s,]+\s*,\s*)+[^\s,]+|[^\s,]+/gi).each(function(value){
			var property = (value.match(/\d+ms/) || CssEffects.Durations.has(value)) ? 'duration'
				: (CssEffects.Transitions.has(value)) ? 'transition'
				: 'property';
			this.addDeclaration(property, value, importance);
		}, this);
		return this;
	},
	
	use: function(){
		$$(this.selector).each(function(element){
			CssEffects.Element.getInstance(element).addRule(this);
		}, this);
	}
});


CssEffects.Parser = {
	addCss: function(text){
		var regExpA = new RegExp('\(?:\\s)*([^{}]+?)\\s*{([^}]*' + CssEffects.prefix + '[^}]*)}', 'gi');
		var regExpB = new RegExp(CssEffects.prefix + '(?:-?([a-z\\-]+))?\\s*:\\s*([^;]+?)\\s*(!important)?\\s*;', 'gi');
		while (a = regExpA.exec(text)){
			var selectors = a[1].split(/\s*,\s*/);
			rules = selectors.map(function(selector){
				return new CssEffects.Rule(selector);
			});
			while (b = regExpB.exec(a[2])){
				var importance = (b[3] == '!important') ? 2 : 1;
				rules.each(function(rule){
					rule.addDeclaration(b[1], b[2], importance);
				});
			}
		}
		return this;
	},
	
	addStylesheet: function(element){
		switch (element.get('tag')){
			case 'style':
				this.addCss(element.get('html'));
				break;
			case 'link':
				new Request({
					onSuccess: function(text){
						this.addCss(text);
					}.bind(this),
					url: element.href,
					method: 'get'
				}).send();
		}
		return this;
	},
	
	processDocument: function(){
		$each(document.styleSheets, function(styleSheet){
			var element = $(styleSheet[styleSheet.ownerNode ? 'ownerNode' : 'owningElement']);
			this.addStylesheet(element);
		}, this);
		return this;
	}
};


CssEffects.Element = new Class({	
	initialize: function(element){
		this.element = element;
		element.store('CssEffects', this);
		
		this.previous = {};
		this.dynamicPseudos = [];
		this.rules = [];
		this.styleAttribute = '';
		
		this.saveStyleAttribute();
		
		this.effect = new Fx.Morph(this.element,{
			onCancel: function(){
				this.previous = this.getStyles(this.dynamicPseudos);
			}.bind(this),
			onComplete: this.element.setStyles.bind(this.element, this.previous)
		});
		
		(function(){
			this.previous = this.getStyles();
			element.setStyles(this.previous);
		}).delay(1, this);
		
		CssEffects.DynamicPseudos.each(function(obj, pseudo){
			['begin', 'end'].each(function(when){
				$splat(obj[when]).each(function(event){
					element.addEvent(event, this.change.bind(this, [when, pseudo]));
				}, this);
			}, this);
		}, this);
	},
	
	change: function(when, pseudo){
		pseudo = pseudo || '';
		(function(){
			if (pseudo.length > 0){
				if (when == 'end' && !this.dynamicPseudos.contains(pseudo)) return;
				this.dynamicPseudos[(when == 'begin') ? 'push' : 'remove'](pseudo);
			}
			this.effect.cancel();
			
			this.restoreStyleAttribute();
			var now = this.getStyles(this.dynamicPseudos);
			this.element.setStyles(this.previous);
			
			var to = {};
			Hash.each(now, function(v, p){
				if (!$defined(this.previous[p]) || this.previous[p] != v)
					to[p] = now[p];
			}, this);
			
			var duration = this.getOwnStyle('duration', this.dynamicPseudos);
			var transition = this.getOwnStyle('transition', this.dynamicPseudos);
			this.effect.options.duration = duration.match(/\d+ms/) ? duration.toInt()
				: CssEffects.Durations.get(duration);
			this.effect.options.transition = CssEffects.Transitions.get(transition);	
			
			this.effect.start(to);
			
			this.previous = now;
		}).delay(1, this)
		return this;
	},
	
	addRule: function(rule){
		this.rules.include(rule);
		return this;
	},
	
	hasRule: function(rule){
		return this.rules.contains(rule);
	},
	
	removeRule: function(rule){
		this.rules.erase(rule);
		return this;
	},
	
	getOwnStyle: function(property, dynamicPseudos){
		dynamicPseudos = dynamicPseudos || [];	
		var pseudoStr = dynamicPseudos.sort().join('$');
		var rules = this.rules.filter(function(rule){
			var rulePseudoStr = rule.dynamicPseudos.sort().join('$');
			return (!rulePseudoStr || rule.values.has(property) && pseudoStr == rulePseudoStr);
		});
		var value = CssEffects.initialValues[property];
		var importance = 0, specificity = 0;
		rules.each(function(rule){
			var mImportance = rule.importances.get(property);
			if (mImportance > importance || (mImportance == importance && rule.specificity >= specificity)){
				importance = mImportance;
				specificity = rule.specificity;
				value = rule.values.get(property);
			}
		});
		return value;
	},
	
	getStyles: function (dynamicPseudos){
		var property = this.getOwnStyle('property', dynamicPseudos);
		if (property == 'none') return {};
		property = (property == 'all') ? CssEffects.Properties : $splat(property);
		return this.element.getStyles.apply(this.element, property);
	},
	
	saveStyleAttribute: function(){
		this.styleAttribute = this.element.get('style') || '';
		return this;
	},
	
	restoreStyleAttribute: function(){
		return this.element.set('style', this.styleAttribute);
	}
});

CssEffects.Element.getInstance = function(element){
	return element.retrieve('CssEffects') || new CssEffects.Element(element);
};


CssEffects.Durations = new Hash({
	'short': 250,
	'normal': 500,
	'long': 750
});

CssEffects.Properties = ['backgroundColor', 'backgroundPosition', 'borderBottomColor',
	'borderBottomStyle', 'borderBottomWidth', 'borderLeftColor', 'borderLeftStyle',
	'borderLeftWidth', 'borderRightColor', 'borderRightStyle', 'borderRightWidth',
	'borderTopColor', 'borderTopStyle', 'borderTopWidth', 'bottom',
	'color', 'fontSize', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
	'marginBottom', 'marginLeft', 'marginRight', 'marginTop', 'maxHeight', 'maxWidth',
	'opacity', 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'right',
	'textIndent', 'top', 'width', 'zIndex', 'zoom'];

CssEffects.DynamicPseudos = new Hash({
	':active': {
		begin: 'mousedown',
		end: ['mouseup', 'mouseout']
	},
	':focus': {
		begin: 'focus',
		end: 'blur'
	},
	':hover': {
		begin: 'mouseenter',
		end: 'mouseleave'
	}
});

CssEffects.Transitions = new Hash({
	linear: Fx.Transitions.linear
});
['Quad', 'Cubic', 'Quart', 'Quint', 'Expo', 'Circ',
 'Sine', 'Back', 'Bounce', 'Elastic'].each(function (transition){
	['In', 'Out', 'InOut'].each(function (ease){
		var alias = transition.toLowerCase() + ease.hyphenate().toLowerCase();
		CssEffects.Transitions.set(alias, Fx.Transitions[transition]['ease' + ease]);
	});
});

window.addEvent('domready', CssEffects.Parser.processDocument.bind(CssEffects.Parser));

})();
