text: "use strict";
  
  var FormatValidators = require("./FormatValidators"),
      JsonValidation   = require("./JsonValidation"),
      Report           = require("./Report"),
      Utils            = require("./Utils");
  
  var SchemaValidators = {
      $ref: function (report, schema) {
          // http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
          // http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03
          if (typeof schema.$ref !== "string") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["$ref", "string"]);
          }
      },
      $schema: function (report, schema) {
          // http://json-schema.org/latest/json-schema-core.html#rfc.section.6
          if (typeof schema.$schema !== "string") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["$schema", "string"]);
          }
      },
      multipleOf: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.1.1.1
          if (typeof schema.multipleOf !== "number") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["multipleOf", "number"]);
          } else if (schema.multipleOf <= 0) {
              report.addError("KEYWORD_MUST_BE", ["multipleOf", "strictly greater than 0"]);
          }
      },
      maximum: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.1.2.1
          if (typeof schema.maximum !== "number") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["maximum", "number"]);
          }
      },
      exclusiveMaximum: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.1.2.1
          if (typeof schema.exclusiveMaximum !== "boolean") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["exclusiveMaximum", "boolean"]);
          } else if (schema.maximum === undefined) {
              report.addError("KEYWORD_DEPENDENCY", ["exclusiveMaximum", "maximum"]);
          }
      },
      minimum: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.1.3.1
          if (typeof schema.minimum !== "number") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["minimum", "number"]);
          }
      },
      exclusiveMinimum: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.1.3.1
          if (typeof schema.exclusiveMinimum !== "boolean") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["exclusiveMinimum", "boolean"]);
          } else if (schema.minimum === undefined) {
              report.addError("KEYWORD_DEPENDENCY", ["exclusiveMinimum", "minimum"]);
          }
      },
      maxLength: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.2.1.1
          if (Utils.whatIs(schema.maxLength) !== "integer") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["maxLength", "integer"]);
          } else if (schema.maxLength < 0) {
              report.addError("KEYWORD_MUST_BE", ["maxLength", "greater than, or equal to 0"]);
          }
      },
      minLength: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.2.2.1
          if (Utils.whatIs(schema.minLength) !== "integer") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["minLength", "integer"]);
          } else if (schema.minLength < 0) {
              report.addError("KEYWORD_MUST_BE", ["minLength", "greater than, or equal to 0"]);
          }
      },
      pattern: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.2.3.1
          if (typeof schema.pattern !== "string") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["pattern", "string"]);
          } else {
              try {
                  RegExp(schema.pattern);
              } catch (e) {
                  report.addError("KEYWORD_PATTERN", ["pattern", schema.pattern]);
              }
          }
      },
      additionalItems: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.1.1
          var type = Utils.whatIs(schema.additionalItems);
          if (type !== "boolean" && type !== "object") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["additionalItems", ["boolean", "object"]]);
          } else if (type === "object") {
              report.path.push("additionalItems");
              exports.validateSchema.call(this, report, schema.additionalItems);
              report.path.pop();
          }
      },
      items: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.1.1
          var type = Utils.whatIs(schema.items);
  
          if (type === "object") {
              report.path.push("items");
              exports.validateSchema.call(this, report, schema.items);
              report.path.pop();
          } else if (type === "array") {
              var idx = schema.items.length;
              while (idx--) {
                  report.path.push("items");
                  report.path.push(idx.toString());
                  exports.validateSchema.call(this, report, schema.items[idx]);
                  report.path.pop();
                  report.path.pop();
              }
          } else {
              report.addError("KEYWORD_TYPE_EXPECTED", ["items", ["array", "object"]]);
          }
  
          // custom - strict mode
          if (this.options.forceAdditional === true && schema.additionalItems === undefined && Array.isArray(schema.items)) {
              report.addError("KEYWORD_UNDEFINED_STRICT", ["additionalItems"]);
          }
          // custome - assume defined false mode
          if (this.options.assumeAdditional && schema.additionalItems === undefined && Array.isArray(schema.items)) {
              schema.additionalItems = false;
          }
      },
      maxItems: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.2.1
          if (typeof schema.maxItems !== "number") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["maxItems", "integer"]);
          } else if (schema.maxItems < 0) {
              report.addError("KEYWORD_MUST_BE", ["maxItems", "greater than, or equal to 0"]);
          }
      },
      minItems: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.3.1
          if (Utils.whatIs(schema.minItems) !== "integer") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["minItems", "integer"]);
          } else if (schema.minItems < 0) {
              report.addError("KEYWORD_MUST_BE", ["minItems", "greater than, or equal to 0"]);
          }
      },
      uniqueItems: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.4.1
          if (typeof schema.uniqueItems !== "boolean") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["uniqueItems", "boolean"]);
          }
      },
      maxProperties: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.1.1
          if (Utils.whatIs(schema.maxProperties) !== "integer") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["maxProperties", "integer"]);
          } else if (schema.maxProperties < 0) {
              report.addError("KEYWORD_MUST_BE", ["maxProperties", "greater than, or equal to 0"]);
          }
      },
      minProperties: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.2.1
          if (Utils.whatIs(schema.minProperties) !== "integer") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["minProperties", "integer"]);
          } else if (schema.minProperties < 0) {
              report.addError("KEYWORD_MUST_BE", ["minProperties", "greater than, or equal to 0"]);
          }
      },
      required: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.3.1
          if (Utils.whatIs(schema.required) !== "array") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["required", "array"]);
          } else if (schema.required.length === 0) {
              report.addError("KEYWORD_MUST_BE", ["required", "an array with at least one element"]);
          } else {
              var idx = schema.required.length;
              while (idx--) {
                  if (typeof schema.required[idx] !== "string") {
                      report.addError("KEYWORD_VALUE_TYPE", ["required", "string"]);
                  }
              }
              if (Utils.isUniqueArray(schema.required) === false) {
                  report.addError("KEYWORD_MUST_BE", ["required", "an array with unique items"]);
              }
          }
      },
      additionalProperties: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.4.1
          var type = Utils.whatIs(schema.additionalProperties);
          if (type !== "boolean" && type !== "object") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["additionalProperties", ["boolean", "object"]]);
          } else if (type === "object") {
              report.path.push("additionalProperties");
              exports.validateSchema.call(this, report, schema.additionalProperties);
              report.path.pop();
          }
      },
      properties: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.4.1
          if (Utils.whatIs(schema.properties) !== "object") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["properties", "object"]);
              return;
          }
  
          var keys = Object.keys(schema.properties),
              idx = keys.length;
          while (idx--) {
              var key = keys[idx],
                  val = schema.properties[key];
              report.path.push("properties");
              report.path.push(key);
              exports.validateSchema.call(this, report, val);
              report.path.pop();
              report.path.pop();
          }
  
          // custom - strict mode
          if (this.options.forceAdditional === true && schema.additionalProperties === undefined) {
              report.addError("KEYWORD_UNDEFINED_STRICT", ["additionalProperties"]);
          }
          // custome - assume defined false mode
          if (this.options.assumeAdditional && schema.additionalProperties === undefined) {
              schema.additionalProperties = false;
          }
          // custom - forceProperties
          if (this.options.forceProperties === true && keys.length === 0) {
              report.addError("CUSTOM_MODE_FORCE_PROPERTIES", ["properties"]);
          }
      },
      patternProperties: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.4.1
          if (Utils.whatIs(schema.patternProperties) !== "object") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["patternProperties", "object"]);
              return;
          }
  
          var keys = Object.keys(schema.patternProperties),
              idx = keys.length;
          while (idx--) {
              var key = keys[idx],
                  val = schema.patternProperties[key];
              try {
                  RegExp(key);
              } catch (e) {
                  report.addError("KEYWORD_PATTERN", ["patternProperties", key]);
              }
              report.path.push("patternProperties");
              report.path.push(key.toString());
              exports.validateSchema.call(this, report, val);
              report.path.pop();
              report.path.pop();
          }
  
          // custom - forceProperties
          if (this.options.forceProperties === true && keys.length === 0) {
              report.addError("CUSTOM_MODE_FORCE_PROPERTIES", ["patternProperties"]);
          }
      },
      dependencies: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.5.1
          if (Utils.whatIs(schema.dependencies) !== "object") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["dependencies", "object"]);
          } else {
              var keys = Object.keys(schema.dependencies),
                  idx = keys.length;
              while (idx--) {
                  var schemaKey = keys[idx],
                      schemaDependency = schema.dependencies[schemaKey],
                      type = Utils.whatIs(schemaDependency);
  
                  if (type === "object") {
                      report.path.push("dependencies");
                      report.path.push(schemaKey);
                      exports.validateSchema.call(this, report, schemaDependency);
                      report.path.pop();
                      report.path.pop();
                  } else if (type === "array") {
                      var idx2 = schemaDependency.length;
                      if (idx2 === 0) {
                          report.addError("KEYWORD_MUST_BE", ["dependencies", "not empty array"]);
                      }
                      while (idx2--) {
                          if (typeof schemaDependency[idx2] !== "string") {
                              report.addError("KEYWORD_VALUE_TYPE", ["dependensices", "string"]);
                          }
                      }
                      if (Utils.isUniqueArray(schemaDependency) === false) {
                          report.addError("KEYWORD_MUST_BE", ["dependencies", "an array with unique items"]);
                      }
                  } else {
                      report.addError("KEYWORD_VALUE_TYPE", ["dependencies", "object or array"]);
                  }
              }
          }
      },
      enum: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.1.1
          if (Array.isArray(schema.enum) === false) {
              report.addError("KEYWORD_TYPE_EXPECTED", ["enum", "array"]);
          } else if (schema.enum.length === 0) {
              report.addError("KEYWORD_MUST_BE", ["enum", "an array with at least one element"]);
          } else if (Utils.isUniqueArray(schema.enum) === false) {
              report.addError("KEYWORD_MUST_BE", ["enum", "an array with unique elements"]);
          }
      },
      type: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.2.1
          var primitiveTypes = ["array", "boolean", "integer", "number", "null", "object", "string"],
              primitiveTypeStr = primitiveTypes.join(","),
              isArray = Array.isArray(schema.type);
  
          if (isArray) {
              var idx = schema.type.length;
              while (idx--) {
                  if (primitiveTypes.indexOf(schema.type[idx]) === -1) {
                      report.addError("KEYWORD_TYPE_EXPECTED", ["type", primitiveTypeStr]);
                  }
              }
              if (Utils.isUniqueArray(schema.type) === false) {
                  report.addError("KEYWORD_MUST_BE", ["type", "an object with unique properties"]);
              }
          } else if (typeof schema.type === "string") {
              if (primitiveTypes.indexOf(schema.type) === -1) {
                  report.addError("KEYWORD_TYPE_EXPECTED", ["type", primitiveTypeStr]);
              }
          } else {
              report.addError("KEYWORD_TYPE_EXPECTED", ["type", ["string", "array"]]);
          }
  
          if (this.options.noEmptyStrings === true) {
              if (schema.type === "string" || isArray && schema.type.indexOf("string") !== -1) {
                  if (schema.minLength === undefined &&
                      schema.enum === undefined &&
                      schema.format === undefined) {
  
                      schema.minLength = 1;
                  }
              }
          }
          if (this.options.noEmptyArrays === true) {
              if (schema.type === "array" || isArray && schema.type.indexOf("array") !== -1) {
                  if (schema.minItems === undefined) {
                      schema.minItems = 1;
                  }
              }
          }
          if (this.options.forceProperties === true) {
              if (schema.type === "object" || isArray && schema.type.indexOf("object") !== -1) {
                  if (schema.properties === undefined && schema.patternProperties === undefined) {
                      report.addError("KEYWORD_UNDEFINED_STRICT", ["properties"]);
                  }
              }
          }
          if (this.options.forceItems === true) {
              if (schema.type === "array" || isArray && schema.type.indexOf("array") !== -1) {
                  if (schema.items === undefined) {
                      report.addError("KEYWORD_UNDEFINED_STRICT", ["items"]);
                  }
              }
          }
          if (this.options.forceMinItems === true) {
              if (schema.type === "array" || isArray && schema.type.indexOf("array") !== -1) {
                  if (schema.minItems === undefined) {
                      report.addError("KEYWORD_UNDEFINED_STRICT", ["minItems"]);
                  }
              }
          }
          if (this.options.forceMaxItems === true) {
              if (schema.type === "array" || isArray && schema.type.indexOf("array") !== -1) {
                  if (schema.maxItems === undefined) {
                      report.addError("KEYWORD_UNDEFINED_STRICT", ["maxItems"]);
                  }
              }
          }
          if (this.options.forceMinLength === true) {
              if (schema.type === "string" || isArray && schema.type.indexOf("string") !== -1) {
                  if (schema.minLength === undefined &&
                      schema.format === undefined &&
                      schema.enum === undefined &&
                      schema.pattern === undefined) {
                      report.addError("KEYWORD_UNDEFINED_STRICT", ["minLength"]);
                  }
              }
          }
          if (this.options.forceMaxLength === true) {
              if (schema.type === "string" || isArray && schema.type.indexOf("string") !== -1) {
                  if (schema.maxLength === undefined &&
                      schema.format === undefined &&
                      schema.enum === undefined &&
                      schema.pattern === undefined) {
                      report.addError("KEYWORD_UNDEFINED_STRICT", ["maxLength"]);
                  }
              }
          }
      },
      allOf: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.3.1
          if (Array.isArray(schema.allOf) === false) {
              report.addError("KEYWORD_TYPE_EXPECTED", ["allOf", "array"]);
          } else if (schema.allOf.length === 0) {
              report.addError("KEYWORD_MUST_BE", ["allOf", "an array with at least one element"]);
          } else {
              var idx = schema.allOf.length;
              while (idx--) {
                  report.path.push("allOf");
                  report.path.push(idx.toString());
                  exports.validateSchema.call(this, report, schema.allOf[idx]);
                  report.path.pop();
                  report.path.pop();
              }
          }
      },
      anyOf: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.4.1
          if (Array.isArray(schema.anyOf) === false) {
              report.addError("KEYWORD_TYPE_EXPECTED", ["anyOf", "array"]);
          } else if (schema.anyOf.length === 0) {
              report.addError("KEYWORD_MUST_BE", ["anyOf", "an array with at least one element"]);
          } else {
              var idx = schema.anyOf.length;
              while (idx--) {
                  report.path.push("anyOf");
                  report.path.push(idx.toString());
                  exports.validateSchema.call(this, report, schema.anyOf[idx]);
                  report.path.pop();
                  report.path.pop();
              }
          }
      },
      oneOf: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.5.1
          if (Array.isArray(schema.oneOf) === false) {
              report.addError("KEYWORD_TYPE_EXPECTED", ["oneOf", "array"]);
          } else if (schema.oneOf.length === 0) {
              report.addError("KEYWORD_MUST_BE", ["oneOf", "an array with at least one element"]);
          } else {
              var idx = schema.oneOf.length;
              while (idx--) {
                  report.path.push("oneOf");
                  report.path.push(idx.toString());
                  exports.validateSchema.call(this, report, schema.oneOf[idx]);
                  report.path.pop();
                  report.path.pop();
              }
          }
      },
      not: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.6.1
          if (Utils.whatIs(schema.not) !== "object") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["not", "object"]);
          } else {
              report.path.push("not");
              exports.validateSchema.call(this, report, schema.not);
              report.path.pop();
          }
      },
      definitions: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.7.1
          if (Utils.whatIs(schema.definitions) !== "object") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["definitions", "object"]);
          } else {
              var keys = Object.keys(schema.definitions),
                  idx = keys.length;
              while (idx--) {
                  var key = keys[idx],
                      val = schema.definitions[key];
                  report.path.push("definitions");
                  report.path.push(key);
                  exports.validateSchema.call(this, report, val);
                  report.path.pop();
                  report.path.pop();
              }
          }
      },
      format: function (report, schema) {
          if (typeof schema.format !== "string") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["format", "string"]);
          } else {
              if (FormatValidators[schema.format] === undefined && this.options.ignoreUnknownFormats !== true) {
                  report.addError("UNKNOWN_FORMAT", [schema.format]);
              }
          }
      },
      id: function (report, schema) {
          // http://json-schema.org/latest/json-schema-core.html#rfc.section.7.2
          if (typeof schema.id !== "string") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["id", "string"]);
          }
      },
      title: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1
          if (typeof schema.title !== "string") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["title", "string"]);
          }
      },
      description: function (report, schema) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1
          if (typeof schema.description !== "string") {
              report.addError("KEYWORD_TYPE_EXPECTED", ["description", "string"]);
          }
      },
      "default": function (/* report, schema */) {
          // http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.2
          // There are no restrictions placed on the value of this keyword.
      }
  };
  
  var validateArrayOfSchemas = function (report, arr) {
      var idx = arr.length;
      while (idx--) {
          exports.validateSchema.call(this, report, arr[idx]);
      }
      return report.isValid();
  };
  
  exports.validateSchema = function (report, schema) {
  
      report.commonErrorMessage = "SCHEMA_VALIDATION_FAILED";
  
      // if schema is an array, assume it's an array of schemas
      if (Array.isArray(schema)) {
          return validateArrayOfSchemas.call(this, report, schema);
      }
  
      // do not revalidate schema that has already been validated once
      if (schema.__$validated) {
          return true;
      }
  
      // if $schema is present, this schema should validate against that $schema
      var hasParentSchema = schema.$schema && schema.id !== schema.$schema;
      if (hasParentSchema) {
          if (schema.__$schemaResolved && schema.__$schemaResolved !== schema) {
              var subReport = new Report(report);
              var valid = JsonValidation.validate.call(this, subReport, schema.__$schemaResolved, schema);
              if (valid === false) {
                  report.addError("PARENT_SCHEMA_VALIDATION_FAILED", null, subReport);
              }
          } else {
              if (this.options.ignoreUnresolvableReferences !== true) {
                  report.addError("REF_UNRESOLVED", [schema.$schema]);
              }
          }
      }
  
      if (this.options.noTypeless === true) {
          // issue #36 - inherit type to anyOf, oneOf, allOf if noTypeless is defined
          if (schema.type !== undefined) {
              var schemas = [];
              if (Array.isArray(schema.anyOf)) { schemas = schemas.concat(schema.anyOf); }
              if (Array.isArray(schema.oneOf)) { schemas = schemas.concat(schema.oneOf); }
              if (Array.isArray(schema.allOf)) { schemas = schemas.concat(schema.allOf); }
              schemas.forEach(function (sch) {
                  if (!sch.type) { sch.type = schema.type; }
              });
          }
          // end issue #36
          if (schema.enum === undefined &&
              schema.type === undefined &&
              schema.anyOf === undefined &&
              schema.oneOf === undefined &&
              schema.not === undefined &&
              schema.$ref === undefined) {
              report.addError("KEYWORD_UNDEFINED_STRICT", ["type"]);
          }
      }
  
      var keys = Object.keys(schema),
          idx = keys.length;
      while (idx--) {
          var key = keys[idx];
          if (key.indexOf("__") === 0) { continue; }
          if (SchemaValidators[key] !== undefined) {
              SchemaValidators[key].call(this, report, schema);
          } else if (!hasParentSchema) {
              if (this.options.noExtraKeywords === true) {
                  report.addError("KEYWORD_UNEXPECTED", [key]);
              }
          }
      }
  
      if (this.options.pedanticCheck === true) {
          if (schema.enum) {
              // break recursion
              var tmpSchema = Utils.clone(schema);
              delete tmpSchema.enum;
              delete tmpSchema.default;
  
              report.path.push("enum");
              idx = schema.enum.length;
              while (idx--) {
                  report.path.push(idx.toString());
                  JsonValidation.validate.call(this, report, tmpSchema, schema.enum[idx]);
                  report.path.pop();
              }
              report.path.pop();
          }
  
          if (schema.default) {
              report.path.push("default");
              JsonValidation.validate.call(this, report, schema, schema.default);
              report.path.pop();
          }
      }
  
      var isValid = report.isValid();
      if (isValid) {
          schema.__$validated = true;
      }
      return isValid;
  };
  