//NETWORK OBJECTS AND METHODS FOR JAVASCRIPT

//REQUIRES utils.js

//THIS WILL TAKE JUST ABOUT ANYTHING AS A MAC ADDRESS, AS LONG AS THERE'S AT
//LEAST ONE THING TO BE CONSTRUED AS A "DIGIT", SO PROBABLY BEST TO USE A
//SEPARATE REGEX TO CHECK THE FORMAT. IT'S ALSO NOTABLE THAT YOU CAN SPECIFY
//THE RADIX (16, 10, 8, 2, ...) OF THE INPUT MAC ADDRESS.
MacAddress = function(newMac,radix) {
	newMac = newMac.toString();
	if (radix == undefined)
		var radix = 16;
	var sep = /[^0-9a-z]/i
	var macSplit = newMac.split(sep);
	var numeric;

	//MAKE SURE THE NUMBER OF ITEMS IN THE SPLIT IS VALID
	var invalidInput =
	   ((12 % macSplit.length) !== 0) || (radix < 2) || (radix > 36);

	if (!invalidInput) {
		//ASSEMBLE AN INTERNAL NUMERIC REPRESENTATION OF THE MAC ADDRESS
		numeric = 0;
		var basePlaceValue = Math.pow(16,12 / macSplit.length);

		var i=0;
		while ((i < macSplit.length) && !invalidInput) {
			var curItem = parseInt(macSplit[i],radix);
			invalidInput = isNaN(curItem) || (curItem >= basePlaceValue);
			if (!invalidInput) {
				var curItemPlaceValue =
				   Math.pow(basePlaceValue,macSplit.length - i - 1);
				numeric += curItem * curItemPlaceValue;
				i++;
			}
		}
	}

	//PUBLIC INTERFACE
	this.hexDash = function() {
		return this.format('-', 2, 16, false);
	}
	this.hexColon = function() {
		return this.format(':', 2, 16, false);
	}
	this.hexColonStripped = function() {
		return this.format(':', 2, 16, true);
	}
	this.hexDot = function() {
		return this.format('.', 4, 16, false);
	}
	this.format = function(separator, unitLength, radix, killHexZeroes) {
		if (separator == undefined) var separator = ':';
		if (unitLength == undefined) var unitLength = 2;
		if (radix == undefined) var radix = 16;
		if (killHexZeroes == undefined) var killHexZeroes = false;

		var format;

		if ((this.hexPadded.length % unitLength) === 0) {
			var formatSplit = new Array();
			var unit, unitHex, unitDec;
			for (var i=0; i < this.hexPadded.length; i += unitLength) {
				unitHex = this.hexPadded.substr(i,unitLength);
				if ((radix == 16) && killHexZeroes) {
					unit = parseInt(unitHex,16).toString(16);
				} else if (radix != 16) {
					unitDec = parseInt(unitHex,16);
					if (radix == 10) unit = unitDec;
					else unit = unitDec.toString(radix);
				} else unit = unitHex;
				formatSplit.push(unit);
			}
			format = formatSplit.join(separator);
		} else throw "Invalid unit length.";

		return format;
	}
	if (!invalidInput) {
		this.numeric = numeric;
		this.hex = numeric.toString(16);
		this.hexPadded = this.hex.lpad(12,0);
	} else return false;
}

//LIKE THE MAC ADDRESS OBJECT, THIS ACCEPTS JUST ABOUT ANYTHING AS AN IP
//ADDRESS THAT HAS A DIGIT. THIS, THOUGH, ONLY WORKS IN BASE 10.
IpAddress = function(newIp) {
	newIp = newIp.toString();
	var ipSplit = newIp.split(/\D/);
	var basePlaceValue = Math.pow(16, 8 / ipSplit.length);
	var invalidInput = false;
	var numeric = 0;

	var i=0;
	while ((i < ipSplit.length) && !invalidInput) {
		var curItem = ipSplit[i];
		invalidInput = (curItem >= basePlaceValue);
		if (!invalidInput) {
			var curItemPlaceValue =
			   Math.pow(basePlaceValue, ipSplit.length - i - 1);
			numeric += curItem * curItemPlaceValue;
		}
		i++;
	}

	//PUBLIC INTERFACE
	if (!invalidInput) {
		this.numeric = numeric;

		this.ascii = function() {
			var hexPadded = this.numeric.toString(16).lpad(8,0);
			var ipSplit = new Array();

			for (var i=0; i < hexPadded.length; i += 2)
				ipSplit.push(parseInt(hexPadded.substr(i,2),16));

			return ipSplit.join('.');
		}
	}

	this.valid = !invalidInput;
}

Uri = function(rawUri) {
	this._splitQueryString = function(queryString) {
		//USE decodeURIComponent HERE BECAUSE WE NEED ALL ESCAPE SEQUENCES
		//CANCELED IN ORDER TO SPLIT BY AMPERSAND RELIABLY.
		var decoded = decodeURIComponent(queryString);
		var pieces = decoded.split('&');
		var allSplit = {};
		for (var p=0; p < pieces.length; p++) {
			var curPiece = pieces[p];
			var match = curPiece.match(/^([^=]+)=(.*)$/);
			if (match) {
				var varName = match[1];
				var varValue = match[2];

				if (isUndefined(allSplit[varName])) allSplit[varName] = [];
				allSplit[varName].push(varValue);
			}
		}

		return allSplit;
	}
	this._splitServerInfo = function(serverInfo) {
		//THE RFC (2396) DOESN'T SPELL OUT USER/PASSWORD DISTINCTION, BUT
		//SINCE IT'S IN COMMON PRACTICE WE'LL CHECK FOR IT.
		var siRegex = /^((\w+)(\:([^@]*))?@)?(([^\:]+)(\:(\d+))?)$/;
		var siMatch = serverInfo.match(siRegex);

		var components = {};

		if (siMatch) {
			var componentLocations = {
				username: 2,
				password: 4,
				host: 6,
				port: 8
			}
			for (var c in componentLocations) {
				var cl = componentLocations[c];
				if (!isUndefined(siMatch[cl]))
					components[c] = siMatch[cl];
			}
		}

		return components;
	}

	this.setRawQueryString = function(queryString) {
		this.query = this._splitQueryString(queryString);
	}
	this.addQueryComponent = function(varName, varValue, preEscaped) {
		var newValue = (preEscaped ? decodeURIComponent(varValue) : varValue);
		if (isUndefined(this.query[varName]))
			this.query[varName] = [];
		this.query[varName].push(newValue);
	}
	this.removeQueryComponent = function(varName) {
		if (!isUndefined(this.query[varName]))
			delete this.query[varName];  //POSSIBLE CROSS-BROWSER ISSUES
	}
	this.getQueryComponent = function(varName) {
		var myArray = this.query[varName];
		return myArray && ((myArray.length > 1) ? myArray : myArray[0]);
	}
	this.resetQuery = function() {
		this.query = {};
	}

	this.getQueryString = function(useBrackets) {
		var querySplit = [];
		for (var varName in this.query) {
			var curArray = this.query[varName];
			for (var i=0; i < curArray.length; i++) {
				curDatum = curArray[i];
				curChunk =
				   varName
				   + (useBrackets ? '['+i+']' : '')
				   + '='+encodeURIComponent(curDatum)
				;
				querySplit.push(curChunk);
			}
		}

		return querySplit.join('&');
	}
		
	this.toString = function(useBrackets) {
		var myPath = this.path || '';
		if ((myPath.length > 0) && (myPath.charAt(0) != '/'))
			myPath = '/'+myPath;

		return (this.scheme ? this.scheme+'://' : '')
		   //ONLY PUT IN USERNAME, PASSWORD, AND PORT IF A HOST IS DEFINED
		   + (!isUndefined(this.host)
		      ? (this.username
		            ? this.username
		               +(this.password ? ':'+this.password : '')
		               +'@'
		            : ''
		         )
		         + (this.host || '')
		         + (this.port ? ':'+this.port : '')
		      : ''
		   )
		   + myPath
		   + (this.query ? '?'+this.getQueryString(useBrackets) : '')
		   + (this.anchor ? '#'+this.anchor : '')
		;
	}

	this.resetQuery();

	//USE toString() SO WE DON'T GET SOME WACKY DOM OBJECT
	if (isUndefined(rawUri)) {
		try {
			rawUri = window.location.toString();
		} catch(e) {
			rawUri = '';
		}
	}

	//COPIED FROM RFC 2396, APPENDIX A (ESCAPED FOR JAVASCRIPT)
	//THE RFC, STRANGELY, DEFINES A PATH AS NEEDING TWO FORESLASHES, SO
	//I'VE CHANGED \/\/ to (?:\/\/)?, SINCE WHAT THE RFC SAYS AIN'T REAL LIFE.
	var uriRegex = /^(([^:\/?#]+):)?((?:\/\/)?([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;

	var componentLocations = {
		scheme: 2,
		serverInfo: 4,
		path: 5,
		queryString: 7,
		anchor: 9
	};
			
	var uriMatch = rawUri.match(uriRegex);
	var components = {};
	for (var c in componentLocations) {
		var cl = componentLocations[c];
		if (!isUndefined(uriMatch[cl]))
			components[c] = uriMatch[cl];
	}

	this.scheme = components.scheme;

	if (!isUndefined(components.serverInfo)) {
		var serverInfoSplit =
		   this._splitServerInfo(components.serverInfo);
		for (var c in serverInfoSplit) this[c] = serverInfoSplit[c];
	}

	this.path = components.path;

	if (!isUndefined(components.queryString)) {
		this.setRawQueryString(components.queryString);
	}

	this.anchor = components.anchor;
}
