var keyCode;
document.onkeydown=DetermineKeycode;
document.onkeyup=DetermineKeycode;

var ajaxLoader;
var minimalCreditAmmount;

var searchRequestComplete;
var offersSearchRequest;
var offersSearchRequestTimeout = null;

var currencies;
var years_units;
var currency = new Array();
var resultsElement;

var currencySymbol = ""; 

//var scrollingIsAllowed = true;
var scrollingIsAllowed = false;
if( location.href.indexOf('?')>=0 ) scrollingIsAllowed = false;


function DetermineKeycode(event){
	if(window.event)event=window.event;keyCode=event.keyCode?event.keyCode:event.which?event.which:null;
	_DelaySearch();

	return true;
}

	
function InitializeCalculator() {
	for(var i=0;i<6;i++){
		currency[i]=$("currency_"+i);
	};
	resultsElement = $('results');
	currencies=document.getElementById("currencies").getElementsByTagName("input");
	
	for(i=0;i<currencies.length;i++){
		Event.observe(currencies[i], "change", ChangeCur);
		Event.observe(currencies[i], "change", SearchOffers);
	};

	f_cost=document.getElementById("cost");f_cred=document.getElementById("credit");f_monp=document.getElementById("monthlypay");f_zp=document.getElementById("zp");f_fp=document.getElementById("firstpay");f_ndfl=document.getElementById("ndfl");f_cred.disabled=true;f_cred.className="disabled";f_monp.disabled=true;f_monp.className="disabled";f_zp.disabled=true;f_zp.className="disabled";f_fp.disabled=true;f_fp.className="disabled";f_ndfl.disabled=true;f_ndfl.className="disabled";f_ndf1=document.getElementById("ndf1");f_ndf2=document.getElementById("ndf2");f_ndf3=document.getElementById("ndf3");f_ndf1.disabled=true;f_ndf1.className="disabled";f_ndf2.disabled=true;f_ndf2.className="disabled";f_ndf3.disabled=true;f_ndf3.className="disabled";f_ndf1.value="1000000";f_ndf2.value="28.0";f_ndf3.value=Math.round(f_ndf1.value/f_ndf2.value);
		
	globalCb = null;
	previousCb = null;
	years_units=document.getElementById("years_units");
	prev_credit_data_input = null;

	
	function DetermineKeycode(event){
		if(window.event)event=window.event;keyCode=event.keyCode?event.keyCode:event.which?event.which:null;
//		SwitchFields();

		if(typeof absoluteInitialPayment != "undefined")
		{
			var currencySymbolString = "";
			currencySymbolString = "&nbsp;" + ( (currencySymbol != "") ? currencySumbol : $('currency')[$('currency').selectedIndex].innerHTML );    
			 
			absoluteInitialPayment.innerHTML = "(" + $F('firstpay_p') * $F('cost') / 100 + currencySymbolString + ")";
		};

		UpdateInitialPayment()
		Calc();
		return true;
	}
	document.onkeydown=DetermineKeycode;
	document.onkeyup=DetermineKeycode;
}


function UpdateInitialPayment() {
	if(typeof absoluteInitialPaymentValueElement == "undefined") {
		absoluteInitialPaymentValueElement = new Element('span', {id: 'absoluteInitialPaymentValue'}); 
		Element.insert(
			$('firstpay_p').parentNode,
			{
				bottom: $(absoluteInitialPaymentValueElement)
			}
		)
	}
	absoluteInitialPaymentValueElement.update( parseFloat($F('firstpay_p'))*$F('cost') / 100 + "&nbsp;р.")
}


function ChangeCur(cur){
	switch(this.value || cur){
	// знак валюты в ИЕ сменяется на такт позднее
		case "3":ChangeCurHTML("р.");break;
		case "4":ChangeCurHTML("&euro;");break;
		case "5":ChangeCurHTML("$");break;
		case "2":ChangeCurHTML("&yen;");break;
		case "1":ChangeCurHTML("CHF");break;
	}
}

function ChangeCurHTML(cur){
	currencySymbol = cur;
	for (var i=0; i<currency.length; i++) {
		if(currency[i] && currency[i].innerHTML)
			currency[i].innerHTML = "&nbsp;" + cur;
		//$("currency_" + i).innerHTML = "&nbsp;" + cur;
	}
}
	

function SwitchFields(cb,selectorUsed){
	cb=document.getElementById("calcby");cb=cb.options[cb.selectedIndex].value;f_cost=document.getElementById("cost");f_cred=document.getElementById("credit");f_monp=document.getElementById("monthlypay");f_zp=document.getElementById("zp");f_fp=document.getElementById("firstpay");
	
	if(previousCb) document.getElementById(previousCb).style.className="disabled";
	if(cb)previousCb=cb;
	else if(previousCb) cb=previousCb;

	globalCb=cb;
	if(!cb) return false;
	else{
		var f_selected=document.getElementById(cb);f_cost.className="disabled";f_cred.className="disabled";f_monp.className="disabled";f_zp.className="disabled";f_fp.className="disabled"
	};
	
	//document.getElementById('zp_label').innerHTML('Зарплата должна быть не менее:');
	switch(cb){
		case"cost":
			f_cost.disabled=false;
			f_cred.disabled=true;f_monp.disabled=true;f_zp.disabled=true;f_fp.disabled=true;f_cost.className="selected";if(selectorUsed)f_cost.focus();
		break;
		
		case"credit":f_cost.disabled=true;f_cred.disabled=false;f_monp.disabled=true;f_zp.disabled=true;f_fp.disabled=true;f_cred.className="selected";if(selectorUsed)f_cred.focus();break;case"monthlypay":f_cost.disabled=true;f_cred.disabled=true;f_monp.disabled=false;f_zp.disabled=true;f_fp.disabled=true;f_monp.className="selected";if(selectorUsed)f_monp.focus();break;
		
		case"zp":f_cost.disabled=true;f_cred.disabled=true;f_monp.disabled=true;f_zp.disabled=false;$('zp_label').update('Зарплата:');f_fp.disabled=true;f_zp.className="selected";if(selectorUsed)f_zp.focus();break;case"firstpay":f_cost.disabled=true;f_cred.disabled=true;f_monp.disabled=true;f_zp.disabled=true;f_fp.disabled=false;f_fp.className="selected";if(selectorUsed)f_fp.focus();break
	}

	var credit_data_input_cont = $("credit_data_input");
	var credit_data_input = $( "select-" + $("calcby").selectedIndex );
	
	if(prev_credit_data_input) resultsElement.appendChild(
		prev_credit_data_input
	);
	credit_data_input.parentNode.removeChild(credit_data_input)
	credit_data_input_cont.appendChild( credit_data_input );

	prev_credit_data_input = credit_data_input;

	Calc()
}
	
	
function PS(stavka,kper,plt,bs,type){
	if(isNaN(bs))bs=0;if(isNaN(type))type=0;if(stavka==0){ps=-bs-plt*kper}else{stp=Math.pow(1+stavka,kper);ps=(-bs-plt*(1+stavka*type)*((stp-1)/stavka))/stp}return ps
}

	
function PLT(stavka,kper,ps,bs,type){
	if(isNaN(bs))bs=0;if(isNaN(type))type=0;if(kper==0)return 0;if(stavka==0){plt=(-bs-ps)/kper}else{stp=Math.pow(1+stavka,kper);plt=(-bs-ps*stp)/((1+stavka*type)*((stp-1)/stavka))}return plt
}
	
	
function Calc(){
	if(!globalCb){cb=document.getElementById("calcby");cb=cb.options[cb.selectedIndex].value}else{cb=globalCb}f_cost=document.getElementById("cost");f_cred=document.getElementById("credit");f_monp=document.getElementById("monthlypay");f_zp=document.getElementById("zp");f_fp=document.getElementById("firstpay");f_ndfl=document.getElementById("ndfl");f_ndf3=document.getElementById("ndf3");f_period=document.getElementById("period");f_percent=document.getElementById("percent");f_fp_p=document.getElementById("firstpay_p");f_family=document.getElementById("family");f_mt=document.getElementById("maintance");if(cb.value<=0){return false}if(f_fp_p.value<0||f_fp_p.value>70){return false}
if(f_family.value<1){return false}if(f_period.value<1){return false}years_units.innerHTML="лет";
if(f_period.value == 1 || (f_period.value%10==1 && f_period.value > 20) )years_units.innerHTML="год";else if((f_period.value<10||f_period.value>20)&&f_period.value%10>1&&f_period.value%10<5)years_units.innerHTML="года";


function CheckFieldValue(field){
	var maxLen=9;if(field.value.length>maxLen){field.value=field.value.substring(0,maxLen);return false}else if(field.value<=0||isNaN(parseFloat(field.value))){field.value=0;return false}field.value=field.value.replace(/[^0-9\.]/,'');field.value=field.value.replace(/\.(\d*)\./,'.$1')}switch(cb){case"cost":CheckFieldValue(f_cost);f_fp.value=Math.round(f_cost.value*f_fp_p.value)/100;f_cred.value=Math.round((f_cost.value-f_fp.value)*100)/100;f_monp.value=Math.round(-PLT((f_percent.value/100)/12,f_period.value*12,f_cred.value));if(f_fp.value==0)f_zp.value=0;else f_zp.value=Math.round(Math.max(f_monp.value/0.4,(parseInt(f_monp.value)+f_family.value*f_mt.value)/0.6));if(parseInt(f_cost.value)>parseInt(f_ndf3.value))f_ndfl.value=Math.round((0.13*f_ndf3.value+0.13*(12*f_period.value*f_monp.value-f_cred.value))*100)/100;else f_ndfl.value=Math.round((0.13*f_cost.value+0.13*(12*f_period.value*f_monp.value-f_cred.value))*100)/100;break;case"credit":CheckFieldValue(f_cred);f_cost.value=Math.round(f_cred.value/(1-f_fp_p.value/100)*100)/100;f_fp.value=Math.round(f_cost.value*f_fp_p.value)/100;f_monp.value=Math.round(-PLT((f_percent.value/100)/12,f_period.value*12,f_cred.value));if(f_fp.value==0)f_zp.value=0;else f_zp.value=Math.round(Math.max(f_monp.value/0.4,(parseInt(f_monp.value)+f_family.value*f_mt.value)/0.6));if(parseInt(f_cost.value)>parseInt(f_ndf3.value))f_ndfl.value=Math.round((0.13*f_ndf3.value+0.13*(12*f_period.value*f_monp.value-f_cred.value))*100)/100;else f_ndfl.value=Math.round((0.13*f_cost.value+0.13*(12*f_period.value*f_monp.value-f_cred.value))*100)/100;break;case"monthlypay":CheckFieldValue(f_monp);f_cred.value=Math.round(-PS((f_percent.value/100)/12,f_period.value*12,f_monp.value));f_cost.value=Math.round(parseInt(f_cred.value)/(1-f_fp_p.value/100));f_fp.value=Math.round(f_cost.value*f_fp_p.value/100);if(f_fp.value==0)f_zp.value=0;else f_zp.value=Math.round(Math.max(f_monp.value/0.4,(parseInt(f_monp.value)+f_family.value*f_mt.value)/0.6));if(parseInt(f_cost.value)>parseInt(f_ndf3.value))f_ndfl.value=Math.round((0.13*f_ndf3.value+0.13*(12*f_period.value*f_monp.value-f_cred.value))*100)/100;else f_ndfl.value=Math.round((0.13*f_cost.value+0.13*(12*f_period.value*f_monp.value-f_cred.value))*100)/100;break;case"zp":CheckFieldValue(f_zp);if(f_zp.value==0)f_monp.value=0;else f_monp.value=Math.round(Math.min(f_zp.value*0.4,(parseInt(f_zp.value)*0.6-f_family.value*f_mt.value)));f_cred.value=Math.round(-PS((f_percent.value/100)/12,f_period.value*12,f_monp.value));f_cost.value=Math.round(parseInt(f_cred.value)/(1-f_fp_p.value/100));f_fp.value=Math.round(f_cost.value*f_fp_p.value/100);if(parseInt(f_cost.value)>parseInt(f_ndf3.value))f_ndfl.value=Math.round((0.13*f_ndf3.value+0.13*(12*f_period.value*f_monp.value-f_cred.value))*100)/100;else f_ndfl.value=Math.round((0.13*f_cost.value+0.13*(12*f_period.value*f_monp.value-f_cred.value))*100)/100;break;case"firstpay":CheckFieldValue(f_fp);if(f_fp_p.value==0)f_cost.value=0;else f_cost.value=Math.round((f_fp.value/(f_fp_p.value/100))*100)/100;f_cred.value=Math.round((f_cost.value-f_fp.value)*100)/100;f_monp.value=Math.round(-PLT((f_percent.value/100)/12,f_period.value*12,f_cred.value)*100)/100;if(f_fp.value==0)f_zp.value=0;else f_zp.value=Math.round(Math.max(f_monp.value/0.4,(parseInt(f_monp.value)+f_family.value*f_mt.value)/0.6)*100)/100;if(parseInt(f_cost.value)>parseInt(f_ndf3.value))f_ndfl.value=Math.round((0.13*f_ndf3.value+0.13*(12*f_period.value*f_monp.value-f_cred.value))*100)/100;else f_ndfl.value=Math.round((0.13*f_cost.value+0.13*(12*f_period.value*f_monp.value-f_cred.value))*100)/100;break}
	
	_DelaySearch();
}





/****
 	SearchOffers()
****/
function SearchOffers(pageNumber) {
	if(typeof offersElement != "undefined") {
		_GetFormValues();

		if(
			( !offersSearchRequest || (offersSearchRequest && searchRequestComplete == true) )
			&&
			( previousParamsSignature != paramsSignature || (pageNumber && pageNumber.length > 0) )
			 
		) {
			if(ajaxLoader) ajaxLoader.style.visibility = 'visible';

			offersSearchRequest = new Ajax.Request(
				'/_java/OffersSearch.p3',
				{
					asynchronous: true,
					encoding: 'UTF-8',
					
					method: 'get',
					parameters: {
						creditAmmount: formValues.get('creditAmmount'),
						creditTerm: formValues.get('creditTerm'),
						interestRate: formValues.get('interestRate'),
						initialPayment: formValues.get('initialPayment'),
						currency: formValues.get('currency'),
						markets: formValues.get('markets'),

						refinancing: formValues.get('refinancing'),
						roomPurchase: formValues.get('roomPurchase'),
						countrySide: formValues.get('countrySide'),

						p: pageNumber,
						rand: Math.random()
					},
					onComplete: function(transport) {
						offersElement.innerHTML = transport.responseText;
						if(ajaxLoader) ajaxLoader.style.visibility = 'hidden';
						this.completed = true;
						
						if(
							scrollingIsAllowed
							&&
							(
								typeof offersSearchRequest.parameters.p == "undefined"
								||
								!offersSearchRequest.parameters.p.length
							)
						) {
							if(resultsElement) Effect.ScrollTo(resultsElement);
							else Effect.ScrollTo(offersElement);
						}
						//scrollingIsAllowed = true
						scrollingIsAllowed = false
					}
				}				
			)
		}
	}
/**** SearchOffers() ****/
}


var paramsSignature;
function _GetFormValues() {
	var marketsParameterValue = "primary secondary";
	if($('primary_market') != null) marketsParameterValue = $F('primary_market') + " " + $F('secondary_market');
	
	var currencyParameterValue;
	if ($('currency') != null) {
		currencyParameterValue = $F('currency');
	} else {
		currencyParameterValue =
			$('calc').getInputs('radio', 'currency').find(
				function(radioButton) { return radioButton.checked; }
			).value;
	}

	formValues = $H({
		creditAmmount: $F('cost'),
		creditTerm: $F('period'),
		interestRate: $F('percent'),
		initialPayment: $F('firstpay_p'),

		refinancing: ($('refinancing') ? $F('refinancing') : ""),
		roomPurchase: ($('room_purchase') ? $F('room_purchase') : ""),
		countrySide: ($('country-side') ? $F('country-side') : ""),

		currency: currencyParameterValue,
		markets: marketsParameterValue
	});
	
	previousParamsSignature = paramsSignature;
	paramsSignature = '';
	formValues.each(
		function(e){
			paramsSignature += e + " "
		}
	);
}



function _DelaySearch() {
	var _searchTimeout = 400;
	
	if(offersSearchRequestTimeout == null){
		if(typeof ajaxLoader == "undefined") {
			var findCredits = document.getElementById('find_credits');

			if(findCredits != null) {
				ajaxLoader = document.createElement('img');
				ajaxLoader.id = 'ajaxLoader';
				ajaxLoader.src = '/_images/ajax-loader-beige.gif';
				findCredits.appendChild(ajaxLoader);
				ajaxLoader.style.visibility = 'hidden';
			}else{
				ajaxLoader = null;
			}
		}
		
		var creditAmmount = $F('cost');
		if( creditAmmount && creditAmmount > 0 && $F('period') && $F('percent') && $F('firstpay_p') )
		{
			offersSearchRequestTimeout = setTimeout(
				function()
				{
					searchRequestComplete = true;
					SearchOffers();
					offersSearchRequestTimeout = null;
				}, _searchTimeout
			);
		}
	}
	else {
		clearTimeout(offersSearchRequestTimeout);
		offersSearchRequestTimeout = null;
		_DelaySearch();
	}
	searchRequestComplete = false;
}


























// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/ 

// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';
  if (this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if (this.slice(0,1) == '#') {  
      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if (this.length==7) color = this.toLowerCase();  
    }  
  }  
  return (color.length==7 ? color : (arguments[0] || this));  
};

/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
};

Element.collectTextNodesIgnoreClass = function(element, className) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
};

Element.setContentZoom = function(element, percent) {
  element = $(element);  
  element.setStyle({fontSize: (percent/100) + 'em'});   
  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  return element;
};

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
};

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

/*--------------------------------------------------------------------------*/

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  Transitions: {
    linear: Prototype.K,
    sinoidal: function(pos) {
      return (-Math.cos(pos*Math.PI)/2) + 0.5;
    },
    reverse: function(pos) {
      return 1-pos;
    },
    flicker: function(pos) {
      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
      return pos > 1 ? 1 : pos;
    },
    wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
    },
    pulse: function(pos, pulses) { 
      pulses = pulses || 5; 
      return (
        ((pos % (1/pulses)) * pulses).round() == 0 ? 
              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
        );
    },
    spring: function(pos) { 
      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
    },
    none: function(pos) {
      return 0;
    },
    full: function(pos) {
      return 1;
    }
  },
  DefaultOptions: {
    duration:   1.0,   // seconds
    fps:        100,   // 100= assume 66fps max.
    sync:       false, // true for combining
    from:       0.0,
    to:         1.0,
    delay:      0.0,
    queue:      'parallel'
  },
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
    
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if (child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            new Element('span', {style: tagifyStyle}).update(
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if (((typeof element == 'object') || 
        Object.isFunction(element)) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || { });
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || { });
    Effect[element.visible() ? 
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create(Enumerable, {
  initialize: function() {
    this.effects  = [];
    this.interval = null;    
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = Object.isString(effect.options.queue) ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if (!this.interval)
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if (this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++) 
      this.effects[i] && this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if (!Object.isString(queueName)) return queueName;
    
    return this.instances.get(queueName) ||
      this.instances.set(queueName, new Effect.ScopedQueue());
  }
};
Effect.Queue = Effect.Queues.get('global');

Effect.Base = Class.create({
  position: null,
  start: function(options) {
    function codeForEvent(options,eventName){
      return (
        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
      );
    }
    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn+(this.options.duration*1000);
    this.fromToDelta  = this.options.to-this.options.from;
    this.totalTime    = this.finishOn-this.startOn;
    this.totalFrames  = this.options.fps*this.options.duration;
    
    eval('this.render = function(pos){ '+
      'if (this.state=="idle"){this.state="running";'+
      codeForEvent(this.options,'beforeSetup')+
      (this.setup ? 'this.setup();':'')+ 
      codeForEvent(this.options,'afterSetup')+
      '};if (this.state=="running"){'+
      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
      'this.position=pos;'+
      codeForEvent(this.options,'beforeUpdate')+
      (this.update ? 'this.update(pos);':'')+
      codeForEvent(this.options,'afterUpdate')+
      '}}');
    
    this.event('beforeStart');
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ? 
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if (timePos >= this.startOn) {
      if (timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if (this.finish) this.finish(); 
        this.event('afterFinish');
        return;  
      }
      var pos   = (timePos - this.startOn) / this.totalTime,
          frame = (pos * this.totalFrames).round();
      if (frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ? 
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if (this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if (!Object.isFunction(this[property])) data.set(property, this[property]);
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
});

Effect.Parallel = Class.create(Effect.Base, {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if (effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Tween = Class.create(Effect.Base, {
  initialize: function(object, from, to) {
    object = Object.isString(object) ? $(object) : object;
    var args = $A(arguments), method = args.last(), 
      options = args.length == 5 ? args[3] : null;
    this.method = Object.isFunction(method) ? method.bind(object) :
      Object.isFunction(object[method]) ? object[method].bind(object) : 
      function(value) { object[method] = value };
    this.start(Object.extend({ from: from, to: to }, options || { }));
  },
  update: function(position) {
    this.method(position);
  }
});

Effect.Event = Class.create(Effect.Base, {
  initialize: function() {
    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || { });
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});


Effect.ScrollTo = function(element) {
  var options = arguments[1] || { },
    scrollOffsets = document.viewport.getScrollOffsets(),
    elementOffsets = $(element).cumulativeOffset(),
    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  

  if (options.offset) elementOffsets[1] += options.offset;

  return new Effect.Tween(null,
    scrollOffsets.top,
    //elementOffsets[1] > max ? max : elementOffsets[1],
    elementOffsets[1],
    options,
    function(p){ scrollTo(scrollOffsets.left, p.round()) }
  );
};


Element.CSS_PROPERTIES = $w(
  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  'fontSize fontWeight height left letterSpacing lineHeight ' +
  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  'right textIndent top width wordSpacing zIndex');
  
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;

String.__parseStyleElement = document.createElement('div');
String.prototype.parseStyle = function(){
  var style, styleRules = $H();
  if (Prototype.Browser.WebKit)
    style = new Element('div',{style:this}).style;
  else {
    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
    style = String.__parseStyleElement.childNodes[0].style;
  }
  
  Element.CSS_PROPERTIES.each(function(property){
    if (style[property]) styleRules.set(property, style[property]); 
  });
  
  if (Prototype.Browser.IE && this.include('opacity'))
    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);

  return styleRules;
};

if (document.defaultView && document.defaultView.getComputedStyle) {
  Element.getStyles = function(element) {
    var css = document.defaultView.getComputedStyle($(element), null);
    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
      styles[property] = css[property];
      return styles;
    });
  };
} else {
  Element.getStyles = function(element) {
    element = $(element);
    var css = element.currentStyle, styles;
    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
      results[property] = css[property];
      return results;
    });
    if (!styles.opacity) styles.opacity = element.getOpacity();
    return styles;
  };
};

Effect.Methods = {
  morph: function(element, style) {
    element = $(element);
    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
    return element;
  },
  visualEffect: function(element, effect, options) {
    element = $(element)
    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
    new Effect[klass](element, options);
    return element;
  },
  highlight: function(element, options) {
    element = $(element);
    new Effect.Highlight(element, options);
    return element;
  }
};

$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  'pulsate shake puff squish switchOff dropOut').each(
  function(effect) { 
    Effect.Methods[effect] = function(element, options){
      element = $(element);
      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
      return element;
    }
  }
);

$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
  function(f) { Effect.Methods[f] = Element[f]; }
);

Element.addMethods(Effect.Methods);
