/**
 * This class will build a JSON object which the widget framework can use to to save 
 * values for a particular widget.  This way an application does not need to know the 
 * particulars of how this object must be built.  Instead it can use the simplified API
 * here.
 *
 * @param base - This is the base object coming from the widget framework.  This will 
 *               be parsed into a builder prepopulated with the values from the base
 *               if you want to build a structure from scratch pass in an empty object.
 */
Cobalt.Core.WidgetPersistenceObjectBuilder = function(baseObject, parentBaseObject, parentKey, unlockFields) {
	var i, key, bobj, ord, PersistantValuePropertyWrapper;
	var THIS = this;
	
	// Before we start here we need to deal with arrays.  They come back in a random order.  To sort them
	// we need to order them by the @id parameter
	if(baseObject instanceof Array) {
		var initialOrder = {};
		for(i=0; i < baseObject.length; i++) {
			bobj = baseObject[i];
			ord = (bobj["@id"] === undefined) ? 99999 : bobj["@id"];
			ord = parseInt(ord,10);
			if( undefined === initialOrder[ord]) {
				initialOrder[ord] = [];
			}
			initialOrder[ord][initialOrder[ord].length] = baseObject[i];
		}
		
		var keys = [];
		for(key in initialOrder) {
			//if(initialOrder.hasOwnProperty(key)) {
				keys.push(key);
			//}
		}
		keys.sort(function(a,b){return a-b;});
		
		// Build a new array with the new order
		var newbaseObject = [];
		for(i=0; i < keys.length; i++) {
			// set the new distinct order to the index in the array of slides we should display in that positon
			var tieItems = initialOrder[keys[i]];
			for(var c=0;c < tieItems.length; c++) {
				newbaseObject[newbaseObject.length] = tieItems[c];
			}
		}
		
		// rebase the object
		baseObject = newbaseObject;
		if( ! (undefined === parentBaseObject || null === parentBaseObject || undefined === parentKey || null === parentKey)) {
			parentBaseObject[parentKey] = newbaseObject;
		}
	}
	
	PersistantValuePropertyWrapper = function(base, key) {
		var baseProperty = base[key];
		this.build = function() { return baseProperty; };
		this.setValue = function(key, value) {
			// The key will always be == to baseProperty but if we do not pass the key
			// we need to add a bunch of code to figure out whether to call the method with one or two args
			// It is simpler to keep the interface the same 
			//console.log("%s: %o", "setValue for key "+key+", value "+value+":", baseProperty);
			baseProperty["$"] = value; 
		};
		// if the this property is a complex type return it's builder, otherwise just return the value.
		this.getValue = function() { 
			return baseProperty["$"]; 
		};
		this.setLocked = function(locked) { 
			//console.log("%s: %o", "Setting locked to '"+locked+"' in :", baseProperty);
			baseProperty["@lock"] = locked; 
		};
		this.getLocked = function() { 
			//console.log("%s: %o", "Getting locked '"+ baseProperty["@lock"]+"' in :", baseProperty);
			
			// This wierdness deals with the booleans being saved as strings
			if(baseProperty["@lock"] == "false") {
				return false;
			}
			return (baseProperty["@lock"] == "true") ? true : baseProperty["@lock"]; 
		};
		
		// determine if this property should be locked or not.  If not specified default to unlocked
		var classname = (base['className']) ? base['className']['$'] : undefined;
		if(classname && THIS.unlockFields[classname]) {
			if($.inArray(key, THIS.unlockFields[classname]) == -1) {
				baseProperty["@lock"] = true;
			}
		}
	};
	
	// set up local properties
	this.base = (baseObject) ? baseObject : {};
	this.properties = {};
	this.boundObject = null;
	this.unlockFields = (unlockFields) ? unlockFields : {}; // If this array is empty all fields will be unlocked, if this array has values only the fields specified will be unlocked an all other fields will be locked so they can be changed only at the master site. 
	
	/**
	 * Binds an object to this builder.  What that means is this method gets all of the properties
	 * of the object we want to bind to and either overrides or creates set and get methods associated
	 * with these properties such that the new set methods both update the local objects value and this 
	 * builders internal value.  If the set method already exists it will be called when the set 
	 * method is called after the builder set value is called so it will not overwrite the method just
	 * add to it.  
	 * 
	 * Here is a simple example:
	 * Foo = function() {
	 *     this.property1 = "a";
	 *     this.property2 = "b";
	 * };
	 *
	 * var foo = new Foo();
	 * var base = {};
	 * builder = new Cobalt.Core.WidgetPersistenceObjectBuilder(base);
	 * builder.bindObject(foo);
	 *
	 * assertEquals("a", foo.property1);
	 * assertEquals(foo.property1, foo.getProperty1());
	 * assertEquals(foo.property1, builder.getValue("property1"));
	 * assertEquals(foo.property1, builder.property1());
	 * assertEquals(foo.property1, base.property1["$"]);
	 *
	 * foo.setProperty1("x");
	 * assertEquals("x", foo.property1);
	 * assertEquals("x", foo.getProperty1());
	 * assertEquals("x", builder.getValue("property1"));
	 * assertEquals("x", builder.property1());
	 * assertEquals("x", base.property1["$"]);
	 *
	 * @param arg - Normally this should be a reference to a function that identifies an object, if so a new instance 
	 *              of that object is created and bound to this builder.  If it is an instantiated class we will just
	 *              use it as a default set of values for this proprty.  In either case if one of the properties is 
	 *              a class with a property that is an object we will wrap a new builder around it and set the property to that.
	 */
	this.bindObject = function(arg) {
		var i, obj, methods, props, className, instance, idx, childBase, lclvalue, propIface, childBaseclassName, childBuilder;
		var createClosedFunctions ;
		
		obj = (typeof arg == "function") ? new arg() : arg;
		this.boundObject = obj;
		
		methods = {};
		props = {};
		for(i in obj) {
			if(typeof obj[i] == "function") {
				// force key to lower case so there is no case issue in comparisons, save case sensitive name in hash
				methods[i.toLowerCase()] = { name : i, ref  : obj[i] };
			} else {
				props[i] = obj[i];
			}
		}
		//console.log("%s: %o", "-> methods :", methods);
		
		// the avove for i in obj adds the list of properties associated with the object we want to bind
		// to the props object.  This allows us to bind values from the persistance object to the bound object
		// if defined and if not there is code below which will add values the other way from the bound 
		// object to the persistance object (if not already defined in it).  This is normally all we want to do
		// however when the base object is an array we must add all of the array nodes from the persistance object
		// and add to the bound object even if they do not exist there.  So that is what the next hunk of code does,
		// add array nodes that do not exist in the bound object array to the bound object.
		if(this.base instanceof Array) {
			for(i=0; i < this.base.length;i++) {
				if(undefined === props[i]) {
					// Setting to undefined for now should be good enough, the code below will populate the data
					// if it is present in the props array.
					lclvalue = this.get(i);
					if(lclvalue instanceof Cobalt.Core.WidgetPersistenceObjectBuilder) {
						if(undefined === lclvalue.base.className) {
							//console.log("%s: %o", "-> missing className:", this.base[i]);
							//throw new Error("className is not defined for "+i+" in "+lclvalue);
						} else {
							//console.log("%s: %o", "-> (1) Found undefined array node for index "+i+":", this.base[i]);
							className = lclvalue.base.className['$'];
							eval('instance = new '+className+'()');
							//instance = new className();
							props[i] = instance;
							obj[i] = props[i];
						}
					} else {
						//console.log("%s: %o", "-> (2) Found undefined array node for index "+i+":", this.base[i]);
						props[i] = this.getValue(i);
					}
					//console.log("%s: %o", "-> (a) Setting overide found array node in "+i+" to :", props[i]);
					obj[i] = props[i];
				}
			}
		}
		
		// go through the list of properties making or overriding set and get methods if needed and setting the
		// properties in the base persistance object if not already there
		for(i in props) {
			//if(props.hasOwnProperty(i)) {
				var propValue = props[i];
				var newBuilder = null;
				
				// This is the base name for the set and get methods below
				var bMethodName = i.charAt(0).toUpperCase() + i.slice(1);
				//console.log("%s: %o", "-> Adding new value to base for "+i+"="+propValue+" to:", this.base);
				
				// make sure this builder has such a property
				if(undefined === this.get(i) || this.get(i) === null) {
					// this local property does not exist yet go ahead and add it (defaulting locked to false) and the value 
					// equal either to the objects value or to a new builder if the value is a complex type
					//console.log("%s: %o", "-> persistance object does NOT have value for "+parentKey+"."+i+" in::", this.base);
					this.put(i, propValue, false);
				} else {
					//console.log("%s: %o", "-> persistance object has value for "+parentKey+"."+i+" in:", obj[i]);
					
					// here we need to override the current value in the object with the value we have in the persistance object.
					propIface = this.get(i);
					if(propIface instanceof Cobalt.Core.WidgetPersistenceObjectBuilder) {
						//propValue = propIface;
						//console.log("%s: %o", "Binding object to builder for "+parentKey+"."+i+" in:", propIface);
						
						// Now if we have a builder for this we should see if we can bind the properties of this builder with
						// the properties of the object.
						propIface.bindObject(obj[i]);
						
						// Since bindObject assumes all properties we need to bind are defined in the object then
						// here we need to check if this object is an array, since an array can be larger then the array
						// defined in the object.  And if this happes we will not be binding the items in the array unless
						// we take this extra step.
						var ifaceBase = propIface.base; 
						if(obj[i] instanceof Array) { // && ifaceBase instanceof Array
							//console.log("%s: %o", "handling ", ifaceBase);
							for(idx in ifaceBase) {
								//if(ifaceBase.hasOwnProperty(idx)) {
									//console.log("%s: %o", "Looking for idx "+idx+" in ", ifaceBase);
									childBase = ifaceBase[idx];
									// If the item already exists in the array it will already be bound so skip it
									if(idx >= obj[i].length) {
										if(childBase.className !== undefined) {
											className = childBaseclassName['$'];
											eval('instance = new '+className+'()');
											//instance = new className();
											if(instance === undefined) {
												throw new Error("Failed to instantiate '"+className+"'");
											} else {
												// Make a new builder and bind this object to it
												childBuilder = new Cobalt.Core.WidgetPersistenceObjectBuilder(childBase,ifaceBase,idx,THIS.unlockFields);
												childBuilder.bindObject(instance); 
												obj[i][idx] = instance;
												
												if(instance.init !== undefined) {
													instance.init();
												}
											}
										} else {
											// make sure all these items are copied to the object and are bound.
											//console.log("%s: %o", "Warning: Data cannot be bound!  Try adding a property when saving called className", ifaceBase);
										}
									}
								//}
							}
						}
					} else {
						// This is a regular node
						var setVal = propIface.getValue();
						
						// Since all values are saved as strings we need to convert boolean strings to boolean values
						if(setVal == "false") {
							setVal = false;
						} else if(setVal == "true") {
							setVal = true;
						} else if(setVal == "null") {
							setVal = null;
						}
						
						if( ! (undefined === setVal || setVal === null)) {
							//console.log("%s: %o", "Setting "+parentKey+"."+i+" to:", setVal);
							obj[i] = setVal;
							propValue = setVal;
						} else {
							//console.log("%s: %o", "Warning: "+i+" is null for type see below for prop dump:", this);
						}
					}
				}
					
				// If prop value created a new builder we need to set propValue to the builder rather then the 
				// native value so it can be passed on below when the functions are added dynamically
				//console.log("%s: %o", "Checking if builder in "+parentKey+"["+i+"]:", this.get(i));
				if(this.get(i) != propValue && this.get(i) instanceof Cobalt.Core.WidgetPersistenceObjectBuilder) {
					newBuilder = this.get(i);
					//console.log("%s: %o", "YES "+parentKey+"["+i+"]:", newBuilder);
				}
			
				// Do not attempt to add methods to an array
				if( ! (this.base instanceof Array)) {
					// I am making a function to create the functions in closure otherwise you get
					// wierd reference issues where function 1 calls function 7 if 7 was the last one added.
					createClosedFunctions = function(builder, propertyName, baseMethodName, pValue, methods, newBuilder) {
						var addMethodName, setMethodName, getMethodName, setLockMethodName, getLockMethodName, value, methName;
						
						// Define common methods to all cases
						getMethodName = "get" + baseMethodName;
						
						var oldget = obj[getMethodName];
						obj[getMethodName] = function() { 
							if(undefined !== oldget) {
								oldget.apply(obj, []);	
							}
							return obj[propertyName];
						};
						//console.log("%s: %o", "get meth "+getMethodName+":", obj);
							
						// If the value is a complex add appropriate builder methods
						//console.log("%s: %o", "Seeing if we should add methods for builder in "+parentKey+"["+i+"]:", newBuilder);
						if(newBuilder !== null) {
							//console.log("%s: %o", "YES builder seeing if array in "+parentKey+"["+i+"]:", newBuilder);
							if(newBuilder.base instanceof Array) {
								//console.log("%s: %o", "YES array in "+parentKey+"["+i+"]:", newBuilder.base);
								var pushMethodName = "push" + baseMethodName;
								//console.log("%s: %o", pushMethodName+" -+> obj :", obj);
								var oldpush = obj[pushMethodName];
								
								obj[pushMethodName] = function(value) {
									if(undefined !== oldpush) {
										oldpush.apply(obj, [value]);	
									} else {
										// Add to the array
										var newKey = obj[propertyName].length;
										obj[propertyName][newKey] = value;
									}
									
									// Now deal with putting it in the builder
									newBuilder.put(newKey, value, false);
									//console.log("%s: %o", "pushed value to "+newKey, value);
								};
							} else {
								//console.log("%s: %o", "NO NOT array in "+parentKey+"["+i+"]:", newBuilder.base);
								// define a special set method	
								setMethodName = "set" + baseMethodName;
								var oldset1 = obj[setMethodName];
								obj[setMethodName] = function(value) {
									// check if this field should be auto-locked
									if(obj.className && THIS.unlockFields[obj.className]) {
										if($.inArray(propertyName, THIS.unlockFields[obj.className]) == -1) {
											builder.get(propertyName).setLocked(true);
										}
									}
									
									if(undefined !== oldset1) {
										oldset1.apply(obj, [value]);	
									} else {
										obj[propertyName] = value;
									}
									// Now deal with puttin it in the builder
									builder.setValue(propertyName, value);
								};
							}
						} else {
							// Only add the set and get methods for simple values
							// Create set and get methods but 
							setMethodName = "set" + baseMethodName;
							setLockMethodName = "set" + baseMethodName+"Lock";
							getLockMethodName = "get" + baseMethodName+"Lock";
							
							//console.log("%s: %o", "No new builder "+setMethodName+":", obj);
							var oldset2 = obj[setMethodName];
							obj[setMethodName] = function(value) {
								//console.log(setMethodName+" called");
								// check if this field should be auto-locked
								if(obj.className && THIS.unlockFields[obj.className]) {
									if($.inArray(propertyName, THIS.unlockFields[obj.className]) == -1) {
										builder.get(propertyName).setLocked(true);
									}
								}
								
								if(undefined !== oldset2) {
									oldset2.apply(obj, [value]);	
								} else {
									obj[propertyName] = value;
								}
								builder.setValue(propertyName, value);
							};
							
							obj[setLockMethodName] = function(val) {
								builder.get(propertyName).setLocked(val);
							};
							obj[getLockMethodName] = function(val) {
								return builder.get(propertyName).getLocked();
							};
						}
					};
					createClosedFunctions(this, i, bMethodName, propValue, methods, newBuilder);
				}
			//}
		}
		
		return obj;
	};
	
	/**
	 * Since we build as we go we just need to return the base object.
	 * @return The base widget framework style object.
	 */
	this.build = function() { return this.base; };
	
	/**
	 * This is only valid for array based builders
	 */
	this.push = function(value) {
		return this.store(null, value, null);
	};
	
	this.put = function(key, value, locked) {
		return this.store(key, value, locked);
	};
	
	/**
	 * This is only valid for object (non-array) type builders.
	 *
	 * puts a value to this object.  This will replace the a value of the same key with the new 
	 * value (if it exists) or create a new value if it does not.
	 *
	 * @param key - the values key
	 * @param value - The value to set for this key.  This may be a regular value or an array.  If you want to add 
	 *				a value for a heiriarchical configuration see putObject.
	 * @param locked - If true the field is not editable at the dealer level, if false it is editable at dealer level
	 */
	this.store = function(key, value, locked) {
		var baseType, storageType, toStore, nb, dval, wrapperInstance, simpleGetMethod, baseProperty;
		
		if(typeof locked != "boolean") {
			locked = false;
		}
		
		storageType = (this.base instanceof Array) ? "array" : "object";
		if(storageType == "array") {
			key = this.base.length;
		}
		//console.log("%s: %o", "store value for key "+key+" storage type "+storageType+":", value);
	
		// Deal with storing the value in the base object
		toStore = null;
		if(typeof value == "object" && value !== null) {
			//console.log("%s: %o", "toStore is object in "+key+" value:", value);
			if( ! (value instanceof Cobalt.Core.WidgetPersistenceObjectBuilder)) {
				//console.log("%s: %o", "making new builder for key "+key+" and binding value:", value);
				baseType = (value instanceof Array) ? [] : {};
				nb = new Cobalt.Core.WidgetPersistenceObjectBuilder(baseType, null, null, THIS.unlockFields);
				nb.bindObject(value);
				value = nb;
			}
			
			if( storageType == "array" && ! (value.base instanceof Array)) {
				// If this is an array we are adding to set the @id property on this child if it is a regular object
				//console.log("%s: %o", "adding @id for key "+key+":", value);
				value.base["@id"] = key;
			}
			
			//console.log("%s: %o", "setting toStore for key "+key+" equal to value for :", value);
			toStore = value.build();
		} else {
			//console.log("%s: %o", "toStore is simple type in "+key+" value:", value);
			
			// Note this part is important.  If the value has a build method we need
			// to set the $ value to the referenece to its raw persistance object style data
			// TODO validate that it is in the right structure?
			dval = (value === null || value === undefined || undefined === value.build) ? value : value.build();
			toStore = { "$" : dval, "@lock" : locked };
		}
		this.base[key] = toStore;
		
		// Deal with storing the builder properties so we can easily work with the object
		wrapperInstance = null;
		simpleGetMethod = null;
		if(value instanceof Cobalt.Core.WidgetPersistenceObjectBuilder) {
			wrapperInstance = value; //new PersistantBuilderPropertyWrapper(this.base, key, value, this.properties[key]);
			simpleGetMethod = function() { return wrapperInstance; };
		} else {
			baseProperty = this.base[key];
			wrapperInstance = new PersistantValuePropertyWrapper(this.base, key);
			simpleGetMethod = function() { return baseProperty["$"];  };
		}
		
		this.properties[key] = wrapperInstance;
		
		// Define a method so that a consumer of this object can call a method named the name of
		// this property so you can do stuff like this:
		//
		// builder.put("foo", "value", false);
		// assertEquals("value", builder.foo());
		//
		// because there is a danger of a property being named the name of a critical local method
		// and overwriting it causeing a major behavioural anomaly we will make sure that if the name
		// of the property is one of these reserved method names we do NOT create the method for it
		if( ! key+"".match(/(bindObject)|(build)|(put)|(get)|(setup)|(setValue)|(getValue)/)) {
			this[key] = simpleGetMethod;
		}
		
		return this;
	};
	
	/** 
	 * Gets the builder property for this key
	 */
	this.get = function(key) {
		return (undefined === this.properties[key]) ? null : this.properties[key];
	};
	
	/**
	 * Gets the value for the builder property of this key
	 */
	this.getValue = function(key) {
		var property = this.get(key);
		return (property !== null) ? property.getValue() : null;
	};
	
	/**
	 * Sets just the value for the builder property of this key
	 */
	this.setValue = function(key, value) {
		var property = this.get(key);
		if(property !== null) {
			//console.log("%s: %o", "builder.setValue for key "+key+" value "+value+" in:", property);
			property.setValue(key, value);
		} else {
			// If there is not a property for this then we should make one
			//console.log("%s: %o", "builder.setValue (put) for key "+key+" value "+value+" in:", property);
			this.put(key, value, false);
		}
		return this;
	};
	
	/**
	 * Sets up this builder with all the properties fronm an existing base object in the widget 
	 * framework JSON style format.
	 * 
	 * @param base - This is the base object coming from the widget framework.  This will 
	 *               be parsed into a builder prepopulated with the values from the base
	 *               if you want to build a structure from scratch pass in an empty object.
	 */
	this.setup = function() {
		var key, value, builder;
		for(key in this.base) {
			//if(this.base.hasOwnProperty(key)) {
				if( ! key.match(/^[\@]/)) { // make sure it does not start with an @
					// Define builder properties for these values
					value = this.base[key];
					//console.log("%s: %o", "Reading value for key "+key+" of "+value+" in:", this.base);
					
					// If this is not a flat value we need to wrap it in a builder
					// so that we can access the valued through a builder.
					if(typeof value == "object") {
						// See if this is a complex type or a simple type.
						if(value instanceof Array || undefined === value["@lock"]) {
							// This is a complex type
							//console.log("%s: %o", "~> Adding value for key "+parentKey+"."+key+" as builder:", value);
							builder = new Cobalt.Core.WidgetPersistenceObjectBuilder(value, this.base, key, THIS.unlockFields);
							this.properties[key] = builder; //new PersistantBuilderPropertyWrapper(this.base, key, builder, this.properties[key]);
						} else {
							// This is a simple type
							//console.log("%s: %o", "-> Adding value for key "+parentKey+"."+key+" as simple type:", value);
							this.properties[key] = new PersistantValuePropertyWrapper(this.base, key);
						}
					//} else {
					//	console.log("%s: %o", "Ignoring value for key "+key+" value is not an object:", value);
					}
				}
			//}
		}
		return this;
	};
	
	// If we have a base object then we should setup the builder with this base
	if(baseObject !== undefined) {
		this.setup();
	}
	
	return this;
};

/**
 * Define static newBuilder method.  this is so you can do stuff like this:
 * 
 * var builder = new Cobalt.Core.WidgetPersistenceObjectBuilder()
 *			.put("key1", "value", false)
 *			.put("key2", Cobalt.Core.WidgetPersistenceObjectBuilder.newBuilder()
 *								.put("key2_1", "value", false)
 *								.put("key2_2"), "value", false)
 *							, false)
 *			.put("key3", Cobalt.Core.WidgetPersistenceObjectBuilder.newBuilder()
 *								.put("key3_1", "value", false)
 *								.put("key4_2"), "value", false)
 *							, false)
 *			.put("key4", "value", false);
 */					
Cobalt.Core.WidgetPersistenceObjectBuilder.newBuilder = function() {
	return new Cobalt.Core.WidgetPersistenceObjectBuilder();
};

