Fx.TransMorph = new Class({ Extends: Fx.CSS }).implement(Fx.Morph.prototype);

Fx.TransMorph.implement({
	
	step: function(){
		var time = $time();
		if (time < this.time + this.options.duration){
			var delta = {};
			var d = (time - this.time) / this.options.duration;
			var t = this.transition(d);
			for (p in this.from){
				var trans = this.transitions[p];
				delta[p] = (trans) ? trans(d) : t;
			}
			this.set(this.compute(this.from, this.to, delta));
		} else {
			this.set(this.compute(this.from, this.to, 1));
			this.complete();
		}
	},
	
	compute: function(from, to, delta){
		var now = {};
		var isObj = ($type(delta) == 'object');
		for (var p in from) now[p] = this.parent(from[p], to[p], isObj ? delta[p] : delta);
		return now;
	},
	
	start: function(properties, transitions){
		if (!this.check(properties, transitions)) return this;
		if (typeof properties == 'string') properties = this.search(properties);
		var from = {}, to = {};
		for (var p in properties){
			var parsed = this.prepare(this.element, p, properties[p]);
			from[p] = parsed.from;
			to[p] = parsed.to;
		}
		transitions = transitions || {};
		$each(transitions, function(trans, key){
			transitions[key] = this.getTransition(trans);
		}, this);
		this.transitions = transitions;
		return this.parent(from, to);
	},
	
	getTransition: function(transition){
		var trans = transition || this.options.transition || Fx.Transitions.Sine.easeInOut;
		if (typeof trans == 'string'){
			var data = trans.split(':');
			trans = Fx.Transitions;
			trans = trans[data[0]] || trans[data[0].capitalize()];
			if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
		}
		return trans;
	}
	
});

Element.Properties.transmorph = {
	
	set: function(options){
		var transmorph = this.retrieve('transmorph');
		if (transmorph) transmorph.cancel();
		return this.eliminate('transmorph').store('transmorph:options', $extend({link: 'cancel'}, options));
	},
	
	get: function(options){
		if (options || !this.retrieve('transmorph')){
			if (options || !this.retrieve('transmorph:options')) this.set('transmorph', options);
			this.store('transmorph', new Fx.TransMorph(this, this.retrieve('transmorph:options')));
		}
		return this.retrieve('transmorph');
	}
	
};

Element.implement({
	
	transmorph: function(props, trans){
		this.get('transmorph').start(props, trans);
		return this;
	}
	
});

Fx.Expand = new Class({
	
	Extends: Fx,
	
	options: {
		overflown: true,
		wrapper: null,
		wrapperClass: null
	},
		
	initialize: function(element, options){
		this.element = $(element);
		if(!this.element) return
		
		this.parent(options);
		this.property = Browser.Engine.trident ? 'border-width' : 'opacity';
		this.build();
		this.assignEvents();
	},
	
	build: function(){
		this.margin = this.element.getStyle('margin-top').toInt() + this.element.getStyle('margin-bottom').toInt();
		var wrapper = this.element.retrieve('wrapper');
		var styles = this.element.getStyles('position', 'overflow');
		if(this.options.overflown) styles = $extend(styles, { 'overflow': 'hidden' });
		if(this.options.wrapper) wrapper = $(this.options.wrapper);
		this.wrapper = wrapper || new Element('div', {
			'styles': styles
		}).wraps(this.element);
		if(this.options.wrapperClass) this.wrapper.addClass(this.options.wrapperClass);
		this.element.store('wrapper', this.wrapper).store('wrapper:styles', styles);
	},
	
	assignEvents: function(){
		this.addEvent('onComplete', function(){
			if(this.open) this.wrapper.setStyle('height', '');
		}.bind(this));
	},
	
	getOffset: function(){
		return this.element.offsetHeight + this.margin;
	},
	
	compute: function(from, to, delta){
		return [0, 1].map(function(i){
			return Fx.compute(from[i], to[i], delta);
		});
	},
		
	set: function(now){
		this.wrapper.setStyle('height', now[0]);
		this.wrapper.setStyle(this.property, now[1]);
		return this;
	},
	
	start: function(how){
		if(!this.check(how)) return this;
		var height = this.wrapper.getStyle('height').toInt();
		var property = this.wrapper.getStyle(this.property).toInt();
		var caseIn = [[height, property], [this.getOffset(), 1]];
		var caseOut = [[height, property], [0, 0]];
		var start;
		switch(how){
			case 'in': start = caseIn; this.open = true; break;
			case 'out': start = caseOut; this.open = false; break;
			case 'toggle': start = this.open ? caseIn : caseOut; this.open = !this.open;
		}
		return this.parent(start[0], start[1]);
	},
	
	rollIn: function(){
		return this.start('in');
	},
	
	rollOut: function(){
		return this.start('out');
	},
		
	hide: function(){
		this.open = false;
		return this.set([0, 0]);
	},
	
	show: function(){
		this.open = true;
		return this.set([this.getOffset(), 1]);
	},
	
	toggle: function(){
		return this.start('toggle');
	}
	
});

Element.Properties.expand = {

	set: function(options){
		var expand = this.retrieve('expand');
		if(expand) expand.cancel();
		return this.eliminate('expand').store('expand:options', $extend({link: 'cancel'}, options));
	},

	get: function(options){
		if(options || !this.retrieve('expand')){
			if(options || !this.retrieve('expand:options')) this.set('expand', options);
			this.store('expand', new Fx.Expand(this, this.retrieve('expand:options')));
		}
		return this.retrieve('expand');
	}

};

Element.implement({

	expand: function(how){
		how = how || 'toggle';
		var expand = this.get('expand');
		switch (how){
			case 'hide': expand.hide(); break;
			case 'show': expand.show(); break;
			default: expand.start(how);
		}
		return this;
	}

});










Class.inherit = function(kls) {
  var args = [];
  for (var i = 1, j = arguments.length; i < j; i++) args.push(arguments[i]);
  return Class.prototype.inherit.apply(kls, args);
};

Class.prototype.inherit = function() {
  var klass = this;
  Array.each(arguments, function(mixin) {
    var baked = new Class;

    //Extends didnt work here in IE. Had to do it like this.

    baked.parent = klass;
    baked.prototype = Class.instantiate(klass);

    this.implement('parent', function(){
      var name = this.caller._name, previous = this.caller._owner.parent.prototype[name];
      if (!previous) throw new Error('The method "' + name + '" has no parent.');
      return previous.apply(this, arguments);
    }.protect());

    baked.implement(Class.instantiate(mixin));

    klass = baked;

  }, this);


  return klass;
};

