define([
	"dojo",
	"dijit",
	"dojox",
	"dijit/_base/manager",	// getUniqueId
	"dijit/_base/popup",
	"dijit/_Widget",
	"dijit/_TemplatedMixin",
	"dijit/_KeyNavContainer",
	"dijit/_WidgetsInTemplateMixin",
	"dijit/TooltipDialog",
	"dijit/Toolbar",
	"dijit/form/CheckBox",
	"dijit/form/_TextBoxMixin",	// selectInputText
	"dijit/form/TextBox",
	"dijit/_editor/_Plugin",
	"dijit/form/Button",
	"dijit/form/DropDownButton",
	"dijit/form/ToggleButton",
	"./ToolbarLineBreak",
	"dojo/_base/connect",
	"dojo/_base/declare",
	"dojo/i18n",
	"dojo/string",
	"dojo/i18n!dojox/editor/plugins/nls/FindReplace"
], function(dojo, dijit, dojox, manager, popup,
			_Widget, _TemplatedMixin, _KeyNavContainer, _WidgetsInTemplateMixin, TooltipDialog,
			Toolbar, CheckBox, _TextBoxMixin, TextBox, _Plugin) {

dojo.experimental("dojox.editor.plugins.FindReplace");

var FindReplaceCloseBox = dojo.declare("dojox.editor.plugins._FindReplaceCloseBox",
	[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], {
	// summary:
	//		Base class for widgets that contains a button labeled X
	//		to close the tool bar.
	
	btnId: "",
	widget: null,
	widgetsInTemplate: true,
	
	templateString:
		"<span style='float: right' class='dijitInline' tabindex='-1'>" +
			"<button class='dijit dijitReset dijitInline' " +
				"id='${btnId}' dojoAttachPoint='button' dojoType='dijit.form.Button' tabindex='-1' iconClass='dijitEditorIconsFindReplaceClose' showLabel='false'>X</button>" +
		"</span>",
	
	postMixInProperties: function(){
		// Set some substitution variables used in the template
		this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
		this.btnId = this.id + "_close";
		this.inherited(arguments);
	},
	startup: function(){
		this.connect(this.button, "onClick", "onClick");
	},
	onClick: function(){
	}
});


var FindReplaceTextBox = dojo.declare("dojox.editor.plugins._FindReplaceTextBox",
	[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],{
	// summary:
	//		Base class for widgets that contains a label (like "Font:")
	//		and a TextBox to pick a value.
	//		Used as Toolbar entry.

	// textId: [public] String
	//		The id of the enhanced textbox
	textId: "",
	
	// label: [public] String
	//		The label of the enhanced textbox
	label: "",
	
	// tooltip: [public] String
	//		The tooltip of the enhanced textbox when the mouse is hovering on it
	toolTip: "",
	widget: null,
	widgetsInTemplate: true,

	templateString:
		"<span style='white-space: nowrap' class='dijit dijitReset dijitInline dijitEditorFindReplaceTextBox' " +
			"title='${tooltip}' tabindex='-1'>" +
			"<label class='dijitLeft dijitInline' for='${textId}' tabindex='-1'>${label}</label>" +
			"<input dojoType='dijit.form.TextBox' intermediateChanges='true' class='focusTextBox' " +
					"tabIndex='0' id='${textId}' dojoAttachPoint='textBox, focusNode' value='' dojoAttachEvent='onKeyPress: _onKeyPress'/>" +
		"</span>",

	postMixInProperties: function(){
		// Set some substitution variables used in the template
		this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
		this.textId = this.id + "_text";
		
		this.inherited(arguments);
	},

	postCreate: function(){
		this.textBox.set("value", "");
		this.disabled =  this.textBox.get("disabled");
		this.connect(this.textBox, "onChange", "onChange");
		dojo.attr(this.textBox.textbox, "formnovalidate", "true");
	},

	_setValueAttr: function(/*String*/ value){
		//If the value is not a permitted value, just set empty string to prevent showing the warning icon
		this.value = value;
		this.textBox.set("value", value);
	},

	focus: function(){
		this.textBox.focus();
	},

	_setDisabledAttr: function(/*Boolean*/ value){
		// summary:
		//		Over-ride for the textbox's 'disabled' attribute so that it can be
		//		disabled programmatically.
		// value:
		//		The boolean value to indicate if the textbox should be disabled or not
		// tags:
		//		private
		this.disabled = value;
		this.textBox.set("disabled", value);
	},

	onChange: function(/*String*/ val){
		// summary:
		//		Stub function for change events on the box.
		// tags:
		//		public
		this.value= val;
	},
	
	_onKeyPress: function(/*Event*/ evt){
		// summary:
		//		Handle the arrow key events
		// evt:
		//		Event object passed to this handler
		// tags:
		//		private
		var start = 0;
		var end = 0;
		
		// If CTRL, ALT or SHIFT is not held on
		if(evt.target && !evt.ctrlKey && !evt.altKey && !evt.shiftKey){
			if(evt.keyCode == dojo.keys.LEFT_ARROW){
				start = evt.target.selectionStart;
				end = evt.target.selectionEnd;
				if(start < end){
					dijit.selectInputText(evt.target, start, start);
					dojo.stopEvent(evt);
				}
			}else if(evt.keyCode == dojo.keys.RIGHT_ARROW){
				start = evt.target.selectionStart;
				end = evt.target.selectionEnd;
				if(start < end){
					dijit.selectInputText(evt.target, end, end);
					dojo.stopEvent(evt);
				}
			}
		}
	}
});


var FindReplaceCheckBox = dojo.declare("dojox.editor.plugins._FindReplaceCheckBox",
	[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],{
	// summary:
	//		Base class for widgets that contains a label (like "Match case: ")
	//		and a checkbox to indicate if it is checked or not.
	//		Used as Toolbar entry.

	// checkId: [public] String
	//		The id of the enhanced checkbox
	checkId: "",
	
	// label: [public] String
	//		The label of the enhanced checkbox
	label: "",
	
	// tooltip: [public] String
	//		The tooltip of the enhanced checkbox when the mouse is hovering it
	tooltip: "",
	
	widget: null,
	widgetsInTemplate: true,

	templateString:
		"<span style='white-space: nowrap' tabindex='-1' " +
			"class='dijit dijitReset dijitInline dijitEditorFindReplaceCheckBox' title='${tooltip}' >" +
			"<input dojoType='dijit.form.CheckBox' " +
					"tabIndex='0' id='${checkId}' dojoAttachPoint='checkBox, focusNode' value=''/>" +
			"<label tabindex='-1' class='dijitLeft dijitInline' for='${checkId}'>${label}</label>" +
		"</span>",

	postMixInProperties: function(){
		// Set some substitution variables used in the template
		this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
		this.checkId = this.id + "_check";
		this.inherited(arguments);
	},

	postCreate: function(){
		this.checkBox.set("checked", false);
		this.disabled =  this.checkBox.get("disabled");
		this.checkBox.isFocusable = function(){ return false; };
	},

	_setValueAttr: function(/*Boolean*/ value){
		// summary:
		//		Passthrough for checkbox.
		// tags:
		//		private
		this.checkBox.set('value', value);
	},

	_getValueAttr: function(){
		// summary:
		//		Passthrough for checkbox.
		// tags:
		//		private
		return this.checkBox.get('value');
	},

	focus: function(){
		// summary:
		//		Handle the focus event when this widget gets focused
		// tags:
		//		private
		this.checkBox.focus();
	},

	_setDisabledAttr: function(/*Boolean*/ value){
		// summary:
		//		Over-ride for the button's 'disabled' attribute so that it can be
		//		disabled programmatically.
		// value:
		//		The flag that indicates if the checkbox is disabled or not.
		// tags:
		//		private
		this.disabled = value;
		this.checkBox.set("disabled", value);
	}
});


var FindReplaceToolbar = dojo.declare("dojox.editor.plugins._FindReplaceToolbar", Toolbar, {
	// summary:
	//		A toolbar that derived from dijit.Toolbar, which
	//		eliminates some unnecessary event response such as LEFT_ARROW pressing
	//		and click bubbling.

	postCreate: function(){
		this.connectKeyNavHandlers([], []); // Prevent arrow key navigation
		this.connect(this.containerNode, "onclick", "_onToolbarEvent");
		this.connect(this.containerNode, "onkeydown", "_onToolbarEvent");
		dojo.addClass(this.domNode, "dijitToolbar");
	},
	
	addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
		// summary:
		//		Add a child to our _Container and prevent the default
		//		arrow key navigation function. This function may bring in
		//		side effect
		dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
	},
	
	_onToolbarEvent: function(/*Event*/ evt){
		// Editor may have special treatment to some events, so stop the bubbling.
		// evt:
		//		The Event object
		// tages:
		//		private
		evt.stopPropagation();
	}
});

var FindReplace = dojo.declare("dojox.editor.plugins.FindReplace",[_Plugin],{
	// summary:
	//		This plugin provides a Find/Replace capability for the editor.
	//		Note that this plugin is NOT supported on Opera currently, as opera
	//		does not implement a window.find or equiv function.

	// buttonClass: [protected]
	//		Define the class of button the editor uses.
	buttonClass: dijit.form.ToggleButton,

	// iconClassPrefix: [const] String
	//		The CSS class name for the button node is formed from `iconClassPrefix` and `command`
	iconClassPrefix: "dijitEditorIconsFindReplace",

	// editor: [protected]
	//		The editor this plugin belongs to
	editor: null,
	
	// button: [protected]
	//		The toggle button
	button: null,
	
	// _frToolbar: [private]
	//		The toolbar that contain all the entries and buttons
	_frToolbar: null,
	
	// _closeBox: [private]
	//		The close button of the F/R toolbar
	_closeBox: null,
	
	// _findField: [private]
	//		The Find field of the F/R toolbar
	_findField: null,
	
	// _replaceField: [private]
	//		The Replace field of the F/R toolbar
	_replaceField: null,
	
	// _findButton: [private]
	//		The Find button of the F/R toolbar
	_findButton: null,
	
	// _replaceButton: [private]
	//		The Replace button of the F/R toolbar
	_replaceButton: null,
	
	// _replaceAllButton: [private]
	//		The ReplaceAll button of the F/R toolbar
	_replaceAllButton: null,
	
	// _caseSensitive: [private]
	//		The case sensitive checkbox
	_caseSensitive: null,
	
	// _backwards: [private]
	//		The backwards checkbox
	_backwards: null,
	
	// _promDialog: [private]
	//		The prompt message box that shows the user some messages
	//		such as the end of a search, the end of a replacement, etc.
	_promDialog: null,
	_promDialogTimeout: null,

	// _strings: [private]
	//		The array that contains globalized strings
	_strings: null,

	_initButton: function(){
		// summary:
		//		Over-ride for creation of the resize button.
		this._strings = dojo.i18n.getLocalization("dojox.editor.plugins", "FindReplace");
		this.button = new dijit.form.ToggleButton({
			label: this._strings["findReplace"],
			showLabel: false,
			iconClass: this.iconClassPrefix + " dijitEditorIconFindString",
			tabIndex: "-1",
			onChange: dojo.hitch(this, "_toggleFindReplace")
		});
		if(dojo.isOpera){
			// Not currently supported on Opera!
			this.button.set("disabled", true);
		}
		//Link up so that if the toggle is disabled, then the view of Find/Replace is closed.
		this.connect(this.button, "set", dojo.hitch(this, function(attr, val){
			if(attr === "disabled"){
				this._toggleFindReplace((!val && this._displayed), true, true);
			}
		}));
	},

	setEditor: function(editor){
		// summary:
		//		This is a callback handler that set a reference to the editor this plugin
		//		hosts in
		this.editor = editor;
		this._initButton();
	},

	toggle: function(){
		// summary:
		//		Function to allow programmatic toggling of the find toolbar.
		// tags:
		//		public
		this.button.set("checked", !this.button.get("checked"));
	},

	_toggleFindReplace: function(/*Boolean*/ show, /*Boolean?*/ ignoreState, /*Boolean?*/ buttonDisabled){
		// summary:
		//		Function to toggle whether or not find/replace is displayed.
		// show:
		//		Indicate if the toolbar is shown or not
		// ignoreState:
		//		Indicate if the status should be ignored or not
		// blurEditor:
		//		Indicate if the focus should be removed from the editor or not
		// tags:
		//		private
		var size = dojo.marginBox(this.editor.domNode);
		if(show && !dojo.isOpera){
			dojo.style(this._frToolbar.domNode, "display", "block");
			// Auto populate the Find field
			this._populateFindField();
			if(!ignoreState){
				this._displayed = true;
			}
		}else{
			dojo.style(this._frToolbar.domNode, "display", "none");
			if(!ignoreState){
				this._displayed = false;
			}
			
			// If the toggle button is disabled, it is most likely that
			// another plugin such as ViewSource disables it.
			// So we do not need to focus the text area of the editor to
			// prevent the editor from an invalid status.
			// Please refer to dijit._editor.plugins.ViewSource for more details.
			if(!buttonDisabled){
				this.editor.focus();
			}
		}

		// Resize the editor.
		this.editor.resize({h: size.h});
	},

	_populateFindField: function(){
		// summary:
		//		Populate the Find field with selected text when dialog initially displayed.
		//		Auto-select text in Find field after it is populated.
		//		If nothing selected, restore previous entry from the same session.
		// tags:
		//		private
		var ed = this.editor;
		var win = ed.window;
		var selectedTxt = ed._sCall("getSelectedText", [null]);
		if(this._findField && this._findField.textBox){
			if(selectedTxt){
				this._findField.textBox.set("value", selectedTxt);
			}
			this._findField.textBox.focus();
			dijit.selectInputText(this._findField.textBox.focusNode);
		}
	},

	setToolbar: function(/*dijit.Toolbar*/ toolbar){
		// summary:
		//		Over-ride so that find/replace toolbar is appended after the current toolbar.
		// toolbar:
		//		The current toolbar of the editor
		// tags:
		//		public
		this.inherited(arguments);
		if(!dojo.isOpera){
			var _tb = (this._frToolbar = new FindReplaceToolbar());
			dojo.style(_tb.domNode, "display", "none");
			dojo.place(_tb.domNode, toolbar.domNode, "after");
			_tb.startup();

			// IE6 will put the close box in a new line when its style is "float: right".
			// So place the close box ahead of the other fields, which makes it align with
			// the other components.
			this._closeBox = new FindReplaceCloseBox();
			_tb.addChild(this._closeBox);
			
			// Define the search/replace fields.
			this._findField = new FindReplaceTextBox(
				{label: this._strings["findLabel"], tooltip: this._strings["findTooltip"]});
			_tb.addChild(this._findField);
			
			this._replaceField = new FindReplaceTextBox(
				{label: this._strings["replaceLabel"], tooltip: this._strings["replaceTooltip"]});
			_tb.addChild(this._replaceField);

			// Define the Find/Replace/ReplaceAll buttons.
			_tb.addChild(new dojox.editor.plugins.ToolbarLineBreak());
			
			this._findButton = new dijit.form.Button({label: this._strings["findButton"], showLabel: true,
				iconClass: this.iconClassPrefix + " dijitEditorIconFind"});
			this._findButton.titleNode.title = this._strings["findButtonTooltip"];
			_tb.addChild(this._findButton);
			
			this._replaceButton = new dijit.form.Button({label: this._strings["replaceButton"], showLabel: true,
				iconClass: this.iconClassPrefix + " dijitEditorIconReplace"});
			this._replaceButton.titleNode.title = this._strings["replaceButtonTooltip"];
			_tb.addChild(this._replaceButton);
			
			this._replaceAllButton = new dijit.form.Button({label: this._strings["replaceAllButton"], showLabel: true,
				iconClass: this.iconClassPrefix + " dijitEditorIconReplaceAll"});
			this._replaceAllButton.titleNode.title = this._strings["replaceAllButtonTooltip"];
			_tb.addChild(this._replaceAllButton);
			
			// Define the option checkboxes.
			this._caseSensitive = new FindReplaceCheckBox(
				{label: this._strings["matchCase"], tooltip: this._strings["matchCaseTooltip"]});
			_tb.addChild(this._caseSensitive);
			
			this._backwards = new FindReplaceCheckBox(
				{label: this._strings["backwards"], tooltip: this._strings["backwardsTooltip"]});
			_tb.addChild(this._backwards);

			// Set initial states, buttons should be disabled unless content is
			// present in the fields.
			this._findButton.set("disabled", true);
			this._replaceButton.set("disabled", true);
			this._replaceAllButton.set("disabled", true);

			// Connect the event to the status of the items
			this.connect(this._findField, "onChange", "_checkButtons");
			this.connect(this._findField, "onKeyDown", "_onFindKeyDown");
			this.connect(this._replaceField, "onKeyDown", "_onReplaceKeyDown");

			// Connect up the actual search events.
			this.connect(this._findButton, "onClick", "_find");
			this.connect(this._replaceButton, "onClick", "_replace");
			this.connect(this._replaceAllButton, "onClick", "_replaceAll");
			
			// Connect up the close event
			this.connect(this._closeBox, "onClick", "toggle");
			
			// Prompt for the message
			this._promDialog = new dijit.TooltipDialog();
			this._promDialog.startup();
			this._promDialog.set("content", "");
		}
	},

	_checkButtons: function(){
		// summary:
		//		Ensure that all the buttons are in a correct status
		//		when certain events are fired.
		var fText = this._findField.get("value");

		if(fText){
			// Only enable if find text is not empty or just blank/spaces.
			this._findButton.set("disabled", false);
			this._replaceButton.set("disabled", false);
			this._replaceAllButton.set("disabled", false);
		}else{
			this._findButton.set("disabled", true);
			this._replaceButton.set("disabled", true);
			this._replaceAllButton.set("disabled", true);
		}
	},
	
	_onFindKeyDown: function(evt){
		if(evt.keyCode == dojo.keys.ENTER){
			// Perform the default search action
			this._find();
			dojo.stopEvent(evt);
		}
	},
	
	_onReplaceKeyDown: function(evt){
		if(evt.keyCode == dojo.keys.ENTER){
			// Perform the default replace action
			if(!this._replace()) this._replace();
			dojo.stopEvent(evt);
		}
	},

	_find: function(/*Boolean?*/ showMessage){
		// summary:
		//		This function invokes a find on the editor document with the noted options for
		//		find.
		// showMessage:
		//		Indicated whether the tooltip is shown or not when the search reaches the end
		// tags:
		//		private.
		// returns:
		//		Boolean indicating if the content was found or not.
		var txt = this._findField.get("value") || "";
		if(txt){
			var caseSensitive = this._caseSensitive.get("value");
			var backwards = this._backwards.get("value");
			var isFound = this._findText(txt, caseSensitive, backwards);
			if(!isFound && showMessage){
				this._promDialog.set("content", dojo.string.substitute(
					this._strings["eofDialogText"], {"0": this._strings["eofDialogTextFind"]}));
				dijit.popup.open({popup: this._promDialog, around: this._findButton.domNode});
				this._promDialogTimeout = setTimeout(dojo.hitch(this, function(){
					clearTimeout(this._promDialogTimeout);
					this._promDialogTimeout = null;
					dijit.popup.close(this._promDialog);
				}), 3000);
				setTimeout(dojo.hitch(this, function(){
					this.editor.focus();
				}), 0);
			}
			return isFound;
		}
		
		return false;
	},

	_replace: function(/*Boolean?*/ showMessage){
		// summary:
		//		This function invokes a replace on the editor document with the noted options for replace
		// showMessage:
		//		Indicate if the prompt message is shown or not when the replacement
		//		reaches the end
		// tags:
		//		private
		// returns:
		//		Boolean indicating if the content was replaced or not.
		var isReplaced = false;
		var ed = this.editor;
		ed.focus();
		var txt = this._findField.get("value") || "";
		var repTxt = this._replaceField.get("value") || "";
		 
		if(txt){
			var caseSensitive = this._caseSensitive.get("value");
			// Check if it is forced to be forwards or backwards
			var backwards = this._backwards.get("value");
			
			//Replace the current selected text if it matches the pattern
			var selected = ed._sCall("getSelectedText", [null]);
			// Handle checking/replacing current selection.  For some reason on Moz
			// leading whitespace is trimmed, so we have to trim it down on this check
			// or we don't always replace.  Moz bug!
			if(dojo.isMoz){
				txt = dojo.trim(txt);
				selected = dojo.trim(selected);
			}
			
			var regExp = this._filterRegexp(txt, !caseSensitive);
			if(selected && regExp.test(selected)){
				ed.execCommand("inserthtml", repTxt);
				isReplaced = true;
			
				if(backwards){
					// Move to the beginning of the replaced text
					// to avoid the infinite recursive replace
					this._findText(repTxt, caseSensitive, backwards);
					ed._sCall("collapse", [true]);
				}
			}
			
			if(!this._find(false) && showMessage){	// Find the next
				this._promDialog.set("content", dojo.string.substitute(
					this._strings["eofDialogText"], {"0": this._strings["eofDialogTextReplace"]}));
				dijit.popup.open({popup: this._promDialog, around: this._replaceButton.domNode});
				this._promDialogTimeout = setTimeout(dojo.hitch(this, function(){
					clearTimeout(this._promDialogTimeout);
					this._promDialogTimeout = null;
					dijit.popup.close(this._promDialog);
				}), 3000);
				setTimeout(dojo.hitch(this, function(){
					this.editor.focus();
				}), 0);
			}
			return isReplaced;
		 }
		 return null;
	},
	
	_replaceAll: function(/*Boolean?*/ showMessage){
		// summary:
		//		This function replaces all the matched content on the editor document
		//		with the noted options for replace
		// showMessage:
		//		Indicate if the prompt message is shown or not when the action is done.
		// tags:
		//		private
		var replaced = 0;
		var backwards = this._backwards.get("value");
		
		if(backwards){
			this.editor.placeCursorAtEnd();
		}else{
			this.editor.placeCursorAtStart();
		}
		
		// The _replace will return false if the current selection deos not match
		// the searched text. So try the first attempt so that the selection
		// is always the searched text if there is one that matches
		if(this._replace(false)) { replaced++; }
		// Do the replace via timeouts to avoid locking the browser up for a lot of replaces.
		var loopBody = dojo.hitch(this, function(){
			if(this._replace(false)){
				replaced++;
				setTimeout(loopBody, 10);
			}else{
				if(showMessage){
					this._promDialog.set("content", dojo.string.substitute(
						this._strings["replaceDialogText"], {"0": "" + replaced}));
					dijit.popup.open({
						popup: this._promDialog,
						around: this._replaceAllButton.domNode
					});
					this._promDialogTimeout = setTimeout(dojo.hitch(this, function(){
						clearTimeout(this._promDialogTimeout);
						this._promDialogTimeout = null;
						dijit.popup.close(this._promDialog);
					}), 3000);
					setTimeout(dojo.hitch(this, function(){
						this._findField.focus();
						this._findField.textBox.focusNode.select();
					}), 0);
				}
			}
		});
		loopBody();
	},

	_findText: function(/*String*/ txt, /*Boolean*/ caseSensitive, /*Boolean*/ backwards){
		// summary:
		//		This function invokes a find with specific options
		// txt: String
		//		The text to locate in the document.
		// caseSensitive: Boolean
		//		Whether or ot to search case-sensitively.
		// backwards: Boolean
		//		Whether or not to search backwards in the document.
		// tags:
		//		private.
		// returns:
		//		Boolean indicating if the content was found or not.
		var ed = this.editor;
		var win = ed.window;
		var found = false;
		if(txt){
			if(win.find){
				found = win.find(txt, caseSensitive, backwards, false, false, false, false);
			}else{
				var doc = ed.document;
				if(doc.selection){
					/* IE */
					// Focus to restore position/selection,
					// then shift to search from current position.
					this.editor.focus();
					var txtRg = doc.body.createTextRange();
					var curPos = doc.selection?doc.selection.createRange():null;
					if(curPos){
						if(backwards){
							txtRg.setEndPoint("EndToStart", curPos);
						}else{
							txtRg.setEndPoint("StartToEnd", curPos);
						}
					}
					var flags = caseSensitive?4:0;
					if(backwards){
						flags = flags | 1;
					}
					//flags = flags |
					found = txtRg.findText(txt,txtRg.text.length,flags);
					if(found){
						txtRg.select();
					}
				}
			}
		}
		return found;
	},

	_filterRegexp: function(/*String*/ pattern, /*Boolean*/ ignoreCase){
		// summary:
		//		Helper function to convert a simple pattern to a regular expression for matching.
		// description:
		//		Returns a regular expression object that conforms to the defined conversion rules.
		//		For example:
		//
		//		- ca*   -> /^ca.*$/
		//		- *ca*  -> /^.*ca.*$/
		//		- *c\*a*  -> /^.*c\*a.*$/
		//		- *c\*a?*  -> /^.*c\*a..*$/
		//
		//		and so on.
		// pattern: string
		//		A simple matching pattern to convert that follows basic rules:
		//
		//		- * Means match anything, so ca* means match anything starting with ca
		//		- ? Means match single character.  So, b?b will match to bob and bab, and so on.
		//		- \ is an escape character.  So for example, \* means do not treat * as a match, but literal character *.
		//		  To use a \ as a character in the string, it must be escaped.  So in the pattern it should be
		//		  represented by \\ to be treated as an ordinary \ character instead of an escape.
		// ignoreCase:
		//		An optional flag to indicate if the pattern matching should be treated as case-sensitive or not when comparing
		//		By default, it is assumed case sensitive.
		// tags:
		//		private

		var rxp = "";
		var c = null;
		for(var i = 0; i < pattern.length; i++){
			c = pattern.charAt(i);
			switch(c){
				case '\\':
					rxp += c;
					i++;
					rxp += pattern.charAt(i);
					break;
				case '$':
				case '^':
				case '/':
				case '+':
				case '.':
				case '|':
				case '(':
				case ')':
				case '{':
				case '}':
				case '[':
				case ']':
					rxp += "\\"; //fallthrough
				default:
					rxp += c;
			}
		}
		rxp = "^" + rxp + "$";
		if(ignoreCase){
			return new RegExp(rxp,"mi"); //RegExp
		}else{
			return new RegExp(rxp,"m"); //RegExp
		}
		
	},
	
	updateState: function(){
		// summary:
		//		Over-ride for button state control for disabled to work.
		this.button.set("disabled", this.get("disabled"));
	},

	destroy: function(){
		// summary:
		//		Cleanup of our custom toolbar.
		this.inherited(arguments);
		if(this._promDialogTimeout){
			clearTimeout(this._promDialogTimeout);
			this._promDialogTimeout = null;
			dijit.popup.close(this._promDialog);
		}
		if(this._frToolbar){
			this._frToolbar.destroyRecursive();
			this._frToolbar = null;
		}
		if(this._promDialog){
			this._promDialog.destroyRecursive();
			this._promDialog = null;
		}
	}
});

// For monkey patching
FindReplace._FindReplaceCloseBox = FindReplaceCloseBox;
FindReplace._FindReplaceTextBox = FindReplaceTextBox;
FindReplace._FindReplaceCheckBox = FindReplaceCheckBox;
FindReplace._FindReplaceToolbar = FindReplaceToolbar;

// Register this plugin.
dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
	if(o.plugin){ return; }
	var name = o.args.name.toLowerCase();
	if(name ===  "findreplace"){
		o.plugin = new FindReplace({});
	}
});

return FindReplace;

});
