/* TUN3R.com - Copyright 2007 by Conalgo Inc.
*/
Drag = {
    _move: null,
    _done: null,
    _mouseout: null,

    dragStartPos: null,
	needlePos: null,
	stationX: 2, stationY: 2,	// impt: C3
	stationZ: 0,
	firstTime: true,
	detailCount: 0,
	curLang: 'all',
	curGenre: 'all',
	curStream: '',		// URL to stream
	curStreamPopup: 0,
	detailsPos: -1,
	liveMode: 0,
	lmWindow: null,

	dancing: false,
	danceTime: 0,

	results: { count: 0 },
	resultPos: 0,

	// pixels
	MAX_Y: 300,			// height
	MAX_X: 2000,		// width
	// stations
	DIAL_H: 15,	
	DIAL_W: 100,	

	lastSearch: "",
	lastSearchType: false,
	focusTrack: true,

	idleTime: 0,

	/* DOM elements */
	needle: null, dial: null,
	textStationPos: null,
	textStationTitle: null,

    init: function() {
        Drag.needle = getElement('needle');
        Drag.dial = getElement('dial');
        Drag.textStationPos = getElement('stationPos');
        Drag.textStationTitle = getElement('stationTitle');
        Drag.textStationHome = getElement('stationHome');
        Drag.textStationStream = getElement('stationStream');
        Drag.textStreamDetails = getElement('stationDetails');
        Drag.textCapturedTrack = getElement('capturedTrack');
        Drag.thumbnailImg = getElement('thumbnailImg');
        Drag.thumbnailLink = getElement('thumblink');
		Drag.tuneinBtn = getElement('tunein');
		Drag.hideRow1 = getElement('hiderow1');
		Drag.hideRow2 = getElement('hiderow2');
		Drag.hideRow3 = getElement('hiderow3');
		Drag.hideRow4 = getElement('hiderow4');
		Drag.playedBox = getElement('playedBox');
		//Drag.keyCatcher = getElement('keycatcher');
		Drag.keyCatcher = getElement('searchBtn');
		Drag.outerDiv = getElement('outerDiv');

		Drag.clearDetails();

		connect(Drag.needle, 'onmousedown', Drag.start);
		connect(Drag.dial, 'onclick', Drag.zoomTo);
		connect(getElement('tun3r_search'), 'onkeyup',
					Drag.searchKeyPress);
		connect(getElement('track_search'), 'onkeyup',
					Drag.searchKeyPress);
		connect(Drag.keyCatcher, 'onkeydown', Drag.keyPress);

		var dialSelectors = getElementsByTagAndClassName('td', 'dialsel');
		for(var i = 0; i < dialSelectors.length; i++) {
			var el = dialSelectors[i];
			connect(el, 'onclick', dialSelect);
			connect(el, 'onmouseover', dialSelOver);
			connect(el, 'onmouseout', dialSelOut);
		}

		//Drag.needle.style.cursor = "hand, -moz-grab";
		//createLoggingPane(true);			// DEBUG ONLY

		// Below works on Firefox, but not Opera. Kinda fixed value anyway
		// W=100, H=1000
		//sz = elementDimensions(Drag.needle);
		//log("Needle size: " + sz.repr());
		//Drag.needle._offset = new MochiKit.DOM.Coordinates(
		//								(sz.w / 2), (sz.h / 2));
		Drag.needle._offset = new MochiKit.DOM.Coordinates(50, 500);

		Drag.needlePos = new MochiKit.DOM.Coordinates(tun3r_startX,
														tun3r_startY);
		Drag.needleTo(Drag.needlePos, true, true);
		Drag.gotoDialZ(tun3r_startZ, true, true);

        connect(document, 'onmouseout', Drag._mouseout);

		// update stats since are we are connected
		Drag.since = new Date();
		callLater(5, Drag.updateStatsNow);

		// setup targeted ads
		//Target.init();
		Drag.redrawBookmarks();

		// show search stuff, now that it can work.
		showElement("matching");
		Drag.keyCatcher.focus();

		// update certain fields, maybe trigger a search.
		if(tun3r_startup) {
			var isTrack = 0;
			if(tun3r_startup.tr) {
				getElement("track_search").value = tun3r_startup.tr;
				isTrack = 1;
			}
			if(tun3r_startup.hp) {
				getElement("tun3r_search").value = tun3r_startup.hp;
			}
			if(tun3r_startup.lg) {
				getElement("filterlang").value = tun3r_startup.lg;
				newLangGenre(0);
			}
			if(tun3r_startup.ge) {
				getElement("filtergenre").value = tun3r_startup.ge;
				newLangGenre(1);
			}
			if(tun3r_startup.start) Drag.newSearch(isTrack);
		}

		if(!!Number(readCookie('t3_livemode'))) {
			liveMode(true, false);
		} else {
			setVisible("lm0area", false);
		}
	},
    
	// Start dragging.
    start: function(e) {
        e.stop();
		Drag.idleTime = 0;
        
		// Need to know pixel and dial position that we started at.
        Drag.dragStartPos = e.mouse().page;
        Drag.dragStartNeedlePos = Drag.needlePos;
		Drag.keyCatcher.focus();
        
		// change the cursor to closed hand.
		//Drag.needle.style.cursor = "move";

		// connect event handles.
        Drag._move = connect(document, 'onmousemove', Drag._drag);
        Drag._done = connect(document, 'onmouseup', Drag._stop);
    },
    
    _diff: function(lhs, rhs) {
        return new MochiKit.DOM.Coordinates(lhs.x - rhs.x, lhs.y - rhs.y);
    },
        
	// Mouse is being dragged.
    _drag: function(e) {
        e.stop();

		Drag.dancing = false;

		// relative pixel motion so far (accumulates)
		var dest = Drag._diff(e.mouse().page, Drag.dragStartPos);

		// offset by starting needle position to make a dial pos
		dest.x += Drag.dragStartNeedlePos.x;
		dest.y += Drag.dragStartNeedlePos.y;

		Drag.needleTo(dest, false, false);
    },
    
    _stop: function(e) {
		// stop dragging events
        disconnect(Drag._move);
        disconnect(Drag._done);
		Drag._move = null;

		// change cursor back to normal
		//Drag.needle.style.cursor = "-moz-grab, grab";

		// snap the needle to centre of current station.
		Drag.needleTo(Drag.needlePos, true, false);
    },

    _mouseout: function(e) {
		if(Drag._move == null) return;

		// want to know when mouse leaves dial area, during drag
		var name = getNodeAttribute(e.target(), 'id');
		var name2 = getNodeAttribute(e.relatedTarget(), 'id');
		//log("n=" + name + " n2=" + name2);

		if(name == 'needle' && name2 != 'dial') {
			// .. there are many other mouse-out events we see that don't care
			// about here..
			if(Drag._move != null) Drag._stop(e);
		}
	},

    zoomTo: function(e) {
		var off = elementPosition(Drag.dial);

		//log("Need correction: " + off);
		Drag.idleTime = 0;
		Drag.dancing = false;
		Drag.keyCatcher.focus();

        e.stop();
		pos = e.mouse().page
		pos.x -= off.x;
		pos.y -= off.y;
		/*log("Pos relative to dial: " + e.mouse().page.x 
										+ "," + e.mouse().page.y);*/

		Drag.needleTo(pos, true, false);
    },

	setScroll: function(sx) {
		// Set horz scroll position. Remember user can override anytime!
		Drag.outerDiv.scrollLeft = sx;
	},

	// Do we need to scroll horizontally? If so, do it.
	// xPos is a pixel position in un-scrolled dial.
/*
	needScroll: function(xPos) {
		// NOTE: xPos is relative to viewport
		var sx = Drag.scrollX;
		var w = getViewportDimensions().w;
		var delta = 0;
		var zone = 60;		// how close to edge b4 moving dial
		var x = xPos + sx;	// viewport X

		if(w < zone*2) return;		// too narrow a window to use!

		//log("xPos-sx=" + (xPos-sx) + "  w=" + w);
		if(x < zone && (sx>0)) {
			// need to scroll leftwards
			delta = -20;
		} else if(x >= (w-zone) && (sx <= (Drag.MAX_X - w))) {
			// need to scroll right.
			delta = 20;
		}
		Drag.textStationPos.firstChild.data = "x="+x
			+" xPos=" + xPos + " sx=" +sx +"  w=" + w + " delta=" + delta;
		if(!delta) return

		sx += delta;
		if(sx < 0) sx = 0;

		// correct any drag underway
		Drag.dragStartNeedlePos.x -= delta;

		Drag.setScroll(sx);
	},
*/

	// Send the needle to indicated dial coordinate
	// (pixels w/ dial top-left origin). Maybe snap to centre of item.
	// Maybe horz-scroll dial.
	needleTo: function(dest, centre, setupTime) {
		if(dest.x < 0) dest.x = 0;
		if(dest.x > Drag.MAX_X) dest.x = Drag.MAX_X-1;
		if(dest.y < 0) dest.y = 0;
		if(dest.y > Drag.MAX_Y) dest.y = Drag.MAX_Y-1;

		if(centre) {
			// point to middle of cell
			Drag.snap(dest);
			dest.x += 10;
			dest.y += 10;
		}

		// remember needle position
		Drag.needlePos = dest;

		// perhaps scroll to keep needle visible
		//Drag.needScroll(dest.x);

		Drag.needleMoved(Math.floor(dest.x / 20), Math.floor(dest.y / 20), setupTime);

		// determine position on page
		dest = Drag._diff(dest, Drag.needle._offset);

		// go there
        setElementPosition(Drag.needle, dest);
    },

	updateStatsNow: function() {
		var d = loadJSONDoc("/fc/stats", { ses: tun3r_session,
				x: Drag.stationX, y:Drag.stationY, z:Drag.stationZ,
				cacheKill: new Date().getTime() });

		var gotData = function(data) {
			var x = getElement('statStations');
			x.firstChild.data = data.stations;
			x = getElement('statUsers');
			x.firstChild.data = data.users;
			x = getElement('statUptime');
			x.firstChild.data = data.uptime;
			x = getElement('statConnected');
			x.firstChild.data = data.connected;
		};
		var fetchFailed = function(err) {
			//alert("Failed to get data.");
		};

		d.addCallbacks(gotData, fetchFailed);

		var now = new Date();
		var mins = Math.floor((now.getTime() - Drag.since.getTime()) / 60000);
/*
		if((mins == 2) && !Target.gotSome) {
			// show something, anything after first 2 minutes of usage
			Target.refresh();
		}
*/
		if(mins == 1) monetize();
		if(mins && ((mins % 5) == 0)) {
			Drag.idleTime++;
		}
		// after three hours, we don't care anymore.
		if(mins < (60*3)) callLater(30, Drag.updateStatsNow);
		else getElement('statConnected').firstChild.data = '3+ hrs';
	},

	updateInfoNow: function() {
		Drag.detailCount++;

		var d = loadJSONDoc("/fc/details", { x: Drag.stationX,
					y:Drag.stationY, z:Drag.stationZ,
					ses: tun3r_session, cacheKill: new Date().getTime() });

		var gotData = function(data) {
			Drag.textStationHome.firstChild.data = data.homepage;
			Drag.textStationHome.href = data.homeurl;
			var current = '';

/*
			Drag.textStationStream.href = data.stream;
			Drag.textStationStream.firstChild.data 
				= data.stream.replace(/([^\/:]\/)/g,"$1 ").replace(/\?.*$/,'');
*/
			Drag.textStationStream.value = data.stream;
			Drag.textStreamDetails.firstChild.data = data.details;

			Drag.thumbnailLink.href = data.homeurl;
			Drag.thumbnailImg.src = "images/busy-big.gif"; // quick
			Drag.thumbnailImg.src = data.thumb;			   // slow
			showElement(Drag.thumbnailImg);
			showElement(Drag.thumbnailLink);

			Drag.curStream = data.stream;
			Drag.curStreamPopup = data.popup;
			if(data.stream != '' && !Drag.liveMode) {
				setVisible("tuneinPlayers", !data.popup);
				setVisible("tuneinBrowser", data.popup);
				showElement(Drag.tuneinBtn);
				makeVisible(Drag.hideRow2);
			}
			makeVisible(Drag.hideRow1);
			makeVisible(Drag.hideRow3);

			// build RE used to highlight searched items.
			var re = 0;
			if(Drag.lastSearch && Drag.lastSearchType) {
				var qry = Drag.lastSearch.replace(/[^a-z0-9 ]/gi, '');
				qry = qry.replace(/ +/gi, ' ');
				var parts = qry.toLowerCase().split(/ /);
				re = 1;
			}
		
			if(data.plst.length > 0) {
				var lk = new Array();
				var firstMatch = -1, gotUrHearing = false;

				for(i=0; i<data.plst.length; i++) {
					var flags = data.plst[i].f;
					var text = data.plst[i].t;
					if(re && (flags<2)) {
						var txt = text.toLowerCase();
						var matches = 0;
						for(var k=0; k<parts.length; k++) {
							if(txt.search(parts[k]) != -1) {
								matches++;
							}
						}
						if(matches == parts.length) {
							flags |= 2;
							if(firstMatch == -1) firstMatch = i;
						}
					}
					if(flags & 1) {
						if(!gotUrHearing) {
							gotUrHearing = true;
							current = text;
						} else flags &= ~1;
					}
					lk[i] = DIV({'class': 'playedF'+flags}, text, BR());
				}

				replaceChildNodes(Drag.playedBox, lk);

				if(firstMatch != -1) {
					var top = elementPosition(lk[firstMatch], Drag.playedBox).y;
					Drag.playedBox.scrollTop = top;
				} else {
					Drag.playedBox.scrollTop = 0;
				}
			} else {
				replaceChildNodes(Drag.playedBox, "  (none)");
			}

			if(current != '' && !Drag.liveMode) {
				Drag.textCapturedTrack.firstChild.data = current;
				makeVisible(Drag.hideRow4);
			}

			if(Drag.liveMode) {
				var pl = Drag.lmWindow;
				if(!pl || pl.closed) {
					// seem to be stopped right now.
					setVisible("lmplay", true);
					setVisible("lmstop", false);
				} else {
					pl.location = "/fc/pl/frame?x=" + Drag.stationX
						+ "&z=" + Drag.stationZ
						+ "&y=" + Drag.stationY + "&ses=" + tun3r_session
						+ "&cacheKill=" + new Date().getTime();
				}
			}

			createCookie('t3_lastpos',
				Drag.stationX+','+Drag.stationY+','+Drag.stationZ, 30);
		};
		var fetchFailed = function(err) {
			//alert("Failed to get data.");
		};

		d.addCallbacks(gotData, fetchFailed);
	},

	clearDetails: function() {
		Drag.textStationHome.firstChild.data = "";
		Drag.textStationHome.href = "/";
		//Drag.textStationStream.firstChild.data = "";
		//Drag.textStationStream.href = "/";
		Drag.textStationStream.value = "";
		Drag.textStreamDetails.firstChild.data = "";
		hideElement(Drag.thumbnailImg);
		//Drag.thumbnailImg.src = "placeholder.png";
		Drag.thumbnailLink.href = "/";
		replaceChildNodes(Drag.playedBox, " ");
		hideElement(Drag.tuneinBtn);
		makeInvisible(Drag.hideRow1);
		makeInvisible(Drag.hideRow2);
		makeInvisible(Drag.hideRow3);
		makeInvisible(Drag.hideRow4);
		Drag.hiliteTunein(false);
	},

	// Needle may have moved to new station. XY are spot numbers (not pixels)
	needleMoved: function(x, y, setupTime) {
		if(x == Drag.stationX && y == Drag.stationY && !setupTime) {
			// hasn't moved.
			return;
		}
		Drag.stationX = x;
		Drag.stationY = y;

		// draw some HTML changes immediately
		var msg = "ABCDEFGHJKMNPQRS".charAt(y) + (x+1);
		if(Drag.stationZ) msg += ':' + Drag.stationZ;
		Drag.textStationPos.firstChild.data = msg;

		var marked = Drag.isBookmarked();
		getElement('clrMark').disabled = !marked;
		getElement('addMark').disabled = marked;

		var dpos = (x*Drag.DIAL_H) + y 
						+ (Drag.stationZ * Drag.DIAL_H * Drag.DIAL_W);
		if(Drag.detailsPos == -1
			|| titles[Drag.detailsPos] != titles[dpos] 
			|| !Drag.delayedUpdate
		) {
			var isEmpty = (titles[dpos] == null);
			if(isEmpty) {
				msg = "(empty)";
			} else {
				msg = titles[dpos];
			}
			Drag.textStationTitle.firstChild.data = msg

			// clear the other two lines for now, and start a timer to
			// load them later.
			Drag.clearDetails();

			if(Drag.delayedUpdate) Drag.delayedUpdate.cancel();
			if(!isEmpty) Drag.delayedUpdate = callLater(1, Drag.updateInfoNow);

			Drag.detailsPos = dpos;
		}

		// "script" the flash movie
		var sp = document["speaker"];
		if(!sp) sp = document["speakerMovie"];		// for Safari w/ UFO
		if(!sp) return;				// trouble
		if(!sp.dialGoto) return;	// trouble
		if(sp && sp.dialGoto) {
			sp.dialGoto(Number(x), Number(y+(Drag.DIAL_H * Drag.stationZ)))
		}

		if(Drag.firstTime && !setupTime) {
			Drag.firstTime = false;
			Drag.stopStartMusic(1, "");
		}
	},

	// Snap coord to mod 20 values.
	snap: function(pos) {
		var ox = pos.x, oy = pos.y;
		pos.x = (Math.floor(pos.x / 20) * 20)
		pos.y = (Math.floor(pos.y / 20) * 20)

		//log(ox + "," + oy +" snap => " + pos.x + "," + pos.y);

		return pos
	},

	gotoStationXY: function(x,y, quickDetails) {
		var w = Math.floor(getViewportDimensions().w / 20) * 20;
		var max = Math.floor((Drag.MAX_X - w) / 20) * 20;

		Drag.dancing = false;
		Drag.idleTime = 0;

		// convert to pixels.
		x *= 20; y *= 20;

		// is scroll good?
		var sx = getElement('outerDiv').scrollLeft;
		if((x < sx) || (x > sx+w-20)) {
			Drag.setScroll(Math.max(0, x-(w/2)));
		}

		var dest = new MochiKit.DOM.Coordinates(x, y);
		Drag.needleTo(dest, true, false);

		if(quickDetails) {
			if(Drag.delayedUpdate) Drag.delayedUpdate.cancel();
			Drag.updateInfoNow();
		}
	},

	newSearch: function(isTrack) {
		Drag.idleTime = 0;
		Drag.dancing = false;
		var ts = getElement("track_search");
		var hs = getElement("tun3r_search");
		var results = getElement('search_result');
		var str;

		if(isTrack) {
			str = ts.value;
			hs.value = ''; txtLabel(hs, 0);
		} else {
			str = hs.value;
			ts.value = ''; txtLabel(ts, 0);
		}
		Drag.lastSearchType = isTrack;
		Drag.lastSearch = str;

		addElementClass("topnotice", "invisible");

		// start by clearing things.
		Drag.results.count = 0;
		Drag.resultPos = 0;
		getElement('nextMatch').disabled = 1;
		getElement('prevMatch').disabled = 1;

		if(str == '') {
			replaceChildNodes(results, " ");
			addElementClass('dial_highlight', "invisible");

			return;
		}

		// show busy.
		replaceChildNodes(results, "Searching...");
		//Target.search()

		var d = loadJSONDoc("/fc/search", { ses: tun3r_session,
					z: Drag.stationZ, 
					q: str, tracks: (isTrack?1:0), lang: Drag.curLang,
					gen: Drag.curGenre,
					cacheKill: new Date().getTime() });

		var gotData = function(data) {
			if(data.gotoZ != Drag.stationZ) {
				Drag.gotoDialZ(data.gotoZ, true, false);
			}

			Drag.results = data;
			Drag.showSearchResults(true);
		};
		var fetchFailed = function(err) {
			replaceChildNodes(results, "TUN3R server failure. Try later.");
		};

		if(d) d.addCallbacks(gotData, fetchFailed);
		else alert("Can't search from localhost");
	},
	showSearchResults: function(newPos) {
		var i, actual = 0, others = 0;
		var boxes = new Array();
		var data = Drag.results;

		// show search results from in-memory table.	
		if(newPos) Drag.resultPos = -1;

		for(i=0; i<data.count; i++) {
			if(data.ret[i].z != Drag.stationZ) {
				// count matches at other Z, but don't show boxes.
				others++;

				continue;
			}

			boxes[actual] = DIV({'class': 'dialhi',
				'style': 
				'top:'+((data.ret[i].y*20)-2)+
					'px;left:'+((data.ret[i].x*20)-2)+
					'px;width:'+(data.ret[i].w*20)+
					'px;height:'+(data.ret[i].h*20)+'px;'}, "");

			connect(boxes[actual], 'onclick', Drag.zoomTo);
			actual++;
			if(Drag.resultPos == -1) Drag.resultPos = i;
		}
		if(Drag.resultPos == -1) Drag.resultPos = 0;

		var hi = getElement('dial_highlight');
		if(actual) {
			if(newPos) Drag.nextPrevResult(0);
			replaceChildNodes(hi, boxes);
			removeElementClass(hi, "invisible");
		} else {
			var results = getElement('search_result');
			var summary = data.summary;
			if(data.count) {
				summary = others + " matches on other dial(s).";
			}
			replaceChildNodes(hi, "");
			replaceChildNodes(results, summary);
			addElementClass(hi, "invisible");
		}
	},
	nextPrevResult: function(dir) {
		var data = Drag.results;

		// XXX more work needed here: Z mismatch!
		if(Drag.resultPos < data.count-1 && (dir>0)) {
			Drag.resultPos++;
		} else if(Drag.resultPos >= 1 && (dir<0)) {
			Drag.resultPos--;
		} else if(dir && (data.count != 1)) {
			return;
		}

		var p = Drag.resultPos;
		var results = getElement('search_result');

		var line1 = "#" + (p+1) + " of " + data.count + " results";
		replaceChildNodes(results, line1);

		Drag.gotoDialZ(data.ret[p].z, false, false);
		gotoStn(0, data.ret[p].x, data.ret[p].y);
		Drag.stopStartMusic(1, "");
	
		getElement('nextMatch').disabled = (p >= data.count-1);
		getElement('prevMatch').disabled = (p == 0);

		if(dir) monetize();
	},

	// Keypress in search area; for enter, but could be progressive...
	searchKeyPress: function(e) {
		var ks = e.key();
		if(ks.code == 13) {
			//alert(ks.code + " str=" +ks.string);
			e.stop();
			Drag.newSearch((e.src() == getElement('track_search')));
		}
	},

	hiliteTunein: function(set) {
        var e1 = Drag.textStationStream;
		if(set) {
			addElementClass(e1, "blinking");
		} else {
			removeElementClass(e1, "blinking");
		}
	},

/*
	danceStart: function() {
		Drag.stopStartMusic(1, "");
		Drag.dancing = true;
		Drag.danceTime = 0;
		Drag.danceAnimate();
		addElementClass("topnotice", "invisible");
	},
	danceDone: function() {
		addElementClass("volumeup", "invisible");
		//removeElementClass("topnotice", "invisible");
	},
	danceAnimate: function() {
		if(!Drag.dancing) {
			// interrupted.
			Drag.danceDone();
			return;
		}
		Drag.danceTime++;
	
		if((Drag.danceTime >> 2) & 1) {
			removeElementClass("volumeup", "invisible");
		} else {
			addElementClass("volumeup", "invisible");
		}

		var pos = { x: 807-(Drag.danceTime*5), y:263-(Drag.danceTime*1.5) };
		if(pos.y < 50) pos.y = 50;
		if(pos.x <= 50) {
			// done
			pos.x = pos.y = 50;		// == C2
			Drag.danceDone();
			Drag.needleTo(pos, true, false);
		} else {
			Drag.needleTo(pos, false, false);
			callLater(0.050, Drag.danceAnimate);
		}
	},
*/
	keyPress: function(e) {		// Keypress almost anywhere.
		var ks = e.key();
		var pos = Drag.needlePos;
		if(e.modifier().any) return;

		if(ks.string == "KEY_B") {
			bossEnable();
			e.stop();
		} else if(ks.string == "KEY_R") {
			gotoStn(1,0,0);		// Random
			e.stop();
		} else if(ks.string == "KEY_I") {
			Drag.hiliteTunein(true);	// debug
		} else if(ks.string == "KEY_X") {
			Drag.nextPrevResult(1);		// undoc'd
		} else if(ks.string == "KEY_Z") {
			Drag.nextPrevResult(-1);	// undoc'd
		} else if(ks.code == 32 || ks.string == "KEY_N") {	// space
			gotoStn(2,0,0);		// Next
			e.stop();
		} else if(ks.string == "KEY_ARROW_UP") {
			pos.y -= 20;
			Drag.needleTo(pos, true, false);
			e.stop();
		} else if(ks.string == "KEY_ARROW_RIGHT") {
			pos.x += 20;
			Drag.needleTo(pos, true, false);
			e.stop();
		} else if(ks.string == "KEY_ARROW_LEFT") {
			pos.x -= 20;
			Drag.needleTo(pos, true, false);
			e.stop();
		} else if(ks.string == "KEY_ARROW_DOWN") {
			pos.y += 20;
			Drag.needleTo(pos, true, false);
			e.stop();
		} else if(ks.string == "KEY_E") {
			spot = Drag.textStationPos.firstChild.data;
			window.open("/edit/?spot="+spot+":"+Drag.stationZ+"&start=1", "editwin");
		} else if(ks.string == "KEY_M") {
			monetize();
		} else if(ks.string == "KEY_W") {
			testit();
		} else {
			//alert(ks.code + " str=" +ks.string);
		}
	},
	clearAllState: function() {		// clear all form state.
		getElement("filterlang").selectedIndex = 0;
		getElement("filtergenre").selectedIndex = 0;

		var ts = getElement("track_search");
		var hs = getElement("tun3r_search");
		var results = getElement('search_result');
		replaceChildNodes(results, " ");

		Drag.results.count = 0;
		Drag.resultPos = 0;
		Drag.lastSearch = '';
		getElement('nextMatch').disabled = 1;
		getElement('prevMatch').disabled = 1;

		addElementClass('dial_highlight', "invisible");
		hs.value = ''; txtLabel(hs, 0);
		ts.value = ''; txtLabel(ts, 0);
		newLangGenre(0);
	},
	stopStartMusic: function(isPlay, msg) {
		// "script" the flash movie
		if(Drag.liveMode) isPlay = false;
		var sp = document["speaker"];
		if(!sp) sp = document["speakerMovie"];		// for Safari w/ UFO
		if(!sp) return;				// trouble
		if(!sp.dialGoto) return;	// trouble
		if(isPlay) {
			sp.restartMusic();
		} else {
			sp.stopMusic(msg);
		}
	},

	// bookmarks
	markCode: function() {
		var ret = 'x' + Drag.stationX + 'y' + Drag.stationY 
		if(Drag.stationZ) ret += 'z' + Drag.stationZ;
		return ret + ',';
	},
	isBookmarked: function() {
		var pos = Drag.markCode();
		if(tun3r_presets.indexOf(pos) != -1) return true;
		return (tun3r_covered.indexOf(pos) != -1);
	},
	setClrBookmark: function(isSet) {
		var pos = Drag.markCode();

		if(Boolean(isSet) == Drag.isBookmarked()) return;

		var d = loadJSONDoc("/fc/bookmark", { ses: tun3r_session,
				x: Drag.stationX, y:Drag.stationY, z:Drag.stationZ,
				set: Number(isSet), was: readCookie('t3_presets'),
				cacheKill: new Date().getTime() });

		var gotData = function(data) {
			tun3r_presets = data.newPresets;
			tun3r_covered = data.newCovered;
			createCookie('t3_presets', data.newCookie, 365);
			createCookie('t3_userid', data.userid);
			eraseCookie('t3_marks');

			Drag.redrawBookmarks();
		};
		var fetchFailed = function(err) {
			alert("Failed to clr/set preset.");
		};

		d.addCallbacks(gotData, fetchFailed);

		getElement('clrMark').disabled = !isSet;
		getElement('addMark').disabled = isSet;
	},
	redrawBookmarks: function() {
		var i, count = 0, actual = 0;
		var marks = tun3r_presets;
		var where = marks.split(',');

		count = where.length - 1;
		if(count < 0) count = 0;

		var boxes = new Array();
		for(i=0; i<count; i++) {
			var w=1, h=1;
			var str = where[i];
			var xy = str.match(/x([0-9]+)y([0-9]+)($|z([0-9]+))/);
			var x = Number(xy[1]), y = Number(xy[2]);
			var z = 0;

			if(xy[4] != null) z = Number(xy[4]);

			//alert("xy="+xy+" (x,y,z)=" + x + "," +y+', '+z);

			if(z != Drag.stationZ) continue;

			var dpos = (x*Drag.DIAL_H) + y 
						+ (Drag.stationZ * Drag.DIAL_H * Drag.DIAL_W);
			if(sizes[dpos] != null) {
				w = sizes[dpos] / 10;
				h = sizes[dpos] % 10;
			}

			boxes[actual] = DIV({'class': 'dialbm',
				'style': 
				'top:'+(y*20)+'px;left:'+(x*20)+
					'px;width:'+(w*20)+'px;height:'+(h*20)+'px;'}, "");

			connect(boxes[actual], 'onclick', Drag.zoomTo);
			actual++;
		}

		var hi = getElement('dial_highlight2');
		if(actual) {
			replaceChildNodes(hi, boxes);
			makeVisible(hi);
		} else {
			replaceChildNodes(hi, "");
			makeInvisible(hi);
		}
		getElement("nextMark").disabled = !count;
		getElement("prevMark").disabled = !count;
	},
	nextPrevBookmark: function(dir) {
		// find where we are
		var target, i, pos = Drag.markCode();
		var marks = tun3r_presets;
		var where = marks.split(',');
		var count = where.length - 1;

		if(!count) return;		// no bookmarks

		for(i=0; i<count; i++) {
			if(pos == where[i]+',') break;
		}

		target = i + dir;
		if(target >= count) target = 0;
		if(target < 0) target = count-1;

		var xy = where[target].match(/x([0-9]+)y([0-9]+)($|z([0-9]+))/);
		var x = Number(xy[1]), y = Number(xy[2]), z = 0;
		if(xy[4] != null) z = Number(xy[4]);

		Drag.gotoDialZ(z, false, false);
		gotoStn(0, x,y);
	},

	gotoDialZ: function(z, noSearchUpdate, noDialImgChg) {
		if(z == Drag.stationZ) return;

		for(var i=0; i<100; i++) {
			var elem = getElement("dialtab"+i);
			if(elem == null) break;
			if(i == z) {
				addElementClass(elem, "tabOn");
				removeElementClass(elem, "tabOff");
			} else {
				removeElementClass(elem, "tabOn");
				addElementClass(elem, "tabOff");
			}
		}
		
		Drag.stationZ = z;

		drawDialSummary(z);
		if(!noDialImgChg) newLangGenre(Drag.curLang == 'all');
		Drag.redrawBookmarks();
		if(!noSearchUpdate) Drag.showSearchResults(false);

		Drag.needleMoved(Drag.stationX, Drag.stationY, true);
		createCookie('t3_lastpos', Drag.stationX+','+Drag.stationY+','+z, 30);
	}
};

connect(window, 'onload', function() { Drag.init() });
//connect(window, 'onresize', function() { Target.resize(0); });

function flashFails(msg) {
	// rarely reached
	alert("TUN3R.com: Sorry!\n" + msg);
}
function flashCB(msg) {
	//alert("Callback:\n" + msg);
	if(msg == 'idle') {
		// hi-light tune-in links.
		Drag.hiliteTunein(true);
	} else if(msg == 'toolong') {
		// nothing yet
	}
}
function flashReady() {
	// once flash is ready, send server the current needle pos.
	var sp = document["speaker"];
	if(!sp) sp = document["speakerMovie"];		// for Safari w/ UFO
	if(!sp) return;				// trouble
	if(!sp.dialGoto) return;	// trouble
	if(sp) {
		sp.dialGoto(Number(Drag.stationX),
						Number(Drag.stationY+(Drag.DIAL_H * Drag.stationZ)))
	}
}

function makeVisible(elem) {
	removeElementClass(elem, "invisible");
}

function makeInvisible(elem) {
	addElementClass(elem, "invisible");
}
function isVisible(elem) {
	return !hasElementClass(elem, "invisible");
}
function setVisible(elem, state) {
	if(!state) {
		addElementClass(elem, "invisible");
	} else {
		removeElementClass(elem, "invisible");
	}
}

function newLangGenre(isG) {
	var ll = getElement("filterlang").value;
	var gn = getElement("filtergenre").value;
	var fname = '/dial'
	if(ll != 'all' && !isG) {
		fname = '/dial-' + ll;
		gn = 'all'
		getElement("filtergenre").selectedIndex = 0;
	} else if(gn != 'all' && isG) {
		fname = '/dial-g-' + gn;
		ll = 'all'
		getElement("filterlang").selectedIndex = 0;
	}

	Drag.dial.src = "images/busy-dial.gif"; // quick
	Drag.dial.src = tun3r_dialdir + "/dials/" + Drag.stationZ + fname + '.png';

	if(Drag.curGenre != gn || Drag.curLang != ll) {
		Drag.curGenre = gn;
		Drag.curLang = ll;
		if(Drag.lastSearch) Drag.newSearch(Drag.lastSearchType);
	}
}

function gotoStn(code, x, y) {
	var quickUpdate = false;
	if(code == 1) { // random
		for(var i=0; i<100; i++) {
			var x = Math.floor(Math.random() * Drag.DIAL_W);
			var y = Math.floor(Math.random() * Drag.DIAL_H);
			var dpos = (x*Drag.DIAL_H) + y 
						+ (Drag.stationZ * Drag.DIAL_H * Drag.DIAL_W);
			if(titles[dpos] != null) break;
		}
	} else if(code == 2) {	// next
		var x = Drag.stationX;
		var y = Drag.stationY;
		if(x % 2) {
			y++;
			if(y == Drag.DIAL_H) {
				y = Drag.DIAL_H-1;
				x++;
			}
		} else {
			y--;
			if(y == -1) {
				y = 0;
				x++;
			}
		}
	} else if(code == 3) {	// quick details
		quickUpdate = true;
	}

	Drag.gotoStationXY(x, y, quickUpdate);
}

function gotoXYZ(x, y, z) {
	Drag.gotoDialZ(z, false, false);
	Drag.gotoStationXY(x, y, true);
}

function scrollX(code) {
	var oldSX = Drag.outerDiv.scrollLeft;
	var sx = oldSX;
	var w = getViewportDimensions().w;
	var step = Math.floor((w / 5) / 20) * 20;
	var max = Math.floor((Drag.MAX_X - w) / 20) * 20;

	if(code == 0) {
		sx = 0;
	} else if(code == 1) {
		sx -= step;
	} else if(code == 2) {
		sx += step;
	} else if(code == 3) {
		sx = max;
	}
		
	if(sx < 0) sx = 0;
	if(sx > max) sx = max;
	var	delta = sx - oldSX;

	Drag.setScroll(sx);
	var dest = new MochiKit.DOM.Coordinates(Drag.needlePos.x, Drag.needlePos.y);
	dest.x += delta;
	Drag.needleTo(dest, true, false);
}

function bossEnable() {
	Drag.stopStartMusic(0, 'Boss!');
	removeElementClass("boss", "invisible");
	addElementClass("keycatcher", "invisible");
	addElementClass("tun3r_search", "invisible");
	addElementClass("track_search", "invisible");
	addElementClass("filterlang", "invisible");
	addElementClass("filtergenre", "invisible");
	addElementClass("playedDiv", "invisible");
	addElementClass("thenews", "invisible");
}
function bossDisable() {
	addElementClass("boss", "invisible");
	removeElementClass("keycatcher", "invisible");
	removeElementClass("tun3r_search", "invisible");
	removeElementClass("track_search", "invisible");
	removeElementClass("filterlang", "invisible");
	removeElementClass("filtergenre", "invisible");
	removeElementClass("playedDiv", "invisible");
	removeElementClass("thenews", "invisible");
	Drag.keyCatcher.focus();
}
function demoStart() { Drag.danceStart(); }

function txtLabel(txt, gotFocus) {
	var msg;

	if(txt.id == 'tun3r_search') {
		msg = ' -- Homepage Search -- ';
	}
	if(txt.id == 'track_search') {
		msg = ' -- Artist/Title Search -- ';
	}

	if(gotFocus) {
		if(txt.value==msg) txt.value='';
		Drag.focusTrack = (txt.id == 'track_search');
	} else {
		if(txt.value=='') {
			txt.value=msg;
			Drag.lastSearch = '';
		}
	}
}
function searchBtn() {
	var track = getElement('track_search');
	var home = getElement('tun3r_search');
	var hasTrack = (track.value.substring(0,4) != ' -- ');
	var hasHome = (home.value.substring(0,4) != ' -- ');

	if(!hasTrack && !hasHome) {
		track.focus();
		return;
	}
	if(hasTrack && hasHome) {
		Drag.newSearch(Drag.focusTrack);
	} else {
		Drag.newSearch(hasTrack);
	}
		
		
}

function listenNow(hover) {
	if(!Drag.curStream) return;
	if(hover) {
		// poor attempt to show true link dest in status bar.
		window.status = "Listen to: " + Drag.curStream;
		return;
	}

	// stop our stream, start the real stream.
	Drag.stopStartMusic(0, "See Media Player/Browser");

	var dest = "/nph-go.cgi/listen?x=" + Drag.stationX
						+ "&z=" + Drag.stationZ
						+ "&y=" + Drag.stationY + "&ses=" + tun3r_session
						+ "&cacheKill=" + new Date().getTime();

	if(!Drag.curStreamPopup) {
		// safe to reload current page, and browser should handle
		// playlist mime type
		window.location = dest;
	} else {
		window.open(dest, "t3_listen");
	}
}

function selectContents(ta) {
	var txt = getElement(ta);
	txt.focus();
	txt.select();
}


function drawLine(srcName, destName, leftEdge) {
	var i, cnt;
	var srcEl = getElement(srcName);
	var srcSz = elementDimensions(srcEl);
	var src = elementPosition(srcEl);

	var destEl = getElement(destName);
	var dest = elementPosition(destEl);
	if(dest == null) return;		// not visible
	var destSz = elementDimensions(destEl);

	// move to center bottom edge of step
	src.x += (srcSz.w / 2);
	src.y += srcSz.h - 10;

	// center of target.
	if(!leftEdge) dest.x += (destSz.w / 2);
	else dest.x += leftEdge;
	//dest.y += (destSz.h / 2);

	var x = src.x;
	var y = src.y;
	var dx = (dest.x - x);
	var dy = (dest.y - y);

	var len = Math.sqrt(Math.pow(dx,2) + Math.pow(dy,2));
	var cnt = Math.ceil((len + 10)/ 20);
	dx /= (cnt+1);
	dy /= (cnt+1);
	
	var body = currentDocument().body;

	for(i=0; i<cnt; i++) {
		var el = SPAN({'class': 'stepdot'}, "\u2022");
		setElementPosition(el, {x: x, y:y});
		appendChildNodes(body, el);

		x += dx;
		y += dy;
	}
}

function stepOn(n) {
	if(n == 1) {
		drawLine('step' + n, 'lm0btn', 0);
		drawLine('step' + n, 'lm1btn', 0);
	} else if(n == 2) {
		drawLine('step' + n, 'dialtab' + Drag.stationZ, 0);
	} else if(n == 3) {
		drawLine('step' + n, 'tun3r_search', -3);
		drawLine('step' + n, 'filtergenre', 30);
		drawLine('step' + n, 'addMark', 0);
	}
}
function stepOff(n) {
	var i, sd = getElementsByTagAndClassName('SPAN', 'stepdot');

	for(i=0; i<sd.length; i++) {
		removeElement(sd[i]);
	}
}

function embed() {
	var track = getElement('track_search');
	var home = getElement('tun3r_search');
	var lang = getElement('filterlang');
	var genre = getElement('filtergenre');
	var hasTrack = (track.value.substring(0,4) != ' -- ');
	var hasHome = (home.value.substring(0,4) != ' -- ');

	window.open("/fc/embed?ses=" + tun3r_session
					+ "&cacheKill=" + new Date().getTime()
					+ (hasTrack?("&tr=" + track.value):"")
					+ (hasHome?("&hp=" + home.value):"")
					+ ((lang.value!='all')?("&lg="+lang.value):"")
					+ ((genre.value!='all')?("&ge="+genre.value):"")
				, "_new");
}

/*
Target = {
	gotSome: 0,
	animator: null,
	scrollDir: 1,
	scrollRate: 1,

	mouseOut: function(e) {
		var name = getNodeAttribute(e.target(), 'id');
		if(name != 'ourads') return;		// bogus event

		if(Target.gotSome && !Target.animator) {
			Target.animator = callLater(2.00, Target.animate);
		}
	},
	mouseIn: function(e) {
		if(Target.animator) {
			Target.animator.cancel();
			Target.animator = null;
		}
	},

	init: function() {
		var ourads = getElement("ourads");
        connect(ourads, 'onmouseout', Target, Target.mouseOut);
        connect(ourads, 'onmouseover', Target, Target.mouseIn);
		Target.resize(0);
	},

	refresh: function() {
		// called every 5 minutes, if the user is doing stuff.
		var d = loadJSONDoc("/promote/promote.cgi", { 
					ses: tun3r_session, refresh:1,
					json:1, cont: tun3r_continent, country: tun3r_country,
					q: Drag.lastSearch, tracks: Drag.lastSearchType,
					lang: Drag.curLang, gen: Drag.curGenre,
					cacheKill: new Date().getTime() });

		var fetchFailed = function(err) {
			//var ourads = getElement("ourads");
			//replaceChildNodes("ourads", "(trouble)");
			// leave what's there.
		};

		if(d) d.addCallbacks(function(data){Target.display(data)}, fetchFailed);
	},

	resize: function(wide) {
		var ourads = getElement("ourads");
		var menu = getElement("linkmenu");

		if(!isVisible(ourads)) return;

		var menuLeft = elementPosition(menu).x;
		var aLeft = elementPosition(ourads).x;

		var wh = elementDimensions(ourads);
		wh.w = menuLeft - aLeft - 10;
		if(wh.w < 1) wh.w = 1;
		wh.h = 88;

		setElementDimensions(ourads, wh);
	},

	animate: function() {
		if(!Target.gotSome) return;

		var ourads = getElement("ourads");
		var was = ourads.scrollLeft, now, after;
	 	
		now = was + (Target.scrollDir * Target.scrollRate);
		ourads.scrollLeft = now;
		after = ourads.scrollLeft;
		if(after == was) {
			if(ourads.scrollLeft < 20 && (Target.scrollDir>0)) {// too narrow
				ourads.scrollLeft = 0;
				return;
			}
			Target.scrollDir *= -1;
			ourads.scrollLeft = was + (Target.scrollDir * Target.scrollRate);
		}

		if(Target.animator) Target.animator.cancel();
		Target.animator = callLater(.250, Target.animate);
	},

	display: function(data) {
		var ourads = getElement("ourads");
		var boxes = new Array(data.count), i;
		var height = 88;

		for(i=0; i<data.count; i++) {
			var cur = data.items[i];

			boxes[i] = SPAN(null,
				A({'href': cur.url, 'target': 'tun3r_targeted',
					'title': cur.desc, 
					'class': 'awsitem'}, 
					IMG( {'width': cur.w, 'height': cur.h,
							'src': cur.img, 'align': 'middle',
							'border': 'no' }, null)));
		}

		Target.gotSome = data.count;
		if(data.count) {
			replaceChildNodes(ourads, boxes);
			makeInvisible("steps");
			makeVisible(ourads);
			Target.resize();

			ourads.scrollLeft = 0;
			Target.scrollDir = 1;
			Target.animate(1);
		} else {
			//replaceChildNodes(ourads, "(No suggestions, sorry.)");
			//makeInvisible(ourads);
		}
	},

	search: function() {
		var ourads = getElement("ourads");
		//replaceChildNodes(ourads, "Checking...");

		var d = loadJSONDoc("/promote/promote.cgi", { 
					ses: tun3r_session,
					json:1, cont: tun3r_continent, country: tun3r_country,
					q: Drag.lastSearch, tracks: Drag.lastSearchType,
					lang: Drag.curLang, gen: Drag.curGenre,
					cacheKill: new Date().getTime() });

		var fetchFailed = function(err) {
			//replaceChildNodes("ourads", "(trouble)");
		};

		if(d) d.addCallbacks(function(data){Target.display(data)}, fetchFailed);
		else alert("Can't search from localhost");
	}
}
*/

function monetize() {
	if(isVisible("shim")) return;

	makeVisible("shim");
	makeInvisible("steps");
}

function showHelp() {
	if(isVisible("steps")) return;

	makeVisible("steps");
	makeInvisible("shim");
}

function testit() {
/*
	var ourads = getElement("ourads");

	var data = 0;

	Target.display(data);
*/
	if(isVisible("steps")) {
		makeInvisible("steps");
		makeVisible("shim");
	} else {
		makeInvisible("shim");
		makeVisible("steps");
	}
}

/* http://www.quirksmode.org/js/cookies.html */
function createCookie(name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/; Domain=.tun3r.com;";
}
function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return '';
}
function eraseCookie(name) {
	createCookie(name,"",-1);
}


function dialSelect(e) {
	var id = getNodeAttribute(e.src(), 'id');
	var dz = Number(id.substring(7,id.length));
	Drag.gotoDialZ(dz, false, false);
}

function drawDialSummary(n){
	if(n == 0) {
		replaceChildNodes("about", "These squares are all ",
				createDOM("B", "radio"),
		" stations. Click anywhere to get started, or drag orange needle.");
	} else {
		replaceChildNodes("about", tun3r_dialsum[n]);
	} 
}

function dialSelOver(e) {
	var id = getNodeAttribute(e.src(), 'id');
	var dz = Number(id.substring(7,id.length));
	drawDialSummary(dz);
}
function dialSelOut(e) {
	drawDialSummary(Drag.stationZ);
}

function liveMode(en, begin) {
	Drag.stopStartMusic(0, "Live mode vs. classic");
	Drag.liveMode = en;
	setVisible("lm0area", Drag.liveMode);
	setVisible("speakerMovie", !Drag.liveMode);
	getElement('lm0btn').checked = !en;
	getElement('lm1btn').checked = en;
	if(en) {
		setVisible(Drag.hideRow4, false);
		if(begin) {
			Drag.lmWindow = window.open(
				"/fc/pl/frame?x=" + Drag.stationX
					+ "&z=" + Drag.stationZ
					+ "&y=" + Drag.stationY + "&ses=" + tun3r_session
					+ "&cacheKill=" + new Date().getTime(),
					"lmwindow",
"width=600,height=200,location=no,menubar=no,resizable=yes,scrollbars=yes");
		}

		setVisible("lmplay", !begin);
		setVisible("lmstop", begin);
	} else {
		if(Drag.lmWindow) {
			// was just: Drag.lmWindow.close()
			Drag.lmWindow.location = "/fc/pl/frame?cmd=stop"
				+ "&cacheKill=" + new Date().getTime()
				+ "&ses=" + tun3r_session;
		}
		//Drag.lmWindow = null;
	}
	createCookie('t3_livemode', String(Number(en)), 365);
}

function lmStartStop(go) {
	var	pl = Drag.lmWindow;

	if(!go) {		// stop
		if(pl) {
			pl.location = "/fc/pl/frame?cmd=stop"
			+ "&cacheKill=" + new Date().getTime()
			+ "&ses=" + tun3r_session;
		}

		setVisible("lmplay", !go);
		setVisible("lmstop", go);
	} else {		// play
		liveMode(true, true);
	}
}

// EOF
