# Source: documents/schema.js

    "use strict";
    /**
     * @fileOverview Enables a schema and validation feature set to your document or other object.
     * @module documents/schema
     * @requires base
     * @requires jjv
     * @require lodash
     */
    var sys = require( "lodash" );
    var Validator = require( "jjv" );
    var Base = require( "../base" );
    /**
     * The validator mixin provides access to the features of the JSON validation system
     * @exports documents/schema
     * @mixin
     */
    var Schema = Base.compose( [Base], /** @lends documents/schema# */{
    	constructor : function () {
    		/**
    		 * The schema that defines the validation rules. This should probably be defined at the prototype for each
    		 * object or model classification. It can be an anonymous schema defined right here, or this can be
    		 * registered schema names to use, or just a single name
    		 *
    		 * @type {object}
    		 * @memberOf documents/schema#
    		 * @name schema
    		 */

    		/**
    		 * If you want to register multiple schemas, use this property instead
    		 *
    		 * @type {object}
    		 * @memberOf documents/schema#
    		 * @name schemas
    		 */

    		/**
    		 * The validation environment
    		 * @private
    		 * @type {jjv}
    		 */
    		var env = new Validator();

    		/**
    		 * The default name of the scheman when you use anonymous schemas. You can define this at the prototype for classified
    		 * schemas. The can also
    		 *
    		 * @type {string|function():{string}}
    		 * @memberOf documents/schema#
    		 * @name _defaultSchemaName
    		 */
    		this._defaultSchemaName = sys.result( this, "_defaultSchemaName" ) || sys.uniqueId( "schema" );

    		/**
    		 * The options to pass to the validator when it runs
    		 * @type {object|function():{object}}
    		 * @name validationOptions
    		 * @memberOf documents/schema#
    		 */
    		this.validationOptions = sys.defaults( {}, sys.result( this, 'validationOptions' ), {checkRequired : true} );

    		/**
    		 * Validate an object against the schema
    		 * @returns {object?}
    		 * @method
    		 * @name validate
    		 * @memberOf documents/schema#
    		 * @param {object=} record The record to validate
    		 * @param {string|object=} schemaName The name of a previously registered schema
    		 * @param {object=} options Options to pass to the validator
    		 * @example
    		 * // This supports these signatures:
    		 *
    		 * instance.validate(record, schemaName, options);
    		 *
    		 *
    		 * instance.validate(); // this, this._defaultSchemaName, this.validationOptions
    		 * instance.validate(record); // record, this._defaultSchemaName, this.validationOptions
    		 * instance.validate(schemaName); //this, schemaName, this.validationOptions
    		 * instance.validate(record, schemaName); //record, schemaName, this.validationOptions
    		 * instance.validate(schemaName, options); //this, schemaName, this.validationOptions
    		 */
    		this.validate = function ( record, schemaName, options ) {
    			if ( arguments.length === 0 ) {
    				record = this;
    				schemaName = this._defaultSchemaName;
    				options = this.validationOptions;
    			} else {
    				if ( sys.isString( record ) ) {
    					schemaName = record;
    					record = this;
    				}
    				if ( sys.isEmpty( options ) ) {
    					options = this.validationOptions;
    				}
    			}

    			return env.validate( schemaName, record, options );
    		};

    		/**
    		 * Initialize the schema collection by registering the with the handler. You can call this at any time and as often as you like. It will be called once
    		 * by the constructor on any instance schemas
    		 * @method
    		 * @name registerSchemas
    		 * @memberOf documents/schema#
    		 * @param {hash} schemas A hash of schemas where the key is the name of the schema
    		 */
    		this.registerSchemas = function ( schemas ) {
    			var schema = sys.result( this, "schema" );
    			var schemas = schemas || sys.result( this, "schemas" );
    			if ( !sys.isEmpty( schema ) ) {
    				env.addSchema( this._defaultSchemaName, schema );
    			}
    			if ( !sys.isEmpty( schemas ) ) {
    				sys.each( schemas, function ( val, key ) {
    					env.addSchema( val, key );
    				} );
    			}
    		};

    		/**
    		 * Extracts only the elements of the object that are defined in the schema
    		 * @memberOf documents/schema#
    		 * @name extract
    		 * @param {object=} record The record to extract from
    		 * @param {string=} schema The name of the schema to attach
    		 * @method
    		 */
    		this.extract = function ( record, schema ) {
    			if ( arguments.length === 0 ) {
    				record = this;
    				schema = this._defaultSchemaName;
    			}
    			if ( sys.isString( record ) ) {
    				schema = record;
    				record = this;
    			}
    		};

    		/**
    		 * Create a type to be used in your schemas to define new validators
    		 * @memberOf documents/schema#
    		 * @name addType
    		 * @method
    		 * @param {string} name The name of the type
    		 * @param {function(object)} operation What to do with the type.
    		 * @param {object} operation.value The value to validation
    		 * @returns {boolean}
    		 */
    		this.addType = env.addType;

    		/**
    		 * It is also possible to add support for additional string formats through the addFormat function.
    		 * @memberOf documents/schema#
    		 * @name addFormat
    		 * @method
    		 * @param {string} name The name of the formatter
    		 * @param {function(object)} formatter How to format it
    		 * @param {object} formatter.value The value to format
    		 * @returns {boolean}
    		 */
    		this.addFormat = env.addFormat;

    		/**
    		 * It is possible to add support for custom checks (i.e., minItems, maxItems, minLength, maxLength, etc.) through the addCheck function
    		 * @memberOf documents/schema#
    		 * @name addCheck
    		 * @method
    		 * @param {string} name The name of the check
    		 * @param {function(...object)} formatter Perform the check
    		 * @param {object} formatter.value The value to check followed by any parameters from the schema
    		 * @returns {boolean}
    		 */
    		this.addCheck = env.addCheck;

    		/**
    		 * Custom coercion rules
    		 *
    		 * @memberOf documents/schema#
    		 * @name addTypeCoercion
    		 * @method
    		 * @param {string} name The name of the coercion
    		 * @param {function(object)} coercer Perform the coercion
    		 * @param {object} coercer.value The value to coerce
    		 * @returns {boolean}
    		 */
    		this.addTypeCoercion = env.addTypeCoercion;

    		/**
    		 * Get a registered schema by name
    		 * @param {string=} schemaName
    		 * @returns {object?}
    		 * @memberOf documents/schema#
    		 * @name getSchema
    		 * @method
    		 */
    		this.getSchema = function ( schemaName ) {
    			if ( sys.isEmpty( schemaName ) || !sys.isString() ) {
    				schemaName = this._defaultSchemaName;
    			}
    			return env.schema[schemaName];
    		}
    	},
    	/**
    	 * This method will create a new object that contains only the fields and no methods or other artifacts. This is useful
    	 * for creating objects to pass over the wire or save in a table. This is not deeply copied, so changes made to the
    	 * extracted object will be represented in this class for reference objects.
    	 *
    	 * @param {string=} schema The schema name to use
    	 * @param {object=} src The object to extract fields from
    	 * @return {object} Data-only version of the class instance.
    	 */
    	extract     : function ( schemaName, src ) {
    		if ( sys.isObject( schemaName ) ) {
    			src = schema;
    			schemaName = this._defaultSchemaName;
    		}

    		if ( sys.isEmpty( src ) ) {
    			src = this;
    		}

    		if ( sys.isFunction( src.toJSON ) ) {
    			src = src.toJSON();
    		}
    		var schema = this.getSchema( schemaName ) || {};
    		var newobj = {};
    		sys.each( schema.properties, function ( prop, propname ) {
    			if ( prop.properties && !sys.isUndefined( src[ propname ] ) ) {
    				newobj[ propname ] = this.extract( prop, src[propname] );
    			} else if ( !sys.isUndefined( src[ propname ] ) ) {
    				newobj[ propname ] = src[ propname ];
    			}
    		}, this );

    		return newobj;
    	},
    	/**
    	 * Builds a default document based on the schema. What this does is create a document from schema and for each property
    	 * that has a default value or is required, the resultant object will contain that property. It is useful for extending
    	 * values from some source that may be incomplete, like options or some such.
    	 * @param {json-schema} schema A schema to use to create the default document
    	 * @returns {object?}
    	 * @name defaultDoc
    	 * @memberOf documents/schema#
    	 * @method
    	 */
    	defaultDoc  : function ( schemaName ) {
    		if ( sys.isEmpty( schemaName ) ) {
    			schemaName = this._defaultSchemaName;
    		}
    		var newdoc = {};
    		var schema;

    		if ( sys.isObject( schemaName ) ) {
    			schema = schemaName;
    		} else {
    			schema = this.getSchema( schemaName ) || {};
    		}
    		sys.each( schema.properties, function ( val, key ) {

    			var def = val[ "default" ]; // keyword and all that
    			if ( val.type === "object" && !sys.isEmpty( val.properties ) ) {
    				newdoc[ key ] = this.defaultDoc( val );
    			} else {
    				if ( sys.isFunction( def ) || sys.isBoolean( def ) || sys.isNumber( def ) || !sys.isEmpty( def ) ) {

    					if ( sys.isFunction( def ) ) {
    						newdoc[ key ] = def( schema );
    					} else {
    						newdoc[ key ] = def;
    					}
    				} else if ( val.required ) {
    					if ( val.type === 'string' ) {
    						newdoc[ key ] = null;
    					} else if ( val.type === 'object' ) {
    						newdoc[ key ] = {};
    					} else if ( val.type === 'array' ) {
    						newdoc[ key ] = [];
    					} else {
    						newdoc[ key ] = null;
    					}
    				}
    			}
    		}, this );

    		return newdoc;
    	}
    } );

    module.exports = Schema;