/* dcwebui.js */
//;(function() {
	
"use strict";

var $ = (document.querySelectorAll ? 
	function(s) {
		var r = document.querySelectorAll(s);
		return (s[0] === '#' && r.length === 1) ? r[0] : r;
	} : 
	function(s) {
		// i'm not writing a selector engine...
		if (! s.length) return [];
		if (s[0] === '#') {
			return document.getElementById(s.slice(1));
		} else if (s[0] === '.') {
			return document.getElementsByClassName(s.slice(1));
		} else {
			return document.getElementsByTagName(s);
		}
	}
);

var nmdc_escape = function(str) {
	return (''+str).length ? (''+str).
		replace(/&/g,'&amp;').replace(/\|/g,'&#124;').replace(/\$/g,'&#36;') :
		' ';
};

var hesc = function(s) {
	var filter = {
		'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&#34;', '\'': '&#39;'
	};
	return s.toString().replace(/[&<>'"]/g, function(s) { return filter[s]; });
};

var nl2br = function(str) { // thanks php.js!
	return (str+'').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1<br>$2');
};

var linkify = function(str) {
	return str.replace(
		/(https?:\/\/[^\s<]+)/g, "<a target='_blank' href=\"$1\">$1</a>"
	);
};

var sanitise = function(s) {
	return linkify(nl2br(hesc(s)));
};

var toggle = function($el) {
	$el.style.display = ($el.style.display === "block") ? "none" : "block";
};

var textContent = function($el) {
	if ($el.textContent) return $el.textContent;
	if ($el.innerText) return $el.innerText;
	return "";
};

// https://gist.github.com/eligrey/1276030
var appendInnerHTML = function($el, html) {
	var child = document.createElement("span");
	child.innerHTML = html;
	
	var node;
	while ((node = child.firstChild)) {
		$el.appendChild(node);
	}
};

// http://stackoverflow.com/a/5598797
function getOffsetLeft( elem ) {
    var offsetLeft = 0;
	do {
		if (!isNaN(elem.offsetLeft)) {
			offsetLeft += elem.offsetLeft;
		}
	} while (elem = elem.offsetParent);
	return offsetLeft;
}

function getOffsetTop( elem ) {
    var offsetTop = 0;
	do {
		if (!isNaN(elem.offsetTop)) {
			offsetTop += elem.offsetTop;
		}
	} while (elem = elem.offsetParent);
	return offsetTop;
}

/* Tab writers */ 

var write = function(tab) {
	var $tab = $('#inner-'+tab);
	return {
		'cls': function() {
			$tab.innerHTML = '';
			return this;
		},
		'scroll': function() {
			$tab.scrollTop = $tab.scrollHeight;
			return this;
		},
		'raw': function(s) {
			appendInnerHTML($tab, s);
			return this.scroll();
		},
		'c': function(c, s) {
			return this.raw('<span class="'+c+'">'+s+'</span>');
		},
		'time': function() {
			var d = new Date();
			var pad = function(s) {
				return (s < 10) ? '0'+s : ''+s ;
			};
			return this.raw(
				'<span class="tx-time">['+
				pad(d.getHours()) + ":" + pad(d.getMinutes())+
				"]</span> "
			);
		},
		'system': function(s) {
			return this.time().c('tx-sys', sanitise(s)).raw('<br/>');
		},
		'pubnick': function(u) {
			return this.raw('<span class="tx-user" ondblclick="switchToPM(\''+hesc(u)+'\');return false;">&lt;'+hesc(u)+'&gt;</span>');
		},
		'pub': function(u, s) {
			return this.time().
				pubnick(u).raw('&nbsp;').
				c('tx-chat', sanitise(s)).raw('<br/>');
		},
		'priv': function(f, t, s) {
			return this.time().
				c('tx-user', '&lt;'+hesc(f)+'&nbsp;-&gt;&nbsp'+hesc(t)+'&gt;').
				raw('&nbsp').
				c('tx-private', sanitise(s)).
				raw('<br/>');
		}
	};
};

/* Userlist */ 

var switchToPM = function(u) {
	writerFor(u);          // create
	tab_set(pm_tabs[u]);   // switch
	writerFor(u).scroll(); // scroll
};

var userMenu = function(u, ev) {
	
	usermenu.hide();
	usermenu = new MenuList(ev.target);
	usermenu.add("Send private message...", function() {
		switchToPM(u);
	});
	// Usercommands
	for (var i = 0; i < user_usercommands.length; i++) (function(i) {
		var raw = user_usercommands[i].raw;
		usermenu.add(user_usercommands[i].title, function() {
			
			var message = usercommand_process( raw .replace(/%\[nick\]/g, u) );
			sock.emit('raw', {message: message});
		});
	})(i);
	
	// Show
	usermenu.show();
	
	//
	ev.preventDefault();
	return false;
};

var userlist = {
	'add': function(u) {
		if (this.has(u)) return;
		
		var userlists = $(".userlist");
		for (var l = 0, e = userlists.length; l !== e; ++l) {
			var userlist = userlists[l];
			
			var to_add = document.createElement('li');
			to_add.innerHTML = hesc(u);
			
			to_add.onclick = function() { switchToPM(u); };
			to_add.oncontextmenu = function(ev) { return userMenu(u, ev); };
			
			var users = userlist.children;
			var cmp   = hesc(u).toUpperCase();
			
			var found = false;
			for (var i = 0; i < users.length; i++) {
				if ((''+users[i].innerHTML).toUpperCase() > cmp) {
					userlist.insertBefore(to_add, users[i]);
					found = true;
					break;
				}
			}
			if (! found) {
				userlist.appendChild(to_add);
			}
		}
		return this;
	},
	'del': function(u) {
		var userlists = $(".userlist");
		for (var l = 0, e = userlists.length; l !== e; ++l) {
			if (! userlists[l].children) continue;
			var userlist = userlists[l];
				
			var users = userlist.children;
			var cmp   = hesc(u).toUpperCase();
			for (var i = 0; i < users.length; i++) {
				if ((''+users[i].innerHTML).toUpperCase() === cmp) {
					userlist.removeChild(users[i]);
					break;
				}
			}
		}
		return this;
	},
	'clear': function() {
		var userlists = $(".userlist");
		for (var i in userlists) {
			if (! userlists[i].children) continue;
			var userlist = userlists[i];
			
			var users = userlist.children;
			for (var j = users.length; j --> 0;) {
				userlist.removeChild(users[j]);
			}
		}
		return this;
	},
	'names': function() {
		var userlist = $(".userlist")[0].children;
		var ret = [];
		for (var i = 0, e = userlist.length; i < e; ++i) {
			ret.push( textContent(userlist[i]) );
		}
		return ret;
	},
	'has': function(u) {
		var userlist = $(".userlist")[0].children;
		for (var i = 0, e = userlist.length; i < e; ++i) {
			if (textContent(userlist[i]) === u) {
				return true;
			}
		}
		return false;
	},
	'count': function() {
		return $(".userlist")[0].children.length;
	}
};

var submit = function() {
	var str = $("#chatbox").value;
	if (! str.length) return;

	if (hub_state === 0) {
		hub_state = 1;
		
		var name_parts = str.split(":", 2);		
		hub_last_nick = name_parts[0];
				
		sock.emit('hello', {'nick' : hub_last_nick, 'pass' : name_parts.length >= 2 ? name_parts[1] : ''});
		$("#chatbox").disabled = true;
		write("tab-main").system("Connecting...");
		
	} else if (hub_state === 2) {
		if (pm_target !== false) {
			sock.emit('priv', {'user': pm_target, 'message': str});
			writerFor(pm_target).pub(hub_last_nick, str );
		} else {
			sock.emit('pub', {'message' : str});
		}
		
	} else {
		write("tab-main").system("Invalid internal state.");
	}
	
	$("#chatbox").value = '';
};

/* tabs */

/**
 * Switch active tab
 * 
 * @param {String} tab  Full tab ID (e.g. tab-main, tab-users, tab-ext-???)
 */
var tab_set = function(tab) {
	
	var tabs = $(".tabpane");
	for (var i in tabs) {
		try {
			tabs[i].style.display = (tabs[i].id === tab ? 'block' : 'none');
		} catch (e) {};
	}
	var tabitems = $(".tabitem");
	for (var i in tabitems) {
		try {
			tabitems[i].className = "tabitem" + (tabitems[i].getAttribute('data-tab') === tab ? ' selected' : '');
		} catch (e) {};
	}
	
	pm_target = false;
	for (var i in pm_tabs) {
		if (pm_tabs[i] === tab) {
			pm_target = i;
			break;
		}
	}
	
	updateTitle();
	
	write(tab).scroll();
	$("#chatbox").focus();
	last_tab = tab;
};

var tab_new = function(id, name) {
	appendInnerHTML($("#bar"),
		' <div class="tabitem" data-tab="tab-ext-'+id+'" id="tabitem-tab-ext-'+id+'">'+
			'<span class="tab-label">'+
				hesc(name)+
			'</span> '+
			'<a class="tab-closer" data-tab="tab-ext-'+id+'">&times;</a>'+
		'</div> '
	);
	appendInnerHTML($("#extratabs"),
		' <div class="tabpane content placement-mid" id="tab-ext-'+id+'" style="display:none;">'+
			'<div class="content-inner" id="inner-tab-ext-'+id+'"></div>'+
		'</div>'
	);
	tab_addHandlers();
	return "tab-ext-"+id;
};

var tab_free = function(id) {
	if (id === "tab-main") return;

	// remove tab item and body
	var el = $("#tabitem-"+id);
	el.parentNode.removeChild(el);
	
	var el = $("#"+id);
	el.parentNode.removeChild(el);
	
	// clear from PM tabs
	for (var i in pm_tabs) {
		if (pm_tabs[i] === id) {
			pm_tabs[i] = false;
		}
	}
	
	// maybe clear the 'new pm' warning
	updateTitle();
	
	// don't leave us with no tab displayed
	if (last_tab === id) {
		tab_set("tab-main");
	}
};

var tab_addHandlers = function() {
	var tabitems = $(".tabitem");
	for (var i = 0; i < tabitems.length; i++) {
		if (! tabitems[i]) continue;
		
		tabitems[i].onclick = function(ev) {
			tab_set( this.getAttribute('data-tab') );
			
			// 360nobubble
			if (ev.stopPropagation) {
				ev.stopPropagation();
			} else {
				ev.cancelBubble = true; // oldIE
			}
			return false;
		};
	}
	
	var tabclosers = $(".tab-closer");
	for (var i = 0; i < tabclosers.length; i++) {
		if (! tabclosers[i]) continue;
		
		tabclosers[i].onclick = function(ev) {
			tab_free( this.getAttribute('data-tab') );
			
			// 360nobubble
			if (ev.stopPropagation) {
				ev.stopPropagation();
			} else {
				ev.cancelBubble = true; // oldIE
			}
			return false;
		};
	}
};

/* */

var writerFor = function(username) {
	var tabid = "";
	if (! username in pm_tabs || ! pm_tabs[username]) {
		tabid = tab_new(next_tabid++, username);
		pm_tabs[username] = tabid;
	} else {
		tabid = pm_tabs[username];
	}
	
	return write(tabid);
};

/* */

var tabcomplete_state = '';

var tabcompletion_start = function() {
	
	var cursor = $("#chatbox").value.replace(/^.*\s([^\s]+)$/, '$1');
	
	if (tabcomplete_state === '') {
		// new tab completion
		tabcomplete_state = cursor;
	}
	
	// Find all users who start with tabcomplete_state and retrieve the first
	//    one after cursor
	
	var users = userlist.names();
	var match = [];
	
	for (var i = 0, e = users.length; i !== e; ++i) {
		if (users[i].toUpperCase().indexOf(tabcomplete_state.toUpperCase()) === 0) {
			match.push(users[i]);
		}
	}
	
	if (match.length === 0) {
		// no matches
		return;
	}
	
	// Is cursor in the list?
	var cpos = -1;
	for (var i = 0, e = match.length; i !== e; ++i) {
		if (match[i] === cursor) {
			cpos = i; break;
		}
	}
	
	var targetName = match[(i + 1) % match.length];
	
	// Replace in textbox
	
	var chatprefix = $("#chatbox").value.substr(0, $("#chatbox").value.length - cursor.length);
	
	$("#chatbox").value = chatprefix + targetName;
	$("#chatbox").focus();
};

var tabcompletion_inactive = function() {
	tabcomplete_state = '';
};

/* */ 

var MenuList = function(el) {
	this.el = el;
	
	this.div = document.createElement("div");
	this.div.classList.add("menu");
	this.div.style.position = "absolute";
	
	this.ul = document.createElement("ul");
	this.div.appendChild(this.ul);
	
	document.body.appendChild(this.div);
	
};
MenuList.prototype.clear = function() {
	while (this.ul.children.length) {
		this.ul.children[0].parentNode.removeChild( this.ul.children[0] );
	}
};
MenuList.prototype.add = function(txt, cb) {
	var li = document.createElement("li");
	li.innerHTML = txt;
	li.onclick = cb;
	this.ul.appendChild(li);
};
MenuList.prototype.show = function() {
	this.div.style.display = "block";
	
	this.div.style.top  = (getOffsetTop(this.el) + this.el.clientHeight)+"px";
	this.div.style.left = (getOffsetLeft(this.el) - 200 + this.el.clientWidth)+"px";
};
MenuList.prototype.hide = function() {
	this.div.style.display = "none";
};
MenuList.prototype.toggle = function() {
	// ES5 strict mode sets `this` to undefined
	(this.div.style.display === "block" ? this.hide : this.show).call(this);
};

/* */ 

var menu = new MenuList($("#menubutton"));

menu.reset = function() {
	this.clear();
	this.add(joinparts_getstr(), toggle_joinparts);
};

var usermenu = new MenuList(document.body);

/**
 * Process all substitutions in a usercommand. May prompt the user for input.
 * 
 * @param {String} str
 * @returns {String}
 */
var usercommand_process = function(str) {
	var message = str
		.replace(/%\[mynick\]/g, hub_last_nick)
	;
	var match = null;
	for(;;) {
		match = message.match(/%\[line:([^\]]*)\]/);
		if (match === null) break;

		var res = prompt(match[1]);
		if (res === null) return; //cancelled

		message =
			message.substr(0, match.index) +
			nmdc_escape(res) +
			message.substr(match.index + match[0].length)
		;
	}
	
	return message;
};

var user_usercommands = [];

/**
 * Called when a $UserCommand is recieved from the server. Register it in all
 *  relevant context menus.
 * 
 * @param {Object} data   Has properties type, context, title, raw
 * @returns {undefined}
 */
var process_usercommand = function(data) {
	switch(data.type) {
		case 0: { /* USERCOMMAND_TYPE_SEPARATOR */
			// ignore 
		} break;
		
		case 1: /* USERCOMMAND_TYPE_RAW */
		case 2:/* USERCOMMAND_TYPE_NICKLIMITED */
		{
			if (data.context & 1) { /* USERCOMMAND_CONTEXT_HUB */ 
				menu.add(data.title, function() {
					sock.emit('raw', {message: usercommand_process(data.raw)});
				});
			}
			
			if (data.context & 2) { /* USERCOMMAND_CONTEXT_USER */ 
				user_usercommands.push(data);
			}
		} break;
		
		case 255: { /* USERCOMMAND_TYPE_CLEARALL */
			if (data.context & 1) {
				menu.reset();
			}
			
			if (data.context & 2) {
				user_usercommands = []; // clear
			}
		} break;
	}
};

var joinparts_getstr = function() {
	return (show_joins ? "&#9745;" : "&#9744;") + " Show Joins/Parts";
};

var toggle_joinparts = function(ev) {
	var $el = ev.target || ev.srcElement;
	show_joins = ! show_joins;
	$el.innerHTML = joinparts_getstr();
};

/* */

var updateTitle = function() {
	document.title = 
		($(".unread").length ? "[NEW PM] " : "") +
		"("+userlist.count()+") "+
		hub_hubname;
};

var sock = {};
var hub_state     = 0; // [disconnected, sent-nick, connected]
var hub_last_nick = '';
var hub_hubname   = 'DCWebUI';

var pm_tabs = {}; // nick => tabid
var next_tabid = 1;
var pm_target = false;

var last_tab = "tab-main";

var show_joins = false;

window.onload = function() {
	write("tab-main").system("Communicating with server...");
	
	document.title = DCWEBUI_CONF.title;
	
	// HTML event handlers
	
	$("#form-none").onsubmit = function(ev) {
		submit();
		
		// don't submit form
		ev.preventDefault();
		return false;
	};
	
	$("#chatbox").onkeydown = function(ev) {
		if (ev.keyCode === 9) {
			// tab
			tabcompletion_start();
			ev.preventDefault();
			return false;
			
		} else {
			tabcompletion_inactive();
		}
	};
	
	window.onresize = function() {
		if (last_tab === "tab-users" && document.documentElement.clientWidth >= 590) {
			tab_set("tab-main");
		}
		
		menu.hide();
		usermenu.hide();
	};
	
	$("#menubutton").onclick = function(ev) {
		menu.toggle();
		ev.preventDefault();
		ev.stopPropagation();
		return false;
	};
	
	window.onclick = function() {
		menu.hide();
		usermenu.hide();
	};
	
	menu.reset();
	
	tab_addHandlers();
	
	// Hacks for WiiU user-agent
	
	if (navigator && navigator.userAgent && !!navigator.userAgent.match("Nintendo WiiU")) {
		document.body.className += " navigator-wiiu";
	}
	
	// Socket event handlers
	
	sock = io.connect(DCWEBUI_CONF.extern);
	sock.on('cls', function() {
		write("tab-main").cls();
		userlist.clear();
		hub_state = 0;
	});
	sock.on('hubname', function(s) {
		write("tab-main").system("Now talking on "+s);
		hub_hubname = s;
		updateTitle();
	});
	sock.on('sys', function(data) {
		write("tab-main").system(data);
	});
	sock.on('raw', function(data) {
		write("tab-main").raw(data);
	});
	sock.on('pub', function(data) {
		write("tab-main").pub( data.user, data.message);
	});
	sock.on('priv', function(data) {
		writerFor(data.user).pub( data.user, data.message);
		if (last_tab !== pm_tabs[data.user] &&
			($("#tabitem-"+pm_tabs[data.user]).className.indexOf('unread') === -1)
		) {
			$("#tabitem-"+pm_tabs[data.user]).className += " unread";
			updateTitle();
		}
	});
	sock.on('hello', function() {
		hub_state = 2;
		$("#chatbox").disabled = false;
	});
	sock.on('part', function(u) {
		userlist.del(u.user);
		if (show_joins) {
			write("tab-main").system("*** Parts: "+u.user); // sanitised
		}
		updateTitle();
	});
	sock.on('join', function(u) {
		userlist.add(u.user);
		if (show_joins) {
			write("tab-main").system("*** Joins: "+u.user);
		}
		updateTitle();
	});
	sock.on('close', function() {
		hub_state = 0;
		userlist.clear();
		write("tab-main").system("Connection closed by remote host.");
	});
	sock.on('disconnect', function() {
		hub_state = 0;
		userlist.clear();
		write("tab-main").system("Lost connection to the server.");
	});
	sock.on('usercommand', function(data) {
		process_usercommand(data);
	});
};

//})();
