//
// URL - URL.js
// Adam Mark Finlayson, WTS
// amf%northwestern!edu
// 
// v0.1.1
//



//====== Constructor ======//

// 
// Iniitialize some variables
// Define the memebers of the object
//
function URL(newURL) {
		// using JavaScript's Location members
	this.hash = getHash(newURL) ;
	this.host = getHost(newURL) ;
	this.hostname = getHostname(newURL) ;
	this.href = newURL ;
	this.pathname = getPathname(newURL) ;
	this.port = getPort(newURL) ;
	this.protocol = getProtocol(newURL) ;
	this.search = getSearch(newURL) ;
	
		// convenience members (from URL spec)
	this.path = this.pathname ;
	this.query = this.search ;
	this.part = this.hash ;
	this.fragment = this.hash ;
	
		// URL Functions
	this.getResource = getResource ;
	this.getSearchHash = getSearchHash ;
	this.getProtocolHost = getProtocolHost ;
	this.joinHREF = joinHREF ;
	this.getNormalHREF = getNormalHREF ;
	this.getFilename = getFilename ;
	this.equalsHREF = equalsHREF ;
}

function newURLFromLocationAndDocument(location, document) {
	var locationURL = new URL(location.href) ;
	var url = locationURL ;
	
	var baseList = document.getElementsByTagName("base") ;
	if(baseList.length > 0) {
		var base = baseList[baseList.length-1] ;
		if(base != null) {
			var baseHREF = base.getAttribute("href") ;
			if(baseHREF != null) {
				joinedHREF = locationURL.joinHREF(baseHREF) ;
				url = new URL(joinedHREF) ;
			}
		}
	}
	
	return url ;
}



//====== Location Functions for Contructor ======//

//
// protocol://hostname:port/pathname?search#HASH
//
function getHash(url) {
	url_parts = url.split("/") ;
	filename_query_hash = url_parts[url_parts.length-1] ;
	
	crosshatchIndex = filename_query_hash.indexOf("#") ;
	if(crosshatchIndex==-1) crosshatchIndex = filename_query_hash.length ;
	hash = filename_query_hash.substring(crosshatchIndex+1, filename_query_hash.length) ;
	
	return hash ;
}

//
// protocol://HOSTNAME:PORT/pathname?search#hash
//
function getHost(url) {
	if(getProtocol(url)=="") return "" ;
	
	hostname = getHostname(url) ;
	port = getPort(url) ;
	
	//if(port==80) return hostname ;
	if(getDefaultPort(getProtocol(url)) == port) {
		return hostname ;	
	}
	return hostname + ":" + port ;
}

//
// protocol://HOSTNAME:port/pathname?search#hash
//
function getHostname(url) {
	if(getProtocol(url)=="") return "" ;
	
	url_parts = url.split("://") ;
	host_plus = url_parts[1] ;
	
	host_plus_parts = host_plus.split("/") ;
	host_port = host_plus_parts[0] ;
	
	host_port_parts = host_port.split(":") ;
	host = host_port_parts[0] ;
	
	return host ;
}

//
// protocol://hostname:port/PATHNAME?search#hash
//
function getPathname(url) {
	var path_query_hash = "" ;
	
	if(getProtocol(url)=="") {
		path_query_hash = url ;
	} else {
		url_parts = url.split("://") ;
		host_plus = url_parts[1] ;
		
		slashIndex = host_plus.indexOf("/") ;
		if(slashIndex==-1) return "" ;
		path_query_hash = host_plus.substring(slashIndex, host_plus.length) ;
	}
	
	questionIndex = path_query_hash.indexOf("?") ;
	if(questionIndex==-1) questionIndex = path_query_hash.length ;
	crosshatchIndex = path_query_hash.indexOf("#") ;
	if(crosshatchIndex==-1) crosshatchIndex = path_query_hash.length ;
	pathname = path_query_hash.substring(0, Math.min(questionIndex,crosshatchIndex)) ;
	
	return pathname ;
	//return "!" ;
}

//
// protocol://hostname:PORT/pathname?search#hash
//
function getPort(url) {
	if(getProtocol(url)=="") return -1 ;
	
	url_parts = url.split("://") ;
	host_plus = url_parts[1] ;
	
	host_plus_parts = host_plus.split("/") ;
	host_port = host_plus_parts[0] ;
	
	host_port_parts = host_port.split(":") ;
	port = host_port_parts[1] ;
	if(port==null) return getDefaultPort(getProtocol(url)) ;
	return port ;
}

//
// PROTOCOL://hostname:port/pathname?search#hash
//
function getProtocol(url) {
	if(url.indexOf("://") > -1) {
		url_parts = url.split("://") ;
		protocol = url_parts[0] ;
		return protocol ;
	} else {
		return "" ;	
	}
}

//
// protocol://hostname:port/pathname?SEARCH#hash
//
function getSearch(url) {
	url_parts = url.split("/") ;
	filename_query_hash = url_parts[url_parts.length-1] ;
	
	questionIndex = filename_query_hash.indexOf("?") ;
	if(questionIndex==-1) questionIndex = filename_query_hash.length ;
	query_hash = filename_query_hash.substring(questionIndex+1, filename_query_hash.length) ;
	
	crosshatchIndex = query_hash.indexOf("#") ;
	if(crosshatchIndex==-1) crosshatchIndex = query_hash.length ;
	query = query_hash.substring(0, crosshatchIndex) ;
	
	return query ;
}



//====== URL Functions ======//

//
// protocol://hostname:port[/PATH?SEARCH#HASH]
// includes leading slash
//
function getResource() {
	var resource = this.pathname ;
	return this.pathname + this.getSearchHash() ;
}

//
// protocol://hostname:port/pathname[?SEARCH#HASH]
// includes leading ? or # (when appropriate)
//
function getSearchHash() {
	var search_hash = "" ;
	if(this.search != "") search_hash += "?" + this.search ;
	if(this.hash != "") search_hash += "#" + this.hash ;
	return search_hash ;
}

//
// [PROTOCOL://HOSTNAME:PORT]/pathname?search#hash
// does not include trailing slash
//
function getProtocolHost() {
	if(this.protocol=="") return "" ;
	return this.protocol + "://" + this.host ;
}

//
// Merge this URL with otherHREF (string)
// Returns string of otherHREF relative to this.href
//
function joinHREF(otherHREF) {
	var otherURL = new URL(otherHREF) ;
	var joinedHREF = "" ;
	
		// if otherHREF has a protocol, it is a fully-qualified URL
	if(otherURL.protocol != "") return otherURL.href ;
	
		// if otherHREF is root-relative
	if(otherURL.pathname.indexOf("/")==0) return (this.getProtocolHost() + otherURL.getResource()) ;
		// otherwise, joinedHREF goes at least through the filename
	joinedHREF = this.getProtocolHost() ;
	if(this.pathname.indexOf("/") != 0) joinedHREF += "/" ;
	joinedHREF += this.pathname ;
	
		// if otherHREF is a search
	if(otherHREF.indexOf("?") == 0) {
		return joinedHREF + otherURL.getSearchHash() ;
	}
		// otherwise, joinedHREF contians at least search
	if(this.search != "") joinedHREF += "?" + this.search ;
	
		// if otherHREF is a hash
	if(otherHREF.indexOf("#") == 0) {
		return joinedHREF + "#" + otherURL.hash ;
	}
	
	
	// if we're still here, otherHREF is document relative (which is hard)
	thisPathStack = this.pathname.split("/") ;
		// if it doesn't end in trail slash, the first thing comes off the stack
	if(this.pathname.lastIndexOf("/") != (this.pathname.length-1)) thisPathStack.pop() ;
	
	otherPathQueue = otherHREF.split("/") ;
	while(otherPathQueue.length > 0) {
		pathElement = otherPathQueue.shift() ;
		if(pathElement == "..") {
			thisPathStack.pop() ;
		} else if(pathElement == ".") {
			// do nothing
		} else {
			thisPathStack.push(pathElement) ;
		}
	}
	
	joinedPath = "" ;
	while(thisPathStack.length > 0) {
		item = thisPathStack.shift() ;
		if(item != "") {
			if(joinedPath != "") joinedPath += "/" ;
			joinedPath += item ;
		}
	}
		// does this start with a slash (i.e., not a relative URL)
	if(otherHREF.charAt(0)=="/" || this.pathname.charAt(0)=="/" || this.getProtocolHost() != "") {
		joinedPath = "/" + joinedPath ;
	}
	return this.getProtocolHost() + joinedPath ;
	
		// tsnh
	//return "-1" ;
}

//
// returns normalized href (removed assumed portions such as index.html)
// *** could use a lot of work as protocols are added to getDefaultPort()
// *** could use normalizing of hostname:port (when default)
// *** could use nromalizing of search, hash (i.e., /file.html# -> file.html)
//
function getNormalHREF() {
	var normalPath = "" ;
	
	// first, normailize . and .. in paths
	var thisPathStack = this.pathname.split("/") ;
	var normalPathElements = new Array() ;
	
		// iterate though each element (/ delimited)
	while(thisPathStack.length > 0) {
		var pathElement = thisPathStack.shift() ;
		if(pathElement == "..") {
			// if it goes up, pop off the normaized version
			normalPathElements.pop() ;
		} else if(pathElement == ".") {
			// do nothing
		} else {
			// if it's a regualr string, push it
			normalPathElements.push(pathElement) ;
		}
	}
	
		// create the normalPath
	while(normalPathElements.length > 0) {
		var item = normalPathElements.shift() ;
		if(item != "") {
			if(normalPath != "") normalPath += "/" ;	// don't add / to the first item
			normalPath += item ;
		}
	}
		// should it start with a slash?
	if(this.pathname.charAt(0)=="/" || getProtocolHost != "") {
		normalPath = "/" + normalPath ;
	}
		// should it end with a slash?
	if(this.pathname.charAt(this.pathname.length-1)=="/") {
		normalPath += "/" ;
	}
	
		// prevent // case when path is /
		// That case satisfies both of the above [v0.1.1]
	if(normalPath == "//") {
		normalPath = "/" ;
	}
	
	//
	// Handle normalization for each protocol
	// *** should be split into various functions, as should the above
	//
	
	
	// http and https
	// trim index.html, index.php, etc
	if(this.protocol=="http" || this.protocol=="https") {
		var filename = this.getFilename() ;
			// if the filename starts with "index."
		if(filename.indexOf("index.")==0) {
			normalPath = normalPath.substring(0, normalPath.lastIndexOf(filename)) ;
		}
	}
	
	return this.getProtocolHost() + normalPath + this.getSearchHash() ;
}

//
// protocol://hostname:port/various/folder/[FILENAME]?search#hash
//
function getFilename() {
		// if doesn't end in "/"
	if(this.pathname.lastIndexOf("/") != (this.pathname.length-1)) {
		var pathParts = this.pathname.split("/") ;
		if(pathParts.length > 0) {
			return pathParts[pathParts.length-1] ;
		}
	}
	return "" ;
}

//
// compare this.href to otherHREF (string) in normailzed form
// return boolean
//
function equalsHREF(otherHREF) {
	var otherURL = new URL(otherHREF) ;
	//alert("this = " + this.getNormalHREF()) ;
	//alert("other = " + otherURL.getNormalHREF()) ;
	
	
		// see if their normal forms compare
	if(this.getNormalHREF() == otherURL.getNormalHREF()) {
			// if they both have filenames, check them (so index.php != index.html)
		if(this.getFilename() != "" && otherURL.getFilename() != "") {
			if(this.getFilename() == otherURL.getFilename()) {
				return true ;
			} else {
				return false ;
			}
		}
		return true ;
	} else {
		return false ;	
	}
}


//====== Internal Functions ======//

//
// returns default ports for common protocols
// *** could use a lot of work
//
function getDefaultPort(protocol) {
	if(protocol=="http") return 80;
	if(protocol=="https") return 443;
	return -1 ;	
}
