﻿
	/**
	  * Check which browser render engine we're dealing with
	  */
	function getBrowser()
	{
		var opera="opera"; var ie="ie"; var gecko="gecko"; var browser="unknown";
		if (window.opera) { browser = opera; }
		else if (Array.every) { browser = gecko; }
		else if (document.all) { browser = ie; }
		return browser;
	}
	
	/**
	  * It's miraculous javascript doesn't have this function
	  */
	function exit()
	{
		// gracious if possible
		if (window.stop) { window.stop(); }
		// force death if gracious doesn't work.
		else System.exit();
	}

	/**
	  * Gets the overflow ratio 'real content':'visible content' if there is overflow.
	  * If there is no overflow, this value is 1.
	  */
	function getVerticleOverflow(id)
	{
		var obj = document.getElementById(id);
		if(obj.innerHTML.length==0) return 0;
		return obj.scrollHeight/obj.clientHeight;
	}

	/**
	  * Gets the overflow ratio 'real content':'visible content' if there is overflow.
	  * If there is no overflow, this value is 1.
	  */
	function getHorizontalOverflow(id)
	{
		var obj = document.getElementById(id);
		if(obj.innerHTML.length==0) return 0;
		return obj.scrollWidth/obj.clientWidth;
	}

	/**
	  * Sets up a new page for filling
	  */
	function addPage(pagenumber)
	{
		var nextpage =	"<a name='page '"+pagenumber+"></a>"+
					"<div class='pageplacer'>"+
						"<div id='offsets"+pagenumber+"' class='offsets'>"+
							"<div class='pageheader'>new chapter "+chapter+"</div>"+
							"<div id='page"+pagenumber+"' class='page'></div>"+
							"<div class='pagenumber'>page "+pagenumber+"</div>"+
							"<div class='pagefooter'>"+
								"<span class='point' onclick='toggleToC()'>toggle toc</span>"+
								"　-　"+
								"<span class='point' onclick='toggleIndex()'>toggle index</span>"+
								"　-　" +
								"<span class='point' onclick='unhighlight()'>unhighlight terms</span>"+
							"</div>"+
						"</div>"+
					"</div>\n";
		document.getElementById('content').innerHTML = document.getElementById("content").innerHTML + nextpage;
	}

	/**
	  * Removes superfluous <br> codes at the start of a page
	  */		
	function pruneStartBR(pagenumber)
	{
		// kill <br> codes at the start of this new page's text
		var killedlines = false;
		var string = document.getElementById("page"+pagenumber).innerHTML;
		var ts = string.length;
		string = string.replace(/^(\s*　*\<br\>)+/, "");
		if(ts != string.length) { killedlines = true; }
		document.getElementById("page"+pagenumber).innerHTML = string;
		return killedlines;
	}

	/**
	  * Replaces multiple <br> at the end of a page with a single <br>
	  */		
	function pruneEndBR(pagenumber)
	{
		// kill <br> codes at the start of this new page's text
		var killedlines = false;
		var string = document.getElementById("page"+pagenumber).innerHTML;
		var ts = string.length;
		string = string.replace(/(\s*　*\<br\>\s*　*)+$/, "");
		if(ts != string.length) { killedlines = true; }
		document.getElementById("page"+pagenumber).innerHTML = string;
		return killedlines;
	}

	/**
	  * Finds the next sentence break position in a text string, kind of like indexOf, but then more custom.
	  * This function will not suggest line breaks when it's inside of an html element, for instance.
	  */
	function getNextBreak(text, prevbreak)
	{
		// used to make sure we don't break up complex html elements
		var inelement = 0;
		var inentity = false;
		for(i=prevbreak; i<text.length;i++) {
			var substr = text.substring(i,i+1);
			if(substr=="<") inelement++;
			else if(substr==">") inelement--;
			else if(substr=="&") inentity=true;
			else if(inentity && substr==";") inentity=false;
			// spaces, semi-colons, colons, full stops, and commas all count as legal break points
			// we could do this with a regexp, but testing shows this makes absolutely zero difference on processing time.
			else if(!inentity && inelement==0 && (substr==" " || substr==":" || substr==";" || substr=="," || substr==".")) return i+1; }
		return -1;
	}

	/**
	  * Tries to move data from a line that would cause overflow into an underfilled page
	  *
	  * This operation is a bottle-neck; we can improve it by using a divide-and-check 
	  * approach instead. But that's for later.
	  */
	function retroFit(pagenumber)
	{
		var divstring = document.getElementById("page"+pagenumber).innerHTML;
		if(currentline<lines.length) 
		{
			var string = lines[currentline];
			var goodpos = -1;
			var pos = 0;
			var head ="";
			var tail="";

			while(pos!=-1 && getVerticleOverflow("page"+pagenumber)<=1) {
				head = string.substring(0,pos);
				tail = string.substring(pos);
				document.getElementById("page"+pagenumber).innerHTML = divstring + head.replace(/\s+$/,''); 
				if(getVerticleOverflow("page"+pagenumber)<=1) { goodpos = pos; }
				pos = getNextBreak(string,pos); }

			// at this point we're overfilled again. "backtrack" to a previous (not overflowing) segmentation.			
			// first, some magic. Javascript will do unpredictable things without the following two lines:
			// if they're missing, somehow the div's scroll:screen ratio stays at what it was at overflow.
			document.getElementById("page"+pagenumber).innerHTML = "empty";
			var r = getVerticleOverflow("page"+pagenumber);			

			// copy all the content up to the last known good position
			head = string.substring(0,goodpos);
			tail = string.substring(goodpos);
			document.getElementById("page"+pagenumber).innerHTML = divstring + head.replace(/\s+$/,'');
			lines[currentline-1] += head;
			lines[currentline] = tail;
		}
	}

	/**
	 * Builds the table of contents
	 * TODO: fix this up - why build an array instead of a direct string?
	 */
	function buildToC()
	{
		var toclines = Array();
		for(i=1; i<page; i++) {
			var divstring = document.getElementById("page"+i).innerHTML;
			var lines = divstring.split(/(<br>|<BR>|<br\s*\/>|<BR\s*\/>)/);
			for(j=0; j<lines.length; j++) {
				var line = lines[j];
				// strips any html element, since they're not relevant to determining whether a line belongs in the ToC
				var matcher = line.replace(/<[^>]+>/g,"");
				// check if it's not a comment, has no lower case letters, and has at least three consecutive capitals
				if(matcher!="" && !matcher.match(/%/) && !matcher.match(/[a-z]/) && matcher.match(/[A-Z][A-Z][A-Z]/)) {
					toclines[toclines.length] =	"<tr>"+
											"<td class='tocentry'>"+
												"<span onclick='toggleToC()'><a href='#page"+i+"'>"+line+"</a></span>"+
											"</td>"+
											"<td align='right'>"+i+"</td>"+
										"</tr>"; }}}
		var tocstring = "<table><tr><td></td><td nowrap='nowrap' align='right' class='small point' onclick='toggleToC()'>close</span></td></tr>";
		for(i=0; i<toclines.length;i++) { if(toclines[i]) tocstring += toclines[i]; }
		tocstring += "</table>";
		document.getElementById("tocdiv").style.display="block";
		document.getElementById("tocdiv").innerHTML = tocstring;
		document.getElementById("tocdiv").style.display="none";
	}

	/**
	 * Shows the table of contents is not visible, or hides it if visible
	 */
	function toggleToC()
	{
		var div = document.getElementById("tocdiv");
		var display = div.style.display;
		if(display=="block") { div.style.display = "none"; }
		else if (display=="none") { div.style.display = "block"; }
	}
	
	/**
	  * Create a comprehensive index
	  * TODO: optimise this a little, it's fairly ... inefficient at the moment.
	  */
	function buildIndex(pagenumber)
	{
		var jp = "";
		var index= new Object();
		for(i=1; i<pagenumber; i++) {
			var divstring = document.getElementById("page"+i).innerHTML;
			// remove html elements, since we definitely don't want to index those
			divstring = divstring.replace(/<[^>]+>/g,"");
			// FIXME: seems to split wrongly, leading to index terms such as 'y' ... 
			var terms = divstring.split(/[^\w'\u3040-\u30FF\u4E00-\u9FFFF\u3005\u3006]+/);	// we want a few symbols from the CJK punctuation block
			for(j=0; j<terms.length; j++) {
					var term = terms[j].toLowerCase();
					if(term.match(/'/)) continue;
					if(!index[term]) { index[term] = "<a href='#page"+i+"'><span onclick='findterm(\""+term+"\","+i+")'>"+i+"</span></a>"; }
					else { index[term] += ", <a href='#page"+i+"'><span onclick='findterm(\""+term+"\","+i+")'>"+i+"</span></a>"; }}}

		// sort the keys
		var keys = Array();
		for (key in index) { keys[keys.length]=key; }
		keys.sort();

		// build html
		var indextable = "<table style='margin-right:1em'><tr><td></td><td align='right' nowrap='nowrap' class='small point' onclick='toggleIndex()'>close</span></td></tr>";
		for (k=0; k<keys.length; k++) {
			var key = keys[k];
			if(key=="") continue;
			var value = index[key];
			var varray = value.split(", ");
			var narray = Array();
			var narpos = 0;
			// what's the number of pages this word features on, as well as the avg freq. per page?
			var numpg=1;
			var curr="";
			for(v=0;v<varray.length;v++) {
				if(varray[v]!=curr) { 
					numpg++; 
					curr = varray[v];
					narray[narpos++] = curr; }}
			var avgpres = varray.length/numpg;
			var cutoff = ((varray.length-numpg)+1)/avgpres;
			// CUTOFF CRITERIA: on less than five pages, or on less than 5 unique pages with a cutoff equal or lower than 5
			if(varray.length<5 || (numpg<5 && cutoff<=5)) { indextable += "<tr width='*'><td nowrap='nowrap' class='indexentry'>"+key+"</td><td nowrap='nowrap'>"+narray.join(", ")+"</td></tr>"; }}
			
		// set html
		indextable += "</table>";
		document.getElementById("indexdiv").style.display = "block";
		document.getElementById("indexdiv").innerHTML = indextable;
		document.getElementById("indexdiv").style.display = "none";
	}

	/**
	  * Toggle the index window
	  */
	function toggleIndex()
	{
		unhighlight();
		var div = document.getElementById("indexdiv");
		var display = div.style.display;
		if(display=="block") { div.style.display = "none"; }
		else if (display=="none") { div.style.display = "block"; }
	}

	/**
	  * called from an index link: turns off the index, then highlights the terms on the relevant page
	  */
	function findterm(term, pagenumber)
	{
		toggleIndex();
		highlight(term, pagenumber);
	}

	// vars used to undo highlighting when the index is pulled up
	var highlighted_term = "";
	var highlighted_page = -1;
	
	/**
	  * Highlight terms on a page when we navigate based on the index
	  */
	function highlight(term, pagenumber)
	{
		highlighted_term = term;
		highlighted_page = pagenumber;
		divstring = document.getElementById('page'+pagenumber).innerHTML;
		// TODO: also highlights inside html elements right now, which is pretty bad
		var re = new RegExp("(^|[^\w]+)"+term+"([^\w]+)","g");
		// for some magical reason, IE, refuses to add quotes for this class
		var newstring = divstring.replace(re, "$1<span class='highlight'>"+term+"</span>$2");
		if(divstring==newstring) { alert("no replacement occurred."); }
		document.getElementById('page'+pagenumber).innerHTML = newstring;
	}
	/**
	  * kill all outstanding highlights
	  */
	function unhighlight()
	{
		if(highlighted_page != -1) {
			var div = document.getElementById('page'+highlighted_page);
			var replaced=div.innerHTML;
			// make sure the replace is tried for no quotes (IE), single quotes (webkit) and double quotes (opera, gecko)... 
			// then ALSO make sure you check for lowercase and uppercase element names... interoperability my ass!
			var replace_none = div.innerHTML.split("<span class=highlight>"+highlighted_term+"</span>");
			if(replace_none.length>1) { replaced = replace_none.join(highlighted_term); }
			else {
				replace_none = div.innerHTML.split("<SPAN class=highlight>"+highlighted_term+"</SPAN>");
				if(replace_none.length>1) { replaced = replace_none.join(highlighted_term); }
				else {
					var replace_single = div.innerHTML.split("<span class='highlight'>"+highlighted_term+"</span>"); 
					if(replace_single.length>1) { replaced = replace_single.join(highlighted_term); }
					else { 
						replace_single = div.innerHTML.split("<SPAN class='highlight'>"+highlighted_term+"</SPAN>"); 
						if(replace_single.length>1) { replaced = replace_single.join(highlighted_term); }
						else {
							var replace_double = div.innerHTML.split("<span class=\"highlight\">"+highlighted_term+"</span>"); 
							if(replace_double.length>1) { replaced = replace_double.join(highlighted_term); }
							else {
								replace_double = div.innerHTML.split("<SPAN class=\"highlight\">"+highlighted_term+"</SPAN>"); 
								if(replace_double.length>1) { replaced = replace_double.join(highlighted_term); }
								else {
									alert("ERROR: problem unhighlighting ["+highlighted_term+"] in ["+div.innerHTML+"]"); }}}}}}
			div.innerHTML = replaced;
			highlighted_term = "";
			highlighted_page = -1; }
	}

	/**
	  * Fills a div with id "page"+pagenumber with content
	  */
	function fillPage(pagenumber)
	{
		document.getElementById('load').innerHTML = document.getElementById('load').innerHTML + "<div class='loadbox'></div>";

		var startline = currentline;

		// fill the div until it's overflowing
		while(getVerticleOverflow("page"+pagenumber)<=1 && currentline<=lines.length) {
			var line = lines[currentline++];
			if(!line) continue;
			document.getElementById("page"+pagenumber).innerHTML = document.getElementById("page"+pagenumber).innerHTML + line; }

		// remove superfluous linebreaks at the start of the page, and if that decreased the content to non-overflowing, add some more data
		var killedleading = pruneStartBR(pagenumber);
		while(killedleading && getVerticleOverflow("page"+pagenumber)<=1 && currentline<=lines.length) {
			document.getElementById("page"+pagenumber).innerHTML = document.getElementById("page"+pagenumber).innerHTML + lines[currentline++]; }

		// after either pruning or pruning and refilling, we end up with guaranteed overflow. can we fix this by removing the last line's "<br>"?
		if(pruneEndBR(pagenumber) && getVerticleOverflow("page"+pagenumber)<=1) { /* done */ }

		// if we couldn't, we're not done yet. Refill the div from scratch up to the line that still fit, and then try to trickle-feed this (underfilled) content with words from the next line.
		else {
			currentline--;
			divstring="";
			for(i=startline; i<currentline; i++) { divstring = divstring + lines[i]; }
			document.getElementById("page"+pagenumber).innerHTML = divstring; 

			// remove superfluous linebreaks at the start of the page again (but only if that did anything the first time we tried it)
			if (killedleading) pruneStartBR(pagenumber);

			// since we know the page is now underfilled, try to progressively move data from the next line to the end of this page's last line.
			retroFit(pagenumber); }

		// If client-side javascript and render engines could be perfectly trusted, we would be done, but it's possible that something got mistreported,
		// so we do one more check to see if there really isn't any overflow. If there is, we mark the page as 'problem' by giving it a slightly red border
		var overflowborder = "#AA0000";
		var vflow = getVerticleOverflow("page"+pagenumber);
		var hflow = getHorizontalOverflow("page"+pagenumber);
		if (vflow>1 || hflow>1) { 
			document.getElementById("offsets"+pagenumber).style.backgroundColor="#FFEEEE";
			if (vflow>1) { document.getElementById("page"+pagenumber).style.borderBottom="1px solid "+overflowborder;  }
			else if (hflow>1) { document.getElementById("page"+pagenumber).style.borderRight="1px solid "+overflowborder;  }}

		if(document.getElementById("page"+pagenumber).innerHTML.length==0)
		{
			document.getElementById("page"+pagenumber).innerHTML = "ERROR: EMPTY PAGE AFTER FILLING!<br>page is ["+pagenumber+"], currentline is ["+currentline+"], line would be ["+lines[currentline]+"]";
			exit(-1);
		}

		// during processing, we hide each page after we build it, as this saves an incredible amount of time (since the render engine won't be trying
		// to dynamically fit all divs generated every time a new one is built)
		document.getElementById("offsets"+pagenumber).style.display = "none";

		// build the next page, with a brief wait so the browser is responsive
		setTimeout('tryPage();', 50);
	}

	/**
	  * load a new chapter
	  */
	function loadChapter()
	{
		var select = document.getElementById("chapters");
		chapter = select.options[select.selectedIndex].value;
		reLoad();
	}
	
	/**
	  * reload chapter data
	  */
	function reLoad()
	{
		var xmlhttp = getNewHTTPObject();
		xmlhttp.open("GET","index.php?getchapter="+chapter,false);
		xmlhttp.send(null);
		lines = eval(xmlhttp.responseText);		
		totalchars = 0;
		for(i in lines) { totalchars += lines[i].length; }
		reGenerate();
	}

	/**
	  * Resets all vars to their initial values, and sets the CSS rules for page dimensions according to what the user specified
	  */
	function reGenerate()
	{
		// get the new dimensions
		if(rule = getCSSRule(".offsets")) rule.style.height = (document.getElementById("pageheight_in_cm").value * cmtoem) + 'em';
		if(rule = getCSSRule(".offsets")) rule.style.width = (document.getElementById("pagewidth_in_cm").value * cmtoem) + 'em';
		if(rule = getCSSRule(".page")) rule.style.height = (document.getElementById("textareaheight_in_cm").value * cmtoem) + 'em';
		if(rule = getCSSRule(".page")) rule.style.width = (document.getElementById("textareawidth_in_cm").value * cmtoem) + 'em';

		// reset the page content
		document.getElementById("content").innerHTML="";
		document.getElementById("propertysheet").innerHTML="";
		document.getElementById("load").innerHTML="<div class='loadbox'></div>";
			
		// reset the generation values
		currentline=0;
		page=1;
		processedchars=0;
		starttime = (new Date()).getTime();
		
		// clear the toc
		toclines = new Array();
		var toc = document.getElementById('tocdiv');
		toc.style.display = "block";
		toc.innerHTML="";
		toc.style.display = "none";
		
		// clear the index
		var index = document.getElementById("indexdiv");
		index.style.display="block";
		index.innerHTML = "";
		index.style.display="none";
		
		// rebuild the page with the new properties
		buildPropertySheet();
		tryPage();
	}

	/**
	  * Try to build a page. This function keeps being called until either the hard limit is reached, or
	  * we run out of content to fill pages with.
	  */			
	function tryPage()
	{
		// if set to true, this limits the page generation to whichever hardlimit you fill in for pagelimit.
		var limit = false;
		var pagelimit = 10;

		// terminating condition
		if ((limit && page>pagelimit) || currentline >= lines.length) {
			// after generating all pages, set them to visible again, and disable any overflow scrollbars that might still exist
			for(p=1; p<page; p++) { 
				document.getElementById("offsets"+p).style.display = "block"; 
				document.getElementById("page"+p).style.overflow = "hidden";
				document.getElementById("page"+p).style.overflowY = "hidden"; }
				
			// report generation time
			var generationtime = (new Date()).getTime() - starttime;
			document.getElementById('loadstatus').innerHTML = "generated "+(page-1)+" pages (generation took "+Math.floor(generationtime*10/1000)/10+"s)"; 
			
			// build the table of contents, and index
			buildToC();
			buildIndex(page);
		}

		// if not terminated, iterate
		else {
			addPage(page); 
			fillPage(page); 
			
			processedchars += document.getElementById("page"+page).innerHTML.length;
			var perc = 100 * processedchars / totalchars;
			document.getElementById('loadstatus').innerHTML = "generating page "+page +" ("+Math.ceil(perc)+"% done)";
			
			page++;
		 }
	}