// all the core logic, timers, network calls, json, etc

// blah globals, get over it
//var NDX = "http://not-quite-ready-yet.index.swlabs.org/whitelist/";
var NDX = "http://index.search.isc.org/";
if (window.location.href && window.location.href.indexOf("wikia-inc.com")>-1) var KT = "http://kt.search.isc.org/kt/";
else var KT = "http://kt.search.isc.org/kttest/";

var un = getCookie("wikicitiesUserName");
if(!un) un = "you";
var tok = getCookie("wikiaSearchToken");

// simple wrapper around the remote script index request
function indexFetch1(q, start)
{
	var s = document.createElement('script');
	s.defer = true;
	s.type = 'text/javascript';
	s.src = NDX + "nutchsearch?query="+ encodeURIComponent(q.q) +"&hitsPerSite=1&lang=en&hitsPerPage=10&type=json&start=" + start;
	document.getElementsByTagName('head')[0].appendChild(s);
}

// async load remote content, in an iframe, please let there be a better way than this!
/*
function callOut(func,url){
	var i = document.createElement('iframe');
	i.style.display="none";
	document.getElementsByTagName('head')[0].appendChild(i);
	var s = i.contentDocument.createElement('script');
	s.innerHTML = "function "+func+"{parent."+func+";}"; // there has to be a better way?!?!
	i.contentDocument.body.appendChild(s);
	var s = i.contentDocument.createElement('script');
	s.defer = true;
	s.type = 'text/javascript';
	s.src = url;
	i.contentDocument.body.appendChild(s);
}
function callOutId(func,url,id){
	var i = document.createElement('iframe');
	i.style.display="none";
	document.getElementsByTagName('head')[0].appendChild(i);
	var s = i.contentDocument.createElement('script');
	s.innerHTML = "function "+func+"(j){parent."+func+"("+id+",j);}"; // there has to be a better way?!?!
	i.contentDocument.body.appendChild(s);
	var s = i.contentDocument.createElement('script');
	s.defer = true;
	s.type = 'text/javascript';
	s.src = url;
	i.contentDocument.body.appendChild(s);
}

function indexFetch(q, start)
{
	var i = document.createElement('iframe');
	i.style.display="none";
	document.getElementsByTagName('head')[0].appendChild(i);
	var s = i.contentDocument.createElement('script');
//	s.innerHTML = "<script>alert('asdf');function processJSON(j){parent.processJSON(j);}</script>";
	s.innerHTML = "function processJSON(j){parent.processJSON(j);}";
	i.contentDocument.body.appendChild(s);
	var s = i.contentDocument.createElement('script');
	s.defer = true;
	s.type = 'text/javascript';
//	s.src = "http://search.isc.swlabs.org/nutchsearch?query="+ encodeURIComponent(q.q) +"&hitsPerSite=1&lang=en&hitsPerPage=10&type=json&start=" + start;
	s.src = "http://not-quite-ready-yet.index.swlabs.org/whitelist/nutchsearch?query="+ encodeURIComponent(q.q+" lang:en") +"&hitsPerSite=1&lang=en&hitsPerPage=10&type=json&start=" + start;
	i.contentDocument.body.appendChild(s);
}
*/

function callOut(func,url){
	var i = document.createElement('iframe');
	i.style.display="none";
	document.getElementsByTagName('head')[0].appendChild(i);
	
	var doc = null;  
	    if(i.contentDocument)  
	       // Firefox, Opera  
	       doc = i.contentDocument;  
	    else if(i.contentWindow)  
	       // Internet Explorer  
	       doc = i.contentWindow.document;  
	    else if(i.document)  
	       // Others?  
	       doc = i.document;  
	   
	    if(doc == null)  
	       throw "Document not initialized";  
	    
	doc.open();
	doc.write("<script type=\"text/javascript\">function "+func+"{parent."+func+";}</script>");
	doc.write("<script type=\"text/javascript\" src=\"" + url + "\" defer=\"defer\"></script>");
	doc.close();
}

var coargs = new Array();
function callOutArg(func,url,arg){
	var i = document.createElement('iframe');
	i.style.display="none";
	document.getElementsByTagName('head')[0].appendChild(i);
	
	var doc = null;  
	    if(i.contentDocument)  
	       // Firefox, Opera  
	       doc = i.contentDocument;  
	    else if(i.contentWindow)  
	       // Internet Explorer  
	       doc = i.contentWindow.document;  
	    else if(i.document)  
	       // Others?  
	       doc = i.document;  
	   
	    if(doc == null)  
	       throw "Document not initialized";  
	coargs[coargs.length] = arg;
	doc.open();
	//doc.write("<script type=\"text/javascript\">function "+func+"(j){parent."+func+"(parent.coargs["+coargs.length+"],j);}</script>");
	doc.write("<script type=\"text/javascript\">function "+func+"(j){parent."+func+"(parent.coargs["+(coargs.length-1)+"],j);}</script>");
	doc.write("<script type=\"text/javascript\" src=\"" + url + "\" defer=\"defer\"></script>");
	doc.close();
	//coargs[coargs.length] = arg;
	
}

function indexFetch(q, start)
{
	var i = document.createElement('iframe');
	i.style.display="none";
	document.getElementsByTagName('head')[0].appendChild(i);
	
	var doc = null;  
	    if(i.contentDocument)  
	       // Firefox, Opera  
	       doc = i.contentDocument;  
	    else if(i.contentWindow)  
	       // Internet Explorer  
	       doc = i.contentWindow.document;  
	    else if(i.document)  
	       // Others?  
	       doc = i.document;  
	   
	    if(doc == null)  
	       throw "Document not initialized";  
	    
	doc.open();
	if(q.y){
		// this is a hack for callback ease!
		coargs[coargs.length] = q;
		doc.write("<script type=\"text/javascript\">function processY(j){parent.processY(parent.coargs["+(coargs.length-1)+"],j);}</script>");
		doc.write("<script type=\"text/javascript\"  defer=\"true\" src=\"http://boss.yahooapis.com/ysearch/web/v1/"+encodeURIComponent(q.q)+"?appid=GD2UGdfIkY1gi6EBoIck4Exv2xLUsVrm&lang="+lang+"&callback=processY&start=" + start + "\"></script>");
	}else{
		doc.write("<script type=\"text/javascript\">function processJSON(j){parent.processJSON(j);}</script>");
		doc.write("<script type=\"text/javascript\"  defer=\"true\" src=\""+NDX+"nutchsearch?query="+ encodeURIComponent(q.q+" lang:"+lang) +"&hitsPerSite=1&lang=en&hitsPerPage=10&type=json&start=" + start + "\"></script>");
	}
	doc.close();
}


// get all of our KT data for this query
function ktFetch(q)
{
	// force skip cached for now!
	var url = KT+"kt.js?t=del&t=add&t=sug&t=spot&t=stars&t=edit&t=bg&t=com&t=selection&t=also&k="+ encodeURIComponent(q.q.toLowerCase()) + "&r=" + Math.random() + "&f=processKT(";
	callOutArg("processKT",url,q);
}

function ktSave(q,tuple,json)
{
	try {
		if (can_save== false) {
			alert(gettext("Unable to save.  User is blocked."));
			return;
		}
	}
	catch(ex){}
	var src = KT+"new.js?t="+tuple+"&k="+ encodeURIComponent(q.q.toLowerCase());
		// if logged in user, use creds
	if(un && tok){
		json.user = un;
		src += "&v=" + encodeURIComponent(tok);
	}
	json.l = lang;
	src += "&j=" + encode64(Object.toJSON(json));
	src += "&r=" + Math.random(); // never use cached!
	var s = document.createElement('script');
	s.defer = true;
	s.type = 'text/javascript';
	s.src = src;
	document.getElementsByTagName('head')[0].appendChild(s);
	// add history items!
	if(!json.user) json.user = un;
	var at = false;
	if(tuple == "del"){
		var hact = (json.del) ? "deleted" : "undeleted";
		histAdd(q,json.user,hact,at,json.url);
	}else if(tuple == "add"){
		histAdd(q,json.user,"added",at,json.url);
	}else if(tuple == "sug"){
		histAdd(q,json.user,"suggested a related result",at,json.url);
	}else if(tuple == "spot"){
		histAdd(q,json.user,"spotlighted",at,json.url);
	}else if(tuple == "stars"){
		histAdd(q,json.user,"rated "+json.rating,at,json.url);
	}else if(tuple == "edit"){
		histAdd(q,json.user,"edited",at,json.url,json.title,json.summary);
	}else if(tuple == "bg"){
		histAdd(q,json.user,"added a background image",at,json.url);
	}else if(tuple == "com"){
		histAdd(q,json.user,"commented on",at,json.url,null,null,json.com);
	}else if(tuple == "selection"){
		histAdd(q,json.user,"annotated",at,json.url);
	}
	histRender();
}

// a few globals
var Q = false; // active query object
var queries = new Object(); // all Q objects indexed by query
var rid=-1; // master result counter
var results = new Array(); // all results indexed by counter id

// what most things call, forces a fully "new" search
function search(query){
	// if we have other internal functions, make this generic?
	if(query.substr(0,5) == "lang:"){
		lang=query.substr(5,2);
		setCookie("ws_lang", lang);
		alert("default language is now "+langs[lang]);
		return;
	}
	cloudRender(query); // force new cloud
	showAds(query); // only show ads on user-generated queries
	canSave(); // make sure we have a fresh save token for KT
	$("q").value = query; // form field sanity enforced
	searchDo(query); // actually do this query :)
}

// the under the hood guts of showing a result
function searchDo(query)
{
	// hide any old query results
	if(Q) {
		// set inactive class on all related elements
		for(var i=0;i<Q.elems.length;i++){ $(Q.elems[i]).addClassName("inactive"); }
		Q = false;
		$("count").style.display="inline"; // show the count by default
		$("top-bar").style.background=""; // reset background
	}

	// make sure urlbar is always accurate
	if(document.location.hash != "#"+query && document.location.hash != "#"+encodeURIComponent(query)){
		document.location.hash = "#"+query;
		lasthash = document.location.hash;
	}
	$("theend").style.display="none";
	document.title = "Wikia Search - Results for: "+query;

	if(query == ""){ document.body.backgroundColor = "black"; return; } // the black hole
	
	if(queries[query])
	{ // already did this query, just show it again
		Q = queries[query];
		// set inactive class on all related elements
		for(var i=0;i<Q.elems.length;i++){ $(Q.elems[i]).removeClassName("inactive"); }
		if(Q.bg) { // if background, set it up again
			$('count').hide();
			$("top-bar").style.background=Q.bg;
		}
	}else{ // new Q!
		var oldq = Q;
		Q = document.createElement('div');
		queries[query] = Q;
		Q.q = query; // reverse index :)
		Q.elems = new Array();
		Q.elems.push(Q); // first post!
		Q.appendChild(document.createElement('span')); // dummy so .firstChild != null for .insertBefore :)
		$("search-results").appendChild(Q);
		indexFetch(Q,0); // get that result going :)
		ktFetch(Q); // hmmz
		// zepheramahntawl!
		callOut("wpRender(j)","http://"+lang+".wikipedia.org/w/api.php?action=query&list=search&srwhat=text&srsearch="+encodeURIComponent(Q.q)+"&format=json&callback=wpRender");
		callOut("fbRender(j)","http://www.freebase.com/api/service/search?limit=3&callback=fbRender&prefix="+encodeURIComponent(Q.q));
	}
	alsoRender(); // temp
}

// monitor the #hash in case it changes
var lasthash = "";
function hasher()
{
	if(!$("verybottom")) return;
  if(document.location.hash == lasthash)
  {
    return;
  }
  lasthash = document.location.hash;
  var newq = decodeURIComponent(document.location.hash.substr(1));
  search(newq);
}
window.setInterval("hasher()",100);

// this is a nice trick so that "legacy" things can make a search url like search.html?keyword and it'll work
if(document.location.hash == "" && document.location.search.length > 1)
{
	document.location.replace(document.location.href+"#"+document.location.search.substr(1));
}

// try to load more results as someone scrolls
function scrolly()
{
	if(!Q) return;
	if(Q.cursorLast == Q.cursor) return; // nothing to do
	var bot = Position.positionedOffset($("verybottom"));
	if((bot[1] - getScrollHeight()) > 600) return;
	//alert("page:" + getPageHeight() + " and scroll:"+ getScrollHeight() + "and inner:" + window.innerHeight + " and verybottom "+bot[1]);
	Q.cursorLast = Q.cursor;
	// TODO: maybe watch for onscroll event and wait till no scrolling, so it's smoother?
	indexFetch(Q,Q.cursor);
}

window.setInterval("scrolly()",100);

function processJSON(j)
{
	$("loading").style.display="none";
	if(!j || !j.search) return;

	// fix the query??
	j.search.query = j.search.query.replace(/&quot;/g,'"');
	j.search.query = j.search.query.substr(0,j.search.query.length-8);
	var q = queries[j.search.query];
	if(!q) return;
//	suggestedCount(q,j.search.numberOfHits);

	processResult(q,j.search);
	reviewKT(q); // apply any possible KT stuff

	// if there were 10 results, set cursor forward
	if(j.search.end - j.search.start == 10){
		q.cursor = j.search.end;
	}else{
		// try Y!
		q.cursor = 0;
		q.y = true;
		indexFetch(q,0);
	}
}

function processY(q,j){
// 	{"ysearchresponse":{"responsecode":"200","nextpage":"/ysearch/web/v1/java%20junk?appid=GD2UGdfIkY1gi6EBoIck4Exv2xLUsVrm&callback=foo&start=10","totalhits":"174328","deephits":"5140000","count":"10","start":"0","resultset_web":[{"abstract":"Over 600 free <b>Java</b> and Javascripts to download and use! <b>...</b> more help with using <b>Java</b> applets or Javascript, try this cool <b>Java</b> Help Page at HTMLclinic.com! <b>...</b>","clickurl":"http://www.javafile.com/nrcplus/","date":"2008/06/18","dispurl":"www.<b>javafile.com</b>/nrcplus","size":"24825","title":"JavaFile.com - free <b>java</b> scripts, <b>java</b> applets!","url":"http://www.javafile.com/nrcplus/"}
	if(!j || !j.ysearchresponse || !j.ysearchresponse.resultset_web) return;

//	suggestedCount(q,j.ysearchresponse.totalhits);

	// process our results inline here
	for (var i=0;i < j.ysearchresponse.resultset_web.length; i++)
	{
    	var doc = j.ysearchresponse.resultset_web[i];
		doc.title = doc.title.replace(/<b>/g,"");
		doc.title = doc.title.replace(/<\/b>/g,"");
		doc.abstract = doc.abstract.replace(/<b>/g,"");
		doc.abstract = doc.abstract.replace(/<\/b>/g,"");

		var r = resultAdd(q,doc.url,doc.title,doc.abstract,false);

	}
	reviewKT(q); // apply any possible KT stuff

	// if there were 10 results, set cursor forward
	if(j.ysearchresponse.count == 10){
		q.cursor = j.ysearchresponse.start+10;
	}else{
		$("theend").style.display="block";
	}
	
}

// handle: processKT(1,{"about":[{"id":"136854775807","the":"sentient","user":"127.0.0.1"}]});
function processKT(q,json){
	if(!q || !json) return;
	q.kt = json;
	var changes = 0;
	if(json.del) changes += json.del.length;
	if(json.add) changes += json.add.length;
	if(json.spot) changes += json.spot.length;
	if(json.stars) changes += json.stars.length;
	if(json.edit) changes += json.edit.length;
	// strip out non-matching languages
	for(var a in json){
		if(!json[a].length || json[a].length <= 0) continue;
		var na = new Array();
		for(var i=0; i < json[a].length; i++){
			if(!json[a][i].l || json[a][i].l == lang){ na.push(json[a][i]); }
		}
		json[a] = na;
	}
	reviewKT(q);
	histRender();
}

// this looks at all KT data and tries do something with it, if it can't it leaves it until called again
function reviewKT(q)
{
	if(!q.kt || !q.urls) return;
	var j = q.kt; // the json
	var u = q.urls; // shorter, it's convenience not obfuscation I tell you!

	// add suggestions!
	if(j.sug)
	{
		// first check for hidden ones to skip
		var hid = new Object();
		for(var i=0; i < j.sug.length; i++){
			if(j.sug[i].hide) hid[j.sug[i].sug]=j.sug[i].id;
		}
		// now add to queue or show
		if(!q.sugQueue) q.sugQueue = new Array();
		for(var i=0; i < j.sug.length; i++){
			if(j.sug[i].hide || hid[j.sug[i].sug] > j.sug[i].id) continue; // skip hidden
			cloudAdd(q.q,j.sug[i].sug);
		}
		delete j.sug;
	}

	// process added urls first
	if(j.add) {
		for(var i=0; i < j.add.length; i++){
			var a = j.add[i];
			if(u[a.url]) continue;
			histAdd(q,a.user,"added",a.id,a.url);
			resultAdd(q,a.url,q.q,"",true);
		}
		delete j.add;
	}

	// check the del tuple
	if(j.del && j.del.length) {
		var dnew = new Array();
		for(var i=0; i < j.del.length; i++){
			var d = j.del[i];
			if(!u[d.url]){
				dnew.push(d); // no match, save it 
				continue;
			}
			var hact = (d.del) ? "deleted" : "undeleted";
			histAdd(q,d.user,hact,d.id,d.url);
			// only apply newest ones!
			var ts = parseInt(d.id);
			if(u[d.url].tsDel && u[d.url].tsDel > ts) continue;
			u[d.url].tsDel = ts;
			if(d.del) u[d.url].addClassName("deleted");
			else u[d.url].removeClassName("deleted");
		}
		j.del = dnew; // skipped ones saved for later
	}

	// apply any edits
	if(j.edit && j.edit.length) {
		var enew = new Array();
		for(var i=0; i < j.edit.length; i++){
			var e = j.edit[i];
			if(!u[e.url]){
				enew.push(e); // no match, save it 
				continue;
			}
			histAdd(q,e.user,"edited",e.id,e.url,e.title,e.summary);
			// only apply newest ones!
			var ts = parseInt(e.id);
			if(u[e.url].tsEd && u[e.url].tsEd > ts) continue;
			u[e.url].tsEd = ts;
			var r = u[e.url];
			r.tytle = e.title;
			r.summary = e.summary;
			resultRender(r);
		}
		j.edit = enew; // skipped ones saved for later
	}
	
	// process spotlights
	if(j.spot && j.spot.length) {
		// we don't handle below the fold spotlights yet :(  )
		var best = new Array();
		best.ts = 0;
		best.url = "";
		for(var i=0; i < j.spot.length; i++){
			var s = j.spot[i];
			if(!u[s.url]) continue;
			histAdd(q,s.user,"spotlighted",s.id,s.url);
			// only apply newest ones!
			var ts = parseInt(s.id);
			if(ts < best.ts) continue;
			best.ts = ts;
			best.url = s.url;
		}
		if(best.url != ""){
			u[best.url].addClassName("spotlight");
			q.spot = u[best.url];
		}
		delete j.spot;
	}

	if(j.stars && j.stars.length > 0)
	{
		var snew = new Array(); // leftovers
		var update = new Object();
		// loop looking for matching urls in the results
		for(var i=0; i < j.stars.length; i++){
			var s = j.stars[i];
			if(!u[s.url]){
				snew.push(s); // no match, save it 
				continue;
			}
			histAdd(q,s.user,gettext("rated")+" "+s.rating+" "+gettext("stars"),s.id,s.url);
			if(!update[s.url]) update[s.url] = u[s.url];
			starsAlive(u[s.url],parseInt(s.rating));
		}
		// render new  calc average of all star ratings per url and apply
		for(var url in update){
			var n = u[url].id;
			$("stars_"+n).style.display="block";
			voted_new[n] = u[url].stars;
			for (var x=1;x<=voted_new[n];x++) {
				$("rating_" + n + "_" + x).src = "kt_files/star_on.gif";
			}
			for (var x=voted_new[n]+1;x<=5;x++) {
				$("rating_" + n + "_" + x).src = "kt_files/star_off.gif";
			}
		}
		starsShine(q); // apply ordering
		j.stars = snew; // ones that didn't match
	}

	// comments
	if(j.com)
	{
		var cnew = new Array(); // leftovers
		var sorted = j.com.sort(function (a,b){return parseInt(b.id) - parseInt(a.id);})
		// loop looking for matching urls in the results
		for(var i=0; i < sorted.length; i++){
			var c = sorted[i];
			if(!u[c.url]){
				cnew.push(c); // no match, save it 
				continue;
			}
			histAdd(q,c.user,"commented on",c.id,c.url,null,null,sorted[i].com);
			var com = getCom(u[c.url]);
			// is this the first one, or not?
			if(!com.first.author){
				com.first.author = c.user;
				com.first.comment = c.com;
			}else{
				var div = document.createElement('div');
				div = $(div);
				div.addClassName("comment");
				div.author = c.user;
				div.comment = c.com;
				comRender(com,div);
				com.rest.appendChild(div);
				com.count++;
			}
			comRender(com,com.first); // re-render the first one always for #
		}
		j.com = cnew; // ones that didn't match
	}
	
	if(j.selection){
		var snew = new Array(); // leftovers
		// loop looking for matching urls in the results
		var rend = new Object(); // touched results
		for(var i=0; i < j.selection.length; i++){
			var s = j.selection[i];
			if(!u[s.url]){
				snew.push(s); // no match, save it 
				continue;
			}
			var type = "";
			if(s.img) type="image";
			if(s.a) type="link";
			if(s.form) type="form";
			if(s.sel) type="text";
			if(type == "") continue;
			histAdd(q,s.user,"annotated with a "+type,s.id,s.url);
			annAdd(u[s.url],type,s);
			rend[s.url] = true;
		}
		j.selection = snew; // ones that didn't match
		for(var url in rend){
			annRender(u[url]);
		}
	}


	// custom header background
	if(j.bg && j.bg.length > 0)
	{
		var tsbest = 0;
		var bgbest = "";
		for(var i=0; i < j.bg.length; i++){
			histAdd(q,j.bg[i].user,"added a background",j.bg[i].id);
			var ts = parseInt(j.bg[i].id);
			if(ts < tsbest) continue;
			tsbest = ts;
			bgbest = j.bg[i].bg;
		}
		q.bg = bgbest;
		if(Q == q) {
			$("top-bar").style.background = bgbest;
			$('count').hide();
			$("logo").style.display="block";
		}
		delete j.bg;
	}

	// customized list of also-try
	if(j.also && j.also.length > 0)
	{
		var tsbest = 0;
		var abest = new Array();
		for(var i=0; i < j.also.length; i++){
			histAdd(q,j.also[i].user,"added another place to see results",j.also[i].id);
			var ts = parseInt(j.also[i].id);
			if(ts < tsbest) continue;
			tsbest = ts;
			abest = j.also[i].also;
		}
		q.also = Alsos;
		for(var i=0;i<abest.length;i++) q.also[abest[i]]++;
		if(Q == q) {
			alsoRender();
		}
		delete j.also;
	}

}

// dummy for now
function processKTN(){}

function selinkt(e)
{
        if(window.event) { e = window.event; }

        var sel = "";
        if(document.getSelection) {
                sel = document.getSelection();
        } else if(document.selection) { 
                sel = document.selection.createRange().text;
        } else if(window.getSelection) {
                sel = window.getSelection();
        }
        var target = e.target || e.srcElement;
		if(sel == "" || target.tagName.toLowerCase() != 'div') return;
		var re = new RegExp("(.*?)(\\S*"+sel+"\\S*)(.*)");
		var a = target.innerHTML.match(re);
		if(!a) return;
		if(a.length != 4) return;
		target.innerHTML = a[1] + '<a class="sel-autolink" href="#'+a[2]+'">'+a[2]+'</a>'+a[3];
		return true;
}

// we have to ask the server for the current time
var nowoffset = 0;
function itsNow(j){
	if(!un || un == "you") un = j.ip;
	var ko = new Date();
	nowoffset = ko.getTime() - parseInt(j.now);
	histRender(); // update history
}
callOut("itsNow(j)",KT+"now.js?f=itsNow(&r="+Math.random());

var keyStr = "ABCDEFGHIJKLMNOP" +
              "QRSTUVWXYZabcdef" +
              "ghijklmnopqrstuv" +
              "wxyz0123456789+/" +
              "=";
function encode64(input) {
   var output = "";
   var chr1, chr2, chr3 = "";
   var enc1, enc2, enc3, enc4 = "";
   var i = 0;

   do {
      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);

      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;

      if (isNaN(chr2)) {
         enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
         enc4 = 64;
      }

      output = output +
         keyStr.charAt(enc1) +
         keyStr.charAt(enc2) +
         keyStr.charAt(enc3) +
         keyStr.charAt(enc4);
      chr1 = chr2 = chr3 = "";
      enc1 = enc2 = enc3 = enc4 = "";
   } while (i < input.length);

   return output;
}