text: (function() {
     var inputEx = YAHOO.inputEx, lang = YAHOO.lang;
   
  /**
   * Namespace containing utility functions for conversion between inputEx JSON format and JSON Schema
   *
   * based on "Json Schema Proposal Working Draft":
   * http://groups.google.com/group/json-schema/web/json-schema-proposal-working-draft
   * The proposal is still under discussion and the implementation is very minimalist.
   *
   *
   * TODO:
   *    - we should provide a lot of json schema examples and instances that should/should not validate
   *    - use the $ref (async calls => provide callbacks to methods)
   *    - Inheritance
   *
   * Limitations:
   *    - ??? Please do not trust inputEx: the getValue may return a value which do NOT validate the schema (provide an example ?)
   *    - no tuple typing for arrays
   *    - no "Union type definition"
   *
   * @class JsonSchema
   * @static
   * @namespace inputEx
   */
  inputEx.JsonSchema = {
     
     /**
      * Convert the inputEx JSON fields to a JSON schema
      */
     inputExToSchema: function(inputExJson) {
        // TODO :P
     }
  
  };
  
  
  /**
   * 
   * @class Builder
   * @namespace inputEx.JsonSchema
   */
  inputEx.JsonSchema.Builder = function(options) {
  	
  	var options = options || {};
  	this.options  = options; 
  	
  	/**
  	 * specify how other schema properties are mapped to inputParam properties
  	 */
  	this.schemaToParamMap = options.schemaToParamMap || {
  		'title':'label',
  		'description':'description',
  		'_inputex':null	// null value means copy child key/value pairs into inputParams directly
  	};
  	
  	/**
  	 * @property referenceResolver
  	 */
  	this.referenceResolver = options.referenceResolver || null;
  	
  	/**
  	 * options to be applied to inputParams unless already specified
  	 * @property defaultOptions
  	 */
  	this.defaultOptions = options.defaultOptions || {};	
  	
  	/**
  	 * key is reference, value is schema
  	 * @property schemaIdentifierMap
  	 */
  	this.schemaIdentifierMap = options.schemaIdentifierMap || {};
  };
  
  inputEx.JsonSchema.Builder.prototype = {
     
     /** 
   	 * return a schema based on the reference value
   	 * default is to look up in map
   	 * @method defaultReferenceResolver
      */
  	defaultReferenceResolver:function(reference) {
  		return this.schemaIdentifierMap[reference] || null;
  	},
  	
  	/**
  	 * Convert a JSON schema to inputEx JSON
  	 * @method schemaToInputEx
  	 * @param {JSONSchema} p
  	 */
  	schemaToInputEx:function(p, propertyName) {
  	
  	   var fieldDef = {inputParams: { label: propertyName, name: propertyName} };
  	   var schemaMap = this.schemaToParamMap;
      	var referencedSchema = p["$ref"]; 
  	    
  	    if(referencedSchema){
  	    	var new_schema = null;
  	    	if(this.referenceResolver) {
  		       new_schema = this.referenceResolver(referencedSchema);
  		    }
  	    	if(new_schema === null) {
  	    		new_schema = this.defaultReferenceResolver(referencedSchema);
  	    	}
  	    	if(new_schema === null) {
  	    		throw "Schema for property :"+propertyName+" $references "+referencedSchema+', not found';
  	    	}
  	    	// copy options into new schema, for example we can overide presentation
  	    	// of a defined schema depending on where it is used
  	    	new_schema = lang.merge(new_schema);	// copy new_schema
  	    	
  	    	for(var pk in p) {
  	    		if(p.hasOwnProperty(pk) && lang.isUndefined(new_schema[pk]) && pk != '$ref') {
  	    			new_schema[pk] = p[pk];
  	    		}
  	    	}
  	    	p = new_schema;
  	    }
  
  	    if(!p.optional) {
  	    	fieldDef.inputParams.required = true;
  	    }
  
  	    for(var key in schemaMap) {
  	        if(schemaMap.hasOwnProperty(key)) {
  	      	  var paramName = schemaMap[key]; 
  	      	  var v = p[key];
  	      	  if(!lang.isUndefined(v)) {
  	      		  if(paramName === null) {
  	      			  // copy / merge values from v directly into inputParams
  	      			  if(lang.isObject(v)) {
  	      				  // v must be an object, copy key/value pairs into inputParams
  	      				  for(var vkey in v) {
  	      					  if(v.hasOwnProperty(vkey)) {
  	      						  fieldDef.inputParams[vkey] = v[vkey];
  	      					  }
  	      				  }
  	      			  }
  	      		  } else {
  	      			  fieldDef.inputParams[paramName] = v;
  	      		  }
  	      	  }
  	        }
  	    }
  	    if(p.type) {	       
  	       var type = p.type;
  	       
  	       // If type is a "Union type definition", we'll use the first type for the field
  	       // "array" <=>  [] <=> ["any"]
  	       if(lang.isArray(type)) {
  	          if(type.length === 0 || (type.length == 1 && type[0] == "any") ) {
  	             type = "array";
  	          }
  	          else {
  	             type = type[0];
  	          }
  	       }
  	       else if(lang.isObject(type) ) {
  	          // What do we do ??
  	          //console.log("type is an object !!");
  	       }
  	       
  	       fieldDef.type = type;
  	       
  	       // default value
  	       if( !lang.isUndefined(p["default"]) ) {
  	          fieldDef.inputParams.value = p["default"];
  	       }
  	    
  	       if(type == "array" ) {
  	          fieldDef.type = "list";
  	          if(lang.isObject(p.items) && !lang.isArray(p.items)) {
  	        	  // when items is an object, it's a schema that describes each item in the list
  	        	  fieldDef.inputParams.elementType = this.schemaToInputEx(p.items, propertyName);
  	          }
  	       }
  	       else if(type == "object" ) {
  	          fieldDef.type = "group";
  	          if(p.title && lang.isUndefined(fieldDef.inputParams.legend)) {
  	        	  fieldDef.inputParams.legend = p.title; 
  	          }
  	          //fieldDef.inputParams = this.schemaToInputEx(p, propertyName);
  	          //fieldDef.inputParams = this._parseSchemaProperty(p, propertyName);
  	          var fields = [];
  	          if(propertyName) {
  	        	  fieldDef.inputParams.name = propertyName;
  	          }
  	
  	          for(var key in p.properties) {
  	             if(p.properties.hasOwnProperty(key)) {
  	                fields.push( this.schemaToInputEx(p.properties[key], key) );
  	             }
  	          }
  	
  	          fieldDef.inputParams.fields = fields;
  	          
  	       }
  	       else if(type == "string" && p["enum"] ) {
  	          fieldDef.type = "select";
  	          
  	          if(p.options) {
    	             fieldDef.inputParams.selectOptions = [];
       	          fieldDef.inputParams.selectValues = [];
  	             for(var i = 0 ; i < p.options.length ; i++) {
  	                var o = p.options[i];
  	                fieldDef.inputParams.selectOptions[i] = o.label;
  	                fieldDef.inputParams.selectValues[i] = o.value;
  	             }
               }
               else {
      	          fieldDef.inputParams.selectValues = p["enum"];
               }
  	       }
  	       else if(type == "string") {
  	    	  if(!lang.isUndefined(p.pattern) && lang.isUndefined(fieldDef.inputParams.regexp)) {
  	    		  if(lang.isString(p.pattern)) {
  	    			  fieldDef.inputParams.regexp = new RegExp(p.pattern);
  	    		  } else {
  	    			  fieldDef.inputParams.regexp = p.pattern;
  	    		  }
  	    	  }
  	    	  if(!lang.isUndefined(p.maxLength) && lang.isUndefined(fieldDef.inputParams.maxLength)) {
  	    		  fieldDef.inputParams.maxLength = p.maxLength; 
  	    	  }
  
  	    	  if(!lang.isUndefined(p.minLength) && lang.isUndefined(fieldDef.inputParams.minLength)) {
  	    		  fieldDef.inputParams.minLength = p.minLength; 
  	    	  }
  
  	    	  if(!lang.isUndefined(p.readonly) && lang.isUndefined(fieldDef.inputParams.readonly)) {
  	    		  fieldDef.inputParams.readonly = p.readonly; 
  	    	  }
  
             // According to http://groups.google.com/group/json-schema/web/json-schema-possible-formats
  	          if( p.format ) {
  	             if(p.format == "html") {
  	                fieldDef.type = "html";
  	             } else if(p.format == "date") {
  	                fieldDef.type = "date";
  	                fieldDef.inputParams.tooltipIcon = true;
  	             } else if(p.format == 'url') {
  	            	 fieldDef.type = 'url';
  	             } else if(p.format == 'email') {
  	            	 fieldDef.type = 'email';
  	             } else if(p.format == 'text') {
  	            	 fieldDef.type = 'text';
  	             } else if(p.format == 'time') {
  	                fieldDef.type = 'time';
  	             } else if(p.format == 'ip-address') {
      	             fieldDef.type = 'IPv4';
      	          } else if(p.format == 'color') {
      	             fieldDef.type = 'color';
      	          }
  	          }
  	       }
  	    }
  	    
  	    // Add the defaultOptions
  	    for(var kk in this.defaultOptions) {
  	        if(this.defaultOptions.hasOwnProperty(kk) && lang.isUndefined(fieldDef.inputParams[kk])) {
  	        	fieldDef.inputParams[kk] = this.defaultOptions[kk]; 
  	        }	    	
  	    }
  	    return fieldDef;
  	},
  
     /**
      * Create an inputEx Json form definition from a json schema instance object
      * Respect the "Self-Defined Schema Convention"
      * @method formFromInstance
      */
     formFromInstance: function(instanceObject) {
        if(!instanceObject || !instanceObject["$schema"]) {
           throw new Error("Invalid json schema instance object. Object must have a '$schema' property.");
        }
        
        var formDef = this.schemaToInputEx(instanceObject["$schema"]);
        
        // Set the default value of each property to the instance value
        for(var i = 0 ; i < formDef.fields.length ; i++) {
           var fieldName = formDef.fields[i].inputParams.name;
           formDef.fields[i].inputParams.value = instanceObject[fieldName];
        }
        
        return formDef;
     }
     
  };
  
  
  
  
  })();