text: 'use strict';
  
  var uri = require('url');
  
  var ValidationError = exports.ValidationError = function ValidationError (message, instance, schema, propertyPath, name, argument) {
    if (propertyPath) {
      this.property = propertyPath;
    }
    if (message) {
      this.message = message;
    }
    if (schema) {
      if (schema.id) {
        this.schema = schema.id;
      } else {
        this.schema = schema;
      }
    }
    if (instance) {
      this.instance = instance;
    }
    this.name = name;
    this.argument = argument;
    this.stack = this.toString();
  };
  
  ValidationError.prototype.toString = function toString() {
    return this.property + ' ' + this.message;
  };
  
  var ValidatorResult = exports.ValidatorResult = function ValidatorResult(instance, schema, options, ctx) {
    this.instance = instance;
    this.schema = schema;
    this.propertyPath = ctx.propertyPath;
    this.errors = [];
    this.throwError = options && options.throwError;
    this.disableFormat = options && options.disableFormat === true;
  };
  
  ValidatorResult.prototype.addError = function addError(detail) {
    var err;
    if (typeof detail == 'string') {
      err = new ValidationError(detail, this.instance, this.schema, this.propertyPath);
    } else {
      if (!detail) throw new Error('Missing error detail');
      if (!detail.message) throw new Error('Missing error message');
      if (!detail.name) throw new Error('Missing validator type');
      err = new ValidationError(detail.message, this.instance, this.schema, this.propertyPath, detail.name, detail.argument);
    }
  
    if (this.throwError) {
      throw err;
    }
    this.errors.push(err);
    return err;
  };
  
  ValidatorResult.prototype.importErrors = function importErrors(res) {
    if (typeof res == 'string' || (res && res.validatorType)) {
      this.addError(res);
    } else if (res && res.errors) {
      Array.prototype.push.apply(this.errors, res.errors);
    }
  };
  
  function stringizer (v,i){
    return i+': '+v.toString()+'\n';
  }
  ValidatorResult.prototype.toString = function toString(res) {
    return this.errors.map(stringizer).join('');
  };
  
  Object.defineProperty(ValidatorResult.prototype, "valid", { get: function() {
    return !this.errors.length;
  } });
  
  /**
   * Describes a problem with a Schema which prevents validation of an instance
   * @name SchemaError
   * @constructor
   */
  var SchemaError = exports.SchemaError = function SchemaError (msg, schema) {
    this.message = msg;
    this.schema = schema;
    Error.call(this, msg);
    Error.captureStackTrace(this, SchemaError);
  };
  SchemaError.prototype = Object.create(Error.prototype,
    { constructor: {value: SchemaError, enumerable: false}
    , name: {value: 'SchemaError', enumerable: false}
    });
  
  var SchemaContext = exports.SchemaContext = function SchemaContext (schema, options, propertyPath, base, schemas) {
    this.schema = schema;
    this.options = options;
    this.propertyPath = propertyPath;
    this.base = base;
    this.schemas = schemas;
  };
  
  SchemaContext.prototype.resolve = function resolve (target) {
    return uri.resolve(this.base, target);
  };
  
  SchemaContext.prototype.makeChild = function makeChild(schema, propertyName){
    var propertyPath = (propertyName===undefined) ? this.propertyPath : this.propertyPath+makeSuffix(propertyName);
    var base = uri.resolve(this.base, schema.id||'');
    var ctx = new SchemaContext(schema, this.options, propertyPath, base, Object.create(this.schemas));
    if(schema.id && !ctx.schemas[base]){
      ctx.schemas[base] = schema;
    }
    return ctx;
  }
  
  var FORMAT_REGEXPS = exports.FORMAT_REGEXPS = {
    'date-time': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(3[01]|0[1-9]|[12][0-9])[tT ](2[0-4]|[01][0-9]):([0-5][0-9]):(60|[0-5][0-9])(\.\d+)?([zZ]|[+-]([0-5][0-9]):(60|[0-5][0-9]))$/,
    'date': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(3[01]|0[1-9]|[12][0-9])$/,
    'time': /^(2[0-4]|[01][0-9]):([0-5][0-9]):(60|[0-5][0-9])$/,
  
    'email': /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/,
    'ip-address': /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
    'ipv6': /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/,
    'uri': /^[a-zA-Z][a-zA-Z0-9+-.]*:[^\s]*$/,
  
    'color': /^(#?([0-9A-Fa-f]{3}){1,2}\b|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/,
  
    // hostname regex from: http://stackoverflow.com/a/1420225/5628
    'hostname': /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/,
    'host-name': /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/,
  
    'alpha': /^[a-zA-Z]+$/,
    'alphanumeric': /^[a-zA-Z0-9]+$/,
    'utc-millisec': function (input) {
      return (typeof input === 'string') && parseFloat(input) === parseInt(input, 10) && !isNaN(input);
    },
    'regex': function (input) {
      var result = true;
      try {
        new RegExp(input);
      } catch (e) {
        result = false;
      }
      return result;
    },
    'style': /\s*(.+?):\s*([^;]+);?/,
    'phone': /^\+(?:[0-9] ?){6,14}[0-9]$/
  };
  
  FORMAT_REGEXPS.regexp = FORMAT_REGEXPS.regex;
  FORMAT_REGEXPS.pattern = FORMAT_REGEXPS.regex;
  FORMAT_REGEXPS.ipv4 = FORMAT_REGEXPS['ip-address'];
  
  exports.isFormat = function isFormat (input, format, validator) {
    if (typeof input === 'string' && FORMAT_REGEXPS[format] !== undefined) {
      if (FORMAT_REGEXPS[format] instanceof RegExp) {
        return FORMAT_REGEXPS[format].test(input);
      }
      if (typeof FORMAT_REGEXPS[format] === 'function') {
        return FORMAT_REGEXPS[format](input);
      }
    } else if (validator && validator.customFormats &&
        typeof validator.customFormats[format] === 'function') {
      return validator.customFormats[format](input);
    }
    return true;
  };
  
  var makeSuffix = exports.makeSuffix = function makeSuffix (key) {
    key = key.toString();
    // This function could be capable of outputting valid a ECMAScript string, but the
    // resulting code for testing which form to use would be tens of thousands of characters long
    // That means this will use the name form for some illegal forms
    if (!key.match(/[.\s\[\]]/) && !key.match(/^[\d]/)) {
      return '.' + key;
    }
    if (key.match(/^\d+$/)) {
      return '[' + key + ']';
    }
    return '[' + JSON.stringify(key) + ']';
  };
  
  exports.deepCompareStrict = function deepCompareStrict (a, b) {
    if (typeof a !== typeof b) {
      return false;
    }
    if (Array.isArray(a)) {
      if (!Array.isArray(b)) {
        return false;
      }
      if (a.length !== b.length) {
        return false;
      }
      return a.every(function (v, i) {
        return deepCompareStrict(a[i], b[i]);
      });
    }
    if (typeof a === 'object') {
      if (!a || !b) {
        return a === b;
      }
      var aKeys = Object.keys(a);
      var bKeys = Object.keys(b);
      if (aKeys.length !== bKeys.length) {
        return false;
      }
      return aKeys.every(function (v) {
        return deepCompareStrict(a[v], b[v]);
      });
    }
    return a === b;
  };
  
  function deepMerger (target, dst, e, i) {
    if (typeof e === 'object') {
      dst[i] = deepMerge(target[i], e)
    } else {
      if (target.indexOf(e) === -1) {
        dst.push(e)
      }
    }
  }
  
  function copyist (src, dst, key) {
    dst[key] = src[key];
  }
  
  function copyistWithDeepMerge (target, src, dst, key) {
    if (typeof src[key] !== 'object' || !src[key]) {
      dst[key] = src[key];
    }
    else {
      if (!target[key]) {
        dst[key] = src[key];
      } else {
        dst[key] = deepMerge(target[key], src[key])
      }
    }
  }
  
  function deepMerge (target, src) {
    var array = Array.isArray(src);
    var dst = array && [] || {};
  
    if (array) {
      target = target || [];
      dst = dst.concat(target);
      src.forEach(deepMerger.bind(null, target, dst));
    } else {
      if (target && typeof target === 'object') {
        Object.keys(target).forEach(copyist.bind(null, target, dst));
      }
      Object.keys(src).forEach(copyistWithDeepMerge.bind(null, target, src, dst));
    }
  
    return dst;
  };
  
  module.exports.deepMerge = deepMerge;
  
  /**
   * Validates instance against the provided schema
   * Implements URI+JSON Pointer encoding, e.g. "%7e"="~0"=>"~", "~1"="%2f"=>"/"
   * @param o
   * @param s The path to walk o along
   * @return any
   */
  exports.objectGetPath = function objectGetPath(o, s) {
    var parts = s.split('/').slice(1);
    var k;
    while (typeof (k=parts.shift()) == 'string') {
      var n = decodeURIComponent(k.replace(/~0/,'~').replace(/~1/g,'/'));
      if (!(n in o)) return;
      o = o[n];
    }
    return o;
  };
  
  function pathEncoder (v) {
    return '/'+encodeURIComponent(v).replace(/~/g,'%7E');
  }
  /**
   * Accept an Array of property names and return a JSON Pointer URI fragment
   * @param Array a
   * @return {String}
   */
  exports.encodePath = function encodePointer(a){
  	// ~ must be encoded explicitly because hacks
  	// the slash is encoded by encodeURIComponent
  	return a.map(pathEncoder).join('');
  };
  
  
  /**
   * Calculate the number of decimal places a number uses
   * We need this to get correct results out of multipleOf and divisibleBy
   * when either figure is has decimal places, due to IEEE-754 float issues.
   * @param number
   * @returns {number}
   */
  exports.getDecimalPlaces = function getDecimalPlaces(number) {
  
    var decimalPlaces = 0;
    if (isNaN(number)) return decimalPlaces;
  
    if (typeof number !== 'number') {
      number = Number(number);
    }
  
    var parts = number.toString().split('e');
    if (parts.length === 2) {
      if (parts[1][0] !== '-') {
        return decimalPlaces;
      } else {
        decimalPlaces = Number(parts[1].slice(1));
      }
    }
  
    var decimalParts = parts[0].split('.');
    if (decimalParts.length === 2) {
      decimalPlaces += decimalParts[1].length;
    }
  
    return decimalPlaces;
  };
  
  