/*global require,module*/
"use strict";
var CodeMirror = require("codemirror");
require("codemirror/addon/edit/continuelist.js");
require("./codemirror/tablist");
require("codemirror/addon/display/fullscreen.js");
require("codemirror/mode/markdown/markdown.js");
require("codemirror/addon/mode/overlay.js");
require("codemirror/addon/display/placeholder.js");
require("codemirror/addon/selection/mark-selection.js");
require("codemirror/mode/gfm/gfm.js");
require("codemirror/mode/xml/xml.js");
var CodeMirrorSpellChecker = require("codemirror-spell-checker");
var marked = require("marked");


// Some variables
var isMac = /Mac/.test(navigator.platform);

// Mapping of actions that can be bound to keyboard shortcuts or toolbar buttons
var bindings = {
	"toggleBold": toggleBold,
	"toggleItalic": toggleItalic,
	"drawLink": drawLink,
	"toggleHeadingSmaller": toggleHeadingSmaller,
	"toggleHeadingBigger": toggleHeadingBigger,
	"drawImage": drawImage,
	"toggleBlockquote": toggleBlockquote,
	"toggleOrderedList": toggleOrderedList,
	"toggleUnorderedList": toggleUnorderedList,
	"toggleCodeBlock": toggleCodeBlock,
	"togglePreview": togglePreview,
	"toggleStrikethrough": toggleStrikethrough,
	"toggleHeading1": toggleHeading1,
	"toggleHeading2": toggleHeading2,
	"toggleHeading3": toggleHeading3,
	"cleanBlock": cleanBlock,
	"drawTable": drawTable,
	"drawHorizontalRule": drawHorizontalRule,
	"undo": undo,
	"redo": redo,
	"toggleSideBySide": toggleSideBySide,
	"toggleFullScreen": toggleFullScreen
};

var shortcuts = {
	"toggleBold": "Cmd-B",
	"toggleItalic": "Cmd-I",
	"drawLink": "Cmd-K",
	"toggleHeadingSmaller": "Cmd-H",
	"toggleHeadingBigger": "Shift-Cmd-H",
	"cleanBlock": "Cmd-E",
	"drawImage": "Cmd-Alt-I",
	"toggleBlockquote": "Cmd-'",
	"toggleOrderedList": "Cmd-Alt-L",
	"toggleUnorderedList": "Cmd-L",
	"toggleCodeBlock": "Cmd-Alt-C",
	"togglePreview": "Cmd-P",
	"toggleSideBySide": "F9",
	"toggleFullScreen": "F11"
};

var getBindingName = function(f) {
	for(var key in bindings) {
		if(bindings[key] === f) {
			return key;
		}
	}
	return null;
};

var isMobile = function() {
	var check = false;
	(function(a) {
		if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true;
	})(navigator.userAgent || navigator.vendor || window.opera);
	return check;
};


/**
 * Fix shortcut. Mac use Command, others use Ctrl.
 */
function fixShortcut(name) {
	if(isMac) {
		name = name.replace("Ctrl", "Cmd");
	} else {
		name = name.replace("Cmd", "Ctrl");
	}
	return name;
}


/**
 * Create icon element for toolbar.
 */
function createIcon(options, enableTooltips, shortcuts) {
	options = options || {};
	var el = document.createElement("a");
	enableTooltips = (enableTooltips == undefined) ? true : enableTooltips;

	if(options.title && enableTooltips) {
		el.title = createTootlip(options.title, options.action, shortcuts);

		if(isMac) {
			el.title = el.title.replace("Ctrl", "⌘");
			el.title = el.title.replace("Alt", "⌥");
		}
	}

	el.tabIndex = -1;
	el.className = options.className;
	return el;
}

function createSep() {
	var el = document.createElement("i");
	el.className = "separator";
	el.innerHTML = "|";
	return el;
}

function createTootlip(title, action, shortcuts) {
	var actionName;
	var tooltip = title;

	if(action) {
		actionName = getBindingName(action);
		if(shortcuts[actionName]) {
			tooltip += " (" + fixShortcut(shortcuts[actionName]) + ")";
		}
	}

	return tooltip;
}

/**
 * The state of CodeMirror at the given position.
 */
function getState(cm, pos) {
	pos = pos || cm.getCursor("start");
	var stat = cm.getTokenAt(pos);
	if(!stat.type) return {};

	var types = stat.type.split(" ");

	var ret = {},
		data, text;
	for(var i = 0; i < types.length; i++) {
		data = types[i];
		if(data === "strong") {
			ret.bold = true;
		} else if(data === "variable-2") {
			text = cm.getLine(pos.line);
			if(/^\s*\d+\.\s/.test(text)) {
				ret["ordered-list"] = true;
			} else {
				ret["unordered-list"] = true;
			}
		} else if(data === "atom") {
			ret.quote = true;
		} else if(data === "em") {
			ret.italic = true;
		} else if(data === "quote") {
			ret.quote = true;
		} else if(data === "strikethrough") {
			ret.strikethrough = true;
		} else if(data === "comment") {
			ret.code = true;
		} else if(data === "link") {
			ret.link = true;
		} else if(data === "tag") {
			ret.image = true;
		} else if(data.match(/^header(\-[1-6])?$/)) {
			ret[data.replace("header", "heading")] = true;
		}
	}
	return ret;
}


// Saved overflow setting
var saved_overflow = "";

/**
 * Toggle full screen of the editor.
 */
function toggleFullScreen(editor) {
	// Set fullscreen
	var cm = editor.codemirror;
	cm.setOption("fullScreen", !cm.getOption("fullScreen"));


	// Prevent scrolling on body during fullscreen active
	if(cm.getOption("fullScreen")) {
		saved_overflow = document.body.style.overflow;
		document.body.style.overflow = "hidden";
	} else {
		document.body.style.overflow = saved_overflow;
	}


	// Update toolbar class
	var wrap = cm.getWrapperElement();

	if(!/fullscreen/.test(wrap.previousSibling.className)) {
		wrap.previousSibling.className += " fullscreen";
	} else {
		wrap.previousSibling.className = wrap.previousSibling.className.replace(/\s*fullscreen\b/, "");
	}


	// Update toolbar button
	var toolbarButton = editor.toolbarElements.fullscreen;

	if(!/active/.test(toolbarButton.className)) {
		toolbarButton.className += " active";
	} else {
		toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, "");
	}


	// Hide side by side if needed
	var sidebyside = cm.getWrapperElement().nextSibling;
	if(/editor-preview-active-side/.test(sidebyside.className))
		toggleSideBySide(editor);
}


/**
 * Action for toggling bold.
 */
function toggleBold(editor) {
	_toggleBlock(editor, "bold", editor.options.blockStyles.bold);
}


/**
 * Action for toggling italic.
 */
function toggleItalic(editor) {
	_toggleBlock(editor, "italic", editor.options.blockStyles.italic);
}


/**
 * Action for toggling strikethrough.
 */
function toggleStrikethrough(editor) {
	_toggleBlock(editor, "strikethrough", "~~");
}

/**
 * Action for toggling code block.
 */
function toggleCodeBlock(editor) {
	var fenceCharsToInsert = editor.options.blockStyles.code;

	function fencing_line(line) {
		/* return true, if this is a ``` or ~~~ line */
		if(typeof line !== "object") {
			throw "fencing_line() takes a 'line' object (not a line number, or line text).  Got: " + typeof line + ": " + line;
		}
		return line.styles && line.styles[2] && line.styles[2].indexOf("formatting-code-block") !== -1;
	}

	function token_state(token) {
		// base goes an extra level deep when mode backdrops are used, e.g. spellchecker on
		return token.state.base.base || token.state.base;
	}

	function code_type(cm, line_num, line, firstTok, lastTok) {
		/*
		 * Return "single", "indented", "fenced" or false
		 *
		 * cm and line_num are required.  Others are optional for efficiency
		 *   To check in the middle of a line, pass in firstTok yourself.
		 */
		line = line || cm.getLineHandle(line_num);
		firstTok = firstTok || cm.getTokenAt({
			line: line_num,
			ch: 1
		});
		lastTok = lastTok || (!!line.text && cm.getTokenAt({
			line: line_num,
			ch: line.text.length - 1
		}));
		var types = firstTok.type ? firstTok.type.split(" ") : [];
		if(lastTok && token_state(lastTok).indentedCode) {
			// have to check last char, since first chars of first line aren"t marked as indented
			return "indented";
		} else if(types.indexOf("comment") === -1) {
			// has to be after "indented" check, since first chars of first indented line aren"t marked as such
			return false;
		} else if(token_state(firstTok).fencedChars || token_state(lastTok).fencedChars || fencing_line(line)) {
			return "fenced";
		} else {
			return "single";
		}
	}

	function insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert) {
		var start_line_sel = cur_start.line + 1,
			end_line_sel = cur_end.line + 1,
			sel_multi = cur_start.line !== cur_end.line,
			repl_start = fenceCharsToInsert + "\n",
			repl_end = "\n" + fenceCharsToInsert;
		if(sel_multi) {
			end_line_sel++;
		}
		// handle last char including \n or not
		if(sel_multi && cur_end.ch === 0) {
			repl_end = fenceCharsToInsert + "\n";
			end_line_sel--;
		}
		_replaceSelection(cm, false, [repl_start, repl_end]);
		cm.setSelection({
			line: start_line_sel,
			ch: 0
		}, {
			line: end_line_sel,
			ch: 0
		});
	}

	var cm = editor.codemirror,
		cur_start = cm.getCursor("start"),
		cur_end = cm.getCursor("end"),
		tok = cm.getTokenAt({
			line: cur_start.line,
			ch: cur_start.ch || 1
		}), // avoid ch 0 which is a cursor pos but not token
		line = cm.getLineHandle(cur_start.line),
		is_code = code_type(cm, cur_start.line, line, tok);
	var block_start, block_end, lineCount;

	if(is_code === "single") {
		// similar to some SimpleMDE _toggleBlock logic
		var start = line.text.slice(0, cur_start.ch).replace("`", ""),
			end = line.text.slice(cur_start.ch).replace("`", "");
		cm.replaceRange(start + end, {
			line: cur_start.line,
			ch: 0
		}, {
			line: cur_start.line,
			ch: 99999999999999
		});
		cur_start.ch--;
		if(cur_start !== cur_end) {
			cur_end.ch--;
		}
		cm.setSelection(cur_start, cur_end);
		cm.focus();
	} else if(is_code === "fenced") {
		if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
			// use selection

			// find the fenced line so we know what type it is (tilde, backticks, number of them)
			for(block_start = cur_start.line; block_start >= 0; block_start--) {
				line = cm.getLineHandle(block_start);
				if(fencing_line(line)) {
					break;
				}
			}
			var fencedTok = cm.getTokenAt({
				line: block_start,
				ch: 1
			});
			var fence_chars = token_state(fencedTok).fencedChars;
			var start_text, start_line;
			var end_text, end_line;
			// check for selection going up against fenced lines, in which case we don't want to add more fencing
			if(fencing_line(cm.getLineHandle(cur_start.line))) {
				start_text = "";
				start_line = cur_start.line;
			} else if(fencing_line(cm.getLineHandle(cur_start.line - 1))) {
				start_text = "";
				start_line = cur_start.line - 1;
			} else {
				start_text = fence_chars + "\n";
				start_line = cur_start.line;
			}
			if(fencing_line(cm.getLineHandle(cur_end.line))) {
				end_text = "";
				end_line = cur_end.line;
				if(cur_end.ch === 0) {
					end_line += 1;
				}
			} else if(cur_end.ch !== 0 && fencing_line(cm.getLineHandle(cur_end.line + 1))) {
				end_text = "";
				end_line = cur_end.line + 1;
			} else {
				end_text = fence_chars + "\n";
				end_line = cur_end.line + 1;
			}
			if(cur_end.ch === 0) {
				// full last line selected, putting cursor at beginning of next
				end_line -= 1;
			}
			cm.operation(function() {
				// end line first, so that line numbers don't change
				cm.replaceRange(end_text, {
					line: end_line,
					ch: 0
				}, {
					line: end_line + (end_text ? 0 : 1),
					ch: 0
				});
				cm.replaceRange(start_text, {
					line: start_line,
					ch: 0
				}, {
					line: start_line + (start_text ? 0 : 1),
					ch: 0
				});
			});
			cm.setSelection({
				line: start_line + (start_text ? 1 : 0),
				ch: 0
			}, {
				line: end_line + (start_text ? 1 : -1),
				ch: 0
			});
			cm.focus();
		} else {
			// no selection, search for ends of this fenced block
			var search_from = cur_start.line;
			if(fencing_line(cm.getLineHandle(cur_start.line))) { // gets a little tricky if cursor is right on a fenced line
				if(code_type(cm, cur_start.line + 1) === "fenced") {
					block_start = cur_start.line;
					search_from = cur_start.line + 1; // for searching for "end"
				} else {
					block_end = cur_start.line;
					search_from = cur_start.line - 1; // for searching for "start"
				}
			}
			if(block_start === undefined) {
				for(block_start = search_from; block_start >= 0; block_start--) {
					line = cm.getLineHandle(block_start);
					if(fencing_line(line)) {
						break;
					}
				}
			}
			if(block_end === undefined) {
				lineCount = cm.lineCount();
				for(block_end = search_from; block_end < lineCount; block_end++) {
					line = cm.getLineHandle(block_end);
					if(fencing_line(line)) {
						break;
					}
				}
			}
			cm.operation(function() {
				cm.replaceRange("", {
					line: block_start,
					ch: 0
				}, {
					line: block_start + 1,
					ch: 0
				});
				cm.replaceRange("", {
					line: block_end - 1,
					ch: 0
				}, {
					line: block_end,
					ch: 0
				});
			});
			cm.focus();
		}
	} else if(is_code === "indented") {
		if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
			// use selection
			block_start = cur_start.line;
			block_end = cur_end.line;
			if(cur_end.ch === 0) {
				block_end--;
			}
		} else {
			// no selection, search for ends of this indented block
			for(block_start = cur_start.line; block_start >= 0; block_start--) {
				line = cm.getLineHandle(block_start);
				if(line.text.match(/^\s*$/)) {
					// empty or all whitespace - keep going
					continue;
				} else {
					if(code_type(cm, block_start, line) !== "indented") {
						block_start += 1;
						break;
					}
				}
			}
			lineCount = cm.lineCount();
			for(block_end = cur_start.line; block_end < lineCount; block_end++) {
				line = cm.getLineHandle(block_end);
				if(line.text.match(/^\s*$/)) {
					// empty or all whitespace - keep going
					continue;
				} else {
					if(code_type(cm, block_end, line) !== "indented") {
						block_end -= 1;
						break;
					}
				}
			}
		}
		// if we are going to un-indent based on a selected set of lines, and the next line is indented too, we need to
		// insert a blank line so that the next line(s) continue to be indented code
		var next_line = cm.getLineHandle(block_end + 1),
			next_line_last_tok = next_line && cm.getTokenAt({
				line: block_end + 1,
				ch: next_line.text.length - 1
			}),
			next_line_indented = next_line_last_tok && token_state(next_line_last_tok).indentedCode;
		if(next_line_indented) {
			cm.replaceRange("\n", {
				line: block_end + 1,
				ch: 0
			});
		}

		for(var i = block_start; i <= block_end; i++) {
			cm.indentLine(i, "subtract"); // TODO: this doesn't get tracked in the history, so can't be undone :(
		}
		cm.focus();
	} else {
		// insert code formatting
		var no_sel_and_starting_of_line = (cur_start.line === cur_end.line && cur_start.ch === cur_end.ch && cur_start.ch === 0);
		var sel_multi = cur_start.line !== cur_end.line;
		if(no_sel_and_starting_of_line || sel_multi) {
			insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert);
		} else {
			_replaceSelection(cm, false, ["`", "`"]);
		}
	}
}

/**
 * Action for toggling blockquote.
 */
function toggleBlockquote(editor) {
	var cm = editor.codemirror;
	_toggleLine(cm, "quote");
}

/**
 * Action for toggling heading size: normal -> h1 -> h2 -> h3 -> h4 -> h5 -> h6 -> normal
 */
function toggleHeadingSmaller(editor) {
	var cm = editor.codemirror;
	_toggleHeading(cm, "smaller");
}

/**
 * Action for toggling heading size: normal -> h6 -> h5 -> h4 -> h3 -> h2 -> h1 -> normal
 */
function toggleHeadingBigger(editor) {
	var cm = editor.codemirror;
	_toggleHeading(cm, "bigger");
}

/**
 * Action for toggling heading size 1
 */
function toggleHeading1(editor) {
	var cm = editor.codemirror;
	_toggleHeading(cm, undefined, 1);
}

/**
 * Action for toggling heading size 2
 */
function toggleHeading2(editor) {
	var cm = editor.codemirror;
	_toggleHeading(cm, undefined, 2);
}

/**
 * Action for toggling heading size 3
 */
function toggleHeading3(editor) {
	var cm = editor.codemirror;
	_toggleHeading(cm, undefined, 3);
}


/**
 * Action for toggling ul.
 */
function toggleUnorderedList(editor) {
	var cm = editor.codemirror;
	_toggleLine(cm, "unordered-list");
}


/**
 * Action for toggling ol.
 */
function toggleOrderedList(editor) {
	var cm = editor.codemirror;
	_toggleLine(cm, "ordered-list");
}

/**
 * Action for clean block (remove headline, list, blockquote code, markers)
 */
function cleanBlock(editor) {
	var cm = editor.codemirror;
	_cleanBlock(cm);
}

/**
 * Action for drawing a link.
 */
function drawLink(editor) {
	var cm = editor.codemirror;
	var stat = getState(cm);
	var options = editor.options;
	var url = "http://";
	if(options.promptURLs) {
		url = prompt(options.promptTexts.link);
		if(!url) {
			return false;
		}
	}
	_replaceSelection(cm, stat.link, options.insertTexts.link, url);
}

/**
 * Action for drawing an img.
 */
function drawImage(editor) {
	var cm = editor.codemirror;
	var stat = getState(cm);
	var options = editor.options;
	var url = "http://";
	if(options.promptURLs) {
		url = prompt(options.promptTexts.image);
		if(!url) {
			return false;
		}
	}
	_replaceSelection(cm, stat.image, options.insertTexts.image, url);
}

/**
 * Action for drawing a table.
 */
function drawTable(editor) {
	var cm = editor.codemirror;
	var stat = getState(cm);
	var options = editor.options;
	_replaceSelection(cm, stat.table, options.insertTexts.table);
}

/**
 * Action for drawing a horizontal rule.
 */
function drawHorizontalRule(editor) {
	var cm = editor.codemirror;
	var stat = getState(cm);
	var options = editor.options;
	_replaceSelection(cm, stat.image, options.insertTexts.horizontalRule);
}


/**
 * Undo action.
 */
function undo(editor) {
	var cm = editor.codemirror;
	cm.undo();
	cm.focus();
}


/**
 * Redo action.
 */
function redo(editor) {
	var cm = editor.codemirror;
	cm.redo();
	cm.focus();
}


/**
 * Toggle side by side preview
 */
function toggleSideBySide(editor) {
	var cm = editor.codemirror;
	var wrapper = cm.getWrapperElement();
	var preview = wrapper.nextSibling;
	var toolbarButton = editor.toolbarElements["side-by-side"];
	var useSideBySideListener = false;
	if(/editor-preview-active-side/.test(preview.className)) {
		preview.className = preview.className.replace(
			/\s*editor-preview-active-side\s*/g, ""
		);
		toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, "");
		wrapper.className = wrapper.className.replace(/\s*CodeMirror-sided\s*/g, " ");
	} else {
		// When the preview button is clicked for the first time,
		// give some time for the transition from editor.css to fire and the view to slide from right to left,
		// instead of just appearing.
		setTimeout(function() {
			if(!cm.getOption("fullScreen"))
				toggleFullScreen(editor);
			preview.className += " editor-preview-active-side";
		}, 1);
		toolbarButton.className += " active";
		wrapper.className += " CodeMirror-sided";
		useSideBySideListener = true;
	}

	// Hide normal preview if active
	var previewNormal = wrapper.lastChild;
	if(/editor-preview-active/.test(previewNormal.className)) {
		previewNormal.className = previewNormal.className.replace(
			/\s*editor-preview-active\s*/g, ""
		);
		var toolbar = editor.toolbarElements.preview;
		var toolbar_div = wrapper.previousSibling;
		toolbar.className = toolbar.className.replace(/\s*active\s*/g, "");
		toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, "");
	}

	var sideBySideRenderingFunction = function() {
		preview.innerHTML = editor.options.previewRender(editor.value(), preview);
	};

	if(!cm.sideBySideRenderingFunction) {
		cm.sideBySideRenderingFunction = sideBySideRenderingFunction;
	}

	if(useSideBySideListener) {
		preview.innerHTML = editor.options.previewRender(editor.value(), preview);
		cm.on("update", cm.sideBySideRenderingFunction);
	} else {
		cm.off("update", cm.sideBySideRenderingFunction);
	}

	// Refresh to fix selection being off (#309)
	cm.refresh();
}


/**
 * Preview action.
 */
function togglePreview(editor) {
	var cm = editor.codemirror;
	var wrapper = cm.getWrapperElement();
	var toolbar_div = wrapper.previousSibling;
	var toolbar = editor.options.toolbar ? editor.toolbarElements.preview : false;
	var preview = wrapper.lastChild;
	if(!preview || !/editor-preview/.test(preview.className)) {
		preview = document.createElement("div");
		preview.className = "editor-preview";
		wrapper.appendChild(preview);
	}
	if(/editor-preview-active/.test(preview.className)) {
		preview.className = preview.className.replace(
			/\s*editor-preview-active\s*/g, ""
		);
		if(toolbar) {
			toolbar.className = toolbar.className.replace(/\s*active\s*/g, "");
			toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, "");
		}
	} else {
		// When the preview button is clicked for the first time,
		// give some time for the transition from editor.css to fire and the view to slide from right to left,
		// instead of just appearing.
		setTimeout(function() {
			preview.className += " editor-preview-active";
		}, 1);
		if(toolbar) {
			toolbar.className += " active";
			toolbar_div.className += " disabled-for-preview";
		}
	}
	preview.innerHTML = editor.options.previewRender(editor.value(), preview);

	// Turn off side by side if needed
	var sidebyside = cm.getWrapperElement().nextSibling;
	if(/editor-preview-active-side/.test(sidebyside.className))
		toggleSideBySide(editor);
}

function _replaceSelection(cm, active, startEnd, url) {
	if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
		return;

	var text;
	var start = startEnd[0];
	var end = startEnd[1];
	var startPoint = cm.getCursor("start");
	var endPoint = cm.getCursor("end");
	if(url) {
		end = end.replace("#url#", url);
	}
	if(active) {
		text = cm.getLine(startPoint.line);
		start = text.slice(0, startPoint.ch);
		end = text.slice(startPoint.ch);
		cm.replaceRange(start + end, {
			line: startPoint.line,
			ch: 0
		});
	} else {
		text = cm.getSelection();
		cm.replaceSelection(start + text + end);

		startPoint.ch += start.length;
		if(startPoint !== endPoint) {
			endPoint.ch += start.length;
		}
	}
	cm.setSelection(startPoint, endPoint);
	cm.focus();
}


function _toggleHeading(cm, direction, size) {
	if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
		return;

	var startPoint = cm.getCursor("start");
	var endPoint = cm.getCursor("end");
	for(var i = startPoint.line; i <= endPoint.line; i++) {
		(function(i) {
			var text = cm.getLine(i);
			var currHeadingLevel = text.search(/[^#]/);

			if(direction !== undefined) {
				if(currHeadingLevel <= 0) {
					if(direction == "bigger") {
						text = "###### " + text;
					} else {
						text = "# " + text;
					}
				} else if(currHeadingLevel == 6 && direction == "smaller") {
					text = text.substr(7);
				} else if(currHeadingLevel == 1 && direction == "bigger") {
					text = text.substr(2);
				} else {
					if(direction == "bigger") {
						text = text.substr(1);
					} else {
						text = "#" + text;
					}
				}
			} else {
				if(size == 1) {
					if(currHeadingLevel <= 0) {
						text = "# " + text;
					} else if(currHeadingLevel == size) {
						text = text.substr(currHeadingLevel + 1);
					} else {
						text = "# " + text.substr(currHeadingLevel + 1);
					}
				} else if(size == 2) {
					if(currHeadingLevel <= 0) {
						text = "## " + text;
					} else if(currHeadingLevel == size) {
						text = text.substr(currHeadingLevel + 1);
					} else {
						text = "## " + text.substr(currHeadingLevel + 1);
					}
				} else {
					if(currHeadingLevel <= 0) {
						text = "### " + text;
					} else if(currHeadingLevel == size) {
						text = text.substr(currHeadingLevel + 1);
					} else {
						text = "### " + text.substr(currHeadingLevel + 1);
					}
				}
			}

			cm.replaceRange(text, {
				line: i,
				ch: 0
			}, {
				line: i,
				ch: 99999999999999
			});
		})(i);
	}
	cm.focus();
}


function _toggleLine(cm, name) {
	if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
		return;

	var stat = getState(cm);
	var startPoint = cm.getCursor("start");
	var endPoint = cm.getCursor("end");
	var repl = {
		"quote": /^(\s*)\>\s+/,
		"unordered-list": /^(\s*)(\*|\-|\+)\s+/,
		"ordered-list": /^(\s*)\d+\.\s+/
	};
	var map = {
		"quote": "> ",
		"unordered-list": "* ",
		"ordered-list": "1. "
	};
	for(var i = startPoint.line; i <= endPoint.line; i++) {
		(function(i) {
			var text = cm.getLine(i);
			if(stat[name]) {
				text = text.replace(repl[name], "$1");
			} else {
				text = map[name] + text;
			}
			cm.replaceRange(text, {
				line: i,
				ch: 0
			}, {
				line: i,
				ch: 99999999999999
			});
		})(i);
	}
	cm.focus();
}

function _toggleBlock(editor, type, start_chars, end_chars) {
	if(/editor-preview-active/.test(editor.codemirror.getWrapperElement().lastChild.className))
		return;

	end_chars = (typeof end_chars === "undefined") ? start_chars : end_chars;
	var cm = editor.codemirror;
	var stat = getState(cm);

	var text;
	var start = start_chars;
	var end = end_chars;

	var startPoint = cm.getCursor("start");
	var endPoint = cm.getCursor("end");

	if(stat[type]) {
		text = cm.getLine(startPoint.line);
		start = text.slice(0, startPoint.ch);
		end = text.slice(startPoint.ch);
		if(type == "bold") {
			start = start.replace(/(\*\*|__)(?![\s\S]*(\*\*|__))/, "");
			end = end.replace(/(\*\*|__)/, "");
		} else if(type == "italic") {
			start = start.replace(/(\*|_)(?![\s\S]*(\*|_))/, "");
			end = end.replace(/(\*|_)/, "");
		} else if(type == "strikethrough") {
			start = start.replace(/(\*\*|~~)(?![\s\S]*(\*\*|~~))/, "");
			end = end.replace(/(\*\*|~~)/, "");
		}
		cm.replaceRange(start + end, {
			line: startPoint.line,
			ch: 0
		}, {
			line: startPoint.line,
			ch: 99999999999999
		});

		if(type == "bold" || type == "strikethrough") {
			startPoint.ch -= 2;
			if(startPoint !== endPoint) {
				endPoint.ch -= 2;
			}
		} else if(type == "italic") {
			startPoint.ch -= 1;
			if(startPoint !== endPoint) {
				endPoint.ch -= 1;
			}
		}
	} else {
		text = cm.getSelection();
		if(type == "bold") {
			text = text.split("**").join("");
			text = text.split("__").join("");
		} else if(type == "italic") {
			text = text.split("*").join("");
			text = text.split("_").join("");
		} else if(type == "strikethrough") {
			text = text.split("~~").join("");
		}
		cm.replaceSelection(start + text + end);

		startPoint.ch += start_chars.length;
		endPoint.ch = startPoint.ch + text.length;
	}

	cm.setSelection(startPoint, endPoint);
	cm.focus();
}

function _cleanBlock(cm) {
	if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
		return;

	var startPoint = cm.getCursor("start");
	var endPoint = cm.getCursor("end");
	var text;

	for(var line = startPoint.line; line <= endPoint.line; line++) {
		text = cm.getLine(line);
		text = text.replace(/^[ ]*([# ]+|\*|\-|[> ]+|[0-9]+(.|\)))[ ]*/, "");

		cm.replaceRange(text, {
			line: line,
			ch: 0
		}, {
			line: line,
			ch: 99999999999999
		});
	}
}

// Merge the properties of one object into another.
function _mergeProperties(target, source) {
	for(var property in source) {
		if(source.hasOwnProperty(property)) {
			if(source[property] instanceof Array) {
				target[property] = source[property].concat(target[property] instanceof Array ? target[property] : []);
			} else if(
				source[property] !== null &&
				typeof source[property] === "object" &&
				source[property].constructor === Object
			) {
				target[property] = _mergeProperties(target[property] || {}, source[property]);
			} else {
				target[property] = source[property];
			}
		}
	}

	return target;
}

// Merge an arbitrary number of objects into one.
function extend(target) {
	for(var i = 1; i < arguments.length; i++) {
		target = _mergeProperties(target, arguments[i]);
	}

	return target;
}

/* The right word count in respect for CJK. */
function wordCount(data) {
	var pattern = /[a-zA-Z0-9_\u0392-\u03c9\u0410-\u04F9]+|[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af]+/g;
	var m = data.match(pattern);
	var count = 0;
	if(m === null) return count;
	for(var i = 0; i < m.length; i++) {
		if(m[i].charCodeAt(0) >= 0x4E00) {
			count += m[i].length;
		} else {
			count += 1;
		}
	}
	return count;
}

var toolbarBuiltInButtons = {
	"bold": {
		name: "bold",
		action: toggleBold,
		className: "fa fa-bold",
		title: "Bold",
		default: true
	},
	"italic": {
		name: "italic",
		action: toggleItalic,
		className: "fa fa-italic",
		title: "Italic",
		default: true
	},
	"strikethrough": {
		name: "strikethrough",
		action: toggleStrikethrough,
		className: "fa fa-strikethrough",
		title: "Strikethrough"
	},
	"heading": {
		name: "heading",
		action: toggleHeadingSmaller,
		className: "fa fa-header",
		title: "Heading",
		default: true
	},
	"heading-smaller": {
		name: "heading-smaller",
		action: toggleHeadingSmaller,
		className: "fa fa-header fa-header-x fa-header-smaller",
		title: "Smaller Heading"
	},
	"heading-bigger": {
		name: "heading-bigger",
		action: toggleHeadingBigger,
		className: "fa fa-header fa-header-x fa-header-bigger",
		title: "Bigger Heading"
	},
	"heading-1": {
		name: "heading-1",
		action: toggleHeading1,
		className: "fa fa-header fa-header-x fa-header-1",
		title: "Big Heading"
	},
	"heading-2": {
		name: "heading-2",
		action: toggleHeading2,
		className: "fa fa-header fa-header-x fa-header-2",
		title: "Medium Heading"
	},
	"heading-3": {
		name: "heading-3",
		action: toggleHeading3,
		className: "fa fa-header fa-header-x fa-header-3",
		title: "Small Heading"
	},
	"separator-1": {
		name: "separator-1"
	},
	"code": {
		name: "code",
		action: toggleCodeBlock,
		className: "fa fa-code",
		title: "Code"
	},
	"quote": {
		name: "quote",
		action: toggleBlockquote,
		className: "fa fa-quote-left",
		title: "Quote",
		default: true
	},
	"unordered-list": {
		name: "unordered-list",
		action: toggleUnorderedList,
		className: "fa fa-list-ul",
		title: "Generic List",
		default: true
	},
	"ordered-list": {
		name: "ordered-list",
		action: toggleOrderedList,
		className: "fa fa-list-ol",
		title: "Numbered List",
		default: true
	},
	"clean-block": {
		name: "clean-block",
		action: cleanBlock,
		className: "fa fa-eraser fa-clean-block",
		title: "Clean block"
	},
	"separator-2": {
		name: "separator-2"
	},
	"link": {
		name: "link",
		action: drawLink,
		className: "fa fa-link",
		title: "Create Link",
		default: true
	},
	"image": {
		name: "image",
		action: drawImage,
		className: "fa fa-picture-o",
		title: "Insert Image",
		default: true
	},
	"table": {
		name: "table",
		action: drawTable,
		className: "fa fa-table",
		title: "Insert Table"
	},
	"horizontal-rule": {
		name: "horizontal-rule",
		action: drawHorizontalRule,
		className: "fa fa-minus",
		title: "Insert Horizontal Line"
	},
	"separator-3": {
		name: "separator-3"
	},
	"preview": {
		name: "preview",
		action: togglePreview,
		className: "fa fa-eye no-disable",
		title: "Toggle Preview",
		default: true
	},
	"side-by-side": {
		name: "side-by-side",
		action: toggleSideBySide,
		className: "fa fa-columns no-disable no-mobile",
		title: "Toggle Side by Side",
		default: true
	},
	"fullscreen": {
		name: "fullscreen",
		action: toggleFullScreen,
		className: "fa fa-arrows-alt no-disable no-mobile",
		title: "Toggle Fullscreen",
		default: true
	},
	"separator-4": {
		name: "separator-4"
	},
	"guide": {
		name: "guide",
		action: "https://simplemde.com/markdown-guide",
		className: "fa fa-question-circle",
		title: "Markdown Guide",
		default: true
	},
	"separator-5": {
		name: "separator-5"
	},
	"undo": {
		name: "undo",
		action: undo,
		className: "fa fa-undo no-disable",
		title: "Undo"
	},
	"redo": {
		name: "redo",
		action: redo,
		className: "fa fa-repeat no-disable",
		title: "Redo"
	}
};

var insertTexts = {
	link: ["[", "](#url#)"],
	image: ["![](", "#url#)"],
	table: ["", "\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text     | Text     | Text     |\n\n"],
	horizontalRule: ["", "\n\n-----\n\n"]
};

var promptTexts = {
	link: "URL for the link:",
	image: "URL of the image:"
};

var blockStyles = {
	"bold": "**",
	"code": "```",
	"italic": "*"
};

/**
 * Interface of SimpleMDE.
 */
function SimpleMDE(options) {
	// Handle options parameter
	options = options || {};


	// Used later to refer to it"s parent
	options.parent = this;


	// Check if Font Awesome needs to be auto downloaded
	var autoDownloadFA = true;

	if(options.autoDownloadFontAwesome === false) {
		autoDownloadFA = false;
	}

	if(options.autoDownloadFontAwesome !== true) {
		var styleSheets = document.styleSheets;
		for(var i = 0; i < styleSheets.length; i++) {
			if(!styleSheets[i].href)
				continue;

			if(styleSheets[i].href.indexOf("//maxcdn.bootstrapcdn.com/font-awesome/") > -1) {
				autoDownloadFA = false;
			}
		}
	}

	if(autoDownloadFA) {
		var link = document.createElement("link");
		link.rel = "stylesheet";
		link.href = "https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css";
		document.getElementsByTagName("head")[0].appendChild(link);
	}


	// Find the textarea to use
	if(options.element) {
		this.element = options.element;
	} else if(options.element === null) {
		// This means that the element option was specified, but no element was found
		console.log("SimpleMDE: Error. No element was found.");
		return;
	}


	// Handle toolbar
	if(options.toolbar === undefined) {
		// Initialize
		options.toolbar = [];


		// Loop over the built in buttons, to get the preferred order
		for(var key in toolbarBuiltInButtons) {
			if(toolbarBuiltInButtons.hasOwnProperty(key)) {
				if(key.indexOf("separator-") != -1) {
					options.toolbar.push("|");
				}

				if(toolbarBuiltInButtons[key].default === true || (options.showIcons && options.showIcons.constructor === Array && options.showIcons.indexOf(key) != -1)) {
					options.toolbar.push(key);
				}
			}
		}
	}


	// Handle status bar
	if(!options.hasOwnProperty("status")) {
		options.status = ["autosave", "lines", "words", "cursor"];
	}


	// Add default preview rendering function
	if(!options.previewRender) {
		options.previewRender = function(plainText) {
			// Note: "this" refers to the options object
			return this.parent.markdown(plainText);
		};
	}


	// Set default options for parsing config
	options.parsingConfig = extend({
		highlightFormatting: true // needed for toggleCodeBlock to detect types of code
	}, options.parsingConfig || {});


	// Merging the insertTexts, with the given options
	options.insertTexts = extend({}, insertTexts, options.insertTexts || {});


	// Merging the promptTexts, with the given options
	options.promptTexts = promptTexts;


	// Merging the blockStyles, with the given options
	options.blockStyles = extend({}, blockStyles, options.blockStyles || {});


	// Merging the shortcuts, with the given options
	options.shortcuts = extend({}, shortcuts, options.shortcuts || {});


	// Change unique_id to uniqueId for backwards compatibility
	if(options.autosave != undefined && options.autosave.unique_id != undefined && options.autosave.unique_id != "")
		options.autosave.uniqueId = options.autosave.unique_id;


	// Update this options
	this.options = options;


	// Auto render
	this.render();


	// The codemirror component is only available after rendering
	// so, the setter for the initialValue can only run after
	// the element has been rendered
	if(options.initialValue && (!this.options.autosave || this.options.autosave.foundSavedValue !== true)) {
		this.value(options.initialValue);
	}
}

/**
 * Default markdown render.
 */
SimpleMDE.prototype.markdown = function(text) {
	if(marked) {
		// Initialize
		var markedOptions = {};


		// Update options
		if(this.options && this.options.renderingConfig && this.options.renderingConfig.singleLineBreaks === false) {
			markedOptions.breaks = false;
		} else {
			markedOptions.breaks = true;
		}

		if(this.options && this.options.renderingConfig && this.options.renderingConfig.codeSyntaxHighlighting === true && window.hljs) {
			markedOptions.highlight = function(code) {
				return window.hljs.highlightAuto(code).value;
			};
		}


		// Set options
		marked.setOptions(markedOptions);


		// Return
		return marked(text);
	}
};

/**
 * Render editor to the given element.
 */
SimpleMDE.prototype.render = function(el) {
	if(!el) {
		el = this.element || document.getElementsByTagName("textarea")[0];
	}

	if(this._rendered && this._rendered === el) {
		// Already rendered.
		return;
	}

	this.element = el;
	var options = this.options;

	var self = this;
	var keyMaps = {};

	for(var key in options.shortcuts) {
		// null stands for "do not bind this command"
		if(options.shortcuts[key] !== null && bindings[key] !== null) {
			(function(key) {
				keyMaps[fixShortcut(options.shortcuts[key])] = function() {
					bindings[key](self);
				};
			})(key);
		}
	}

	keyMaps["Enter"] = "newlineAndIndentContinueMarkdownList";
	keyMaps["Tab"] = "tabAndIndentMarkdownList";
	keyMaps["Shift-Tab"] = "shiftTabAndUnindentMarkdownList";
	keyMaps["Esc"] = function(cm) {
		if(cm.getOption("fullScreen")) toggleFullScreen(self);
	};

	document.addEventListener("keydown", function(e) {
		e = e || window.event;

		if(e.keyCode == 27) {
			if(self.codemirror.getOption("fullScreen")) toggleFullScreen(self);
		}
	}, false);

	var mode, backdrop;
	if(options.spellChecker !== false) {
		mode = "spell-checker";
		backdrop = options.parsingConfig;
		backdrop.name = "gfm";
		backdrop.gitHubSpice = false;

		CodeMirrorSpellChecker({
			codeMirrorInstance: CodeMirror
		});
	} else {
		mode = options.parsingConfig;
		mode.name = "gfm";
		mode.gitHubSpice = false;
	}

	this.codemirror = CodeMirror.fromTextArea(el, {
		mode: mode,
		backdrop: backdrop,
		theme: "paper",
		tabSize: (options.tabSize != undefined) ? options.tabSize : 2,
		indentUnit: (options.tabSize != undefined) ? options.tabSize : 2,
		indentWithTabs: (options.indentWithTabs === false) ? false : true,
		lineNumbers: false,
		autofocus: (options.autofocus === true) ? true : false,
		extraKeys: keyMaps,
		lineWrapping: (options.lineWrapping === false) ? false : true,
		allowDropFileTypes: ["text/plain"],
		placeholder: options.placeholder || el.getAttribute("placeholder") || "",
		styleSelectedText: (options.styleSelectedText != undefined) ? options.styleSelectedText : true
	});

	if(options.forceSync === true) {
		var cm = this.codemirror;
		cm.on("change", function() {
			cm.save();
		});
	}

	this.gui = {};

	if(options.toolbar !== false) {
		this.gui.toolbar = this.createToolbar();
	}
	if(options.status !== false) {
		this.gui.statusbar = this.createStatusbar();
	}
	if(options.autosave != undefined && options.autosave.enabled === true) {
		this.autosave();
	}

	this.gui.sideBySide = this.createSideBySide();

	this._rendered = this.element;


	// Fixes CodeMirror bug (#344)
	var temp_cm = this.codemirror;
	setTimeout(function() {
		temp_cm.refresh();
	}.bind(temp_cm), 0);
};

// Safari, in Private Browsing Mode, looks like it supports localStorage but all calls to setItem throw QuotaExceededError. We're going to detect this and set a variable accordingly.
function isLocalStorageAvailable() {
	if(typeof localStorage === "object") {
		try {
			localStorage.setItem("smde_localStorage", 1);
			localStorage.removeItem("smde_localStorage");
		} catch(e) {
			return false;
		}
	} else {
		return false;
	}

	return true;
}

SimpleMDE.prototype.autosave = function() {
	if(isLocalStorageAvailable()) {
		var simplemde = this;

		if(this.options.autosave.uniqueId == undefined || this.options.autosave.uniqueId == "") {
			console.log("SimpleMDE: You must set a uniqueId to use the autosave feature");
			return;
		}

		if(simplemde.element.form != null && simplemde.element.form != undefined) {
			simplemde.element.form.addEventListener("submit", function() {
				localStorage.removeItem("smde_" + simplemde.options.autosave.uniqueId);
			});
		}

		if(this.options.autosave.loaded !== true) {
			if(typeof localStorage.getItem("smde_" + this.options.autosave.uniqueId) == "string" && localStorage.getItem("smde_" + this.options.autosave.uniqueId) != "") {
				this.codemirror.setValue(localStorage.getItem("smde_" + this.options.autosave.uniqueId));
				this.options.autosave.foundSavedValue = true;
			}

			this.options.autosave.loaded = true;
		}

		localStorage.setItem("smde_" + this.options.autosave.uniqueId, simplemde.value());

		var el = document.getElementById("autosaved");
		if(el != null && el != undefined && el != "") {
			var d = new Date();
			var hh = d.getHours();
			var m = d.getMinutes();
			var dd = "am";
			var h = hh;
			if(h >= 12) {
				h = hh - 12;
				dd = "pm";
			}
			if(h == 0) {
				h = 12;
			}
			m = m < 10 ? "0" + m : m;

			el.innerHTML = "Autosaved: " + h + ":" + m + " " + dd;
		}

		this.autosaveTimeoutId = setTimeout(function() {
			simplemde.autosave();
		}, this.options.autosave.delay || 10000);
	} else {
		console.log("SimpleMDE: localStorage not available, cannot autosave");
	}
};

SimpleMDE.prototype.clearAutosavedValue = function() {
	if(isLocalStorageAvailable()) {
		if(this.options.autosave == undefined || this.options.autosave.uniqueId == undefined || this.options.autosave.uniqueId == "") {
			console.log("SimpleMDE: You must set a uniqueId to clear the autosave value");
			return;
		}

		localStorage.removeItem("smde_" + this.options.autosave.uniqueId);
	} else {
		console.log("SimpleMDE: localStorage not available, cannot autosave");
	}
};

SimpleMDE.prototype.createSideBySide = function() {
	var cm = this.codemirror;
	var wrapper = cm.getWrapperElement();
	var preview = wrapper.nextSibling;

	if(!preview || !/editor-preview-side/.test(preview.className)) {
		preview = document.createElement("div");
		preview.className = "editor-preview-side";
		wrapper.parentNode.insertBefore(preview, wrapper.nextSibling);
	}

	// Syncs scroll  editor -> preview
	var cScroll = false;
	var pScroll = false;
	cm.on("scroll", function(v) {
		if(cScroll) {
			cScroll = false;
			return;
		}
		pScroll = true;
		var height = v.getScrollInfo().height - v.getScrollInfo().clientHeight;
		var ratio = parseFloat(v.getScrollInfo().top) / height;
		var move = (preview.scrollHeight - preview.clientHeight) * ratio;
		preview.scrollTop = move;
	});

	// Syncs scroll  preview -> editor
	preview.onscroll = function() {
		if(pScroll) {
			pScroll = false;
			return;
		}
		cScroll = true;
		var height = preview.scrollHeight - preview.clientHeight;
		var ratio = parseFloat(preview.scrollTop) / height;
		var move = (cm.getScrollInfo().height - cm.getScrollInfo().clientHeight) * ratio;
		cm.scrollTo(0, move);
	};
	return preview;
};

SimpleMDE.prototype.createToolbar = function(items) {
	items = items || this.options.toolbar;

	if(!items || items.length === 0) {
		return;
	}
	var i;
	for(i = 0; i < items.length; i++) {
		if(toolbarBuiltInButtons[items[i]] != undefined) {
			items[i] = toolbarBuiltInButtons[items[i]];
		}
	}

	var bar = document.createElement("div");
	bar.className = "editor-toolbar";

	var self = this;

	var toolbarData = {};
	self.toolbar = items;

	for(i = 0; i < items.length; i++) {
		if(items[i].name == "guide" && self.options.toolbarGuideIcon === false)
			continue;

		if(self.options.hideIcons && self.options.hideIcons.indexOf(items[i].name) != -1)
			continue;

		// Fullscreen does not work well on mobile devices (even tablets)
		// In the future, hopefully this can be resolved
		if((items[i].name == "fullscreen" || items[i].name == "side-by-side") && isMobile())
			continue;


		// Don't include trailing separators
		if(items[i] === "|") {
			var nonSeparatorIconsFollow = false;

			for(var x = (i + 1); x < items.length; x++) {
				if(items[x] !== "|" && (!self.options.hideIcons || self.options.hideIcons.indexOf(items[x].name) == -1)) {
					nonSeparatorIconsFollow = true;
				}
			}

			if(!nonSeparatorIconsFollow)
				continue;
		}


		// Create the icon and append to the toolbar
		(function(item) {
			var el;
			if(item === "|") {
				el = createSep();
			} else {
				el = createIcon(item, self.options.toolbarTips, self.options.shortcuts);
			}

			// bind events, special for info
			if(item.action) {
				if(typeof item.action === "function") {
					el.onclick = function(e) {
						e.preventDefault();
						item.action(self);
					};
				} else if(typeof item.action === "string") {
					el.href = item.action;
					el.target = "_blank";
				}
			}

			toolbarData[item.name || item] = el;
			bar.appendChild(el);
		})(items[i]);
	}

	self.toolbarElements = toolbarData;

	var cm = this.codemirror;
	cm.on("cursorActivity", function() {
		var stat = getState(cm);

		for(var key in toolbarData) {
			(function(key) {
				var el = toolbarData[key];
				if(stat[key]) {
					el.className += " active";
				} else if(key != "fullscreen" && key != "side-by-side") {
					el.className = el.className.replace(/\s*active\s*/g, "");
				}
			})(key);
		}
	});

	var cmWrapper = cm.getWrapperElement();
	cmWrapper.parentNode.insertBefore(bar, cmWrapper);
	return bar;
};

SimpleMDE.prototype.createStatusbar = function(status) {
	// Initialize
	status = status || this.options.status;
	var options = this.options;
	var cm = this.codemirror;


	// Make sure the status variable is valid
	if(!status || status.length === 0)
		return;


	// Set up the built-in items
	var items = [];
	var i, onUpdate, defaultValue;

	for(i = 0; i < status.length; i++) {
		// Reset some values
		onUpdate = undefined;
		defaultValue = undefined;


		// Handle if custom or not
		if(typeof status[i] === "object") {
			items.push({
				className: status[i].className,
				defaultValue: status[i].defaultValue,
				onUpdate: status[i].onUpdate
			});
		} else {
			var name = status[i];

			if(name === "words") {
				defaultValue = function(el) {
					el.innerHTML = wordCount(cm.getValue());
				};
				onUpdate = function(el) {
					el.innerHTML = wordCount(cm.getValue());
				};
			} else if(name === "lines") {
				defaultValue = function(el) {
					el.innerHTML = cm.lineCount();
				};
				onUpdate = function(el) {
					el.innerHTML = cm.lineCount();
				};
			} else if(name === "cursor") {
				defaultValue = function(el) {
					el.innerHTML = "0:0";
				};
				onUpdate = function(el) {
					var pos = cm.getCursor();
					el.innerHTML = pos.line + ":" + pos.ch;
				};
			} else if(name === "autosave") {
				defaultValue = function(el) {
					if(options.autosave != undefined && options.autosave.enabled === true) {
						el.setAttribute("id", "autosaved");
					}
				};
			}

			items.push({
				className: name,
				defaultValue: defaultValue,
				onUpdate: onUpdate
			});
		}
	}


	// Create element for the status bar
	var bar = document.createElement("div");
	bar.className = "editor-statusbar";


	// Create a new span for each item
	for(i = 0; i < items.length; i++) {
		// Store in temporary variable
		var item = items[i];


		// Create span element
		var el = document.createElement("span");
		el.className = item.className;


		// Ensure the defaultValue is a function
		if(typeof item.defaultValue === "function") {
			item.defaultValue(el);
		}


		// Ensure the onUpdate is a function
		if(typeof item.onUpdate === "function") {
			// Create a closure around the span of the current action, then execute the onUpdate handler
			this.codemirror.on("update", (function(el, item) {
				return function() {
					item.onUpdate(el);
				};
			}(el, item)));
		}


		// Append the item to the status bar
		bar.appendChild(el);
	}


	// Insert the status bar into the DOM
	var cmWrapper = this.codemirror.getWrapperElement();
	cmWrapper.parentNode.insertBefore(bar, cmWrapper.nextSibling);
	return bar;
};

/**
 * Get or set the text content.
 */
SimpleMDE.prototype.value = function(val) {
	if(val === undefined) {
		return this.codemirror.getValue();
	} else {
		this.codemirror.getDoc().setValue(val);
		return this;
	}
};


/**
 * Bind static methods for exports.
 */
SimpleMDE.toggleBold = toggleBold;
SimpleMDE.toggleItalic = toggleItalic;
SimpleMDE.toggleStrikethrough = toggleStrikethrough;
SimpleMDE.toggleBlockquote = toggleBlockquote;
SimpleMDE.toggleHeadingSmaller = toggleHeadingSmaller;
SimpleMDE.toggleHeadingBigger = toggleHeadingBigger;
SimpleMDE.toggleHeading1 = toggleHeading1;
SimpleMDE.toggleHeading2 = toggleHeading2;
SimpleMDE.toggleHeading3 = toggleHeading3;
SimpleMDE.toggleCodeBlock = toggleCodeBlock;
SimpleMDE.toggleUnorderedList = toggleUnorderedList;
SimpleMDE.toggleOrderedList = toggleOrderedList;
SimpleMDE.cleanBlock = cleanBlock;
SimpleMDE.drawLink = drawLink;
SimpleMDE.drawImage = drawImage;
SimpleMDE.drawTable = drawTable;
SimpleMDE.drawHorizontalRule = drawHorizontalRule;
SimpleMDE.undo = undo;
SimpleMDE.redo = redo;
SimpleMDE.togglePreview = togglePreview;
SimpleMDE.toggleSideBySide = toggleSideBySide;
SimpleMDE.toggleFullScreen = toggleFullScreen;

/**
 * Bind instance methods for exports.
 */
SimpleMDE.prototype.toggleBold = function() {
	toggleBold(this);
};
SimpleMDE.prototype.toggleItalic = function() {
	toggleItalic(this);
};
SimpleMDE.prototype.toggleStrikethrough = function() {
	toggleStrikethrough(this);
};
SimpleMDE.prototype.toggleBlockquote = function() {
	toggleBlockquote(this);
};
SimpleMDE.prototype.toggleHeadingSmaller = function() {
	toggleHeadingSmaller(this);
};
SimpleMDE.prototype.toggleHeadingBigger = function() {
	toggleHeadingBigger(this);
};
SimpleMDE.prototype.toggleHeading1 = function() {
	toggleHeading1(this);
};
SimpleMDE.prototype.toggleHeading2 = function() {
	toggleHeading2(this);
};
SimpleMDE.prototype.toggleHeading3 = function() {
	toggleHeading3(this);
};
SimpleMDE.prototype.toggleCodeBlock = function() {
	toggleCodeBlock(this);
};
SimpleMDE.prototype.toggleUnorderedList = function() {
	toggleUnorderedList(this);
};
SimpleMDE.prototype.toggleOrderedList = function() {
	toggleOrderedList(this);
};
SimpleMDE.prototype.cleanBlock = function() {
	cleanBlock(this);
};
SimpleMDE.prototype.drawLink = function() {
	drawLink(this);
};
SimpleMDE.prototype.drawImage = function() {
	drawImage(this);
};
SimpleMDE.prototype.drawTable = function() {
	drawTable(this);
};
SimpleMDE.prototype.drawHorizontalRule = function() {
	drawHorizontalRule(this);
};
SimpleMDE.prototype.undo = function() {
	undo(this);
};
SimpleMDE.prototype.redo = function() {
	redo(this);
};
SimpleMDE.prototype.togglePreview = function() {
	togglePreview(this);
};
SimpleMDE.prototype.toggleSideBySide = function() {
	toggleSideBySide(this);
};
SimpleMDE.prototype.toggleFullScreen = function() {
	toggleFullScreen(this);
};

SimpleMDE.prototype.isPreviewActive = function() {
	var cm = this.codemirror;
	var wrapper = cm.getWrapperElement();
	var preview = wrapper.lastChild;

	return /editor-preview-active/.test(preview.className);
};

SimpleMDE.prototype.isSideBySideActive = function() {
	var cm = this.codemirror;
	var wrapper = cm.getWrapperElement();
	var preview = wrapper.nextSibling;

	return /editor-preview-active-side/.test(preview.className);
};

SimpleMDE.prototype.isFullscreenActive = function() {
	var cm = this.codemirror;

	return cm.getOption("fullScreen");
};

SimpleMDE.prototype.getState = function() {
	var cm = this.codemirror;

	return getState(cm);
};

SimpleMDE.prototype.toTextArea = function() {
	var cm = this.codemirror;
	var wrapper = cm.getWrapperElement();

	if(wrapper.parentNode) {
		if(this.gui.toolbar) {
			wrapper.parentNode.removeChild(this.gui.toolbar);
		}
		if(this.gui.statusbar) {
			wrapper.parentNode.removeChild(this.gui.statusbar);
		}
		if(this.gui.sideBySide) {
			wrapper.parentNode.removeChild(this.gui.sideBySide);
		}
	}

	cm.toTextArea();

	if(this.autosaveTimeoutId) {
		clearTimeout(this.autosaveTimeoutId);
		this.autosaveTimeoutId = undefined;
		this.clearAutosavedValue();
	}
};

module.exports = SimpleMDE;