/**
 * @requires Util
 */

/**
 * @type String
 */
String.BASE64_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

/**
 * @type Object
 */
String.JSON_MAPPING = {
	'\b': '\\b',
	'\t': '\\t',
	'\n': '\\n',
	'\f': '\\f',
	'\r': '\\r',
	'"' : '\\"',
	'\\': '\\\\'
};

/**
 * Remove os espaços da esquerda da string
 *
 * @addon
 * @type String
 */
String.prototype.ltrim = function() {
	return this.replace(/\s*((\S+\s*)*)/, "$1");
}

/**
 * Remove os espaços da esquerda da string
 *
 * @addon
 * @type String
 */
String.prototype.rtrim = function() {
	return this.replace(/((\s*\S+)*)\s*/, "$1");
}

/**
 * Remove os espaços da esquerda e da direita da string
 *
 * @addon
 * @type String
 */
String.prototype.trim = function() {
	return this.rtrim().ltrim();
}

/**
 * Indica se uma determinada string está continda nesta
 *
 * @param {String} needle
 * @addon
 * @type Boolean
 */
String.prototype.inString = function(needle) {
	return this.indexOf(needle) != -1;
}

/**
 * Verifica se a string inicia com uma determinada substring
 *
 * @param {String} needle
 * @addon
 * @type Boolean
 */
String.prototype.startsWith = function(needle) {
	return (this.indexOf(needle) == 0);
}


/**
 * Retorna o valor da string codificado para uma URI
 *
 * @addon
 * @type String
 */
String.prototype.uriEncode = function() {
	return escape(this).replace(new RegExp('\\+','g'),'%2B').replace(new RegExp('%20','g'),'+');
}

/**
 * Retorna o valor decodificado de uma string URI
 *
 * @addon
 * @type String
 */
String.prototype.uriDecode = function() {
	return unescape(this.replace(new RegExp('\\+','g'),' '));
}

/**
 * Retorna a string codificada em base64
 *
 * @addon
 * @type string
 */
String.prototype.encodeBase64 = function() {
	var output = "";
	var chr1, chr2, chr3;
	var enc1, enc2, enc3, enc4;
	var i = 0;
	var base64Key = String.BASE64_KEY;
	var length = this.length;

	do {
		chr1 = this.charCodeAt(i++);
		chr2 = this.charCodeAt(i++);
		chr3 = this.charCodeAt(i++);

		enc1 = chr1 >> 2;
		enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
		enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
		enc4 = chr3 & 63;

		if (isNaN(chr2)) {
			enc3 = enc4 = 64;
		} else if (isNaN(chr3)) {
			enc4 = 64;
		}

		output += (base64Key.charAt(enc1) + base64Key.charAt(enc2) +	base64Key.charAt(enc3) + base64Key.charAt(enc4));
	} while (i < length);

	return output;
}

/**
 * Retorna a string decodificada a partir de um base64
 *
 * @addon
 * @type String
 */
String.prototype.decodeBase64 = function() {
	var output = "";
	var chr1, chr2, chr3;
	var enc1, enc2, enc3, enc4;
	var i = 0;
	var base64Key = String.BASE64_KEY;
	var input = this.replace(/[^A-Za-z0-9\+\/\=]/g, "");
	var length = input.length;

	do {
		enc1 = base64Key.indexOf(input.charAt(i++));
		enc2 = base64Key.indexOf(input.charAt(i++));
		enc3 = base64Key.indexOf(input.charAt(i++));
		enc4 = base64Key.indexOf(input.charAt(i++));

		chr1 = (enc1 << 2) | (enc2 >> 4);
		chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
		chr3 = ((enc3 & 3) << 6) | enc4;

		output = output + String.fromCharCode(chr1);

		if (enc3 != 64) {
			output = output + String.fromCharCode(chr2);
		}
		if (enc4 != 64) {
			output = output + String.fromCharCode(chr3);
		}
	} while (i < length);

	return output;
}

/**
 * Retorna a string transformado o primeiro caracter em letra maiúscula
 *
 * @addon
 * @type String
 */
String.prototype.ucFirst = function() {
	return this.charAt(0).toUpperCase() + this.substr(1);
}

/**
 * Retorna a string concatenada com uam repetição de caracteres até obter um determinado tamanho
 * 
 * @param {String} chr
 * @param {Integer} length
 * @param {Boolean} inverse 
 * @type String
 */
String.prototype.strPad = function(chr, length, inverse) {
	var str = "" + this;
	while (str.length < length) {		
		str = inverse ? str + chr : chr + str;
	}

	return str;
}

/**
 * Retorna a string convertendo as quebras de linhas para "&lt;br /&gt;"
 * 
 * @type String
 */
String.prototype.nl2br = function() {
	return this.replace(/\n/g, "<br />");
}

/**
 * Indica se uma determinada string está continda nesta
 *
 * @param {String} needle
 * @addon
 * @type Boolean
 */
String.prototype.contains = function(needle) {
	return (this.indexOf(needle) != -1);
}

/**
 * Retorna o valor armazenado na string JSON
 * 
 * @see http://www.json.org/json.js
 * @param {Function} filter [undefined] 
 * @type mixed
 */
String.prototype.parseJSON = function(filter) {
	var j;
	
	function walk(k, v) {
	    var i;
	    if (v && typeof v === 'object') {
	        for (i in v) {
	            if (v.hasOwnProperty(i)) {
	                v[i] = walk(i, v[i]);
	            }
	        }
	    }
	    return filter(k, v);
	}

	if (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/.test(this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) {

	    j = eval('(' + this + ')');

        if (typeof filter === 'function') {
            j = walk('', j);
        }
        return j;
    }

    throw new SyntaxError('String#parseJSON');
};    

/**
 * Retorna uma string JSON representando a string
 * 
 * @see http://www.json.org/json.js
 * @return {String}
 */
String.prototype.toJSONString = function () {

    if (/["\\\x00-\x1f]/.test(this)) {
        return (
            '"' + this.replace(
                /([\x00-\x1f\\"])/g, 
                function (a, b) {
		            var c = String.JSON_MAPPING[b];
		            if (c) {
		                return c;
		            }
		            c = b.charCodeAt();
		            return ('\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16));
		        }
            ) + '"'
        );
    }
    
    return '"' + this + '"';
};

/**
 * Retorna a String com todas as ocorrências do caracter desejado substituído pelo novo.
 * Diferente do replace, que substituí apenas o primeiro caracter encontrado.
 * Função criada para evitar o looping infinito que outras funções de replaceAll geram ao 
 * trocar um caracter x por 2 caracteres xx. ex replaceAll("'","''");
 *  
 * @param {char} de - deve ser apenas um caracter
 * @param {String} para - qualquer caracter ou conjunto de caracteres
 * @type String
 * @return {String}
 */
String.prototype.replaceAllChar = function(de, para) {		
	var str = this;
	var result = "";	
	for(i = 0; i < str.length; i++){
		if(str.charAt(i) == de){
			result += para;
		}else{
			result += str.charAt(i);		
		}				
	}
	return result;
};

/**
 * Retorna a String com todas as ocorrências do caracter desejado substituído pelo novo.
 * Diferente do replace, que substituí apenas o primeiro caracter encontrado.
 * Não subtituir o caracter desejado por n caracter iguais, senão entra em looping.
 *  
 * @param {String} de
 * @param {String} para
 * @type String
 * @return {String}
 */
String.prototype.replaceAll = function(de, para) {		
	var str = this;
	var pos = str.indexOf(de);
	while(pos > -1){
		str = str.replace(de, para);
		pos = str.indexOf(de);
	}	
	return str;	
};
