if (typeof Fox == 'undefined') {
	Fox = {};
}

Fox.Utils = {
	composeDate: function(mysqlTimestamp) {
		if ((mysqlTimestamp % 1) === 0) {
			return new Date(parseInt(mysqlTimestamp) * 1000);
		}
		var parts = mysqlTimestamp.match(/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/);
		return new Date(parts[1], parts[2] - 1, parts[3], parts[4], parts[5], parts[6]);
	},
	
	int2ip: function(i) {
		return [i >> 24 & 0xFF, i >> 16 & 0xFF, i >> 8 & 0xFF, i & 0xFF].join(".");
	},
	
	dateFormat: function(format, timestamp) {
		function pad(number) {
			return number.toString().replace(/^([0-9])$/, '0$1');
		}
		
		if (typeof timestamp == 'Date') {
			var date = timestamp;
		} else {
			var date = new Date(timestamp);
		}
		
		var hours = date.getHours(),
			day = date.getDay(),
			dayOfMonth = date.getDate(),
			month = date.getMonth(),
			fullYear = date.getFullYear(),
			weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
			months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
			
			// list all format keys
			replacements = {
				// Day
				'a': weekdays[day].substr(0, 3), // Short weekday, like 'Mon'
				'A': weekdays[day], // Long weekday, like 'Monday'
				'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31 
				'e': dayOfMonth, // Day of the month, 1 through 31 
				
				// Week (none implemented)			
				
				// Month
				'b': months[month].substr(0, 3), // Short month, like 'Jan'
				'B': months[month], // Long month, like 'January'
				'm': pad(month + 1), // Two digit month number, 01 through 12
				
				// Year
				'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
				'Y': fullYear, // Four digits year, like 2009
				
				// Time
				'H': pad(hours), // Two digits hours in 24h format, 00 through 23
				'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
				'l': (hours % 12) || 12, // Hours in 12h format, 1 through 11
				'M': pad(date.getMinutes()), // Two digits minutes, 00 through 59
				'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
				'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
				'S': pad(date.getSeconds()) // Two digits seconds, 00 through  59
			};
		
		// do the replaces
		for (var key in replacements) {
			format = format.replace('%' + key, replacements[key]);
		}
			
		return format;
	},
	
	generateRandomString: function(length, alphabetString) {
		if (typeof alphabetString == 'undefined') {
			alphabetString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
		}
		var alphabet = alphabetString.split('');
		var random = [];
		while (random.length < length) {
			random.push(alphabet[Math.floor(Math.random() * alphabet.length)]);
		}
		return random.join('');
	},
	
	passwordStrength: function(pwd) {
		var nAlphaUC = 0, nAlphaLC = 0, nNumber = 0, nSymbol = 0, nMidChar = 0, nRepChar = 0, nRepInc = 0, nConsecAlphaUC = 0, nConsecAlphaLC = 0,
			nConsecNumber = 0, nConsecSymbol = 0, nConsecCharType = 0, nSeqAlpha = 0, nSeqNumber = 0, nSeqSymbol = 0, nReqChar = 0,
			nTmpAlphaUC = '', nTmpAlphaLC = '', nTmpNumber = '', nTmpSymbol = '';
		
		var nMinPwdLen = 8;
		var nLength = pwd.length;
		var nScore = nLength * 4;
		var arrPwd = pwd.replace(/\s+/g, '').split(/\s*/);
		var arrPwdLen = arrPwd.length;
		
		/* Loop through password to check for Symbol, Numeric, Lowercase and Uppercase pattern matches */
		for (var a = 0; a < arrPwdLen; a++) {
			if (arrPwd[a].match(/[A-Z]/g)) {
				if (nTmpAlphaUC !== '' && nTmpAlphaUC + 1 == a) {
					nConsecAlphaUC++;
					nConsecCharType++;
				}
				
				nTmpAlphaUC = a;
				nAlphaUC++;
			} else if (arrPwd[a].match(/[a-z]/g)) {
				if (nTmpAlphaLC !== '' && nTmpAlphaLC + 1 == a) {
					nConsecAlphaLC++;
					nConsecCharType++;
				}
				
				nTmpAlphaLC = a;
				nAlphaLC++;
			} else if (arrPwd[a].match(/[0-9]/g)) {
				if (a > 0 && a < arrPwdLen - 1) {
					nMidChar++;
				}
				
				if (nTmpNumber !== '' && nTmpNumber + 1 == a) {
					nConsecNumber++;
					nConsecCharType++;
				}
				
				nTmpNumber = a;
				nNumber++;
			} else if (arrPwd[a].match(/[^a-zA-Z0-9_]/g)) {
				if (a > 0 && a < arrPwdLen - 1) {
					nMidChar++;
				}
				
				if (nTmpSymbol !== '' && nTmpSymbol + 1 == a) {
					nConsecSymbol++;
					nConsecCharType++;
				}
				
				nTmpSymbol = a;
				nSymbol++;
			}
			
			/* Internal loop through password to check for repeat characters */
			var bCharExists = false;
			
			for (var b = 0; b < arrPwdLen; b++) {
				if (arrPwd[a] == arrPwd[b] && a != b) { /* repeat character exists */
					bCharExists = true;
					/* 
					Calculate icrement deduction based on proximity to identical characters
					Deduction is incremented each time a new match is discovered
					Deduction amount is based on total password length divided by the
					difference of distance between currently selected match
					*/
					nRepInc += Math.abs(arrPwdLen / (b - a));
				}
			}
			
			if (bCharExists) {
				nRepChar++;
				nUnqChar = arrPwdLen - nRepChar;
				nRepInc = nUnqChar ? Math.ceil(nRepInc / nUnqChar) : Math.ceil(nRepInc);
			}
		}
		
		/* Check for sequential alpha string patterns (forward and reverse) */
		for (var s = 0; s < 23; s++) {
			var sFwd = 'abcdefghijklmnopqrstuvwxyz'.substring(s, s + 3);
			if (pwd.toLowerCase().indexOf(sFwd) != -1 || pwd.toLowerCase().indexOf(sFwd.split('').reverse().join('')) != -1) {
				nSeqAlpha++;
			}
		}
		
		/* Check for sequential numeric string patterns (forward and reverse) */
		for (var s = 0; s < 8; s++) {
			var sFwd = '01234567890'.substring(s, s + 3);
			if (pwd.toLowerCase().indexOf(sFwd) != -1 || pwd.toLowerCase().indexOf(sFwd.split('').reverse().join('')) != -1) {
				nSeqNumber++;
			}
		}
		
		/* Check for sequential symbol string patterns (forward and reverse) */
		for (var s=0; s < 8; s++) {
			var sFwd = ')!@#$%^&*()'.substring(s, s + 3);
			if (pwd.toLowerCase().indexOf(sFwd) != -1 || pwd.toLowerCase().indexOf(sFwd.split('').reverse().join('')) != -1) {
				nSeqSymbol++;
			}
		}
		
		/* Modify overall score value based on usage vs requirements */
		
		if (nAlphaUC > 0 && nAlphaUC < nLength) {
			nScore += (nLength - nAlphaUC) * 2;
		}
		
		if (nAlphaLC > 0 && nAlphaLC < nLength) {
			nScore += (nLength - nAlphaLC) * 2;
		}
		
		if (nNumber > 0 && nNumber < nLength) {
			nScore += nNumber * 4;
		}
		
		if (nSymbol > 0) {
			nScore += nSymbol * 6;
		}
		
		if (nMidChar > 0) {
			nScore += nMidChar * 2;
		}
		
		/* Point deductions for poor practices */
		
		if ((nAlphaLC > 0 || nAlphaUC > 0) && nSymbol === 0 && nNumber === 0) { // Only Letters
			nScore -= nLength;
		}
		
		if (nAlphaLC === 0 && nAlphaUC === 0 && nSymbol === 0 && nNumber > 0) {  // Only Numbers
			nScore -= nLength;
		}
		
		if (nRepChar > 0) { // Same character exists more than once
			nScore -= nRepInc;
		}
		
		if (nConsecAlphaUC > 0) { // Consecutive Uppercase Letters exist
			nScore -= nConsecAlphaUC * 2;
		}
		
		if (nConsecAlphaLC > 0) { // Consecutive Lowercase Letters exist
			nScore -= nConsecAlphaLC * 2;
		}
		
		if (nConsecNumber > 0) { // Consecutive Numbers exist
			nScore -= nConsecNumber * 2;
		}
		
		if (nSeqAlpha > 0) { // Sequential alpha strings exist (3 characters or more)
			nScore -= nSeqAlpha * 3;
		}
		
		if (nSeqNumber > 0) { // Sequential numeric strings exist (3 characters or more)
			nScore -= nSeqNumber * 3;
		}
		
		if (nSeqSymbol > 0) { // Sequential symbol strings exist (3 characters or more)
			nScore -= nSeqSymbol * 3;
		}
		
		var requirements = [nLength, nAlphaUC, nAlphaLC, nNumber, nSymbol];
		
		for (var i = 0, l = requirements.length; i < l; i++) {
			if (requirements[i] >= (i == 0 ? nMinPwdLen - 1 : 0) + 1) {
				nReqChar++;
			}
		}
		
		if (nReqChar > (pwd.length >= nMinPwdLen ? 3 : 4)) { // One or more required characters exist
			nScore += nReqChar * 2; 
		}
		
		/* Determine complexity based on overall score */
		
		if (nScore > 100) {
			nScore = 100;
		} else if (nScore < 0) {
			nScore = 0;
		}
		
		if (nScore >= 0 && nScore < 20) {
			complexity = 'very_weak';
		} else if (nScore >= 20 && nScore < 40) {
			complexity = 'weak';
		} else if (nScore >= 40 && nScore < 60) {
			complexity = 'good';
		} else if (nScore >= 60 && nScore < 80) {
			complexity = 'strong';
		} else if (nScore >= 80 && nScore <= 100) {
			complexity = 'very_strong';
		}
		
		return [nScore, complexity];
	},
	
	parseTime: function(pureSeconds, zeroPad) {
		if (typeof zeroPad == 'undefined') {
			zeroPad = true;
		}
		
		var minutes = Math.floor(pureSeconds / 60);
		var hours = Math.floor(minutes / 60);
		var minutes = minutes - hours * 60;
		var seconds = pureSeconds - (hours * 3600 + minutes * 60);
		return {hours: zeroPad ? (hours < 10 ? '0' + hours : hours) : hours, minutes: zeroPad ? (minutes < 10 ? '0' + minutes : minutes) : minutes, seconds: zeroPad ? (seconds < 10 ? '0' + seconds : seconds) : seconds};
	},
	
	evalJSON: function(json) {
		return eval('('  + json +  ')');
	},

	patternTemplates: [new Template('', /(^|.|\r|\n)(\$\{(.*?)\})/), new Template('', /(^|.|\r|\n)(\$\[(.*?)\])/)],

	applyPatterns: function(string, data) {
		var template;
		var length = Fox.Utils.patternTemplates.length;
		for (var i = 0; i < length; i++) {
			template = Fox.Utils.patternTemplates[i];
			template.template = string;
			string = template.evaluate(data);
		}
		return string;
	},

	applyPatternsToDom: function(elt, data) {
		if (typeof elt == 'string') {
			elt = $(elt);
		}
		
		elt.innerHTML = Fox.Utils.applyPatterns(elt.innerHTML, data);
	}
}

Fox.Utils.timeSince = (function() {
	var
		periods = {year: 86400 * 30 * 12, month: 86400 * 30, week: 604800, day: 86400, hour: 3600, minute: 60, second: 1},
		i18n = function(count, unit, language) {
			switch (language) {
				case 'en':
					return count + ' ' + unit + (count > 1 ? 's' : '');
				
				case 'ru':
					switch (unit) {
						case 'year':
							return count + (count == 1 ? ' год' : (count < 5 ? ' года ' : ' лет '));
						
						case 'month':
							return count + (count == 1 ? ' месяц ' : (count < 5 ? ' месяца ' : ' месяцев '));
						
						case 'week':
							return count + (count == 1 ? ' неделю ' : (count < 5 ? ' недели ' : ' недель '));
						
						case 'day':
							return count + (count == 1 ? ' день ' : (count < 5 ? ' дня ' : ' дней '));
						
						case 'hour':
							return count + (count == 1 ? ' час ' : (count < 5 ? ' часа ' : ' часов '));
						
						case 'minute':
							return count + (count == 1 ? ' минуту ' : (count < 5 ? ' минуты ' : ' минут '));
						
						case 'second':
							return count + (count == 1 ? ' секунду ' : (count < 5 ? ' секунды ' : ' секунд '));
					}
			}
		}
	;
	
	return function(date, grade, language) {
		var difference = Math.abs(new Date(date).getTime() / 1000 - new Date().getTime() / 1000);
		var output = '';
		
		if (typeof grade == 'undefined') {
			grade = 2;
		}
		
		for (var period in periods) {
			var value = periods[period];
			
			if (difference >= value) {
				output += i18n(Math.round(difference / value), period, language);
				difference %= value;
			}
			
			if (--grade == 0) {
				break;
			}	
		}
		
		return output;
	}
})();

