// JavaScript Document


//naming functions
function fieldname(parametername) {
	return "opt_"+parametername;
}

function getinputname(parametername) {
	return "P"+parametername;
}

//Utility functions
function _isProperty(obj,key) {
	if (obj.hasOwnProperty) {
		return obj.hasOwnProperty(key);
	} else {
		return typeof(obj[key])!=="function";
	}
}

//Execute function func against each member of array or object. Break if func returns non-false.
function _each(obj,func) {
	var isarray=false;
	try {
		isarray=(obj.length!==undefined);
	} catch (ex) {}
	if (isarray) {
		for (var i=0;i<obj.length;i++) {
			if (_isProperty(obj,i))
				{
					var result=func(obj[i],i);
					if (result) {
						break;
					}
				}
		}
	} else {
		for (var a in obj) {
			if (_isProperty(obj,a))
				{
					var result=func(obj[a],a);
					if (result) {
						break;
					}
				}
		}
	}
}

function _keys(obj) {
	var result=[];
	for (var k in obj) {
		if (_isProperty(obj,k))
			{result.push(k);}
	}
	return result;
}

function _values(obj) {
	var result=[];
	for (var k in obj) {
		if (_isProperty(obj,k))
		{result.push(obj[k]);}
	}
	return result;
}
var getArray=_values;

function _filter(array,predicate) {
	var result=[];
	for (var i=0;i<array.length;i++) {
		try {
			if (predicate(array[i])) {
				result.push(array[i]);
			}
		}catch (e) {};
	}
	return result;
}

function mkArray(obj) {
	var arr=[],result=[];
	for (var k in obj) {
		if (!isNaN(k))
		{arr[k]=obj[k];}
	}
	while (arr.length>0) {
		result.push(arr.shift());
	}
	return result;
	
}

function arrayContains(array,value) {
	for (var i=0;i<array.length;i++) {
		if (array[i]==value) {
			return true;
		}
	}
	return false;
}

function _copyfields(dest, src) {
	_each(dest, function(val, name) {
		if (src[name]!==undefined) {						 
			dest[name]=src[name];
		}
	});
}

function _extend(original, extended){
	for (var key in (extended || {})) {
		if (_isProperty(extended,key)) {
			original[key] = extended[key];
		}
	}
	return original;
}

function _select(dict,array) {
	var result={};
	for (var i=0;i<array.length;i++) {
		if (dict[array[i]]) {
			result[array[i]]=dict[array[i]];
		}
	}
	return result;
}

function flatten(obj,list) {
	if (list===undefined) {list=[];}
	if (typeof obj=="array" || typeof obj=="object") {
		_each(obj,function(val) {
			list=flatten(val,list); 
		});
	} else {list.push(obj);}
	return list;
}

function _merge(){
	var mix = [];
	var i = 0, l = arguments.length;
	var object;
	for (i = 0; i < l; i++){
		object = arguments[i];
		if (typeof(object) != 'object'|| object===null) {continue;}
		if (object.length===undefined) { 
			mix={};
		}
	}
	for (i = 0; i < l; i++){
		object = arguments[i];
		if (typeof(object) != 'object' || object===null) {continue;}
		var key,op,mp;
		if (object.length!==undefined) { 
			for (key=0;key<object.length; key++) {
				op = object[key];
				mp = mix[key];
				mix[key] = (typeof(op) == 'object' && typeof(mp) == 'object') ? _merge(mp, op) : (op!==undefined ? op:mp);
			}
		} else {
			for (key in object){
				if (_isProperty(object,key)) {
					op = object[key];
					mp = mix[key];
					mix[key] = (typeof(op) == 'object' && typeof(mp) == 'object') ? _merge(mp, op) : (op!==undefined ? op:mp);
				}
			}
		}
	}
	return mix;
}

function _arrayProp(arr,propname) {
	var result=[];
	_each(arr,function(obj) {
		if (obj && typeof(obj)=="object") {
			if (obj[propname]!==undefined) {
				result.push(obj[propname]);
			}
		}
	});
	return result;
}

function _clone(obj) {
	return _merge(obj);
}

function _unlink(object){
	var unlinked;
	switch (typeof(object)){
		case 'object':
			unlinked = {};
			for (var p in object) {
				if (_isProperty(object,p)) {
					unlinked[p] = _unlink(object[p]);
				}
			}
		break;
		case 'array':
			unlinked = [];
			for (var i = 0, l = object.length; i < l; i++) {
				unlinked[i] = _unlink(object[i]);
			}
				
		break;
		default: return object;
	}
	return unlinked;
}

function _set() {
	var obj=arguments[0];
	var val=arguments[arguments.length-1];
	var prop;
	for (var i=1; i<arguments.length-2;i++) {
		prop=arguments[i];
		if (obj[prop]===undefined) {
			obj[prop]={};
		}
		obj=obj[prop];
	}
	obj[prop]=val;
}

function _revkeys(obj) {
	var rev={};
	for (var key in obj) {
		if (_isProperty(obj,key)) {
			rev[obj[key]]=key;
		}
	}
	return rev;
}

function n0(val) {
	if (isNaN(val)) {
		return 0;
	}else {
		return val;
	}
}

function round2dp(num) {
/*	num=Math.round(num*100)+"";
	var l=num.length;
	num=num.substr(0,l-2)+"."+num.substr(l-2,2);
	return num;*/
	return Math.round(num*100)/100;
}


//conventions:
//get - get by whatever means
//load - retrieve from storage (eg ajax) virtual functions


//singleton class ProductSystem
//organises data and functions relating to products and options
//many functions are virtual, and declared elsewhere


var All="ALL";
var ProductSystem  = {
	//Collection of all loaded data
	data: {
		producttypes: {},
		parameters: {},
		optionitems: {},
		products: {},
		productconfigs: {}
	},
	
	//miscellaneous global settings
	settings: {},
	
	haveAll: {},
	
	//Buffer of keys of data to load
	dataToLoad: {
		producttypes: [],
		parameters: [],
		optionitems: [],
		products: [],
		optionlists: [],
		productoptions: [] //[{productid:N,byID:[comboid],byOpts:[{parameterid:optionid}],broad:true}]
	},
	
	eventListeners: {},
	liveEvents:{},
	
	//Add a given key or list of keys to the buffer (ignoring values already loaded)
	//	datatype: property in dataToLoad
	//	key: key value for desired object, or array of key values, or All
	prepareToLoad: function(datatype,key) {
		if (!ProductSystem.dataToLoad[datatype]) {
			ProductSystem.dataToLoad[datatype]=[];
		}
		var keylist=ProductSystem.dataToLoad[datatype];
		if (!ProductSystem.haveAll[datatype] && key!==null) {
			if (key==All) {
				keylist.length=0;
				keylist.push(All);
			} else if (keylist.length>0 && keylist[0]==All) {
				return;
			} else if (typeof(key)=="object") {
				for (var i=0;i<key.length;i++) {
					if (!ProductSystem.data[datatype][key[i]]) {
						keylist.push(key[i]);
					}
				}
			} else {
				if (!ProductSystem.data[datatype][key]) {
					keylist.push(key);
				}
			}
		}
	},
	
	//Virtual function loadData
	//	load data keys specified in dataToLoad into data. Reset dataToLoad.
	loadData: function() {},
	
	
	//immediately load an object
	loadItem: function(datatype, key) {
		ProductSystem.prepareToLoad(datatype,key);
		ProductSystem.loadData();
	},
	
	//generic item retrieval
	getItem: function(datatype, key) {
		if (!ProductSystem.data[datatype]) {
			ProductSystem.data[datatype]={};
		}
		if (key===null) {
			return null;
		}
		if (key==All) {
			if (!ProductSystem.haveAll[datatype]) {
				ProductSystem.loadItem(datatype,key);
			}
			return ProductSystem.data[datatype];
		} else if (typeof(key)=="object") {
			if (key.length===undefined) {
				key=_keys(key);
			}
			var toLoad=[],i=0,result={};
			for (i=0; i<key.length;i++) {
				if (ProductSystem.data[datatype][key[i]]) {
					result[key[i]]=ProductSystem.data[datatype][key[i]];
				} else {
					toLoad.push(key[i]);
				}
			}
			if (toLoad.length===0) {
				return result;
			} else {
				ProductSystem.loadItem(datatype,toLoad);
			}
			for (i=0;i<key.length;i++) {
				if (ProductSystem.data[datatype][key[i]]) {
					result[key[i]]=ProductSystem.data[datatype][key[i]];
				}
			}
			return result;
		} else {
			if (!ProductSystem.data[datatype][key]) {
				ProductSystem.loadItem(datatype,key);
			}
			return ProductSystem.data[datatype][key];
		}
	},
		
	getProductType: function(typeid) {
		return ProductSystem.getItem("producttypes",typeid);
	},
	loadAllProductTypes: function() {
		ProductSystem.loadItem("producttypes",All);
	},
	
	getParameter: function(parameterid) {
		return ProductSystem.getItem("parameters",parameterid);
	},
	getParameterList: function(parameteridlist) {
		return ProductSystem.getItem("parameters",parameteridlist);
	},
	loadAllParameters: function() {
		ProductSystem.loadItem("parameters",All);
	},


	getOption: function(optionid) {
		return ProductSystem.getItem("optionitems",optionid);
	},
	getOptionList: function(optionidlist) {
		return ProductSystem.getItem("optionitems",optionidlist);
	},
	loadOptions: function(optionidlist) {
		ProductSystem.loadItem(optionidlist);
	},
	
	prepareToLoadCombo: function(productid,comboidlist,optiondictlist,broad) {
		ProductSystem.dataToLoad.productoptions.push({productid:productid,
													 byID:comboidlist,
													 byOpts:optiondictlist,
													 broad:broad});
	},
	
	
	getProduct: function(productid) {
		return ProductSystem.getItem("products",productid);
	},

	loadProduct: function(productid) {
		ProductSystem.loadItem("products",productid);
	},
	
	addProduct: function(product) {
		ProductSystem.data.products[product.productid]=product;
	},
	
	truefalse: {
		1:true,
		0:false
	},
	
	getProductConfig: function(prodconfigid) {
		return ProductSystem.getItem("productconfigs",prodconfigid);
	},
	
	getConfiguredProducts: function() {
		return _values(ProductSystem.data.productconfigs);
	},
	
	addListener: function(id,eventname,obj,funct,data) {
		var ref=id+"_"+eventname;
		if (!ProductSystem.eventListeners[ref]) {
			ProductSystem.eventListeners[ref]=[];
		}
		ProductSystem.eventListeners[ref].push({f:funct,o:obj,d:data});
	},
	
	fireEvent: function(id,eventname,data) {
		var ref=id+"_"+eventname;
		if (!ProductSystem.liveEvents[id]) {
			ProductSystem.liveEvents[id]=1;
			var listeners=ProductSystem.eventListeners[ref];
			if (listeners) {
				for (var i=0;i<listeners.length;i++) {
					var func=listeners[i].f;
					var xdata=listeners[i].d;
					var obj=listeners[i].o;
					xdata=_merge(data,xdata);
					func.call(obj,xdata);
				}
			}
			delete ProductSystem.liveEvents[id];
		}
	},

	none:null
};


//Data classes

function OptionCombo(data) {
	if (!data) {
		throw "NoData";
	} else if (data.prodoptionid) {
		this.id=data.prodoptionid;
		//fieldnames munged for serialisation.
		this.r=data.refcode; //refcode
		this.pp=data.priceadd_percent; //priceadd_perecent
		this.pf=data.priceadd_fixed; //priceadd_fixed
		this.wp=data.weightadd_percent; //weightadd_percent
		this.wf=data.weightadd_fixed; //weightadd_fixed
		this.s=data.stock; //stock
		this.d=!!data.optiondisabled; //disabled
	} else if (data.id) {
		this.id=data.id;
		//fieldnames munged for serialisation.
		this.r=data.r; //refcode
		this.pp=data.pp; //priceadd_perecent
		this.pf=data.pf; //priceadd_fixed
		this.wp=data.wp; //weightadd_percent
		this.wf=data.wf; //weightadd_fixed
		this.s=data.s; //stock
		this.d=!!data.d; //disabled
	}
	this.pf=n0(this.pf);
	this.pp=n0(this.pp);
	this.wf=n0(this.wf);
	this.wp=n0(this.wp);
	
	
	this.refcode=function() {return this.r;};
	this.priceadd_percent=function() {return this.pp;};
	this.priceadd_fixed=function() {return this.pf;};
	this.weightadd_percent=function() {return this.wp;};
	this.weightadd_fixed=function() {return this.wf;};
	this.stock=function() {return this.s;};
	this.disabled=function() {return this.d;};
}
function Product(data) {
	if (data) {
		this.productid=data.productid;
		this.data={
			productid:0,
			productname:'',
			producttypeid:'',
			defaultoptionid:null,
			baseprice:0,
			baseweight:0,
			applyvat:0,
			fields: {},
			optlists:{}, // {parameterid: {optionid:optionindex}}
			combodata: [], // [optionindex: [optionindex:...{optioncombo}]]
			componentdata:{}
		};
		
		_copyfields(this.data,data);
		this.typeid=this.data.producttypeid;
		this.name=this.data.productname;
		this.vatrate=this.data.applyvat ? ProductSystem.settings.vatrate : 0;
		this.catid=0;
	}
	//combolookup: {comboid: optiondict}
	var combolookup={};
	//optranks: {parameterid: {index:optionid}}
	var optranks={};
	var params=[];
	
	var componentGroups={};

	this.init=function() {
		_each(this.getParameters(), function(param) {
			if (param.isChoice()) {
				params.push(param.parameterid);
			}
		 });
		ProductSystem.prepareToLoad("producttypes",this.data.producttypeid);
		if (this.data.defaultoptionid) {
			ProductSystem.prepareToLoadCombo(this.productid,[this.data.defaultoptionid],null,true);
		}
		_each(this.data.optlists,function(optlist,paramid) {
			optranks[paramid]=_revkeys(optlist);
		});
		this.addComponents(this.data.componentdata);
	};
	

	this.producttype=function() {
		return ProductSystem.getProductType(this.data.producttypeid);
	};
	
	this.getParameters=function() {
		return this.producttype().getParameters();
	};
	
	function scanCombo(combodata,rank,optiondict) {
		_each(combodata,function(data,index) {
			if (data) {
				var parameterid=params[rank];
				optiondict[parameterid]=optranks[parameterid][index];
				if (rank<params.length-1) {
					scanCombo(data,rank+1,optiondict);
				} else {
					combolookup[data.id]=_clone(optiondict);
				}
			}
		});
	}
	this.addCombos=function(combodata) {
		this.data.combodata=_merge(this.data.combodata,combodata);
		scanCombo(combodata,0,{});
	};

	this.hasParameter=function(parameterid) {
		return this.producttype().hasParameter(parameterid);
	};
	
	this.hasOptions=function() {
		var hasOptions=false;
		_each(this.getParameters(), function(param) {
			if (param.isChoice()) {
				hasOptions= true;
				return true;//break
			}
		});
		return hasOptions;
	};
	
	this.getField=function(fieldname) {
		return this.data.fields[fieldname.toLowerCase()];
	};
	
	this.onPreOrder=function() {
		return !!this.data.fields.preorder;
	}
	
	this.getOptionsFor=function(parameterid) {
		if (this.hasParameter(parameterid)) {
			var param=ProductSystem.getParameter(parameterid);
			if (param.type()=="opt") {
				if (this.data.optlists[parameterid]) {
					return ProductSystem.getOptionList(this.data.optlists[parameterid]);
				}else {
					return [];
				}
			} else if (param.type()=="bool") {
				return ProductSystem.truefalse;
			} else {
				return [];
			}
		}
		return [];
	};
	

	this.getOptionComboByID= function(comboid){
		if (!combolookup[comboid]) {
			ProductSystem.prepareToLoadCombo(this.productid,[comboid],null,true);
			ProductSystem.loadData();
		}
		return this.getOptionComboByOpts(combolookup[comboid]);
	};
	
	this.getOptionDictForID=function(comboid) {
		return combolookup[comboid];
	};

	
	this.getOptionComboByOpts=function(optiondict,tryagain) {
		var ptr=this.data.combodata;
		var optlists=this.data.optlists;
		try {
		_each(this.getParameters(), function(param) {
			if (param.data.parametertype=="opt" || param.data.parametertype=="bool") {
				var optid=optiondict[param.parameterid];
				if (optid===undefined) {throw "MissingParameter"+param.parameterid;}
				var optindex=optlists[param.parameterid][optid];
				if (optindex===undefined) {throw "IllegalOption" + optid;}
				ptr=ptr[optindex];
				if (!ptr) {throw "NoData";}
			}
		 });
		} catch (ex) {
			if (ex=="NoData" && !tryagain) {
				ProductSystem.prepareToLoadCombo(this.productid,[],[optiondict],true);
				ProductSystem.loadData(); 
				return this.getOptionComboByOpts(optiondict,true);
			} else{
			
			}
			return null;
		}
		return new OptionCombo(ptr);
	};
	
	this.hasOpts=function(optiondict) {
		var result=true;
		var params=this.getParameters();
		_each(params,function(param,paramid) {
			if (param===undefined) {return false;}
			var optid=this.data.optlists[paramid];
			if (param.parametertype=="opt") {
				if (this.data.optlists[paramid][optid]===undefined) {
					result=false;
					return true;//break
				}
			}
			if (param.parametertype=="bool") {
				if (optid!==false && optid!==true) {
					result=false;
					return true;//break
				}
			}
		});
		return result;
	};
	
	
	this.hasOptID=function(parameterid,optionid) {
		if (!this.data.optlists[parameterid]) {return false;}
		return this.data.optlists[parameterid][optionid]!==undefined;
	};

	this.getOptionNamed=function(parameterid,optname) {
		if (optname && typeof(optname)=='string') {
			var o=optname.toLowerCase();
			var opts=this.getOptionsFor(parameterid);
			for (var optid in opts) {
				if (_isProperty(opts,optid)) {
					var opt=opts[optid];
					if (opt.loptname==o) {return opt;}
				}
			}
		}
	};

	this.getOption=function(parameterid,optid) {
		if (this.data.optlists[parameterid]) {
			if (this.data.optlists[parameterid][optid]!==undefined) {
				return ProductSystem.getOption(optid);
			}
		}
	}
	this.getOptionIndex=function(parameterid,optid) {
		if (this.data.optlists[parameterid]) {
			if (this.data.optlists[parameterid][optid]!==undefined) {
				return this.data.optlists[parameterid][optid];
			}
		}
	}
	
	this.getDefaultCombo=function() {
		if (!this.data.defaultoptionid) {
			return null;
		} else {
			return this.getOptionComboByID(this.data.defaultoptionid);
		}
	};
	
	
		
	
	this.weightof=function(optcombo) {
		if (optcombo)
			{return this.data.baseweight*(1+optcombo.weightadd_percent()/100)+optcombo.weightadd_fixed();}
		else
			{return this.data.baseweight*1;}
	};
	
	this.priceof=function(optcombo) {
		if (optcombo)
			{return round2dp(this.data.baseprice*(1+optcombo.priceadd_percent()/100)+optcombo.priceadd_fixed());}
		else
			{return round2dp(this.data.baseprice*1);}
	};
	
	this.priceofInc=function(optcombo) {
		return round2dp(this.priceof(optcombo) * (1+this.vatrate));
	}
	
	this.vatof=function(optcombo) {
		return round2dp(this.priceofInc(optcombo)-this.priceof(optcombo));
	}
		
	var hasComponents=false;
	this.addComponents=function(componentgroups) {
		this.data.componentdata=componentgroups;
		_each(componentgroups,function(cgdata) {
			var cg=new ComponentGroup(cgdata);
			componentGroups[cg.groupid]=cg;
			if (cg.init) {cg.init();}
			
			hasComponents=true;
		});
	};
	
	this.hasComponents=function() {
		return hasComponents;
	}
	
	this.getComponentGroups=function() {
		return _values(componentGroups);	
	}
	this.getComponentGroup=function(groupid) {
		return componentGroups[groupid];
	}
	this.getComponents=function(groupid) {
		var cg=componentGroups[groupid];
		if (cg) {
			return cg.getComponents();
		}
	}
	this.getComponent=function(groupid,componentid) {
		var cg=componentGroups[groupid];
		if (cg) {
			return cg.getComponent(componentid);
		}
	}
}

function ProductConfiguration(data) {
	this.data={
		productid:null,
		prefix:"",
		editable:false,
		currentOptions:{}, //optiondict
		currentComponents:{} //{groupid:componentid	}
	};
	_copyfields(this.data,data);
	this.productid=data.productid;
	this.component=null;
	this.componentGroup=null;

	this.prefix=function() {return this.data.prefix;};
	
	var currentOptionCombo=null;
	
	var subcomponentconfs={}; //{groupid:{componentid:prodconf}}
	
	this.init=function() {

		this.product=ProductSystem.getProduct(this.data.productid);
		
		if (this.product.data.defaultoptionid) {
			this.setComboID(this.product.data.defaultoptionid);
		} 
		var product=this.product;
		
		var me=this;
		ProductSystem.addListener(this.prefix(),"change",this,this.onComponentChange);

		_each(this.product.getComponentGroups(),function(group) {
			subcomponentconfs[group.groupid]={};
			_each(group.getComponents(),function(component) {
				var pc=new ProductConfiguration({
									   productid:component.componentid,
									   prefix:me.prefix()+"G"+group.groupid+"C"+component.componentid,
									   editable:me.data.editable
									   });
				me.attachComponent(group.groupid,pc);
			});

		});
		
		if (this.data.editable && this.requestComponents) {
			this.requestComponents();
		}
		if (this.data.editable && this.requestOptions) {
			this.requestOptions();
		}
		if (this.data.editable && this.registerInputs) {
			this.registerInputs();
		}

		_each(this.product.getComponentGroups(),function(group) {
			if (me.data.currentComponents[group.groupid]===undefined) {
				if (group.componentCount()>0) {
					me.setComponent(group.groupid,first(group.getComponents()).componentid);
				}
			}
		});

/*		var currentOptions=this.data.currentOptions;
		_each(this.product.getParameters(),function(param,parameterid) {
			if (currentOptions[parameterid]===undefined && param.isChoice()) {
				var optlist=me.getOptionsOrdered(parameterid);
				if (count(optlist)>0) {
					if (param.parametertype=="bool") {
						currentOptions[parameterid]=first(optlist);
					} else {
					currentOptions[parameterid]=first(optlist).optid;
				} 
			}
			}
		});*/
		var srch=new ComboSearch(this,function(combo) {return !combo.disabled();})
		this.data.currentOptions=srch.next();
	};
	
	this.attachComponent=function(groupid,componentconf) {
		var group=this.product.getComponentGroup(groupid);
		var component=group.getComponent(componentconf.data.productid);
		
		subcomponentconfs[group.groupid][component.componentid]=componentconf;
		componentconf.component=component;
		componentconf.componentGroup=group;
		componentconf.parentprefix=this.prefix();
		componentconf.init();
		if (group.data.bound) {
			ProductSystem.addListener(this.prefix(),"change",componentconf,componentconf.onParentOptionChange);
		}
	};
	
	this.hasVariables=function() {
		var result=false;
		var params=this.product.getParameters();
		var me=this;
		_each(params,function(param,paramid) {
			if (param.parametertype=="opt") {
				if (count(me.getOptions(paramid))>0) {
					result=true;
					return true;//break
				}
			} else {
				result=true;
				return true;//break
			}
		});
		
		_each(subcomponentconfs,function(group) {
			_each(group,function(conf) {
				if (conf.hasVariables()) {
					result=true;
					return true;//break
				}
			});
		 });
		return result;
	};

	
	this.getProduct=function() {
		return ProductSystem.getProduct(this.data.productid);
	}
	
	this.getParent=function() {
		if (this.parentprefix) {
			return ProductSystem.getProductConfig(this.parentprefix);
		}
	};
							
	this.getInputName=function(param) {
		return this.prefix()+getinputname(param.parametername());
	};


	this.getCurrentOptionCombo=function() {
		if (!currentOptionCombo) {
			currentOptionCombo=this.product.getOptionComboByOpts(this.data.currentOptions);
			
		}
		return currentOptionCombo;
	};
	
	this.setOption=function(parameter,value) {
		var parameterid;
		var changed=false;
		if (!isNaN(parameter)) {
			parameterid=parameter;
		} else if (this.product.producttype().parameterNamed(parameter)) {
			parameterid=this.product.producttype().parameterNamed(parameter).parameterid;
		}
		var param=ProductSystem.getParameter(parameterid);
		if (param.parametertype=='opt') {
			if (!this.product.hasOptID(parameterid,value)) {
				value=this.product.getOptionNamed(parameterid,value);
				if (value) {value=value.optid;}
			}
		}
		if (this.product.producttype().hasParameter(parameterid)) {
			if (this.data.currentOptions[parameterid]!=value) {
				this.data.currentOptions[parameterid]=value;
				changed=true;
			}
		}
		currentOptionCombo=null;
		if (changed) {
			ProductSystem.fireEvent(this.prefix(),"change",{parameterid:parameterid,value:value});
			this.checkOptionLists();
		}
	};
	this.setOptions=function(opthash) {
		this.data.currentOptions=_merge(this.data.currentOptions,opthash);
		currentOptionCombo=null;
	};
	
	this.setComboID=function(comboid) {
		currentOptionCombo=this.product.getOptionComboByID(comboid);
		var combodict=this.product.getOptionDictForID(comboid);
		if (combodict) {
			this.data.currentOptions=_clone(combodict);
		}
	};
	
	this.getOption=function(parameterid) {
		return this.data.currentOptions[parameterid];
	};
	
	this.getFullOption=function(parameterid) {
		if (ProductSystem.getParameter(parameterid).parametertype=='opt') {
		return ProductSystem.getOption(this.data.currentOptions[parameterid]);
		} else {
			this.data.currentOptions[parameterid];
		}
	};
	
	this.checkOptionLists=function() {
		var me=this;
		_each(this.product.getParameters(),function(param) {
			if (param.isChoice()) {
				ProductSystem.fireEvent(me.prefix(),"optlistchange",{parameterid:param.parameterid});
			}
		});
	}
	
	this.getOptions=function(parameterid) {
		var myoptions=this.product.getOptionsFor(parameterid);
		if (this.component) {
			var boundoption=this.component.data.bound[parameterid];
			if (boundoption!==undefined && myoptions[boundoption]!==undefined) {
				var opt=myoptions[boundoption];
				myoptions={};
				myoptions[boundoption]=opt;
			} 
		} 
		if (this.componentGroup) {
			if (arrayContains(this.componentGroup.data.bound,parameterid)) {
				if (this.getParent()) {
					var parentopts=this.getParent().getOptions(parameterid);
					/*var newopts={};
					_each(myoptions,function(opt,key) {
						if (parentopts[key]!==undefined) {
							newopts[key]=opt;
						}
					 });
					myoptions=newopts;*/
					var optid=this.getParent().getOption(parameterid);
					if (myoptions[optid]) {
						var opt=myoptions[optid];
						myoptions={};
						myoptions[optid]=opt;
					}
				}
			}
		}

		return myoptions;
	};
	
	this.getOptionsOrdered=function(parameterid) {
		var result=[];
		var prod=this.product;
		var opts=this.getOptions(parameterid);
		_each(opts,function(opt,optid) {
			var index;
			if (typeof opt=="object") {
				index=prod.getOptionIndex(parameterid,optid);
			} else {
				index=prod.getOptionIndex(parameterid,opt);
			}
			result[index]=opt;
		});
		return result;
	}
	
	this.getCurrentOptions=function(parameterid) {
		var result=[];
		var prod=this.product;
		var selection=_merge(this.data.currentOptions);
		_each(this.getOptions(parameterid),function(opt) {
			if (opt.optid!==undefined) {										
				selection[parameterid]=opt.optid;
			}else {
				selection[parameterid]=opt;
			}
			var combo=prod.getOptionComboByOpts(selection);
			if (combo && !combo.disabled()) {
				result.push(opt);
			}
		});
		return result;
	};
	
	this.setComponent=function(groupid,componentid) {
		var group=this.product.getComponentGroup(groupid);
		if (!group) {return;}
		if (componentid===null) {
			if (!group.isOptional) {
				return;
			}
		}
		var comp=this.getCurrentComponent(groupid);
		if (comp) {
			comp.fireEvent("unselect");
		}
		this.data.currentComponents[groupid]=componentid;
		comp=this.getCurrentComponent(groupid);
		if (comp) {
			comp.fireEvent("select");
		}
		this.fireEvent("change");
	};
	
	this.setDefaultComponent=function(groupid) {
		this.setComponent(groupid,this.getDefaultComponentID(groupid));
	};
	
	this.getDefaultComponentID=function(groupid) {
		var comp=this.product.getComponents(groupid)[0];
		if (comp) {return comp.componentid;}
	};
	
	this.getCurrentComponent=function(groupid) {
		return this.getComponent(groupid,this.getCurrentComponentID(groupid));
	};
	this.getCurrentComponentID=function(groupid) {
		return this.data.currentComponents[groupid];
	};
	
	this.getComponent=function(groupid,componentid) {
		var grp=subcomponentconfs[groupid];
		if (grp) {
			return grp[componentid];
		} else {
			return null;
		}
	};
	
	this.getCurrentComponents=function() {
		var list={};
		var me=this;
		_each(_keys(subcomponentconfs),function(groupid) {
			var conf=me.getCurrentComponent(groupid);
			if (conf) {
				list[groupid]=conf;
			}
		});
		return list;
	};
	this.getComponents=function(groupid) {
		return _values(subcomponentconfs[groupid]);
	};
	
	
	this.isOptional=function() {
		if (this.componentGroup) {
			return this.componentGroup.data.isoptional;
		} else {
			return false;
		}
	};
	
	this.price=function() {
		var price=this.product.priceof(this.getCurrentOptionCombo());
		_each(this.getCurrentComponents(),function(compconf,groupid) {
			price+=compconf.price();
		});
		price=price*this.priceMultiplier()*this.componentQty();
		return round2dp(price);
	};

	this.weight=function() {
		var weight=this.product.weightof(this.getCurrentOptionCombo());
		_each(this.getCurrentComponents(),function(compconf,groupid) {
			if (compconf) {										 
				weight+=compconf.weight();
			}
		});
		weight=weight*this.weightMultiplier()*this.componentQty();
		return weight;
	}
	this.priceInc=function() {
		return round2dp(this.price()*(1+this.product.vatrate));
	};
	this.vat=function() {
		return round2dp(this.price()*this.product.vatrate);
	};
	
	this.priceMultiplier=function() {
		var multiplier=1;
		if (this.component) {
			if (this.component.data.useprice!==null) {
				multiplier=this.component.data.useprice;
			} else if (this.componentGroup) {
				if (this.componentGroup.data.useprice!==null) {
					multiplier=this.componentGroup.data.useprice;
				}else {
					multiplier=0;
				}
			}
		}
		return multiplier;
	}
	this.componentQty=function() {
		var qty=1;
		if (this.component) {
			if (this.component.data.qty!==null) {
				qty=this.component.data.qty;
			} else if (this.componentGroup) {
				if (this.componentGroup.data.qty!==null) {
					qty=this.componentGroup.data.qty;
				}else {
					qty=1;
				}
			}
		}
		return qty;
	};

	this.weightMultiplier=function() {
		var multiplier=1;
		if (this.component) {
			if (this.component.data.useweight!==null) {
				multiplier=this.component.data.useweight;
			} else if (this.componentGroup) {
				if (this.componentGroup.data.useweight!==null) {
					multiplier=this.componentGroup.data.useweight;
				}else {
					multiplier=0;
				}
			}
		}
		return multiplier;
	}


	this.code=function() {
		var code="";
		if (currentOptionCombo) {
			code=this.getCurrentOptionCombo().refcode();
		}
		if (!code || String(code)=="") {code=this.product.getField("productref");}
		return code;
	};
	
	this.useDescription=function() {
		var result=false;
		if (this.component) {
			if (this.component.data.usedescription!==null) {
				result=this.component.data.usedescription;
			}else if (this.componentGroup) {
				if (this.componentGroup.data.usedescription!==null) {
					result=this.componentGroup.data.usedescription;
				}
			}
		}
		return result;
	}

	this.useImage=function() {
		var result=false;
		if (this.component) {
			if (this.component.data.useimage!==null) {
				result=this.component.data.useimage;
			}else if (this.componentGroup) {
				if (this.componentGroup.data.useimage!==null) {
					result=this.componentGroup.data.useimage;
				}
			}
		} else {
			result=true;
		}
		return result;
	}

	this.getAllComponents=function() {
		var result=[];
		result.push(this);
		_each(subcomponentconfs,function(group) {
			_each(group,function(comp) {
				result=result.concat(comp.getAllComponents());
			 });
	    });
		return result;
	};

	this.getDescComponents=function() {
		var result=[];
		result.push(this);
		_each(this.getAllComponents(),function(comp) {
			if (comp.useDescription()) {
				result.push(comp);
			}
		});
		return result;
	};
	
	this.getImageComponents=function() {
		var result=[];
		result.push(this);
		_each(this.getAllComponents(),function(comp) {
			if (comp.useImage()) {
				result.push(comp);
			}
		});
		return result;
	};
	
	this.addEvent=function(eventname,func,data) {
		ProductSystem.addListener(this.prefix(),eventname,this,func,data);
	};
	
	this.fireEvent=function(eventname,data) {
		ProductSystem.fireEvent(this.prefix(),eventname,data);
	};
	
	this.onComponentChange=function(data) {
		ProductSystem.fireEvent(this.parentprefix,"change",data);
	};
	this.onParentOptionChange=function(data) {
		if (data.parameterid!==undefined) {
			if (this.componentGroup && this.componentGroup.data.bound) {
				if (arrayContains(this.componentGroup.data.bound,data.parameterid)) {
					this.setOption(data.parameterid,data.value);
				}
			}
		}
	}
	
	this.getGroupSelector=function(groupid) {
		return this.prefix()+"G"+groupid+"Select";
	};
	this.getGroupEnabler=function(groupid) {
		return this.prefix()+"G"+groupid+"Enable";
	};
	
	this.getComponentSelector=function(groupid,componentid) {
		return this.prefix()+"G"+groupid+"C"+componentid+"Select";
	};
	
	this.getPresence=function() {
		return this.prefix()+"Present";
	};
}

function ComboSearch(prodconf,predicate) {
	this.product=prodconf.product;
	this.predicate=predicate;
	var indexes=[];
	var opts=[];
	var chosenopts={};
	var params=_values(this.product.getParameters());
	params=_filter(params,function(param) {return param.isChoice();});

	for (var i=0;i<params.length;i++) {
		opts[i]=prodconf.getOptionsOrdered(params[i].parameterid);
		indexes[i]=0;
	}
	
	this.next=function() {
		var i;
		do {
			for (i=0;i<params.length;i++) {
				var parameterid=params[i].parameterid;
				var optlist=opts[i];
				var index=indexes[i];
				var opt=optlist[index];
				if (params[i].parametertype=="opt") {
					opt=opt.optid;
				}
				chosenopts[parameterid]=opt;
			}
			var combo=this.product.getOptionComboByOpts(chosenopts);
			var carry=true;
			i=0;
			while (carry && i<indexes.length) {
				indexes[i]+=1;
				if (indexes[i]>=opts[i].length) {
					indexes[i]=0;
					carry=true
				} else {
					carry=false;
				}
				i+=1;
			} 
		} while (combo && !this.predicate(combo));
		if (combo) {
			return _merge(chosenopts);
		}
	}
}

function OptItem(data) {
	this.optid=data.optid;
	this.data={
		optid:0,
		optname:"",
		optdesc:"",
		ldesc:""
	};
	_copyfields(this.data,data);
	this.optiondescription=function() {return this.data.optdesc;};
	this.optname=this.data.optname;
	this.loptname=this.data.optname.toLowerCase();
}

function ComponentGroup(data) {
	this.groupid=data.componentgroupid;
	this.data={
		componentgroupid:0,
		basegroupid:0,
		isoptional:null,
		groupname:null,
		qty:null,
		useprice:null,
		useweight:null,
		usedescription:null,
		useimage:null,
		ishidden:false,
		bound:[]
	}

	_copyfields(this.data,data);
	this.data.isoptional=!!this.data.isoptional;
	this.data.ishidden=!!this.data.ishidden;
	
	var components={};
	
	var componentCount=0;
	this.addComponents=function(componentdata) {
		_each(componentdata,function(cdata) {
			var c=new Component(cdata);
			components[c.componentid]=c;
			c.init();
			componentCount+=1;
		});
	};
	this.addComponents(data.components);

	this.componentCount=function() {
		return componentCount;
	}
	
	this.getComponents=function() {
		return _values(components);
	};
	this.getComponent=function(componentid) {
		return components[componentid];
	};
	
	this.init=function() {
		this.name=this.data.groupname;
		this.isOptional=this.data.isoptional;
	}
}

function Component(data) {
	this.componentid=data.componentid;
	this.data={
		componentid:null,
		qty:null,
		useprice:null,
		useweight:null,
		usedescription:null,
		useimage:null,
		bound:{}
	}
	_copyfields(this.data,data);
	ProductSystem.prepareToLoad("products",this.data.componentid);
	
	
	this.init=function() {
		this.productid=this.data.componentid;
		this.product=ProductSystem.getProduct(this.data.componentid);
	};
}

function ProductType(data) {
	this.producttypeid=data.producttypeid;
	this.data={
		typename:"",
		parameters:[] //parameterid
	};
	_copyfields(this.data,data);
	
	this.typename=function() {
		return this.data.typename;
	};
	
	ProductSystem.prepareToLoad("parameters",this,data.parameters);
	
	var parameters=null;
	var parametersByName={};

	this.readyParameters=function() {
		if (parameters===null) {
			parameters=ProductSystem.getParameterList(this.data.parameters);
			
			_each(parameters,function(p) {
				  parametersByName[p.parametername().toLowerCase()]=p;
			  });
		}
	};
	
	this.getParameters=function() {
		this.readyParameters();
		return parameters;
	};
	
	
	this.hasParameter=function(parameterid) {
		this.readyParameters();
		return (!!parameters[parameterid]);
	};
	this.parameterNamed=function(parametername) {
		this.readyParameters();
		return parametersByName[(parametername+"").toLowerCase()];
	};
	
	this.choiceParameters=function() {
		var params=[];
		_each(parameters,function(param) {
			if (param.isChoice()) {
				params.push(param);
			}
		});
		return params;
	}
	this.freeParameters=function() {
		var params=[];
		_each(parameters,function(param) {
			if (!param.isChoice()) {
				params.push(param);
			}
		});
		return params;
	}
}

function ParameterDef(data) {
	this.parameterid=data.parameterid;
	this.data={
		parameterid:0,
		parametername:"",
		parametertype:"",
		displayname:""
	};
	_copyfields(this.data,data);
	this.type=function() {
		return this.data.parametertype;
	};
	this.parametername=function() {return this.data.parametername;};
	this.fieldname=function() {return fieldname(this.data.parametername);};
	this.displayname=function() {return this.data.displayname;};
	this.parametertype=this.data.parametertype;
	
	this.isChoice=function() {
		return this.data.parametertype=="opt" || this.data.parametertype=="bool";
	};
}



/* a few util functions...*/

var Debug={
	on:true,
	show: function(value) {
		if (Debug.on) {
			if (value===null) {
				Debug.write("null");
			}else if (value===undefined) {
				Debug.write("undefined");
			}else {
				Debug.write(Debug.toJSON(value));
			}
		}
	},
	write: function(string) {
	},
	toJSON:function(obj) {
	}
	
};
var MDebug=Debug;
