var Validate = {
	messageTemplates: {},
	setElementValidators: function(element, validators){
		
	},
	addTemplate: function(key, templateString) {
		if (this.messageTemplates[key]) return;
		var syntax =  /(^|.|\r|\n)(%(\w+)%)/;
		this.messageTemplates[key] = new Template(templateString, syntax);	
	},
	errorMessage: function(key, data) {
		if (this.messageTemplates[key]) return this.messageTemplates[key].evaluate(data);
		return 'Error: ' + data.toString();
	},
	is: function(validator, value, options) {
		console.log(options);
		if (this[validator]) return this[validator](value, options);
		return true;
	}
};
Validate.addTemplate('noValidatorFound', "'%value%' is not a valid '%validator%'");
Validate.addTemplate('stringEmpty', "'%value%' is an empty string");

/**
 * Alpha
 */
Validate.addTemplate('notAlnum', "'%value%' has not only alphabetic and digit characters");
Validate.Alnum = function(value, options) {
	// Extra Params
	var allowWhiteSpace = (options.allowWhiteSpace) ? Boolean(options.allowWhiteSpace) : false;
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString };
	
	if (valueString.length == 0) return true;
	if (allowWhiteSpace && valueString.empty()) return this.errorMessage('stringEmpty', errorData);
	if (!allowWhiteSpace && valueString.blank()) return this.errorMessage('stringEmpty', errorData);
	
	var pattern = '[^a-zA-Z0-9';
	if (allowWhiteSpace) pattern += '\s';
	pattern += ']';
		
	if (new RegExp(pattern, 'g').test(valueString)) return this.errorMessage('notAlnum', errorData);
	
	return true;
}

/**
 * Alpha
 */
Validate.addTemplate('notAlpha', "'%value%' has not only alphabetic characters");
Validate.Alpha = function(value, options) {
	// Extra Params
	var allowWhiteSpace = (options.allowWhiteSpace) ? Boolean(options.allowWhiteSpace) : false;
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString };
	
	if (valueString.length == 0) return true;
	if (allowWhiteSpace && valueString.empty()) return this.errorMessage('stringEmpty', errorData);
	if (!allowWhiteSpace && valueString.blank()) return this.errorMessage('stringEmpty', errorData);
	
	var pattern = '[^a-zA-Z';
	if (allowWhiteSpace) pattern += '\s';
	pattern += ']';
		
	if (new RegExp(pattern, 'g').test(valueString)) return this.errorMessage('notAlpha', errorData);
	
	return true;
}

/**
 * Barcode
 */
Validate.Barcode = function(value, options) {
	return this.is('todo-Barcode', value, options);
}

/**
 * Ccnum
 */
Validate.addTemplate('ccnumLength', "'%value%' must contain between 13 and 19 digits");
Validate.addTemplate('ccnumChecksum', "Luhn algorithm (mod-10 checksum) failed on '%value%'");
Validate.Ccnum = function(value, options) {
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString };
	
	if (valueString.length == 0) return true;
	
	var valueFiltered = Filter.Digits(valueString);

    var length = valueFiltered.length;

    if (length < 13 || length > 19) return this.errorMessage('ccnumLength', errorData);
	
	var digit;
	var sum    = 0;
	var weight = 2;

	for (var i = length - 2; i >= 0; i--) {
	    digit = weight * Number(valueFiltered[i]);
	    sum += Math.floor(digit / 10) + digit % 10;
	    weight = weight % 2 + 1;
	}

	if ((10 - sum % 10) % 10 != Number(valueFiltered[length - 1])) return this.errorMessage('ccnumChecksum', errorData);
	
	return true;	
}

/**
 * Between
 */
Validate.addTemplate('notBetween', "'%value%' is not between '%min%' and '%max%', inclusively");
Validate.addTemplate('notBetweenStrict', "'%value%' is not strictly between '%min%' and '%max%'");
Validate.Between = function(value, options) {
	
	// Extra Params
	var min = options.min;
	var max = options.max;
	var inclusive = (options.inclusive) ? Boolean(options.inclusive) : true;
	
	// Set defaults
	var valueNumber = (!Number(value)) ? (min + max) : Number(value);
	var errorData = { 'value': value, 'min': min, 'max': max };
	
	if (String(value).length == 0) return true;
	
	if (inclusive) {
        if (min > valueNumber || valueNumber > max) return this.errorMessage('notBetween', errorData);
    } else {
        if (min >= valueNumber || valueNumber >= max) return this.errorMessage('notBetweenStrict', errorData);
    }
	
    return true;	
}

/**
 * Date
 */
Validate.Date = function(value, options) {
	return true;
}

/**
 * Digits
 */
Validate.addTemplate('notDigits', "'%value%' contains not only digit characters");
Validate.Digits = function(value, options) {
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString };
	
	if (String(value).length == 0) return true;
	if (valueString.blank()) return this.errorMessage('stringEmpty', errorData);
	
	if (valueString != Filter.Digits(valueString)) return this.errorMessage('notDigits', errorData);
	
    return true;
}

Validate.addTemplate('emailAddressInvalid', "'%value%' is not a valid email address in the basic format local-part@hostname");
Validate.addTemplate('emailAddressInvalidHostname', "'%hostname%' is not a valid hostname for email address '%value%'");
Validate.addTemplate('emailAddressInvalidMxRecord', "'%hostname%' does not appear to have a valid MX record for the email address '%value%'");
Validate.addTemplate('emailAddressDotAtom', "'%localPart%' not matched against dot-atom format");
Validate.addTemplate('emailAddressQuotedString', "'%localPart%' not matched against quoted-string format");
Validate.addTemplate('emailAddressInvalidLocalPart', "'%localPart%' is not a valid local part for email address '%value%'");
Validate.EmailAddress = function(value, options) {
	// IE is messing things up...
	if (!Prototype.Browser.IE) {

		// Extra params
		var validateMx = (options.validateMx) ? Boolean(options.validateMx) : false;
	
		// Set defaults
		var valueString = String(value);
		var errorData = { 'value': valueString };
		var matches = valueString.split(/^(.+)@([^@]+)$/);
	
		if (String(value).length == 0) return true;
		if (matches.length < 4 && matches.length != 0) return this.errorMessage('emailAddressInvalid', errorData);
	
		var localPart = matches[1];
		errorData.localPart = localPart;
	    var hostname  = matches[2];
		errorData.hostname = hostname;
	
	
		// Match hostname part
	    if (this.Hostname(hostname, options) !== true) return this.errorMessage('emailAddressInvalidHostname', errorData);

		// MX check on hostname via dns_get_record()
		if (validateMx) {
			// Ajax Validate
		}
	
		// First try to match the local part on the common dot-atom format
		var localResult = false;
	}
	// Dot-atom characters are: 1*atext *("." 1*atext)
	// atext: ALPHA / DIGIT / and "!", "#", "$", "%", "&", "'", "*",
	//        "-", "/", "=", "?", "^", "_", "`", "{", "|", "}", "~"
	var atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d';
	if (new RegExp('^[' + atext + ']+(\x2e+[' + atext + ']+)*$').test(localPart)) {
	    localResult = true;
	} else {
	    // Try quoted string format

	    // Quoted-string characters are: DQUOTE *([FWS] qtext/quoted-pair) [FWS] DQUOTE
	    // qtext: Non white space controls, and the rest of the US-ASCII characters not
	    //   including "\" or the quote character
	    noWsCtl    = '\x01-\x08\x0b\x0c\x0e-\x1f\x7f';
	    qtext      = noWsCtl + '\x21\x23-\x5b\x5d-\x7e';
	    ws         = '\x20\x09';
	    if (new RegExp('^\x22([' + ws + qtext + '])*[' + ws + ']?\x22$').test(localPart)) {
	        localResult = true;
	    } else {
			return [			
				this.errorMessage('emailAddressDotAtom', errorData),
				this.errorMessage('emailAddressQuotedString', errorData),
				this.errorMessage('emailAddressInvalidLocalPart', errorData)
			];
	    }
	}
	
	if (!localResult) return false;
	
	return true;
}

/**
 * Float
 */
Validate.addTemplate('notFloat', "'%value%' does not appear to be a float");
Validate.Float = function(value, options) {
	
	// Extra params
	var thousandsSeparator = (options.thousandsSeparator) ? options.thousandsSeparator : '.';
	var decimalPoint = (options.decimalPoint) ? options.decimalPoint : ',';
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString };
	
	if (String(value).length == 0) return true;
	
	var valueFiltered = valueString.replace(thousandsSeparator, '');
    valueFiltered = valueFiltered.replace(decimalPoint, '.');

    if (parseFloat(valueFiltered).toString() != valueFiltered) return this.errorMessage('notFloat', errorData);

    return true;
}

/**
 * Greater Than
 */
Validate.addTemplate('notGreaterThan', "'%value%' is not greater than '%min%'");
Validate.GreaterThan = function(value, options) {
	// Extra params
	var min = (options.min) ? options.min : 0;
	
	// Set defaults
	var valueNumber = (!Number(value)) ? (min -1) : Number(value);
	var errorData = { 'value': value, 'min': min }
	
	if (String(value).length == 0) return true;
	
	if (valueNumber <= min) return this.errorMessage('notGreaterThan', errorData);
	
	return true;	
}

/**
 * Hex
 */
Validate.addTemplate('notHex', "'%value%' has not only hexadecimal digit characters");
Validate.Hex = function(value, options) {
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString };
	
	if (String(value).length == 0) return true;
	
	if (!new RegExp('^[a-fA-F0-9]+$').test(valueString)) return this.errorMessage('notHex', errorData);
	
	return true;	
}

/**
 * Hostname
 */
Validate.addTemplate('hostnameIpAddressNotAllowed', "'%value%' appears to be an IP address, but IP addresses are not allowed");
Validate.addTemplate('hostnameUnknownTld', "'%value%' appears to be a DNS hostname but cannot match TLD against known list");
Validate.addTemplate('hostnameDashCharacter', "'%value%' appears to be a DNS hostname but contains a dash (-) in an invalid position");
Validate.addTemplate('hostnameInvalidHostnameSchema', "'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'");
Validate.addTemplate('hostnameUndecipherableTld', "'%value%' appears to be a DNS hostname but cannot extract TLD part");
Validate.addTemplate('hostnameInvalidHostname', "'%value%' does not match the expected structure for a DNS hostname");
Validate.addTemplate('hostnameInvalidLocalName', "'%value%' does not appear to be a valid local network name");
Validate.addTemplate('hostnameLocalNameNotAllowed', "'%value%' appears to be a local network name but local network names are not allowed");
Validate.Hostname = function(value, options) {
	// Ajax validate
	
	var utf8Chars = {}; 
	
	/*
	// At
	utf8Chars.at = "\x{00EO}-\x{00F6}\x{00F8}-\x{00FF}\x{0153}\x{0161}\x{017E}";
	// Ch
	//utf8Chars.ch = '\x{00EO}-\x{00F6}\x{00F8}-\x{00FF}\x{0153}';
	// De
	utf8Chars.de = '\x{00E1}\x{00E0}\x{0103}\x{00E2}\x{00E5}\x{00E4}\x{00E3}\x{0105}\x{0101}\x{00E6}\x{0107}';
    utf8Chars.de += '\x{0109}\x{010D}\x{010B}\x{00E7}\x{010F}\x{0111}\x{00E9}\x{00E8}\x{0115}\x{00EA}\x{011B}';
    utf8Chars.de += '\x{00EB}\x{0117}\x{0119}\x{0113}\x{011F}\x{011D}\x{0121}\x{0123}\x{0125}\x{0127}\x{00ED}';
    utf8Chars.de += '\x{00EC}\x{012D}\x{00EE}\x{00EF}\x{0129}\x{012F}\x{012B}\x{0131}\x{0135}\x{0137}\x{013A}';
    utf8Chars.de += '\x{013E}\x{013C}\x{0142}\x{0144}\x{0148}\x{00F1}\x{0146}\x{014B}\x{00F3}\x{00F2}\x{014F}';
    utf8Chars.de += '\x{00F4}\x{00F6}\x{0151}\x{00F5}\x{00F8}\x{014D}\x{0153}\x{0138}\x{0155}\x{0159}\x{0157}';
    utf8Chars.de += '\x{015B}\x{015D}\x{0161}\x{015F}\x{0165}\x{0163}\x{0167}\x{00FA}\x{00F9}\x{016D}\x{00FB}';
    utf8Chars.de += '\x{016F}\x{00FC}\x{0171}\x{0169}\x{0173}\x{016B}\x{0175}\x{00FD}\x{0177}\x{00FF}\x{017A}';
    utf8Chars.de += '\x{017E}\x{017C}\x{00F0}\x{00FE}';
	// Fi
	utf8Chars.fi = '\x{00E5}\x{00E4}\x{00F6}';
	// Hu
	utf8Chars.hu = '\x{00E1}\x{00E9}\x{00ED}\x{00F3}\x{00F6}\x{0151}\x{00FA}\x{00FC}\x{0171}';
	// Li
	utf8Chars.li = '\x{00EO}-\x{00F6}\x{00F8}-\x{00FF}\x{0153}';
	// No
	utf8Chars.no = '\x{00E1}\x{00E0}\x{00E4}\x{010D}\x{00E7}\x{0111}\x{00E9}\x{00E8}\x{00EA}\x{014B}';
    utf8Chars.no += '\x{0144}\x{00F1}\x{00F3}\x{00F2}\x{00F4}\x[00F6]\x{0161}\x{0167}\x{00FC}\x{017E}\x{00E6}';
    utf8Chars.no += '\x{00F8}\x{00E5}';
	// se
	utf8Chars.se = '\x{00E5}\x{00E4}\x{00F6}\x{00FC}\x{00E9}';
	*/
	var validTlds = [
		'ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao',
		'aq', 'ar', 'arpa', 'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb',
		'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'biz', 'bj', 'bm', 'bn', 'bo',
		'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc', 'cd',
		'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop',
		'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do',
		'dz', 'ec', 'edu', 'ee', 'eg', 'er', 'es', 'et', 'eu', 'fi', 'fj',
		'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh',
		'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu',
		'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il',
		'im', 'in', 'info', 'int', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm',
		'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw',
		'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu',
		'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mil', 'mk', 'ml', 'mm',
		'mn', 'mo', 'mobi', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'museum', 'mv',
		'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc', 'ne', 'net', 'nf', 'ng',
		'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe',
		'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt',
		'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd',
		'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr',
		'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th', 'tj',
		'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw',
		'tz', 'ua', 'ug', 'uk', 'um', 'us', 'uy', 'uz', 'va', 'vc', 've',
		'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'yu', 'za', 'zm',
		'zw'
	];
	
	// Extra params
	var allowDNS = (options.allowDNS) ? Boolean(options.allowDNS) : true;
	var allowIp = (options.allowIp) ? Boolean(options.allowIp) : false;
	var allowLocal = (options.allowLocal) ? Boolean(options.allowLocal) : false;
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString };
	
	if (String(value).length == 0) return true;
	
	// Check input against IP address schema
    if (this.Ip(valueString) === true) {
        if (!allowIp) return this.errorMessage('hostnameIpAddressNotAllowed', errorData);
        return true;
    }

	// Check input against DNS hostname schema
    var domainParts = valueString.split('.');
	if ((domainParts.length > 1) && (valueString.length >= 4) && (valueString.length <= 254)) {
		
		// Set TLD
		var tld = domainParts.last().toLowerCase();
		errorData['tld'] = tld;
		
		// Match TLD against known list
		if (validTlds.indexOf(tld) == -1) return this.errorMessage('hostnameUnknownTld', errorData);
		
		/**
         * Match against IDN hostnames
         * @see Zend_Validate_Hostname_Interface
         */
        var labelChars = 'a-z0-9';
		if (utf8Chars[tld]) labelChars += utf8Chars[tld];

        // Keep label regex short to avoid issues with long patterns when matching IDN hostnames
        var regexLabel = '^[' + labelChars + '\x2d]{1,63}$';
		
		// Check each hostname part
        var valid = true;
		var domainPart;
        for (var i = 0; domainParts.length > i; i++) {
			domainPart = domainParts[i];
			
            // Check dash (-) does not start, end or appear in 3rd and 4th positions
            if (domainPart.indexOf('-') === 0 || 
				(domainPart.length > 2 && domainPart.indexOf('-') == 2 && domainPart.indexOf('-') == 3) || 
				domainPart.indexOf('-') === domainPart.length - 1) {
				
				return this.errorMessage('hostnameDashCharacter', errorData);
            }
			
			// Allowed Chars
			if (!new RegExp(regexLabel, 'i').test(domainPart)) return this.errorMessage('hostnameInvalidHostnameSchema', errorData);
		}
		
		
		
		
	} else {
		return this.errorMessage('hostnameInvalidHostname', errorData);
	}
	
	if (allowDNS) return true;
	
	// Check input against local network name schema; last chance to pass validation
    var regexLocal = '^(([a-zA-Z0-9\x2d]{1,63}\x2e)*[a-zA-Z0-9\x2d]{1,63}){1,254}$';
	var status = new RegExp(regexLocal, 'i').test(valueString);
	
    // If the input passes as a local network name, and local network names are allowed, then the
    // hostname passes validation
    if (status && allowLocal) {
        return true;
    }

    // If the input does not pass as a local network name, add a message
    if (!status) {
		return this.errorMessage('hostnameInvalidLocalName', errorData);
    }

    // If local network names are not allowed, add a message
    if (status && !allowLocal) {
        return this.errorMessage('hostnameLocalNameNotAllowed', errorData);
    }

    return false;
	
}

/**
 * Identical
 */
Validate.addTemplate('notSame', 'Tokens do not match');
Validate.addTemplate('missingToken', 'No token was provided to match against');
Validate.Identical = function(value, options) {
	return true;
	// Extra params
	var token = (options.token) ? options.token : '';
	
	// Set defaults
	var errorsData = {};
	
	if (String(value).length == 0) return true;
	
	if (token.empty()) return this.errorMessage('missingToken', errorsData);
	
	if (token !== value) return this.errorMessage('notSame', errorsData);
	
	return true;
}

/**
 * InArray
 */
Validate.addTemplate('notInArray', "'%value%' was not found in the haystack");
Validate.InArray = function(value, options) {
	
	// Extra params
	var haystack = (options.haystack) ? options.haystack : [];
	var strict = (options.haystack) ? Boolean(strict) : false;
	
	// Set defaults
	var errorsData = { 'value': value };
	
	if (String(value).length == 0) return true;
	
	if (haystack.indexOf(value) == -1) return this.errorMessage('notInArray', errorsData)
	
	return true;
}

/**
 * Int
 */
Validate.addTemplate('notInt', "'%value%' does not appear to be an integer");
Validate.Int = function(value, options) {
	
	// Extra params
	var thousandsSeparator = (options.thousandsSeparator) ? options.thousandsSeparator : '.';
	var decimalPoint = (options.decimalPoint) ? options.decimalPoint : ',';
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString };
	
	if (String(value).length == 0) return true;
	
	var valueFiltered = valueString.replace(decimalPoint, '.');
	valueFiltered = valueFiltered.replace(thousandsSeparator, '');
    
    if (parseInt(valueFiltered).toString() != valueFiltered) return this.errorMessage('notInt', errorData);

    return true;
}

/**
 * Ip
 */
Validate.addTemplate('notIpAddress', "'%value%' does not appear to be a valid IP address");
Validate.Ip = function(value, options) {
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString };
	
	if (String(value).length == 0) return true;
	
	var pattern = '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}';
	
	if (!new RegExp(pattern, 'g').test(valueString)) return this.errorMessage('notIpAddress', errorData);
	
	return true;
}

/**
 * Less Than
 */
Validate.addTemplate('notLessThan', "'%value%' is not less than '%max%'")
Validate.LessThan = function(value, options) {
	
	// Extra params
	var max = (options.max) ? options.max : null;
	
	// Set defaults
	var valueNumber = (!Number(value)) ? (max + 1) : Number(value);
	var errorData = { 'value': value, 'max': max };
	
	if (String(value).length == 0) return true;
	
	if (max <= valueNumber) return this.errorMessage('notLessThan', errorData);

    return true;
}

/**
 * Not Empty
 */
Validate.addTemplate('isEmpty', "Value is empty, but a non-empty value is required")
Validate.NotEmpty = function(value, options) {
		
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString };
	
	console.log(valueString.empty());
	
	if (valueString.empty()) return this.errorMessage('isEmpty', errorData);

    return true;
}

/**
 * Regex
 */
Validate.addTemplate('regexNotMatch', "'%value%' does not match against pattern '%pattern%'")
Validate.Regex = function(value, options) {
	
	// Extra params
	var pattern = (options.pattern) ? String(options.pattern) : '';
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString, 'pattern': pattern };
	
	if (String(value).length == 0) return true;
	
	var result = false;
	
	var regexFlags = null;
	if (pattern[0] == '/') pattern = pattern.substr(1);
	if (pattern.lastIndexOf('/') != -1) {
		regexFlags = pattern.substr(pattern.lastIndexOf('/') + 1);
		pattern = pattern.substr(0, pattern.lastIndexOf('/'));
	}
		
	try { 
		result = new RegExp(pattern, regexFlags).test(valueString);
	} catch(e) {
		result = false;
	}
	
	if (!result) return this.errorMessage('regexNotMatch', errorData);

    return true;
}

/**
 * String Length
 */
Validate.addTemplate('stringLengthTooShort', "'%value%' is less than %min% characters long");
Validate.addTemplate('stringLengthTooLong', "'%value%' is greater than %max% characters long");
Validate.StringLength = function(value, options) {
	
	// Extra params
	var min = (options.min) ? options.min : 0;
	var max = (options.max) ? options.max : null;
	
	// Set defaults
	var valueString = String(value);
	var errorData = { 'value': valueString, 'min': min, 'max': max };
	var length = valueString.length;
	
	if (String(value).length == 0) return true;
	
	if (length < min) return this.errorMessage('stringLengthTooShort', errorData);
	if (null !== max && max < length) return this.errorMessage('stringLengthTooLong', errorData);
    
    return true;
}