var Delicious = {}; tags = {}

var onclicks = {
	rm:	rmPostConfirm, 			rmYes: rmPostYes, 		rmNo: postNo,
	share: sharePostConfirm, 	shareYes: sharePostYes,	shareNo: postNo,
	edit: editPost
}

document.onclick = function(e){
	e = e||window.event
	var t = e.target||e.srcElement
	if (t.onclick) return;
	var r = true
	if(t.className){
		t.className.splitrim(' ').each(function(c){
			if(onclicks[c]) {
				onclicks[c].call(t, e)
				r = false
	}})}
	return r
}

var Fold = {
	folded : {},
	go : function(){
		var label,newfold,newlabel,i
		
		Cookies.get('_fold').split(' ').each(function(i){ Fold.folded[i] = 1 })
		
		var folds = $c('bundles',document,'ul').mapc(function(ul){
			return Array.from(ul.childNodes).map(function(child){
				if(isA(child, 'fold')) return child
			})
		})
		this.ffhack() //weird spacing bug in ff
		folds.each(function(f){
			label = $c('label', f)[0]
			if(label) {
				label.label = $tag('span',label).innerHTML.replace(/[ ;]+/g,'')
				Fold.makeArrow(label,'d');

				newfold = create('li', {css: f.className})
				swapClass(newfold,'fold','grey')

				newlabel = label.cloneNode(true)
				$tag('img', newlabel).src="/static/img/arrow.r.gif"

				newfold.appendChild(newlabel)
				f.parentNode.insertBefore(newfold, f)

				var defold = isA(f, 'defold')

				if((!defold && Fold.folded[label.label]) || (defold && !Fold.folded[label.label])) { newfold.style.display = 'block'; f.style.display = 'none' }
				else newfold.style.display = 'none'

				label.style.cursor = newlabel.style.cursor = 'pointer'
				var setCookie = defold ? false : true
				label.onclick = Fold.makeToggle(newfold, f, label.label, setCookie)
				newlabel.onclick = Fold.makeToggle(f, newfold, label.label, !setCookie)
	}})},
	makeArrow : function(label,i){
		var img = create('img', {src: "/static/img/arrow."+i+".gif", width: 8, height: 8})
		if(i == 'r') img.className = 'grey'
		addClass(label,'arrow')
		label.insertBefore(img, label.firstChild)
	},
	makeToggle : function(show,hide,cookieKey,cookieSet){ return (function(){
		var cookie='',f
		show.style.display = 'block'; hide.style.display = 'none'
		if(cookieSet) Fold.folded[cookieKey] = 1
		else delete Fold.folded[cookieKey]
		for(f in Fold.folded) cookie += f + ' '; Cookies.set('_fold', cookie, 30)
	})},
	ffhack : function() {
		$c('bundles',document,'ul').each( function(o){
			o.insertBefore( create('li', {html: '&nbsp;', css: 'bundle ffhack'}), o.firstChild )
		})
}}

var Cookies = {
	set : function(name, value, days){ days = days||30
		var date = new Date(); date.setTime( date.getTime()+(days*24*60*60*1000) )
		document.cookie = name + "=" + value + "; expires=" + date.toGMTString() + "; domain=."+location.host+"; path=/"
	},
	get : function(name){
		var ca = document.cookie.split(';'),i,c; name += '='
		for(i=0; i < ca.length; i++) {
			c = ca[i];
			while (c.charAt(0)==' ') c = c.substring(1,c.length)
			if (c.indexOf(name) == 0) return c.substring(name.length,c.length)
		}
		return ''
}}

var Mp3 = {
	playimg: null,
	player: null,
	go: function() {
		$c('mp3', document, 'a').each( function(a){
			var img = create('img', {src: '/static/img/mp3/play.gif', title: 'listen', css: 'player', height: 12, width: 12})
			img.onclick = Mp3.makeToggle(img, a.href)
			a.parentNode.insertBefore(img, a)
	})},
	toggle: function(img, url) {
		if (Mp3.playimg == img) Mp3.destroy()
		else {
			if (Mp3.playimg) Mp3.destroy()
			img.src = '/static/img/mp3/stop.gif'; Mp3.playimg = img;
			Mp3.player = create('span', {html: '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"' +
			'codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0"' +
			'width="50" height="15" align="middle" class="player">' +
			'<param name="allowScriptAccess" value="sameDomain" />' +
			'<param name="flashVars" value="theLink='+url+'" />' +
			'<param name="movie" value="/static/swf/mp3.swf" /><param name="quality" value="high" />' +
			'<param name="bgcolor" value="#ffffff" />' +
			'<embed src="/static/swf/mp3.swf" flashVars="theLink='+url+'"'+
			'quality="high" bgcolor="#ffffff" width="50" height="15" name="player"' +
			'align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"' +
			' pluginspage="http://www.macromedia.com/go/getflashplayer" class="player" /></object>'})
			img.parentNode.insertBefore(Mp3.player, img.nextSibling)
	}},
	destroy: function() {
		Mp3.playimg.src = '/static/img/mp3/play.gif'; Mp3.playimg = null
		Mp3.player.removeChild(Mp3.player.firstChild); Mp3.player.parentNode.removeChild(Mp3.player); Mp3.player = null
	},
	makeToggle: function(img, url) { return function(){ Mp3.toggle(img, url) }}
}

var Crumb = {
	go: function(root){
		var p = $id('crumb')
		var tag = p.innerHTML.unescHtml();
        var f = create('form', { method:'get', action:'/', onsubmit: function(){ return false; } });
		var r = create('input', {type:'hidden', name: 'root', value: root});
		var o = create('input', {name: 'crumb', css: 'crumb', originalValue: tag, value: tag, root: root || '/tag/',
			onblur: Crumb.blur, onfocus: Crumb.focus, onmouseover: Crumb.mouseover, onmouseout: Crumb.mouseout,
			onkeyup: Crumb.keyhandler, onkeypress: Crumb.keyhandler});
        f.appendChild(r);
        f.appendChild(o); f.className = 'crumbform';
		p.innerHTML = ''; p.appendChild(f)
		Crumb.sizer = makeTextSize(getTextStyle(o), p)
		o.style.width = getTextSize(tag, Crumb.sizer) + 40 + 'px'
	},
	mouseover: function() { addClass(this, 'crumb-focus') },
	mouseout: function() { if(!this.focused) rmClass(this, 'crumb-focus') },
	focus: function() { this.focused = true; addClass(this, 'crumb-focus') },
	blur: function() {
		if (this.submitting) return false
		this.focused = false
		this.value = this.originalValue
		rmClass(this, 'crumb-focus')
		this.style.width = getTextSize(this.value, Crumb.sizer) + 40 + 'px'
	},
	keyhandler: function(e) { e = e||window.event
		if (e.type == 'keypress' && e.keyCode == 13) {
			var tag = this.value.replace(/ +/g, '+')
			if (tag) {
				this.submitting = true
				location.href = this.root + encodeURIComponent(tag)
		}}
		this.style.width = getTextSize(this.value, Crumb.sizer) + 40 + 'px'
}}

function footer() {
	var h = windowHeight(), footer = $id('footer'), bottom = getY($id('bottom')), sidebar = $id('sidebar')
	var fh = footer.clientHeight || footer.offsetHeight // todo: why does IE report 0 for clientHeight?
	if (bottom != 0) {
		if (bottom < h - fh) footer.style.marginTop = ((h - fh)-bottom-3)+'px'
		else footer.style.marginTop = 0
	}
	footer.parentNode.style.visibility = 'visible'
}

function addEngine(){
	if ((typeof window.sidebar == "object") &&
		(typeof window.sidebar.addSearchEngine == "function")) {
			window.sidebar.addSearchEngine(
				"http://del.icio.us/search/delicious.src",
				"http://del.icio.us/static/img/search/delicious.gif",
				"delicious",
				'Web' );
}}

handOver = function(o){ o=o||this; o.style.cursor = 'pointer' }
handOut = function(o){ o=o||this; o.style.cursor = 'default' }

function postNo(){
	var confirm = this.parentNode, commands = nextElement(confirm)
	commands.style.display = 'inline'
	remove(confirm)
}

function sharePostConfirm(){
	var commands = this.parentNode
	commands.style.display = 'none'
	var s = create('div', {css: 'commands'})
	s.appendChild( create('span', {html: '&nbsp;share this post?&nbsp;', css: 'important'}))
	s.appendChild( create('a', {html: 'no', css: 'hand shareNo', onmouseover: handOver, onmouseout: handOut }))
	s.appendChild( create('text', ' / ') )
	s.appendChild( create('a', {html: 'yes', css: 'hand shareYes', onmouseover: handOver, onmouseout: handOut }))
	commands.parentNode.insertBefore(s, commands)
}

function sharePostYes(){
	var confirm = this.parentNode, commands = nextElement(confirm), post = confirm.parentNode, share = $('.share', post)[0]
	remove(share.previousSibling, share, confirm, $('.private', post)[0])
	commands.style.display = 'inline'
	var data = getPostData(post)
	var postBody = 'jump=no&format=none&private=0'
	var fields = { description: data.desc, url: data.url, oldurl: data.url, date: data.isoDate, notes: data.notes, tags: data.tags, key: data.key}
	for(var i in fields) postBody += '&' + i + '=' + encodeURIComponent(fields[i])
	var url = 'http://' + location.host + '/' + Delicious.cuser
	new Ajax({url: url, method: 'post', postBody: postBody })
}

function rmPostConfirm(){
	var commands = this.parentNode, post = commands.parentNode
	commands.style.display = 'none'
	var s = create('div', {css: 'commands'})
	s.appendChild( create('span', {html: '&nbsp;delete this post?&nbsp;', css: 'important'}))
	s.appendChild( create('a', {html: 'no', css: 'hand rmNo', onmouseover: handOver, onmouseout: handOut }))
	s.appendChild( create('text', ' / ') )
	s.appendChild( create('a', {html: 'yes', css: 'hand rmYes', onmouseover: handOver, onmouseout: handOut, href: this.href }))
	post.insertBefore(s, commands)
}
function rmPostYes(){
	var post = this.parentNode.parentNode
	if ($('.flickr-thumbnail-hook',post).length) {
		post = post.parentNode.parentNode.parentNode.parentNode; // td -> tr -> table -> li
	}
	;(new fx.FadeSize(
		post, { duration: 100, onComplete: function(){ remove(post) } }
	)).toggle('height')
	new Ajax({ url: this.href + '&format=none', method: 'get' })
}

loadTags = function(t) { tags = t; prepareTagSearch() }

function getPostData(post) {
	var link = $('.desc', post).$('a')[0]
	return { link: link,
		commands: $('.commands', post)[0],
		desc: (link.innerHTML || '').unescHtml(),
		url: link.href || '',
		notes: $('.notes', post).map( function(o){ return o.innerHTML } ).join('').unescHtml(),
		tags: $('.tag', post).map( function(o){ return o.innerHTML } ).join(' ').unescHtml(),
		date: $('.date', post).map( function(o){ return o.innerHTML } ).join(''),
		isoDate: $('.date', post).map( function(o){ return o.title } ).join(''),
		meta: $('.meta', post)[0],
		privacy: $('.private', post)[0] ? true : false,
		autoprivacy: $('.autoprivate')[0] ? true : false,
		n: $('.notes', post)[0],
		username: $('.user', post).map( function(o){ return o.innerHTML } ).join(''),
		editUrl: $('.edit', post).map( function(o){ return o.href } ).join(''),
		copyUrl: $('.copy', post).map( function(o){ return o.href } ).join(''),
		className: post.className,
		key: post.getAttribute('key'),
		isFlickrThumb: $('.flickr-thumbnail-hook',post).length
}}

function editPost(type){
	type = 'edit'
	var post = this.parentNode.parentNode
	var data = getPostData(post)
	if (!data.link) return true
	var form = makePostForm(data, post, type)
	post.parentNode.insertBefore(form, post)
	hide(post)
}

function attachScript(id, url){
	if($id(id)) return
	var s = create('script', {type: 'text/javascript', src: url, id: id })
	$tag('head').appendChild(s)
}

function makePostForm(data, post, type) {
	data = extend(Object.fromArray(['desc','url','isoDate','notes','tags','date', 'privacy'], ''), data)
	// (new fx.FadeSize(post, {duration: 3000})).toggle()
	// moofx doesnt work on things with padding :(

	if (data.tags.length) data.tags += ' '
	type = type || 'edit'
	if(type == 'copy'){ data.notes = data.tags = data.isoDate = '' }

	var form = create(data.isFlickrThumb ? 'div' : 'li', {css: data.className})

	var flickrThumbFactor = data.isFlickrThumb ? 0.8 : 1.0;
	var inputStyle = $id('fp-recent') ? { width: parseInt($id('fp-recent').offsetWidth * 0.8 * flickrThumbFactor) + 'px' }  : { width: parseInt($id('main').offsetWidth * 0.75 * flickrThumbFactor) + 'px' }
	
	var fields = {
		description: create('input', { value: data.desc, style: inputStyle, css: 'desc', attr:{maxlength:255}, onkeyup: keysubmit}),
		url: create('input', { value: data.url, style: inputStyle, css: 'url', onkeyup: keysubmit }),
		oldurl: create('input', { value: data.url, type: 'hidden' }),
		privacy: create('input', { type: 'checkbox', checked: (data.privacy ? true : false), defaultChecked: (data.privacy ? true : false) }), 
		date: create('input', {value: data.isoDate, type: 'hidden' }),
		notes: create('input', { value: data.notes, style: inputStyle, css: 'notes', attr:{maxlength:255}, onkeyup: keysubmit }),
		tags: create('input', {value: data.tags, style: inputStyle, autocomplete: 'off', css: 'tags',
		onfocus: showSuggest, onblur: hideSuggest, onkeypress: keypress, onkeyup: keyup, onkeydown: keydown }),
		save: create('button', { html: 'save', css:'save', style: { fontWeight: 'bold', cssFloat: 'left' }, onclick: save }),
		cancel: create('button', { html: 'cancel', onclick: cancel, css:'cancel', style:{marginLeft: '10px', clear:'none', cssFloat: 'left'} }),
		full: create('a', { html: 'full-screen edit', attr:{href: data.editUrl || data.copyUrl}, style: {marginLeft: '10px', cssFloat: 'right'} }),
		dont: create('label', {html: 'do not share', onclick: function(){ fields.privacy.checked = !fields.privacy.checked }})		
	}
   
	var table = TABLE([
		ROW(['title', fields.description]),
		ROW(['url', fields.url]),
		ROW(['notes', fields.notes]),
		ROW(['tags', fields.tags]),
		((global_user_flag_private && (!data.autoprivacy)) ?
			ROW(['&nbsp;', [fields.save, fields.cancel, fields.privacy, fields.dont, fields.oldurl, fields.date, fields.full]])
			:
			ROW(['&nbsp;', [fields.save, fields.cancel, fields.oldurl, fields.date, fields.full]])
		)
	], {css: 'post-form'} )
   form.appendChild(table)

   function showSuggest(){ Suggestions.show(this) }
	function hideSuggest(){ Suggestions.hide() }
	// todo: move these into their own obj
	function keysubmit(e){ e=e||window.event
		switch(e.keyCode){
			case 13:
				save()
	}}
	function keypress(e){ e=e||window.event
		switch(e.keyCode){
			case 38: case 40:
				prevent(e)
				break
			case 9:
				if(Suggestions.parent) prevent(e)
				break
			case 13:
				if(Suggestions.parent && Suggestions.list.picked > 0) prevent(e)
				break
			default: Suggestions.lastValue = this.value
		}
	}
	function keyup(e){ e=e||window.event
		switch(e.keyCode){
			case 38: case 40:
				prevent(e)
				break
			case 9:
				if(Suggestions.parent) {
					if (Suggestions.list.picked == 0) Suggestions.suggest(1)
					Suggestions.complete()
					prevent(e)
				}
				break
			case 13:
				if(Suggestions.parent && Suggestions.list.picked > 0) {
					Suggestions.complete()
					prevent(e)
				} else save()
				break
			case 35: //end
			case 36: //home
			case 39: //right
			case 37: //left
			//case 32: //space
				Suggestions.hide()
				break
			default: Suggestions.upd(this)
		}
	}
	function keydown(e) { e=e||window.event
		if(Suggestions.parent == this) {
			switch(e.keyCode) {
				case 27:
					Suggestions.hide()
					prevent(e)
					break
				case 40:
					Suggestions.suggest((Suggestions.list.picked + 1) % Suggestions.list.length)
					Suggestions.scrollDropdown()
					prevent(e)
					break
				case 38:
					Suggestions.suggest(Suggestions.list.picked == 0 ? Suggestions.list.length - 1 : Suggestions.list.picked - 1)
					Suggestions.scrollDropdown()
					prevent(e)
					break
	}}}
	function save(){
		if(!fields.url.value.trim() || !fields.description.value.trim()) return
		Suggestions.hide()
		fields.url.value = suckyCanonicalize(fields.url.value)
		if (changed()) {
			var postBody = ['jump=no','ajaxedit=1','key='+data.key], dat = {}
			for(var i in fields) {
				if(fields[i].nodeName != 'INPUT') continue
				if (fields[i].type == 'checkbox') {
					if (i == 'privacy') {
						// remap the 'privacy' fields property to the expected post param 'private'
						postBody[postBody.length] = 'private=' + encodeURIComponent(fields[i].checked ? '1' : '0')
					} else {
						postBody[postBody.length] = i + encodeURIComponent(fields[i].checked ? '1' : '0')
					}
					dat[i] = fields[i].checked ? 1 : 0
				} else {
					postBody[postBody.length] = i + '=' + encodeURIComponent(fields[i].value)
					dat[i] = fields[i].value
				}
			}
			dat.isFlickrThumb = data.isFlickrThumb;
			var url = 'http://' + location.host + '/' + Delicious.cuser;

            form.className += ' saving';
            $('.save', form)[0].disabled = true;
            $('.cancel', form)[0].disabled = true;

            new Ajax({
                url: url, method: 'POST', postBody: postBody.join('&') + '&format=none',
                onComplete: function(req) {
                    dat.tags = dat.tags.splitrim(' ')
                    dat.date = data.date; dat.isoDate = data.isoDate
                    dat.className = data.className
                    dat.autoprivacy = data.autoprivacy;
                    var newPost = buildPost(dat, type)
                    post.parentNode.insertBefore(newPost, form)
                    //if (type == 'edit') remove(post)
                    //else show(post)
                    remove(post, form)
                },
                onError: function(req) {

                }
            });
		} else cancel()
	}
	function cancel(){
		if (data.isFlickrThumb) {
			remove(form)
			show(post)
		} else {
			show(post)
			;(new fx.Height(
				form, { duration: 20, onComplete: function(){ remove(form) } }
			)).toggle('height')
			//remove(form)
		}
	}
	function changed(){ return true }

	return form
}

// todo: replace with something correct
function suckyCanonicalize(t) {
	if(!t.match(/:/)) t = 'http://' + t
	if(t.match(/^http(s)?:/g)) {
		var m = t.match(/\//g)
		if(m.length && m.length < 3) {
			t = t.split('?')
			t[0] = t[0] + '/'
			t = t.join('')
		}
	}
	return t
}

function buildPost(data, type) {
	data = extend({url: '', description: '', notes: '', tags: [], timestamp: null, date: '', className: 'post'}, data || {})
	var user = Delicious.cuser
	if (!data.date) data.date = '... just posted' //formatDate(timestamp)
	if (data.tags) data.tags = data.tags.map(function(t){ return '<a class="tag" href="/'+user+'/'+t.encodeTag()+'">'+t.escHtml()+'</a>' }).join(' ')
	var urlEncoded = encodeURIComponent(data.url)

	var li = create(data.isFlickrThumb ? 'div' : 'li', {css: data.className})
	
	li.innerHTML = '<h4 class="desc"><a href="'+data.url+'" rel="nofollow">'+data.description.escHtml()+'</a>'+
	(data.isFlickrThumb ? '<em class="flickr-thumbnail-hook"></em>' : '')+
	'</h4>'+
	'<div class="commands"> &nbsp;<a href="/'+user+'?url='+urlEncoded+';jump=no" class="edit">edit</a><span> / </span>'+
	((data.privacy && (!data.autoprivacy)) ? '<a href="/'+user+'?url='+urlEncoded+';jump=no" class="share">share</a> / ' : '') +
	'<a href="/'+user+'?deleteurl='+urlEncoded+';jump=no" class="rm" rel="nofollow">delete</a></div>' +
	(data.notes ? '<p class="notes">'+data.notes.escHtml()+'</p>' : '') +
	'<div class="meta">' +
	(type == 'copy' ? 'by <a href="/'+user+'" class="user">'+user+'</a> ' : '') +
	(data.tags ? 'to ' + data.tags : '') +
	(data.privacy ? '<span class="private'+(data.autoprivacy ? ' autoprivate' : '')+'">... <span style="color: red">not shared'+(data.autoprivacy ? ' <a href="/doc/dangerous">[?]</a>' : '')+'</span></span>' : '') +
	'<span class="date" title='+data.isoDate+'>' +data.date+'</span></div>'
	return li
}

function cancelPost(o) {
	var form = o.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
	form.nextSibling.style.display = 'block'
	
	;(new fx.FadeSize(
		form, { duration: 20, onComplete: function(){ remove(form) }}
	)).toggle('height')
}

function newPostInit(){
	var post = $id('new-post')
	if (post) post.onclick = function(){ newPostToggle(); return false }
}

function newPostToggle(){
	var postForm = $id('new-post-form')
	if(postForm) {
		(new fx.Height(
			postForm, { duration: 100, onComplete: function(){ remove(postForm) }}
		)).toggle('height')
		//remove(postForm)
	} else {
		postForm = makePostForm2()
		postForm.id = 'new-post-form'
		$id('main').insertBefore(postForm, $id('main').firstChild)
		var slider = new fx.Height( postForm, { duration: 20 })
		slider.hide('height') // i hate having to do this
		slider.toggle('height')
	}
}

function makePostForm2(options) {
	options = extend({url: '', desc: '', notes: '', tags: ''}, options || {})
	var onclick = options.url ? 'cancelPost(this)' : 'newPostToggle()'
	var form = create('ol', {css: 'posts'})
	form.innerHTML = '<li><form><table><tbody>' +
	'<tr><td class="rs">title</td><td><input type="text" name="description" id="description" size="80" value="'+options.desc+'" /></td></tr>' +
	'<tr><td class="rs">url</td><td><input type="text" name="url" id="url" size="80" value="'+options.url+'" /></td></tr>' +
	'<tr><td class="rs">notes</td><td><input type="text" name="notes" id="notes" size="80" value="'+options.notes+'" /></td></tr>' +
	'<tr><td class="rs">tags</td><td><input type="text" name="tags" id="tags" size="80" autocomplete="off" value="'+options.tags+'" /></td></tr>' +
	'<tr><td class="rs">&nbsp;</td><td><input type="submit" value="save" style="font-weight:bold" /> &nbsp;<button onclick="'+onclick+'">cancel</button></td></tr>' +
	'</tbody></table>' +
	'<input type="hidden" name="jump" value="no" />' +
	'</form></li>'
	return form
	//<input name="oldurl" value="color:f00,0f0,00f" type="hidden" />
	
}

function prepareTagSearch(){ if (!window.tagSearch){
	tagSearch = ''
	var freqSort=[], freqMap={}
	for(var t in tags){
		if (!freqMap[tags[t]]) { freqMap[tags[t]] = {}; freqSort[freqSort.length] = tags[t] }
		freqMap[tags[t]][t] = true
		// make alpha section
	}
	freqSort.sort( function(a, b){ return b - a } )
	for(var i in freqSort) {
		for(t in freqMap[freqSort[i]]) {
			tagSearch += t + ' '
			// make freq section
	}}
}}

addLoadEvent(function(){
	prepareTagSearch()
	Suggestions = new function(){
		this.html = create('div', {id: 'inline-suggestions', style: { visibility: 'hidden' },
			onmouseover: function(){ Suggestions.mouseovered = true },
			onmouseout: function(){ Suggestions.mouseovered = false }
		 })
		document.body.appendChild(this.html)
		this.clear = function(){
			this.parent = null
			this.word = {text: '', index: -1}
			this.lastValue = ''
			this.html.innerHTML = ''
		}
		
		this.show = function(o){ var me = this.html
			attachScript('json-tags', '/feeds/json/tags/'+Delicious.cuser+'?count=500&callback=loadTags')
			if (!me.firstChild) return
			var pos = 0, tagz = o.value.split(' ')
			var maxHeight = tagHeight * 20 // number of tags to show before making a scrollbar
			for(var i=0; i < this.word.index; i++) { pos += tagz[i].length+1 }
			var text = o.value.substr(0, pos)
			var textWidth = getTextWidth(text, getTextStyle(o), o.parentNode)

			me.style.top = getY(o) + o.offsetHeight - 1 + 'px'

			extend(me.style, { height: 'auto', width: 'auto', overflow: 'visible' })
			var suggestHeight = getY(me) + me.offsetHeight

			if(pageScrollY() + windowHeight() < suggestHeight) {
				extend(me.style, { height: pageScrollY() + windowHeight() - getY(me) - 2 + 'px', overflow: 'auto' })
				if(me.clientWidth < me.scrollWidth) me.style.width = me.scrollWidth + (me.scrollWidth - me.clientWidth) + 'px' // get rid of horizontal scrollbars on ie overflow divs
			}

			if(maxHeight < me.offsetHeight) {
				extend(me.style, { height: maxHeight + 'px', overflow: 'auto' })
				if(me.clientWidth < me.scrollWidth) me.style.width = me.scrollWidth + (me.scrollWidth - me.clientWidth) + 'px' // get rid of horizontal scrollbars on ie overflow divs
			}
			
			me.style.left = getX(o) + textWidth + 'px' // put dropdown right below current typed tag
			if(getX(me) + me.offsetWidth > getX(o) + o.offsetWidth) { // force dropdown to right align to tags input
				me.style.left = getX(me) - (getX(me) + me.offsetWidth - getX(o) - o.offsetWidth) + 'px'
			}
			this.parent = o
			this.lastValue = o.value
			show(me)
			me.scrollTop = 0 // needs to be after becoming visible (opera)
		}
		this.hide = function(force){
			if(this.mouseovered && !force) return
			var me = this.html
			this.clear()
			invisible(me)
		}
		this.upd = function(o){

			var input = $tags('input')[1]

			var c = getChangedWord(this.lastValue, o.value)
			//log(c)
			if(c.confused || c.empty) this.hide(true)
			else if(c.changed) {
				if(this.word.text == '') this.hide(true)
				this.word.text = c.word
				this.word.index = c.index
				this.populate(o, this.word.text.escRegExp())
			}
		}
		this.scrollDropdown = function(){
			var me = this.html
			var amt = Math.ceil((Math.ceil(me.offsetHeight - tagHeight) / tagHeight) / 2 )
			var scrollTo = (this.list.picked * tagHeight) - (amt * tagHeight)
			me.scrollTop = (scrollTo < 0) ? 0 : scrollTo
		}
		this.suggest = function(index) {
			var list = this.list
			if(list.length == 1) index = 0
			if(list[list.picked].className) rmClass(list[list.picked], 'selected')
			addClass(list[list.picked = index], 'selected')
		}
		this.complete = function(tag) {
			var safariSelect = true
			var input = this.parent, tagArray = input.value.split(' '), list = this.list
			if(typeof(tag) == 'undefined') { // tab completion rather than click complete
				tag = list[list.picked].innerHTML.unescHtml()
				safariSelect = false
			}
			tagArray[this.word.index] = tag
			var text = tagArray.join(' ')
			input.value = (text.substr(-1,1) == ' ' ? text : text + ' ' )
			Suggestions.mouseovered = false
			Suggestions.hide()
			//updateHilight()
			focusTo(input, safariSelect)
		}
		this.list = new Sug()
		this.populate = function(o, txt){

			var me = this.html
			me.innerHTML = ''
			
			var tagHash = Object.fromArray(o.value.toLowerCase().splitrim(' '))
			
			delete this.list
			this.list = new Sug()
			var list = this.list

			var search = tagSearch.match(new RegExp(("(?:^| )("+txt+"[^ ]+)"), "gi"))
			if(search){
				for (i = 0; i< search.length; i++) {
					tl = search[i].trim()
					if(tagHash[tl])  continue // do not suggest already typed tag
					var text = '' // tags[tl] ? ' ('+tags[tl]+')' : ''
					list[list.length] = makeTag(me, tl, 'complete', text)
					list.length++
			}}
			if (list.length > 1) this.show(o)
			else this.hide()
		}
	}
	Suggestions.clear()
})

Sug = function(){ this.length=1; this.picked=0; this[0] = 'dummy' }

function tagClick(t){ return function(){ Suggestions.complete(t) }}
function tagOver(){ addClass(this, 'selected') }
function tagOut(){ rmClass(this, 'selected') }

var tagHeight = 0
function makeTag(parent, tag, js, post, style) {
	var text = tag
	if(post) text += post

	style = extend({}, style)
	var obj = create('div', { css: 'tag', html: text, style: style, onmouseover: tagOver, onmouseout: tagOut, onclick: tagClick(tag) })
	
	if(tags[tag] < 2) obj.style.color = '#555'
	else if(tags[tag] == 2) obj.style.color = '#333'
	//if(tags[tag] > 10) obj.style.fontSize = '90%'
	parent.appendChild(obj)
	if (tagHeight == 0) tagHeight = obj.offsetHeight
	return obj
}

function getChangedWord(o, n){
	if(o.toLowerCase() == n.toLowerCase()) return {nochange:true}
	
	var os = o.split(' '),ns = n.split(' ')

	var c = 0
	while (os.length && os[0] == ns[0]) { os.splice(0, 1); ns.splice(0, 1); c++}
	os.reverse(); ns.reverse()
	
	var c2 = 0
	while (os.length && os[0] == ns[0]) { os.splice(0, 1); ns.splice(0, 1); c2++}
	os.reverse(); ns.reverse()
	
	if(ns.length > 1) return {confused:true}
	else if(!ns.length && !os.length) return {empty:true}
	else if(!ns.length) return {changed:true, word:n.split(' ')[c-1], index:c-1}
	else if(ns[0] == '') return {empty:true}
	else return {changed:true, word:ns[0], index:c}
}

function mailer(oName,oDomain) {
 email="mailto:" + oName + "@" + oDomain;
 window.location=email;
}

//addLoadEvent(newPostInit)
//addLoadEvent(editPostInit)

Delicious.path = (location.href.split('?')[0] || '').toLowerCase()
Delicious.cuser = (Cookies.get('_user').split('%20')[0] || '').toLowerCase()
Delicious.puser = (location.pathname.substring(1).split('/')[0] || '').toLowerCase()

function doSearchType() {
	if ($id('searchtype').value == 'web') {
		$id('searchform').target = '_blank';
		$id('searchform').action = 'http://search.yahoo.com/search';
	} else {
		$id('searchform').target = '';
		$id('searchform').action = '/search/';
	}
}

// HACK: Prevent breakage when Firebug console logging left in place.
if (typeof console == 'undefined') {
    console = { log: function(msg) { } };
}

/**
    Code to support dynamic client-side insertion of shortcut search query
    forms.
*/
Delicious.Shortcuts = {

    /**
        Initialize shortcut posts on page load.
    */
    init: function() {
        var _this = this;
        forEach($('.posts').$('.shortcut'), function(p) { 

            // Only wire up shortcuts with '%s' placeholders.
            var link = $('.desc', p).$('a')[0];
            var href = link.getAttribute('href');
            if (!/\%s/.test(unescape(href))) return;

            _this.wireUpShortcutPost(p); 

            // Memory leak voodoo
            p = null;
        })
    },

    /**
        Utility to generate a new unique ID for each new form.
    */
    form_id: 0,
    newFormID: function() {
        return 'shortcutform'+this.form_id++;
    },

    /**
        Given the DOM node to a shortcut post not owned by the logged in user,
        report error / help message on click.
    */
    wireUpOthersShortcutPost: function(p) {
        var _this = this; // Handy obj ref for use in nested / distant scopes.

        var link = $('.desc', p).$('a')[0];
        link.onclick = function() {
            // TODO: Need a better message than this.
            alert("This bookmark is a shortcut.  You should save it to your own collection to use it.");
            return false;
        };

        // Memory leak voodoo
        link = null;
    },

    /**
        Given the DOM node to a shortcut post, build and inject a search form.
    */
    wireUpShortcutPost: function(p) {
        var _this = this; // Handy obj ref for use in nested / distant scopes.

        var link = $('.desc', p).$('a')[0];
        var url  = link.getAttribute('href');
        var fid  = this.newFormID();

        // Build the magic shortcut query form, whose submit button calls formOnSubmit back here.
        var form = $FORM({ 'id':fid, 'style':'display:none', 'class':'shortcutform', 'onsubmit':function() { return _this.formOnSubmit(fid) } },
            $INPUT({'name':'url',    'id':fid+'url',   'type':'hidden', 'value':url}),
            $INPUT({'name':'query',  'id':fid+'query', 'type':'text', 'class':'query'}),
            $INPUT({'name':'submit', 'type':'submit', 'value':'go'})
        );

        // Intercept the link click, redirect it to focus on the shortcut query form.
        link.onclick = function() {
            $('#'+fid)[0].style.display = 'block';
            $('#'+fid+'query')[0].focus();
            return false;
        };

        // Shove the form in just before the post metadata.
        var meta = $('.meta', p)[0];
        p.insertBefore(form, meta);
        
        // Memory leak voodoo
        form = null;
        link = null;
    },

    /**
        Upon submission of a shortcut form, perform the query substitution and go there.
    */
    URL_RE: new RegExp("%s", 'i'),
    formOnSubmit: function(fid) {
        var url = $('#'+fid+'url')[0].value;
        var query = $('#'+fid+'query')[0].value;
        var result = url.replace(this.URL_RE, encodeURIComponent(query));
        location.href = result;
        return false;
    },

    /* Object terminator for great comma justice */
    EOF:null
}

addLoadEvent(function() { Delicious.Shortcuts.init() });

Delicious.TagDesc = function() {
    
    return { 

        MAX_CHAR_LEN: 1000,
        TEXTAREA_ID:  'tagdesc',
        COUNTER_ID:   'tagdesc_char_count',

        init: function() {
            addLoadEvent(this.onLoad.bind(this));
            return this;
        },

        onLoad: function() {
            this.wireUpDisplay();
            this.wireUpEditor();
        },

        wireUpDisplay: function() {
            var desc = $('.tagdesc_display');
            if (desc.length) {
                // Hijack links found in tag description display.
                var actions = ['hide', 'show', 'edit', 'delete'];
                for (var i=0,n; n=actions[i]; i++) {
                    var links = $('#tagdesc_'+n);
                    if (links.length) links[0].onclick = this[n+'Desc'].bind(this);
                }
            }

            var delete_yes = $('.tagdesc_delete').$('.delete');
            if (delete_yes.length) 
                delete_yes[0].onclick = this.deleteDescConfirm.bind(this);

            var delete_no = $('.tagdesc_delete').$('.cancel');
            if (delete_no.length) 
                delete_no[0].onclick = this.deleteDescCancel.bind(this);

            var create = $('.tagdesc_create');
            if (create.length) 
                create[0].onclick = this.editDesc.bind(this);
        },

        wireUpEditor: function() {
            var editors = $('.tagdesc_editor');
            if (editors.length) {

                // Wire up the description text area counter.
                var ta = $('#'+this.TEXTAREA_ID)[0];
                this.startCounterUpdating();

                $('.tagdesc_editor').$('.cancel')[0].onclick = 
                    this.cancelEditDesc.bind(this);
                $('.tagdesc_editor').$('.save')[0].onclick = 
                    this.saveDesc.bind(this);
                /*
                $('.tagdesc_editor').$('.delete')[0].onclick = 
                    this.deleteDescButton.bind(this);
                */

            }
        },

        startCounterUpdating: function() {
            this.stopCounterUpdating();
            this.counter_interval = window.setInterval(this.updateCounter.bind(this), 375);
        },

        stopCounterUpdating: function() {
            if (this.counter_interval) {
                window.clearInterval(this.counter_interval);
                this.counter_interval = null;
            }
        },

        updateCounter: function() {
            var ta = document.getElementById(this.TEXTAREA_ID);
            if (!ta) return this.stopCounterUpdating();

            var cnt = document.getElementById(this.COUNTER_ID);
            var char_left = this.MAX_CHAR_LEN - ta.value.length;
            cnt.innerHTML = Math.abs(char_left) + ' characters ' + ( (char_left >= 0) ?
                'remaining' : 'too many' );
            cnt.parentNode.className = (char_left >= 0) ? 'counter' : 'counter_over';
            // $('.tagdesc_editor').$('.save')[0].disabled = (char_left < 0);
            return char_left;
        },

        hideDesc: function(evt) {

            // Set the cookie to prevent display on next refresh.
            Cookies.set('hidedesc', 'hide');

            // Hide the paragraph with the extended tag description.
            var para = $('.tagdesc_display').$('p')[0];
            para.style.display = 'none';

            // Add the (more) link if necessary, and show it.
            var shows = $('#tagdesc_show_wrapper');
            if (shows.length == 0) {
                var h4 = $('.tagdesc_display').$('h4')[0];
                var show = $SPAN({'id':'tagdesc_show_wrapper'}, ['( ', $A({'id':'tagdesc_show', 'href':'?showdesc'}, ["more"]), ' )'] );
                h4.appendChild(show);
                show.onclick = this.showDesc.bind(this);
                shows = [show];
            }
            if (shows.length > 0)
                shows[0].style.visibility = 'visible';

            return false;

        },

        showDesc: function(evt) {

            // Set the cookie to prevent display on next refresh.
            Cookies.set('hidedesc', 'show');

            var paras = $('.tagdesc_display').$('p');
            if (paras.length == 0) {
                // If there's no description in-page, load one up via AJAX.
                var disp = $('.tagdesc_display')[0];
                var user = $('#curr_user')[0].innerHTML;
                var tag  = $('#curr_tag')[0].innerHTML;

                new Ajax({ 
                    method: 'GET',
                    url: '/show_tag_description?' + this.encodeParams({
                        'username' : user,
                        'tag'      : tag,
                        '__'       : (new Date()).getTime()
                    }),
                    onComplete: function(req) {
                        var desc = $('.tagdesc_display')[0];
                        desc.innerHTML = req.responseText;
                        this.wireUpDisplay();
                    }.bind(this),
                    onError: function(req) {
                        window.location.href = window.location.href +'?showdesc';
                    }.bind(this)
                });

            } else {
                // Reveal the existing extended description.
                paras[0].style.display = 'block';
            }

            // Hide the (more) link.
            var shows = $('#tagdesc_show_wrapper');
            if (shows.length > 0) shows[0].style.visibility = 'hidden';

            prevent(evt); return false;
        },

        editDesc: function(evt) {

            // If the editor is already there, just reveal it.
            var existing = $('.tagdesc_editor')[0];
            if (existing) {
                
                /*
                var curr_title_ele = $('#curr_tagdesc_title')[0];
                if (curr_title_ele) $('#tagtitle')[0].value = curr_title_ele.innerHTML.unescHtml();

                var curr_desc_ele  = $('#curr_tagdesc_description')[0];
                if (curr_desc_ele) $('#tagdesc')[0].value = curr_desc_ele.innerHTML.unescHtml();
                */

                existing.style.display = 'block';
                this.wireUpEditor();

                var desc = $('.tagdesc_display')[0];
                if (desc) desc.style.display = 'none';
                
                prevent(evt); return false;
            }

            var main = $('#main')[0];
            var new_form = $DIV({'class':'tagdesc_editor'});
            new_form.innerHTML = "Loading tag description editor...";
            main.parentNode.insertBefore(new_form, main);

            var tag = $('#curr_tag')[0].innerHTML;
            new Ajax({ 
                method: 'GET',
                url:    '/edit_tag_description?tag='+encodeURIComponent(tag)+'&__='+(new Date()).getTime(),
                onComplete: function(req) {
                    var desc = $('.tagdesc_display')[0];
                    if (desc) desc.style.display = 'none';
                    new_form.innerHTML = req.responseText;
                    this.wireUpEditor();
                }.bind(this),
                onError: function(req) {
                    window.location.href = window.location.href +'?editdesc';
                }.bind(this)
            });

            prevent(evt); return false;
        },

        cancelEditDesc: function(evt) {
            var desc = $('.tagdesc_display')[0];
            if (desc) {
                // Un-hide the original description if available.
                desc.style.display = 'block';  
            }

            // Kill the editor div.
            var editor = $('.tagdesc_editor')[0];
            editor.style.display = 'none';
            this.setEditorButtonsEnabled(true);
            // editor.parentNode.removeChild(editor);

            prevent(evt); return false;
        },

        encodeParams: function(params) {
            var parts = [];
            for (k in params) 
                parts.push(k + '=' + encodeURIComponent(params[k]));
            return parts.join('&');
        },

        saveDesc: function(evt) {
            var params = {
                tag:         $('.tagdesc_editor').$('.tag')[0].value,
                key:         $('.tagdesc_editor').$('.key')[0].value,
                title:       $('#tagtitle')[0].value,
                description: $('#tagdesc')[0].value
            };
            var data  = this.encodeParams(params);

            new Ajax({
                method:   'POST',
                url:      '/set_description?mode=ajax',
                postBody: data,
                
                onComplete: function(req) {
                    var disp = $('.tagdesc_display')[0];
                    if (!disp) {
                        disp = $DIV({'class':'tagdesc_display'});
                        var main = $('#main')[0];
                        main.insertBefore(disp, main.firstChild);
                    }
                    disp.innerHTML = req.responseText;
                    
                    var create_link = $('.tagdesc_create_wrapper')[0];
                    if (create_link) create_link.parentNode.removeChild(create_link);

                    this.cancelEditDesc(evt);
                    this.wireUpDisplay();
                }.bind(this),
                
                onError: function(req) {
                    alert("There was a problem saving your tag description, please try again.");
                    this.setEditorButtonsEnabled(true);
                }.bind(this)

            });

            this.setEditorButtonsEnabled(false);
            prevent(evt); return false;
        },

        setEditorButtonsEnabled: function(enabled) {
            var buttons = ['cancel', 'save', 'delete'];
            for (var i=0,bn; bn=buttons[i]; i++) {
                var button = $('.tagdesc_editor').$('.'+bn)[0];
                if (button) {
                    button.disabled = !enabled;
                    if (bn == 'save') 
                        button.value = (!enabled) ? 'Saving...' : 'Save';
                }
            }
        },

        deleteDesc: function(evt) {
            var disp = $('.tagdesc_display')[0];
            var user = $('#curr_user')[0].innerHTML;
            var tag  = $('#curr_tag')[0].innerHTML;

            new Ajax({ 
                method: 'GET',
                url: '/show_tag_description?' + this.encodeParams({
                    'deletedesc' : 1,
                    'username'   : user,
                    'tag'        : tag,
                    '__'         : (new Date()).getTime()
                }),
                onComplete: function(req) {
                    var desc = $('.tagdesc_display')[0];
                    desc.innerHTML = req.responseText;
                    this.wireUpDisplay();
                }.bind(this),
                onError: function(req) {
                    window.location.href = window.location.href +'?deletedesc';
                }.bind(this)
            });
            
            prevent(evt); return false;
        },

        deleteDescCancel: function(evt) {
            var delete_form = $('.tagdesc_delete')[0];
            if (delete_form)
                delete_form.parentNode.removeChild(delete_form);
            prevent(evt); return false;
        },

        deleteDescConfirm: function(evt) {
            var params = {
                'tag':    $('.tagdesc_delete').$('.tag')[0].value,
                'key':    $('.tagdesc_delete').$('.key')[0].value,
                'delete': 'yes'
            };
            var data  = this.encodeParams(params);

            new Ajax({
                method:   'POST',
                url:      '/set_description?mode=ajax',
                postBody: data,
                
                onComplete: function(req) {
                    
                    var disp = $('.tagdesc_display')[0];
                    if (disp) disp.parentNode.removeChild(disp);

                    var editor = $('.tagdesc_editor')[0];
                    if (editor) editor.parentNode.removeChild(editor);

                    var create_link_wrapper = $('.tagdesc_create_wrapper')[0];
                    if (!create_link_wrapper) {
                        create_link_wrapper = $SPAN({'class':'tagdesc_create_wrapper'}, [
                            ' (', $A({'class':'tagdesc_create', 'href':'?editdesc'}, [ "create tag description" ]), ')'
                        ]);
                        var curr_tag = $('#curr_tag')[0];
                        curr_tag.parentNode.insertBefore(create_link_wrapper, curr_tag.nextSibling);
                        this.wireUpDisplay();
                    }

                }.bind(this),
                
                onError: function(req) {
                    alert("There was a problem deleting your tag description, please try again.");
                }.bind(this)

            });

            prevent(evt); return false;
        },

        deleteDescButton: function(evt) {
            return confirm("Delete this tag description, are you sure?");
            /*
            if ( confirm("Delete this tag description, are you sure?") ) {
                return this.deleteDescConfirm(evt);
            }
            prevent(evt); return false;
            */
        },

        EOF: null

    };

}().init();

function warnDangerousURL() {
	alert('Warning!\n\nThis link is not to a normal web page, so it may not be safe -- make sure the address is correct, especially if you typed it in. del.icio.us has disabled visiting this link in order to protect your account. If you are SURE it is safe and from a trusted source, there are two ways you can still use it:\n\n1. Add the link to your browser\'s bookmarks: on your bookmarks page, right-click (or control-click) the link and choose "Add to Favorites" or "Bookmark This Link".\n\n2. Visit the link manually: on your bookmarks page, right-click (or control-click) the link and choose "Copy Shortcut" or "Copy Link Location". Then, paste the url into your address bar.');
	return false;
}
