ProgressiveDropdowns=new Class({
	Implements:[Options,Events],
	options:{
		id:null,												//Säiliölle annettava id
		'class':'progressive-dropdowns',						//Säiliön luokka
		requestOptions:{
			url:'server.php',									//Serveriskriptin osoite
			method:'post',
			noCache:false										//Yritetään käyttää välimuistia. Headereissa lukee kyllä no-cache eli ei tietoa, toimiiko
		},
		timeout:2000											//Aika, jonka jälkeen kyselyä yritetään uudelleen. Vanha kysely jätetään taustalle odottamaan.
		
	},
	fields:[],													//Rekisteröidyt kentät
	fieldIds:$H(),												//Lista kenttien id:istä muodossa {kenttäId:kenttäIndex_fieldsMuuttujassa}
	elements:{
		wrap:null,												//Säiliöelementti
		selects:[]												//Kaikki select-elementit
	},
	initialize:function(fields,options)
	{
		this.setOptions(options);
		fields.each(function(field){
			if(field==undefined)
				return false;
				
			this.addField(field.id,field.name,field['default']);
		},this);
	},
	/**
	* Palauttaa id:tä vastaavan kentän indeksin
	*
	**/
	getFieldIndex:function(fieldId){
		return this.fieldIds.get(fieldId);
	},
	
	/**
	* Palauttaa id:tä vastaavan kentän
	*
	**/
	getField:function(fieldId){
		var index=this.getFieldIndex(fieldId);
		if(index===null)
			return false;
			
		return this.fields[index];
	},
	/**
	* Palauttaa select-elementin id:tä vastaavalle kentälle
	*
	**/
	getElement:function(fieldId){
		return this.elements.selects.get(fieldId);
	},
	/**
	* Palauttaa listan annetun id:n jälkeen tulevista kentistä arrayna
	*
	**/
	getFieldsAfter:function(fieldId){
		var result=[];
		var ok=false;
		this.fields.each(function(field){
			if(ok)
			{
				result.push(field);
				return false;
			}
			if(fieldId==field.id)
				ok=true;
		});
		return result;
	},
	/**
	* Lisää kentän tälle instanssille
	*
	* @param string kentän id(tunnistemerkkijono, ei numeerinen)
	* @param string name kentän nimi. Käytetään labelissa
	* @param mixed vakioarvo. Kelpuuttaa merkkijonon(=valinnan teksti, arvo='') tai arrayn ([tyhjänKentänArvo,tyhjänKentänTeksti])
	**/
	addField:function(id,name,def)
	{
		if(!def)
			def='';
		if(def==='')
			def=['',''];
		else if($type(def)=='string')
			def=['',def];
		this.fields.push({
			id:id,
			name:name,
			'default':def,
			value:'',
			element:null
		});
		this.fieldIds.set(id,this.fields.length-1);
		
		return this;
	},
	/**
	* Asettaa id:tä vastaavan kentän valinnat
	*
	* Tyhjentää kentän ja asettaa uudet valinnat. Uudet valinnat muodossa {kentänArvo:kentänTeksti}
	*
	* @param string kentän id
	* @param object 1-tasoinen parilista valintakentistä
	**/
	setFieldOptions:function(fieldId,options)
	{
		var fieldIndex=this.getFieldIndex(fieldId);
		if(fieldIndex===null)
			return false;

		var newOptions=[];
		
		//Tyhjän arvon lisääminen
		newOptions.push(new Element('option',{
			'text':this.fields[fieldIndex]['default'][1],
			'value':this.fields[fieldIndex]['default'][0]
		}));
		
		
		if($defined(options))
		{
			options=$H(options);
			options.each(function(text,value){
				newOptions.push(new Element('option',{
					'value':value,
					'text':text
				}));
			});
		}
		
		
		
		this.elements.selects[fieldIndex].empty().adopt(newOptions);
	},
	
	/**
	* Luo valintakentät ja injektoi ne annettuun kohteeseen
	*
	* Suorittaa samalla ensimmäiselle kentälle arvojen päivityksen
	*
	* @param mixed $:n kelpuuttama elementti/id
	**/
	build:function(target)
	{
		target=$(target);
		var wrapElm=new Element('div',{
			id:this.options.id,
			'class':this.options['class']
		}).inject(target);
		
		this.elements.wrap=wrapElm;
		this.elements.selects=[];
		var that=this;
		this.fields.each(function(field,index)
		{
			var fieldWrapElm, fieldElm, labelElm;
			
			
			fieldWrapElm=new Element('div').inject(wrapElm);
			if(field.name)
			{
				labelElm=new Element('label',{
					text:field.name
				}).inject(fieldWrapElm);
			}
			fieldElm=new Element('select',{
				events:{
					change:function(){
						that.onSelectChange(this.retrieve('progressiveDropdown.id'),this.get('value'))
					}
				},
				name:field.id
			}).inject(fieldWrapElm)
			.store('progressiveDropdown.Index',index)
			.store('progressiveDropdown.id',field.id);
			
			that.elements.selects[index]=fieldElm;
			that.setFieldOptions(field.id);
			
			field.wrapElm=fieldWrapElm;
			field.selectElm=fieldElm;
			
		},this);
		this.fireEvent('build');
		this.update();
	},
	/**
	* Suoritetaan, kun kenttien arvoja muutetaan
	*
	* Käynnistää päivityspyynnön
	*
	* @param string muutetun kentän id
	* @param string muutetun kentän uusi arvo
	**/
	onSelectChange:function(fieldId,fieldValue)
	{
		var fieldIndex=this.getFieldIndex(fieldId);
		
		this.fields[fieldIndex].value=fieldValue;
		this.update(fieldId,fieldValue);
		this.fireEvent('change',1);
	},
	/**
	* Päivittää annettua kenttä-id:tä seuraavan kentän
	*
	* @param string muutetun kentän tunniste
	**/
	update:function(changedFieldId){
	
		var requestOptions, includeFields, trigger, that, fieldId, field, useFields,addNewFields,nextIsRequestField,fieldIndex,requestFieldId,requestField;
		if(!$defined(changedFieldId))
			changedFieldId=false;
			
		useFields=[];
		
		if(changedFieldId===false)
		{
			if(!this.fields.length)
			{
				if(window.console)
				{
					console.log('Kenttiä ei määritetty');
					return false;
				}
			}
			else
			{
				requestFieldId=this.fields[0].id;
				requestField=this.fields[0];
			}
		}		
		else
		{
			fieldIndex=this.getFieldIndex(changedFieldId);
			//Jos tämä on viimeinen kenttä, ei voida päivittää alempia -> lopetetaan suoritus.
			if(this.fields.length-1==fieldIndex)
				return false;
				
			
			addNewFields=true;
			nextIsRequestField=false;
			
			this.fields.each(function(field)
			{
				if(addNewFields)
				{
					useFields.push([field.id,field.value]);
					//console.log('adding field',field);
				}

				if(nextIsRequestField)
				{
					//console.log('requestField:',field);
					nextIsRequestField=false;
					requestFieldId=field.id;
					requestField=field;
				}
				else if(field.id==changedFieldId)
				{
					addNewFields=false;
					nextIsRequestField=true;
				}
			});
		}
			
		that=this;
		
		requestOptions={
			onComplete:(function(requestFieldId){
			
				return function(result)
				{
					return that.updateComplete(requestFieldId,result);
				}
			})(requestFieldId),
			
			onFailure:this.updateFailure.bind(this)
		}
		requestOptions=$merge(this.options.requestOptions,requestOptions);
		
		this.fireEvent('update',[
			requestField,
			this.getFieldsAfter(changedFieldId)
		]);
		
		var requestValues={fields:useFields,requestFieldId:requestFieldId};
		this.request(requestOptions,requestValues);
	},
	/**
	* Asettaa päivitettävän kentän valinnat
	* 
	* Suoritetaan automaattiesti onnistuneen päivityksen jälkeen
	*
	* @param string muutettavan kentän id
	* @param object päivityspyynnön palautettu arvo
	**/
	updateComplete:function(fieldId,result)
	{
		var that;
		that=this;
		result=$H(result);
		//console.info(arguments);
		if(result.fieldOptions)
		{
			that.setFieldOptions(fieldId,result.fieldOptions)
		}
		
		
		var field=this.getField(fieldId);
		this.fireEvent('updateComplete',[
			field,
			[field].extend(this.getFieldsAfter(fieldId))
		]);
	},
	/**
	* Jos päivitys failaa, tämä suoritetaan
	* 
	* Suoritetaan ainoastaan yhteysvirheen takia. Koodivirhe menee virheettä läpi
	**/
	updateFailure:function()
	{
		this.fireEvent('updateFailure',arguments);
		//console.error(arguments);
	},
	/**
	* Luo JSON-päivityspyynnön ja huolehtii, että se valmistuu
	*
	* Tekee options.timeout:in välein uuden pyynnön virheiden varalta ja
	* ensimmäisen valmistuessa peruuttaa muut keskeneräiset lataukset.
	*
	* Tämä on lähinnä firefoxilla ilmaantuvan ongelman kiertämiseksi, jossa
	* pyyntö luodaan tuplana ja vain toinen pyynnöistä valmistuu käynnistämättä
	* eventejä ensimmäisen latauksen jäädessä odottamaan loputtomiin.
	*
	* @param object requestille annettavat asetukset
	* @param object getissä tai postissa välitettävät parametrit
	**/
	request:function(requestOptions,requestValues){
		var requests=[];
		var events={
			complete:function(){
				//console.log('COMPLETE',arguments);
				$clear(timer);
				requests.each(function(request){
					request.cancel();
				});
			}
		};
		
		requests.push(this._request(requestOptions,requestValues,events));
		var timer=(function(requests){
			requests.push(this._request(requestOptions,requestValues,events));
		}).periodical(this.options.timeout,this,[requests]);
		
		
		
	},
	_request:function(requestOptions,requestValues,events){
		var request=new Request.JSON(requestOptions)
		if(events)
			request.addEvents(events);
		return request.post(requestValues);
	}
});
