/**
 * jquery.tinymce.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*global tinymce:true, jQuery */

(function($) {
	var undef,
		lazyLoading,
		patchApplied,
		delayedInits = [],
		win = window;

	$.fn.tinymce = function(settings) {
		var self = this, url, base, lang, suffix = "";

		// No match then just ignore the call
		if (!self.length) {
			return self;
		}

		// Get editor instance
		if (!settings) {
			return window.tinymce ? tinymce.get(self[0].id) : null;
		}

		self.css('visibility', 'hidden'); // Hide textarea to avoid flicker

		function init() {
			var editors = [], initCount = 0;

			// Apply patches to the jQuery object, only once
			if (!patchApplied) {
				applyPatch();
				patchApplied = true;
			}

			// Create an editor instance for each matched node
			self.each(function(i, node) {
				var ed, id = node.id, oninit = settings.oninit;

				// Generate unique id for target element if needed
				if (!id) {
					node.id = id = tinymce.DOM.uniqueId();
				}

				// Only init the editor once
				if (tinymce.get(id)) {
					return;
				}

				// Create editor instance and render it
				ed = new tinymce.Editor(id, settings, tinymce.EditorManager);
				editors.push(ed);

				ed.on('init', function() {
					var scope, func = oninit;

					self.css('visibility', '');

					// Run this if the oninit setting is defined
					// this logic will fire the oninit callback ones each
					// matched editor instance is initialized
					if (oninit) {
						// Fire the oninit event ones each editor instance is initialized
						if (++initCount == editors.length) {
							if (typeof func === "string") {
								scope = (func.indexOf(".") === -1) ? null : tinymce.resolve(func.replace(/\.\w+$/, ""));
								func = tinymce.resolve(func);
							}

							// Call the oninit function with the object
							func.apply(scope || tinymce, editors);
						}
					}
				});
			});

			// Render the editor instances in a separate loop since we
			// need to have the full editors array used in the onInit calls
			$.each(editors, function(i, ed) {
				ed.render();
			});
		}

		// Load TinyMCE on demand, if we need to
		if (!win.tinymce && !lazyLoading && (url = settings.script_url)) {
			lazyLoading = 1;
			base = url.substring(0, url.lastIndexOf("/"));

			// Check if it's a dev/src version they want to load then
			// make sure that all plugins, themes etc are loaded in source mode as well
			if (url.indexOf('.min') != -1) {
				suffix = ".min";
			}

			// Setup tinyMCEPreInit object this will later be used by the TinyMCE
			// core script to locate other resources like CSS files, dialogs etc
			// You can also predefined a tinyMCEPreInit object and then it will use that instead
			win.tinymce = win.tinyMCEPreInit || {
				base: base,
				suffix: suffix
			};

			// url contains gzip then we assume it's a compressor
			if (url.indexOf('gzip') != -1) {
				lang = settings.language || "en";
				url = url + (/\?/.test(url) ? '&' : '?') + "js=true&core=true&suffix=" + escape(suffix) +
					"&themes=" + escape(settings.theme || 'modern') + "&plugins=" +
					escape(settings.plugins || '') + "&languages=" + (lang || '');

				// Check if compressor script is already loaded otherwise setup a basic one
				if (!win.tinyMCE_GZ) {
					win.tinyMCE_GZ = {
						start: function() {
							function load(url) {
								tinymce.ScriptLoader.markDone(tinymce.baseURI.toAbsolute(url));
							}

							// Add core languages
							load("langs/" + lang + ".js");

							// Add themes with languages
							load("themes/" + settings.theme + "/theme" + suffix + ".js");
							load("themes/" + settings.theme + "/langs/" + lang + ".js");

							// Add plugins with languages
							$.each(settings.plugins.split(","), function(i, name) {
								if (name) {
									load("plugins/" + name + "/plugin" + suffix + ".js");
									load("plugins/" + name + "/langs/" + lang + ".js");
								}
							});
						},

						end: function() {
						}
					};
				}
			}

			var script = document.createElement('script');
			script.type = 'text/javascript';
			script.onload = script.onreadystatechange = function(e) {
				e = e || window.event;

				if (lazyLoading !== 2 && (e.type == 'load' || /complete|loaded/.test(script.readyState))) {
					tinymce.dom.Event.domLoaded = 1;
					lazyLoading = 2;

					// Execute callback after mainscript has been loaded and before the initialization occurs
					if (settings.script_loaded) {
						settings.script_loaded();
					}

					init();

					$.each(delayedInits, function(i, init) {
						init();
					});
				}
			};
			script.src = url;
			document.body.appendChild(script);
		} else {
			// Delay the init call until tinymce is loaded
			if (lazyLoading === 1) {
				delayedInits.push(init);
			} else {
				init();
			}
		}

		return self;
	};

	// Add :tinymce psuedo selector this will select elements that has been converted into editor instances
	// it's now possible to use things like $('*:tinymce') to get all TinyMCE bound elements.
	$.extend($.expr[":"], {
		tinymce: function(e) {
			var editor;

			if (e.id && "tinymce" in window) {
				editor = tinymce.get(e.id);

				if (editor && editor.editorManager === tinymce) {
					return true;
				}
			}

			return false;
		}
	});

	// This function patches internal jQuery functions so that if
	// you for example remove an div element containing an editor it's
	// automatically destroyed by the TinyMCE API
	function applyPatch() {
		// Removes any child editor instances by looking for editor wrapper elements
		function removeEditors(name) {
			// If the function is remove
			if (name === "remove") {
				this.each(function(i, node) {
					var ed = tinyMCEInstance(node);

					if (ed) {
						ed.remove();
					}
				});
			}

			this.find("span.mceEditor,div.mceEditor").each(function(i, node) {
				var ed = tinymce.get(node.id.replace(/_parent$/, ""));

				if (ed) {
					ed.remove();
				}
			});
		}

		// Loads or saves contents from/to textarea if the value
		// argument is defined it will set the TinyMCE internal contents
		function loadOrSave(value) {
			var self = this, ed;

			// Handle set value
			/*jshint eqnull:true */
			if (value != null) {
				removeEditors.call(self);

				// Saves the contents before get/set value of textarea/div
				self.each(function(i, node) {
					var ed;

					if ((ed = tinymce.get(node.id))) {
						ed.setContent(value);
					}
				});
			} else if (self.length > 0) {
				// Handle get value
				if ((ed = tinymce.get(self[0].id))) {
					return ed.getContent();
				}
			}
		}

		// Returns tinymce instance for the specified element or null if it wasn't found
		function tinyMCEInstance(element) {
			var ed = null;

			if (element && element.id && win.tinymce) {
				ed = tinymce.get(element.id);
			}

			return ed;
		}

		// Checks if the specified set contains tinymce instances
		function containsTinyMCE(matchedSet) {
			return !!((matchedSet) && (matchedSet.length) && (win.tinymce) && (matchedSet.is(":tinymce")));
		}

		// Patch various jQuery functions
		var jQueryFn = {};

		// Patch some setter/getter functions these will
		// now be able to set/get the contents of editor instances for
		// example $('#editorid').html('Content'); will update the TinyMCE iframe instance
		$.each(["text", "html", "val"], function(i, name) {
			var origFn = jQueryFn[name] = $.fn[name],
				textProc = (name === "text");

			$.fn[name] = function(value) {
				var self = this;

				if (!containsTinyMCE(self)) {
					return origFn.apply(self, arguments);
				}

				if (value !== undef) {
					loadOrSave.call(self.filter(":tinymce"), value);
					origFn.apply(self.not(":tinymce"), arguments);

					return self; // return original set for chaining
				} else {
					var ret = "";
					var args = arguments;

					(textProc ? self : self.eq(0)).each(function(i, node) {
						var ed = tinyMCEInstance(node);

						if (ed) {
							ret += textProc ? ed.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g, "") : ed.getContent({save: true});
						} else {
							ret += origFn.apply($(node), args);
						}
					});

					return ret;
				}
			};
		});

		// Makes it possible to use $('#id').append("content"); to append contents to the TinyMCE editor iframe
		$.each(["append", "prepend"], function(i, name) {
			var origFn = jQueryFn[name] = $.fn[name],
				prepend = (name === "prepend");

			$.fn[name] = function(value) {
				var self = this;

				if (!containsTinyMCE(self)) {
					return origFn.apply(self, arguments);
				}

				if (value !== undef) {
					if (typeof value === "string") {
						self.filter(":tinymce").each(function(i, node) {
							var ed = tinyMCEInstance(node);

							if (ed) {
								ed.setContent(prepend ? value + ed.getContent() : ed.getContent() + value);
							}
						});
					}

					origFn.apply(self.not(":tinymce"), arguments);

					return self; // return original set for chaining
				}
			};
		});

		// Makes sure that the editor instance gets properly destroyed when the parent element is removed
		$.each(["remove", "replaceWith", "replaceAll", "empty"], function(i, name) {
			var origFn = jQueryFn[name] = $.fn[name];

			$.fn[name] = function() {
				removeEditors.call(this, name);

				return origFn.apply(this, arguments);
			};
		});

		jQueryFn.attr = $.fn.attr;

		// Makes sure that $('#tinymce_id').attr('value') gets the editors current HTML contents
		$.fn.attr = function(name, value) {
			var self = this, args = arguments;

			if ((!name) || (name !== "value") || (!containsTinyMCE(self))) {
				if (value !== undef) {
					return jQueryFn.attr.apply(self, args);
				} else {
					return jQueryFn.attr.apply(self, args);
				}
			}

			if (value !== undef) {
				loadOrSave.call(self.filter(":tinymce"), value);
				jQueryFn.attr.apply(self.not(":tinymce"), args);

				return self; // return original set for chaining
			} else {
				var node = self[0], ed = tinyMCEInstance(node);

				return ed ? ed.getContent({save: true}) : jQueryFn.attr.apply($(node), args);
			}
		};
	}
})(jQuery);
