// ----------------------------------------------------------------------------
// markItUp! Universal MarkUp Engine, JQuery plugin
// v 1.0.1 beta
// Dual licensed under the MIT and GPL licenses.
// ----------------------------------------------------------------------------
// Copyright (C) 2007 Jay Salvat
// http://markitup.jaysalvat.com/
// ----------------------------------------------------------------------------
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ----------------------------------------------------------------------------
(function($) {
	$.fn.markItUp = function(settings, extraSettings) {	
		var options = {	nameSpace:				"",
						previewIFrame:			true,
						previewIFrameRefresh:	true,
						previewBaseUrl:			"",
						previewCharset:			"utf-8",
						previewCssPath:			"",
						previewBodyId:			"",
						previewBodyClassName:	"",
						previewParserPath:		"",
						previewParserVar:		"data",
						beforeInsert:			"",
						afterInsert:			"",
						onEnter:			 	{},
						onShiftEnter:		 	{},
						onCtrlEnter:		 	{},
						onTab:				 	{},
						markupSet:			[	{ /* ... */ } ]						
					};
		$.extend(options, settings, extraSettings);
		var ctrlKey = false;
		var shiftKey = false;
		var altKey = false;
 
		return this.each(function() {		
			var $$ = $(this);
			var textArea = this;
			var levels = [];
			var iFrame = false;
			var scrollPosition = 0;
			var caretPosition = 0; 
			var caretOffset = -1;
			var clicked, hash;
			var header, footer;
			var win;
			init();
			
			// init and build editor
			function init() {
				$$.wrap('<span class="'+options.nameSpace+'"></span>');	
				$$.wrap('<div id="'+($$.attr("id")||"")+'" class="markItUp"></div>');		
				$$.wrap('<div class="markItUpContainer"></div>');
				$$.attr("id", "").addClass("markItUpEditor");	
				
				// add the header before textarea
				header = $('<div class="markItUpHeader"></div>').insertBefore($$);
				$(dropMenus(options.markupSet)).appendTo(header);
				
				// add the resize handle after textarea
				resizeHandle = $('<div class="markItUpResizeHandle"></div>')
					.insertAfter($$)
					.bind("mousedown", function(e) {
						var h = $$.height();
						var y = e.clientY;
						var mouseMove = function(e) {
							$$.css("height", Math.max(20, e.clientY+h-y)+"px");
						};
						var mouseUp = function(e) {
							$("html").unbind("mousemove", mouseMove).unbind("mouseup", mouseUp);
						};
						$("html").bind("mousemove", mouseMove).bind("mouseup", mouseUp);
				});
				footer = $('<div class="markItUpFooter"></div>').insertAfter($$);
				footer.append(resizeHandle);
				
				// listen key events
				$$.keydown(keyPressed).keyup(keyPressed);				  
				
				// bind an event to catch external calls
				$$.bind("insertion", function(e, settings) {
					if (settings.target != false) {
						get();
					}
					if (textArea == $.markItUp.focused) {
						markup(settings);
					}
				});
				
				// remember the last focus
				$$.focus(function() {
				   $.markItUp.focused = this;
				});
			}
			
			// recursively build header with dropMenus from markupset
			function dropMenus(markupSet) {
				var ul = $("<ul></ul>"), i = 0;
				$("li:hover > ul", ul).css("display", "block");
				$(markupSet).each(function() {
					var button = this, t = ""; 
					var title = (button.key) ? " [Ctrl+"+button.key+"]" : "";
					if (button.separator) {
						li = $('<li class="markItUpSeparator">'+(button.separator||"")+'</li>').appendTo(ul);							
					} else {
						i++;
						for (n = levels.length, j = n-1; j >= 0; j--) {
							t += levels[j]+"-";
						}
						li = $('<li class="'+(button.className||"")+' markItUpButton markItUpButton'+t+(i)+'"><a href="#" accesskey="'+(button.key||"")+'" title="'+(button.name+title||"")+'">'+(button.name||"")+'</a></li>')
						.click(function(e) { 
//							e.preventDefault(); 
//							e.stopPropagation()
							if (button.call) {
								eval(button.call)(); 
							}
							markup(button);
							return false;
						}).hover(function() {
								$("> ul", this).show();
							}, function() {
								$("> ul", this).hide();	
							}
						).appendTo(ul);	
						if (button.dropMenu) {
							levels.push(i);
							$(li).addClass("markItUpDropMenu").append(dropMenus(button.dropMenu));
						}
					}
				}); 
				levels.pop();
				return ul;
			}

			// markItUp! markups
			function magicMarkups(string) {
				if (string) {
					string = string.toString();
					// (!(alternative)!), (!(default|!|alternative)!)
					string = string.replace(/\(\!\((.*?)\)\!\)/gm,
						function(x, a) {
							b = a.split("|!|");
							if (altKey == true) {
								return (b[1] != undefined) ?  b[1] : b[0];
							} else {
								return (b[1] == undefined) ? "" : b[0];
							}														
						});
					// [![prompt]!], [![prompt:!:value]!]
					string = string.replace(/\[\!\[(.*?)\]\!\]/gm, 
						function (a) { 
							b = a.replace(/(\[\!\[|\]\!\])/gm, "").split(":!:"); 
							return prompt(b[0], (b[1]) ? b[1] : "")||""; 
						});	
					return string;
				}
				return "";
			}
			
			// prepare action
			function prepare(action)	{
				if ($.isFunction(action)) action = action(hash);
				return magicMarkups(action);
			}
			
			// build block to insert
			function build(string)	{
				openWith 	= prepare(clicked.openWith);
				placeHolder = prepare(clicked.placeHolder);
				replaceWith = prepare(clicked.replaceWith); 
				closeWith 	= prepare(clicked.closeWith);
				
				if (replaceWith != "") {
					block = openWith + replaceWith + closeWith;				
				} else if (selection == "" && placeHolder != "") { 
					block = openWith + placeHolder + closeWith;
				} else {
					block = openWith + (string||selection) + closeWith;								
				}
				
				return {	block:		block, 
							openWith:	openWith, 
							replaceWith:replaceWith, 
							placeHolder:placeHolder,
							closeWith:	closeWith
						};		
			}
			
			// define markup to insert
			function markup(button) {	
				hash = clicked = button;
				get();
				
				$.extend(hash, { line:"", textarea:textArea, selection:(selection||""), placeHolder:button.placeHolder, caretPosition:caretPosition  }); 

				// callbacks before insertion
				prepare(options.beforeInsert);
				prepare(clicked.beforeInsert);
				if (ctrlKey == true && shiftKey == true) {
					prepare(clicked.beforeMultiInsert);
				}
				
				$.extend(hash, { line:1 }); 
				
				if (ctrlKey == true && shiftKey == true) {				
					lines = selection.split((($.browser.mozilla) ? "\n" : "\r\n"));
					for (j = 0, n = lines.length, i = 0; i < n; i++) {
						if ($.trim(lines[i]) != "")	{
						$.extend(hash, { line:++j, selection:lines[i] } ); 
						lines[i] = build(lines[i]).block;
						} else {
							lines[i] = "";
						}
					}
					string = { block:lines.join("\n")};
					start = caretPosition;
					len = string.block.length + (($.browser.opera) ? n : 0);				
				} else if (ctrlKey == true) {
					string = build(selection);
					start = caretPosition + string.openWith.length;
					len = string.block.length - string.openWith.length - string.closeWith.length;
				} else if (shiftKey == true) {
					string = build(selection);
					start = caretPosition;
					len = string.block.length;			
				} else {
					string = build(selection);
					start = caretPosition + string.block.length ;	
					len = 0;
				}
				if ((selection == "" && string.replaceWith == "")) { 
					if ($.browser.opera) { // opera bug fix
						caretPosition += (string.block.length - string.block.replace(/^\n*/g, "").length);
					}
					start = caretPosition + string.openWith.length;
					len = string.block.length - string.openWith.length - string.closeWith.length;
					caretOffset = $$.val().substring(caretPosition,  $$.val().length).length;
				}
				
				$.extend(hash, { caretPosition:caretPosition } ); 
				
				// do job
				insert(string.block);
				set(start, len);
				get();
				
				$.extend(hash, { line:"", selection:selection }); 
				
				// callbacks after insertion
				if (ctrlKey == true && shiftKey == true) {
					prepare(clicked.afterMultiInsert);
				}
				prepare(clicked.afterInsert);
				prepare(options.afterInsert);
				
				// refresh preview if opened
				if (iFrame && win && options.previewIFrameRefresh) refreshPeview(); 
			}

			// add markup
			function insert(block) {
				if (document.selection) {					
					newSelection = document.selection.createRange();
					newSelection.text = block;
				} else { 
					$$.val($$.val().substring(0, caretPosition)	+ block + $$.val().substring(caretPosition + selection.length, $$.val().length));
				}
			}
			
			// set a selection
			function set(start, len) {
				if (textArea.createTextRange){
					range = textArea.createTextRange();
					range.collapse(true);
					range.moveStart("character", start); 
					range.moveEnd("character", len); 
					range.select();
				} else if (textArea.setSelectionRange ){
					textArea.setSelectionRange(start, start + len);
				}
				textArea.scrollTop = scrollPosition;
				textArea.focus();	
			}

			// get the selection
			function get() {
				textArea.focus();
				scrollPosition = textArea.scrollTop;
				if (document.selection) {		
					selection = document.selection.createRange().text;
					if ($.browser.msie) { // ie
						var range = document.selection.createRange();
						var rangeCopy = range.duplicate();
						rangeCopy.moveToElementText(textArea);
						caretPosition = -1;
						while(rangeCopy.inRange(range)) { // fix most of the ie bugs with linefeeds...
							rangeCopy.moveStart("character");
							caretPosition ++;
						}					
					} else { // opera
						caretPosition = textArea.selectionStart;
					}
				} else { // gecko
					caretPosition = textArea.selectionStart;
					selection = $$.val().substring(caretPosition, textArea.selectionEnd);
				} 
				return selection;
			}
			
			// open preview window
			function preview() {
				if (!iFrame) {
					if (options.previewIFrame == true) {
						iFrame = $('<iframe class="markItUpPreviewFrame"></iframe>').insertAfter(footer).show();
						win = iFrame[iFrame.length-1].contentWindow || frame[iFrame.length-1];
						//refreshPeview();
					} else {
						win = window.open("", "preview", "resizable=yes, scrollbars=yes");
						//refreshPeview();
					}
				} else {
					if (altKey && iFrame) {
						iFrame.remove(); 
						iFrame = false;	
						win = false;
					} //else  {
//						refreshPeview();				
//					}
				}
			}

			// refresh Preview window
			function refreshPeview() {
				if (options.previewParserPath != "") {
					$.ajax({ 
						type: "POST", 
						async: false,
						url: options.previewParserPath, 
						data: options.previewParserVar+"="+escape($$.val()), // thanks Ben for "escape"
						success: function(data) { html = data; },
						error: function() { alert("markItUp! Error: Parser not found.") }
					});
				} else {
					html = '<html>\n<head>\n<meta http-equiv="content-type" content="text/html; charset='+options.previewCharset+'">\n';
					html+= '<title></title>\n<base href="'+options.previewBaseUrl+'" />\n<link href="'+options.previewCssPath+'" rel="stylesheet" type="text/css">\n</head>\n';
					html+= '<body id="'+options.previewBodyId+'" class="'+options.previewBodyClassName+'">\n'+$$.val()+'\n</body>\n';
					html+= '</html>';
				}

				win.document.open();
				win.document.write(html);
				setTimeout(function() { win.document.close();}, 100); // FF needs time to apply css
				
				if (iFrame == false) win.focus();
			}

			// set keys pressed
			function keyPressed(e) { // safari and opera don't fire event on shift, control and alt key properly

				shiftKey = e.shiftKey;
				altKey = e.altKey;
				ctrlKey = (!(e.altKey && e.ctrlKey)) ? e.ctrlKey : false; 

				$.extend(hash, { ctrlKey:ctrlKey, shiftKey:shiftKey, altKey:altKey  }); 

				if (e.type == "keydown") {	
					if (ctrlKey) {
						a = $("a[accesskey="+String.fromCharCode(e.keyCode)+"]", header);
						if (a.length != 0) {
							ctrlKey = false;
							a.parent("li").trigger("click");
							e.preventDefault(); 
							e.stopPropagation(); 
							return false;
						}						
					}
					
					if (e.keyCode == 13 || e.keyCode == 10) { // Enter key
						if (ctrlKey == true) {  // Enter + Ctrl
							ctrlKey = false;
							markup(options.onCtrlEnter);	
							return options.onCtrlEnter.keepDefault;
						} else if (shiftKey == true) { // Enter + Shift
							shiftKey = false;
							markup(options.onShiftEnter);	
							return options.onShiftEnter.keepDefault;
						} else { // only Enter
							markup(options.onEnter);	
							return options.onEnter.keepDefault;
						}
					}
					if (e.keyCode == 9) { // Tab key
						if (caretOffset != -1) {
							get();
							caretOffset = $$.val().length - caretOffset;
							set(caretOffset, 0);
							caretOffset = -1;
							return false;
						} else {
							markup(options.onTab);	
							caretOffset = -1;
							return options.onTab.keepDefault;							
						}
					}
				}
			}
		});		
	};
	
	$.markItUp = function(settings) {
		var options = {	target:false };
		$.extend(options, settings);
		if (options.target) {
			return $(options.target).each(function() {	
				$("textarea", this).trigger("insertion", [options]);	
			});
		} else {
			$("textarea").trigger("insertion", [options]);
		}
	};
})(jQuery);
