/*

Textile Editor v0.1
created by: dave olsen, wvu web services
created on: march 17, 2007
project page: slateinfo.blogs.wvu.edu

inspired by: 
 - Patrick Woods, http://www.hakjoon.com/code/38/textile-quicktags-redirect & 
 - Alex King, http://alexking.org/projects/js-quicktags

features:
 - supports: IE7, FF2, Safari2
 - ability to use "simple" vs. "extended" editor
 - supports all block elements in textile except footnote
 - supports all block modifier elements in textile
 - supports simple ordered and unordered lists
 - supports most of the phrase modifiers, very easy to add the missing ones
 - supports multiple-paragraph modification
 - can have multiple "editors" on one page, access key use in this environment is flaky
 - access key support
 - select text to add and remove tags, selection stays highlighted
 - seamlessly change between tags and modifiers
 - doesn't need to be in the body onload tag
 - can supply your own, custom IDs for the editor to be drawn around

todo:
 - a clean way of providing image and link inserts
 - get the selection to properly show in IE

more on textile:
 - Textism, http://www.textism.com/tools/textile/index.php
 - Textile Reference, http://hobix.com/textile/

*/

//document.write('<script src="/js/teh/textile-editor-config.js" type="text/javascript"></script>');

// // initiliaze the quicktags													
// function initQuicktags(canvasi) {
// 	for (var i = 0; i < canvasi.length; i++) {
// 		edToolbar(document.getElementById(canvasi[i]),canvasi[i]);
// 	}
// }

// draw the toolbar
function edToolbar(canvas, view) {
	var toolbar = document.createElement("div");
	toolbar.id = "textile-toolbar-" + canvas;
	toolbar.className = 'textile-toolbar';
	canvas = document.getElementById(canvas);
	
	canvas.parentNode.insertBefore(toolbar, canvas);
	// Create the local Button array by assigning theButtons array to edButtons
	var edButtons = new Array();
	edButtons = theButtons;
	for (var i = 0; i < edButtons.length; i++) {
		var thisButton = edShowButton(edButtons[i], canvas);
		if (view == 's') {
			if (edButtons[i].sve == 's') {
				toolbar.appendChild(thisButton);
			}
		}
		else {
			toolbar.appendChild(thisButton);
		}
	}
}

// draw individual buttons
function edShowButton(button, edCanvas) {
	var theButton = document.createElement("button");
	theButton.id = button.id;
	theButton.className = 'ed_button';
	theButton.className += 'selected';
	theButton.tagStart = button.tagStart;
	theButton.tagEnd = button.tagEnd;
	theButton.accessKey = button.access;
	theButton.title = button.title;
	theButton.open = button.open;
	theButton.onclick = function() { edInsertTag(edCanvas,this); return false; } 
	return theButton;	
}

// if clicked, no selected text, tag not open highlight button
function edAddTag(button) {
	if (button.tagEnd != '') {
		edOpenTags[edOpenTags.length] = button;
		var el = document.getElementById(button.id);
		el.className = 'selected';
	}
}

// if clicked, no selected text, tag open lowlight button
function edRemoveTag(button) {
	for (i = 0; i < edOpenTags.length; i++) {
		if (edOpenTags[i] == button) {
			edOpenTags.splice(button, 1);
			var el = document.getElementById(button.id);
			el.className = 'unselected';
		}
	}
}

// see if there are open tags. for the remove tag bit...
function edCheckOpenTags(button) {
	var tag = 0;
	for (i = 0; i < edOpenTags.length; i++) {
		if (edOpenTags[i] == button) {
			tag++;
		}
	}
	if (tag > 0) {
		return true; // tag found
	}
	else {
		return false; // tag not found
	}
}	

// insert the tag. this is the bulk of the code.
function edInsertTag(myField, button) {
	
	myField.focus();
				
	var textSelected = false;
	var finalText = '';
	var FF = false;
	
	// grab the text that's going to be manipulated, by browser
	if (document.selection) { // IE support
		sel = document.selection.createRange();
		
		// set-up the text vars
		var beginningText = '';
		var followupText = '';
		var selectedText = sel.text;
		
		// check if text has been selected
		if (sel.text.length > 0) {
			textSelected = true;	
		}
		
		// set-up newline regex's so we can swap tags across multiple paragraphs
		var newlineReplaceRegexClean = /\r\n\s\n/g;
		var newlineReplaceRegexDirty = '\\r\\n\\s\\n';
		var newlineReplaceClean = '\r\n\n';
	}
	else if (myField.selectionStart || myField.selectionStart == '0') { // MOZ/FF/NS/S support
		
		// figure out cursor and selection positions
		var startPos = myField.selectionStart;
		var endPos = myField.selectionEnd;
		var cursorPos = endPos;
		var scrollTop = myField.scrollTop;
		FF = true; // note that is is a FF/MOZ/NS/S browser
		
		// set-up the text vars
		var beginningText = myField.value.substring(0, startPos);
		var followupText = myField.value.substring(endPos, myField.value.length);
		
		// check if text has been selected
		if (startPos != endPos) {
			textSelected = true;
			var selectedText = myField.value.substring(startPos, endPos);	
		}
		
		// set-up newline regex's so we can swap tags across multiple paragraphs
		var newlineReplaceRegexClean = /\n\n/g;
		var newlineReplaceRegexDirty = '\\n\\n';
		var newlineReplaceClean = '\n\n';
	}


	// if there is text that has been highlighted...
	if (textSelected) {
		
		// set-up some defaults for how to handle bad new line characters
		var newlineStart = '';
		var newlineStartPos = 0;
		var newlineEnd = '';
		var newlineEndPos = 0;
		var newlineFollowup = '';

		// set-up some defaults for how to handle placing the beginning and end of selection
		var posDiffPos = 0;
		var posDiffNeg = 0;
		var mplier = 1;
		
		// remove newline from the beginning of the selectedText.
		if (selectedText.match(/^\n/)) {
			selectedText = selectedText.replace(/^\n/,'');
			newlineStart = '\n';
			newlineStartpos = 1;
		}
		
		// remove newline from the end of the selectedText.
		if (selectedText.match(/\n$/g)) {
			selectedText = selectedText.replace(/\n$/g,'');
			newlineEnd = '\n';
			newlineEndPos = 1;
		}
		
		// no clue, i'm sure it made sense at the time i wrote it
		if (followupText.match(/^\n/)) {
			newlineFollowup = '';
		}
		else {
			newlineFollowup = '\n\n';
		}
							
		// first off let's check if the user is trying to mess with lists
		if ((button.tagStart == ' * ') || (button.tagStart == ' # ')) {
			
			listItems = 0; // sets up a default to be able to properly manipulate final selection
			
			// set-up all of the regex's
			re_start = new RegExp('^ (\\*|\\#) ','g');
			if (button.tagStart == ' # ') {
				re_tag = new RegExp(' \\# ','g'); // because of JS regex stupidity i need an if/else to properly set it up, could have done it with a regex replace though
			}
			else {
				re_tag = new RegExp(' \\* ','g');
			}
			re_replace = new RegExp(' (\\*|\\#) ','g');
			
			// try to remove bullets in text copied from ms word **Mac Only!** 
			re_word_bullet_m_s = new RegExp('• ','g'); // mac/safari
			re_word_bullet_m_f = new RegExp('∑ ','g'); // mac/firefox
			selectedText = selectedText.replace(re_word_bullet_m_s,'').replace(re_word_bullet_m_f,'');
			
			// if the selected text starts with one of the tags we're working with...
			if (selectedText.match(re_start)) {
				
				// if tag that begins the selection matches the one clicked, remove them all
				if (selectedText.match(re_tag)) {
					finalText = beginningText
								  + newlineStart
								  + selectedText.replace(re_replace,'')
								  + newlineEnd
								  + followupText;
					if (matches = selectedText.match(/ (\*|\#) /g)) {
						listItems = matches.length;
					}
					posDiffNeg = listItems*3; // how many list items were there because that's 3 spaces to remove from final selection
				}
				
				// else replace the current tag type with the selected tag type
				else {
					finalText = beginningText
								  + newlineStart
								  + selectedText.replace(re_replace,button.tagStart)
								  + newlineEnd
								  + followupText;
				}
			}
			
			// else try to create the list type
			// NOTE: the items in a list will only be replaced if a newline starts with some character, not a space
			else {
				finalText = beginningText
							  + newlineStart
				              + button.tagStart
							  + selectedText.replace(newlineReplaceRegexClean,newlineReplaceClean + button.tagStart).replace(/\n(\S)/g,'\n' + button.tagStart + '$1')
							  + newlineEnd
							  + followupText;
				if (matches = selectedText.match(/\n(\S)/g)) {
					listItems = matches.length;
				}
				posDiffPos = 3 + listItems*3;
			}	
		}

		// now lets look and see if the user is trying to muck with a block or block modifier
		else if (button.tagStart.match(/^(h1|h2|h3|h4|bq|p|\>|\<\>|\<|\=|\(|\))/g)) {
			
			var insertTag = '';
			var insertModifier = '';
			var tagPartBlock = '';
			var tagPartModifier = '';
			var tagPartModifierOrig = ''; // ugly hack but it's late
			var drawSwitch = '';
			var captureIndentStart = false;
			var captureListStart = false;
			var periodAddition = '\\. ';
			var periodAdditionClean = '. ';
			var listItemsAddition = 0;
				
			var re_list_items = new RegExp('(\\*+|\\#+)','g'); // need this regex later on when checking indentation of lists

			var re_block_modifier = new RegExp('^(h1|h2|h3|h4|bq|p| [\\*]{1,} | [\\#]{1,} |)(\\>|\\<\\>|\\<|\\=|[\\(]{1,}|[\\)]{1,6}|)','g');
			if (tagPartMatches = re_block_modifier.exec(selectedText)) {
				tagPartBlock = tagPartMatches[1];
				tagPartModifier = tagPartMatches[2];
				tagPartModifierOrig = tagPartMatches[2];
				tagPartModifierOrig = tagPartModifierOrig.replace(/\(/g,"\\(");
			}

			// if tag already up is the same as the tag provided replace the whole tag
			if (tagPartBlock == button.tagStart) { 
				insertTag  = tagPartBlock + tagPartModifierOrig; // use Orig because it's escaped for regex
				drawSwitch = 0; 
			}
			// else if let's check to add/remove block modifier
			else if ((tagPartModifier == button.tagStart) || (newm = tagPartModifier.match(/[\(]{2,}/g))) {
				if ((button.tagStart == '(') || (button.tagStart == ')')) {
					var indentLength = tagPartModifier.length;
					if (button.tagStart == '(') {
						indentLength = indentLength + 1;
					}
					else {
						indentLength = indentLength - 1;
					}
					for (var i = 0; i < indentLength; i++) {
						insertModifier = insertModifier + '(';
					}
					insertTag = tagPartBlock + insertModifier;
				}
				else {
					if (button.tagStart == tagPartModifier) {
						insertTag =  tagPartBlock;
				    } // going to rely on the default empty insertModifier
					else {
						
						if (button.tagStart.match(/(\>|\<\>|\<|\=)/g)) {
							insertTag = tagPartBlock + button.tagStart;
						}
						else {
							insertTag = button.tagStart + tagPartModifier;
						}
					}
					
				}
				drawSwitch = 1;
			}
			// indentation of list items
			else if (listPartMatches = re_list_items.exec(tagPartBlock)) {
					var listTypeMatch = listPartMatches[1];
					var indentLength = tagPartBlock.length - 2;
					var listInsert = '';
					if (button.tagStart == '(') {
						indentLength = indentLength + 1;
					}
					else {
						indentLength = indentLength - 1;
					}
					if (listTypeMatch.match(/[\*]{1,}/g)) {
						var listType = '*';
						var listReplace = '\\*';
					}
					else {
						var listType = '#';
						var listReplace = '\\#';
					}
					for (var i = 0; i < indentLength; i++) {
						listInsert = listInsert + listType;
					}
					if (listInsert != '') {
						insertTag = ' ' + listInsert + ' ';
					}
					else {
						insertTag = '';
					}
					tagPartBlock = tagPartBlock.replace(/(\*|\#)/g,listReplace);
					drawSwitch = 1;
					captureListStart = true;
					periodAddition = '';
					periodAdditionClean = '';
					if (matches = selectedText.match(/\n\s/g)) {
						listItemsAddition = matches.length;
					}
			}
			// must be a block modification e.g. p>. to p<.
			else {
					
				// if this is a block modification/addition
				if (button.tagStart.match(/(h1|h2|h3|h4|bq|p)/g)) { 
					if (tagPartBlock == '') {
						drawSwitch = 2;
					}
					else {
						drawSwitch = 1;
					}
					
					insertTag = button.tagStart + tagPartModifier;
				}
				
				// else this is a modifier modification/addition
				else {
					if ((tagPartModifier == '') && (tagPartBlock != '')) {
						drawSwitch = 1;
					}
					else if (tagPartModifier == '') {
						drawSwitch = 2;
					}
					else {
						drawSwitch = 1;
					}
					
					// if no tag part block but a modifier we need at least the p tag
					if (tagPartBlock == '') {
						tagPartBlock = 'p';
					}
					
					//make sure to swap out outdent
					if (button.tagStart == ')') {
						tagPartModifier = '';
					}
					else {
						tagPartModifier = button.tagStart;
						captureIndentStart = true; // ugly hack to fix issue with proper selection handling
					}
					
					insertTag = tagPartBlock + tagPartModifier;
				}
			}
			
			mplier = 0;
			if (captureListStart || (tagPartModifier.match(/[\(\)]{1,}/g))) {
				re_start = new RegExp(insertTag.escape + periodAddition,'g'); // for tags that mimic regex properties, parens + list tags
			}
			else {
				re_start = new RegExp(insertTag + periodAddition,'g'); // for tags that don't, why i can't just escape everything i have no clue
			}
			re_old = new RegExp(tagPartBlock + tagPartModifierOrig + periodAddition,'g');
			re_middle = new RegExp(newlineReplaceRegexDirty + insertTag.escape + periodAddition.escape,'g');
			re_tag = new RegExp(insertTag.escape + periodAddition.escape,'g');
			
			// *************************************************************************************************************************
			// this is where everything gets swapped around or inserted, bullets and single options have their own if/else statements
			// *************************************************************************************************************************
			if ((drawSwitch == 0) || (drawSwitch == 1)) {
				if (drawSwitch == 0) { // completely removing a tag
					finalText = beginningText
								  + newlineStart
								  + selectedText.replace(re_start,'').replace(re_middle,newlineReplaceClean)
								  + newlineEnd
								  + followupText;
					if (matches = selectedText.match(newlineReplaceRegexClean)) {
						mplier = mplier + matches.length;
					}
					posDiffNeg = insertTag.length + 2 + (mplier*4);
				}
				else { // modifying a tag, though we do delete bullets here
					finalText = beginningText
								  + newlineStart
								  + selectedText.replace(re_old,insertTag + periodAdditionClean)
								  + newlineEnd
								  + followupText;
					
					if (matches = selectedText.match(newlineReplaceRegexClean)) {
						mplier = mplier + matches.length;
					}
					// figure out the length of various elements to modify the selection position
					if (captureIndentStart) { // need to double-check that this wasn't the first indent
						tagPreviousLength = tagPartBlock.length;
						tagCurrentLength = insertTag.length;
					}
					else if (captureListStart) { // if this is a list we're manipulating
						if (button.tagStart == '(') { // if indenting
							tagPreviousLength = listTypeMatch.length + 2;
							tagCurrentLength = insertTag.length + listItemsAddition;
						}
						else if (insertTag.match(/(\*|\#)/g)) { // if removing but still has bullets
							tagPreviousLength = insertTag.length + listItemsAddition;
							tagCurrentLength = listTypeMatch.length;
						}
						else {  // if removing last bullet
							tagPreviousLength = insertTag.length + listItemsAddition;
							tagCurrentLength = listTypeMatch.length - (3*listItemsAddition) - 1;
						}
					}
					else { // everything else
						tagPreviousLength = tagPartBlock.length + tagPartModifier.length;
						tagCurrentLength = insertTag.length;
					}
					if (tagCurrentLength > tagPreviousLength) {
						posDiffPos = (tagCurrentLength - tagPreviousLength) + (mplier*(tagCurrentLength - tagPreviousLength));
					}
					else {
						posDiffNeg = (tagPreviousLength - tagCurrentLength) + (mplier*(tagPreviousLength - tagCurrentLength));
					}
				}
			}
			else { // for adding tags other then bullets (have their own statement)
				finalText = beginningText
							  + newlineStart
				              + insertTag + '. '
							  + selectedText.replace(newlineReplaceRegexClean,button.tagEnd + '\n' + insertTag + '. ')
							  + newlineFollowup
							  + newlineEnd
							  + followupText;
				if (matches = selectedText.match(newlineReplaceRegexClean)) {
					mplier = mplier + matches.length;
				}
				posDiffPos = insertTag.length + 2 + (mplier*4);
			}				
		}
		
		// swap in and out the simple tags around a selection like bold
		else {
				
			mplier = 1; // the multiplier for the tag length
			re_start = new RegExp('^\\' + button.tagStart,'g');
			re_end =  new RegExp('\\' + button.tagEnd + '$','g');
			re_middle = new RegExp('\\' + button.tagEnd + newlineReplaceRegexDirty + '\\' + button.tagStart,'g');
			if (selectedText.match(re_start) && selectedText.match(re_end)) {
				finalText = beginningText
							  + newlineStart
							  + selectedText.replace(re_start,'').replace(re_end,'').replace(re_middle,newlineReplaceClean)
							  + newlineEnd
							  + followupText;
				if (matches = selectedText.match(newlineReplaceRegexClean)) {
					mplier = mplier + matches.length;
				}
				posDiffNeg = button.tagStart.length*mplier + button.tagEnd.length*mplier;
			}
			else {
				finalText = beginningText
							  + newlineStart
				              + button.tagStart
							  + selectedText.replace(newlineReplaceRegexClean,button.tagEnd + newlineReplaceClean + button.tagStart)
							  + button.tagEnd
							  + newlineEnd
							  + followupText;
				if (matches = selectedText.match(newlineReplaceRegexClean)) {
					mplier = mplier + matches.length;
				}
				posDiffPos = (button.tagStart.length*mplier) + (button.tagEnd.length*mplier);
			}
		}
		
		cursorPos += button.tagStart.length + button.tagEnd.length;
		
	}
	
	// just swap in and out single values, e.g. someone clicks b they'll get a *
	else {
		var buttonStart = '';
		var buttonEnd = '';
		var re_p = new RegExp('(\\<|\\>|\\=|\\<\\>|\\(|\\))','g');
		var re_h = new RegExp('^(h1|h2|h3|h4|p|bq)','g');
		if (!edCheckOpenTags(button) || button.tagEnd == '') { // opening tag
			
		 	if (button.tagStart.match(re_h)) {
				buttonStart = button.tagStart + '. ';
			}
			else {
				buttonStart = button.tagStart;
			}
			if (button.tagStart.match(re_p)) { // make sure that invoking block modifiers don't do anything
				finalText = beginningText 
				           + followupText;
				cursorPos = startPos;
			}
			else {
				finalText = beginningText 
				            + buttonStart
				            + followupText;
				edAddTag(button);
				cursorPos = startPos + buttonStart.length;
			}
		
		}
		else {  // closing tag
			if (button.tagStart.match(re_p)) {
				buttonEnd = '\n\n';
			}
			else if (button.tagStart.match(re_h)) {
				buttonEnd = '\n\n';
			}
			else {
				buttonEnd = button.tagEnd
			}
			finalText = beginningText 
			            + button.tagEnd
			            + followupText;
			edRemoveTag(button);
			cursorPos = startPos + button.tagEnd.length;
		}
	}

	// set the appropriate DOM value with the final text
	if (FF == true) {
		myField.value = finalText;
		myField.scrollTop = scrollTop;
	}
	else {
		sel.text = finalText;
	}
	
	// build up the selection capture, doesn't work in IE
	if (textSelected) {
		myField.selectionStart = startPos + newlineStartPos;
		myField.selectionEnd = endPos + posDiffPos - posDiffNeg - newlineEndPos;
		//alert('s: ' + myField.selectionStart + ' e: ' + myField.selectionEnd + ' sp: ' + startPos + ' ep: ' + endPos + ' pdp: ' + posDiffPos + ' pdn: ' + posDiffNeg)
	}
	else {
		myField.selectionStart = cursorPos;
		myField.selectionEnd = cursorPos;
	}
}