/**
 * @author Justin
 */

try {
    if (typeof(ncBPFramework.DOM) == 'undefined') {
        throw "";
    }
} catch (e) {
    throw "ncBPFramework.Selection depends on ncBPFramework.DOM!";
}


if (typeof(ncBPFramework.Selection) == 'undefined') {
    ncBPFramework.Selection = {};
}

ncBPFramework.Selection.NAME = "ncBPFramework.Selection";
ncBPFramework.Selection.VERSION = "1.0.0";

ncBPFramework.Base.update(ncBPFramework.Selection, {

    __repr__: function () {
        return '[' + this.NAME + ' ' + this.VERSION + ']';
    },

    toString: function () {
        return this.__repr__();
    }
});

ncBPFramework.Selection.Selection = function (windowObj) {
	this._window = windowObj;
	this._document = windowObj.document;
	if (this._window.getSelection) {
		this._selectionObject = this._window.getSelection();
		this._selectionType = 'W3C';
	}
	else if (this._document.selection) { // should come last; Opera!
		this._selectionObject = this._document.selection;
		this._selectionType = 'TextRange';
	}
};

ncBPFramework.Base.update(ncBPFramework.Selection.Selection.prototype, {
	GetRange: function () {
		if (this._selectionType == 'TextRange') {
			return new ncBPFramework.Selection.Range(this._window, this._selectionObject.createRange());
		}
		else if (this._selectionObject.getRangeAt)
			return new ncBPFramework.Selection.Range(this._window, this._selectionObject.getRangeAt(0));
		else { // Safari!
			var range = this._document.createRange();
			range.setStart(this._selectionObject.anchorNode,this._selectionObject.anchorOffset);
			range.setEnd(this._selectionObject.focusNode,this._selectionObject.focusOffset);
			return new ncBPFramework.Selection.Range(this._window, range);
		}
	}
});

ncBPFramework.Selection.Range = function (windowObj, rngObj) {
	this._window = windowObj;
	this._document = windowObj.document;
	if (typeof(rngObj) == 'undefined') {
		this._range = this._document.createRange();
	} else {
		this._range = rngObj;
	}
	if (this._window.getSelection) {
		this._rangeType = 'W3C';
	} else if (this._document.selection) { // IE 4+
		this._rangeType = 'TextRange';
	}
};

ncBPFramework.Base.update(ncBPFramework.Selection.Range.prototype, {
    NAME: 'ncBPFramework.Selection.Range',
	VERSION: '1.0.0',
	__repr__: function () {
        var result = '[' + this.NAME + ' ' + this.VERSION + ']';
		result += '[rangeType='+this._rangeType+']';
		if (this.parentElement()) {
			result += '[parentElement='+this.parentElement().tagName+']';
		}
		if (this._rangeType == 'W3C') {
			result += '[startContainer type='+this._range.startContainer.nodeType+']';
			result += '[startContainer nodeValue='+this._range.startContainer.nodeValue+']';
			result += '[startOffset='+this._range.startOffset+']';
			result += '[endContainer type='+this._range.endContainer.nodeType+']';
			result += '[endContainer nodeValue='+this._range.endContainer.nodeValue+']';
			result += '[endOffset='+this._range.endOffset+']';

		}
		 
		return result;
    },
    toString: function () {
        return this.__repr__();
    },
	moveToElementText: function (element) {
		if (this._rangeType == 'W3C') {
			this._range.selectNodeContents(element);
		} else {
			this._range.moveToElementText(element);
		}
	},
	parentElement: function () {
		if (this._rangeType == 'W3C') {
			if (this._range.startContainer.nodeType == 3) {
				return this._range.startContainer.parentNode;
			} else {
				return this._range.startContainer;
			}
		} else {
			try {
				return this._range.parentElement();
			}catch(e){
				// If the range is a Control Range there is no parentElement
				return null;
			}
		}
	},
	select: function() {
		if (this._rangeType == 'W3C') {
			var oRange = this._range;
			var oDocRange = this._document.createRange() ;
 	        oDocRange.setStart( oRange.startContainer, oRange.startOffset ) ;
 	
 	        try
 	        {
 	        	oDocRange.setEnd(oRange.endContainer, oRange.endOffset ) ;
 	        }
 	        catch ( e )
 	        {
 	        	// There is a bug in Firefox implementation (it would be too easy
 	            // otherwhise). The new start can't be after the end (W3C says it can).
 	            // So, let's create a new range and collapse it to the desired point.
 	            if ( e.toString().Contains( 'NS_ERROR_ILLEGAL_VALUE' ) )
 	            {
 	            	this.oRange.collapse( true ) ;
 	                oDocRange.setEnd( oRange.endContainer, oRange.endOffset ) ;
 	            }
 	            else
 	            	throw( e ) ;
 	        }
 	        var oSel = this._window.getSelection() ;
 	        oSel.removeAllRanges() ;
 	        // We must add a clone otherwise Firefox will have rendering issues.
 	        oSel.addRange( oDocRange ) ;
			this._range = oDocRange;
		} else {
			this._range.select();
		}
	},
	duplicate: function () {
		if (this._rangeType == 'W3C') {
			return new ncBPFramework.Selection.Range(this._window, this._range.cloneRange());
		} else {
			return new ncBPFramework.Selection.Range(this._window, this._range.duplicate());
		}
	},
	_isWhiteSpace: function(ch) {
		var re = new RegExp(/\B/);
		return re.test(ch);
	},
	expand: function () {
		if (this._rangeType == 'W3C') {
			var r = this._range.cloneRange();
			// 1. (if Text node) Reduce startOffset until it = position of whitespace+1
			if (r.startContainer.nodeType == 3 && r.startOffset>0) {
				var txt=r.startContainer.nodeValue;
				var i = r.startOffset;
				while (i>0&&!this._isWhiteSpace(txt.charAt(i-1))) {
					i--;
				}
				// 2. setStart
				r.setStart(r.startContainer, i);
			}
			// 3. Increase endOffset until it = position of whitespace-1
			if (r.endContainer.nodeType == 3) {
				var txt=r.endContainer.nodeValue;
				var i = r.endOffset;
				while (i<txt.length&&!this._isWhiteSpace(txt.charAt(i))) {
					i++;
				}
				// 4. setEnd
				r.setEnd(r.endContainer, i);
			}
			this._range = r;
		} else {
			this._range.expand('word');
		}
	},
	getText: function() {
		if (this._rangeType == 'W3C'){
			return this._range.toString();
		} else {
			return this._range.text;
		}
	},
	setText: function(text) {
		if (this._rangeType == 'W3C') {
			var txtNode = this._document.createTextNode(text);
			this._range.deleteContents();
			this._range.insertNode(txtNode);
		} else {
			this._range.text = text;
		}
	},
	getHtmlText: function() {
		if (this._rangeType == 'W3C') {
			var clonedSelection = this._range.cloneContents();
			var div = this._document.createElement('div');
			div.appendChild(clonedSelection);
			return div.innerHTML;
		} else {
			return this._range.htmlText;
		}
	},
	setHtmlText: function(html) {
		if (this._rangeType == 'W3C') {
			var range = this._range;
			var p=document.createElement("htmlSection");
			p.innerHTML=html;
			range.deleteContents();
			range.insertNode(p) 
		} else {
			this._range.htmlText = html;
		}
	},
	findText: function(searchString, searchElem, wholeWord,caseSensitive) {
		return this._performSingleSearch(searchString, searchElem,wholeWord,caseSensitive);
	},

/* performMultSearch / performSingleSearch / findTypeNodes
 * The JavaScript Source!! http://javascript.internet.com
 * Created by: Robin Winslow | http://www.robinwinslow.me.uk */
/******************************************************************
 * Keep for potential future use
 * 
// Find and select all text in the document that matches the "value" of the element passed
// If no element if passed, it will attempt to use the value of "this"
// Only works in a browser that supports the "window.getSelection" method, as other selection methods don't support multiple selections
_performMultiSearch: function (elem,searchElem) {
    // set up variables
    var searchString; // Will hold the text to search for
    var theSelection; // Will hold the document's selection object
    var textNodes; // Will hold all the text nodes in the document
    
    // Set it to search the entire document if we haven't been given an element to search
    if(!searchElem || typeof(searchElem) == 'undefined') searchElem = document.body;
    
    // Get the string to search for
    if(elem && elem.value) searchString = elem.value;
    else if(this && this.value) searchString = this.value;
    
    // Get all the text nodes in the document
    textNodes = this._findTypeNodes(searchElem,3);
    
    // Get the selection object
    if(this._window.getSelection) theSelection = this._window.getSelection(); // firefox
    else { // some other browser - doesn't support multiple selections at once
        alert("sorry this searching method isn't supported by your browser");
        return;
    }
    
    // Empty the selection
    theSelection.removeAllRanges(); // We want to empty the selection regardless of whether we're selecting anything
    
    if(searchString.length > 0) { // make sure the string isn't empty, or it'll crash.
        // Search all text nodes
        for(var i = 0; i < textNodes.length; i++) {
            // Create a regular expression object to do the searching
            var reSearch = new RegExp(searchString,'gmi'); // Set it to 'g' - global (finds all instances), 'm' - multiline (searches more than one line), 'i' - case insensitive
            var stringToSearch = textNodes[i].textContent;
            while(reSearch(stringToSearch)) { // While there are occurrences of the searchString
                // Add the new selection range
                var thisRange = this._document.createRange();
                thisRange.setStart(textNodes[i],reSearch.lastIndex - searchString.length); // Start node and index of the selection range
                thisRange.setEnd(textNodes[i],reSearch.lastIndex); //  End node and index of the selection
                theSelection.addRange(thisRange); // Add the node to the document's current selection
            }
        }
    }
    
    return;
},
*******************************************************************************/

// Will find and select the first instance of the srchValue, then when called again will move on to the next instance
	_performSingleSearch: function (srchValue,searchElem,wholeWord,caseSensitive) {
		// set up variables
	    var searchString; // Will hold the text to search for
	    var theSelection; // Will hold the document's selection object
	    var textNodes; // Will hold all the text nodes in the document
	    
	    // Set it to search the entire document if we haven't been given an element to search
	    if(!searchElem || typeof(searchElem) == 'undefined') searchElem = this._document.body;
	    
	    // Get the string to search for
	    searchString = srchValue;
	    
	    if(searchString && searchString.length > 0) { // make sure the string isn't empty, or it'll crash.
	        if(this._window.getSelection) { // Firefox
	            // Get the selection
	            theSelection = this._window.getSelection();
	    
	            // Get all the text nodes in the document
	            textNodes = this._findTypeNodes(searchElem,3);
	            
	            // If there's already a selection, and it's the string we're searching for
				var searchMatch = null;
				if (caseSensitive) {
					searchMatch = new RegExp(searchString,'');
				} else {
					searchMatch = new RegExp(searchString,'i');
				}
	            if(theSelection.rangeCount == 1 && searchMatch(theSelection.getRangeAt(0).toString())) {
	                var currentRange = theSelection.getRangeAt(0);
	                theSelection.removeAllRanges();
	                
	                var newRange = null;
	                var reSearch = null;
					var reFlags = 'gm';
					if (caseSensitive) {
						reFlags+='i';
					}
					if (wholeWord) {
						reSearch = new RegExp('(?=\\b)'+searchString+'(?=\\b)',reFlags);
					} else {
						reSearch = new RegExp(searchString,reFlags);
					}
					
	                // Move on to the next occurrence of it by iterating through text nodes...:
	                for(var i = 0; i < textNodes.length; i++) {
	                    // If this text node is before the currentRange, ignore it and carry on to the next one
	                    if(currentRange.comparePoint(textNodes[i],0) == -1 && currentRange.startContainer != textNodes[i]) continue;
	                    // If this text node is the same as the currentRange, find the point in the currentRange
	                    else if((currentRange.comparePoint(textNodes[i],0) == -1 && currentRange.startContainer == textNodes[i]) || (currentRange.comparePoint(textNodes[i],0) == 0)) {
	                        // Create a regular expression object to do the searching
	                        var stringToSearch = textNodes[i].textContent;
	                        while(reSearch(stringToSearch)) { // While there are occurrences of the searchString
	                            // Test if the index is after the currentRange's position
	                            if(reSearch.lastIndex - searchString.length > currentRange.startOffset) {
	                                // This is the new search position - empty the old selection and add the new selection range
	                                theSelection.removeAllRanges();
	                                newRange = this._document.createRange();
	                                newRange.setStart(textNodes[i],reSearch.lastIndex - searchString.length); // Start node and index of the selection range
	                                newRange.setEnd(textNodes[i],reSearch.lastIndex); //  End node and index of the selection
	                                break; // We're not interested in the other results, so break out of this while loop.
	                            }
	                        }
	                        if(newRange) break; // If we found a new range, break out of this for loop, cos there's nothing more to do.
	                        else continue; // Otherwise continue
	                    } 
	                    // If this text node is after the current one, search to see if it has any occurrences of the searchString
	                    else if(currentRange.comparePoint(textNodes[i],0) == 1) {
	                        // Create a regular expression object to do the searching
	                        var stringToSearch = textNodes[i].textContent;
	                        // If we had a find, use it
	                        if(reSearch(stringToSearch)) {
	                            // This is the new search position - empty the old selection and add the new selection range
	                            theSelection.removeAllRanges();
	                            newRange = this._document.createRange();
	                            newRange.setStart(textNodes[i],reSearch.lastIndex - searchString.length); // Start node and index of the selection range
	                            newRange.setEnd(textNodes[i],reSearch.lastIndex); //  End node and index of the selection
	                            break; // We're not interested in the other results, so break out of this while loop.
	                        } else continue;
	                    }
	                }
	                
	                if(newRange) {
	                    theSelection.addRange(newRange); // Add the node to the document's current selection
	                    // Make the new range visible
	                    newRange.startContainer.parentNode.scrollIntoView(false);
						this._range = newRange;
	                    return;
	                } else performSingleSearch(elem,searchElem);
	            }
	            
	            // If we don't already have a selection, just find the first instance
	            else {
	                // Search all text nodes
	                for(var i = 0; i < textNodes.length; i++) {
	                    // Create a regular expression object to do the searching
	                    var reSearch = new RegExp(searchString,'gmi'); // Set it to 'g' - global (finds all instances), 'm' - multiline (searches more than one line), 'i' - case insensitive
	                    var stringToSearch = textNodes[i].textContent;
	                    if(reSearch(stringToSearch)) { // If there are occurrences of the searchString
	                        // This is the new search position - empty the old selection and add the new selection range
	                        theSelection.removeAllRanges();
	                        // Add the new selection range
	                        var thisRange = this._document.createRange();
	                        thisRange.setStart(textNodes[i],reSearch.lastIndex - searchString.length); // Start node and index of the selection range
	                        thisRange.setEnd(textNodes[i],reSearch.lastIndex); //  End node and index of the selection
	                        theSelection.addRange(thisRange); // Add the node to the document's current selection
	                        thisRange.startContainer.parentNode.scrollIntoView(false);
							this._range = thisRange;
	                        break; // We're done
	                    }
	                }
	            }
	        }
	        else if(this._document.selection) { // Internet Explorer
	            theSelection = this._document.selection;
	            var currentRange = theSelection.createRange();
	            var srchFlags = 0;
				if (wholeWord) {
					srchFlags += 2;
				}
				if (caseSensitive){
					srchFlags += 4;
				}
				
	            // Create text range to cover the whole of the searchElem
	            var searchRange = this._document.body.createTextRange();
	            this._range = searchRange;
				searchRange.moveToElementText(searchElem);
	            
	            // Empty the current selection
	            theSelection.empty();
	            // If this text is already selected, find the next selection
// --NN--	            if(currentRange && currentRange.text && currentRange.text.match(eval('/'+searchString+'/i'))) {
	            if(currentRange && currentRange.text ) {
	                // Move start position of range past this word
	                currentRange.moveStart('character');
	                // Move end position to end of the search element
	                currentRange.setEndPoint('EndToEnd',searchRange);
	                // Search again
	                if(currentRange.findText(searchString,0,srchFlags)) {
	                    // We have found another instance
	                    try{
	                    	currentRange.select();
	                    	this._range = currentRange;
	                    	return true;
	                    } catch(e) {
	                    	alert(e)
	                    }
	                } else performSingleSearch(elem,searchElem);
	            } else {
	                // Find the first occurrence of the searchString
	                if(searchRange.findText(searchString,0,srchFlags)) {
	                    // If we've found something, select it
	                    setTimeout('window.focus()',1000);	                    
											try{
												searchRange.select()
											}catch(e){
												alert(e);
											};
											return true; 
	                } else {
	                    return false;
	                }
	            }
	        } else alert("Sorry your browser doesn't support a supported selection object"); 
	    }
	},
// Recursively find all text nodes within an element
	_findTypeNodes: function (elem,type) {
	    // Remove superfluous text nodes and merge adjacent text nodes
	    elem.normalize();
	    
	    var typeNodes = new Array();
	    // Search all children of this element to see which ones are the right type of node
	    for(var nodeI = 0; nodeI < elem.childNodes.length; nodeI++) {
	        if(elem.childNodes[nodeI].nodeType == type) typeNodes.push(elem.childNodes[nodeI]); // If it is a the right type of node, add it to the array
	        else {
	            // If not a the right type of node, search it in turn
	            typeNodes = typeNodes.concat(this._findTypeNodes(elem.childNodes[nodeI],type));
	        }
	    }
	    return typeNodes; // return the array
	},
	moveEnd: function(endOffset) {
		if (this._rangeType == 'W3C') {
			this._range.setEnd(this._range.endContainer, this._range.endOffset+endOffset);
		} else {
			this._range.moveEnd('character', endOffset);
		}
	},
	pasteHTML: function(html) {
		// expected input: <tagName attributes.....>html</tagName>
		// e.g: <a href="...">innerHTML</a>
		/* Regular Expressions:
		 * 1) Find start tagName: (<[^ ]*)/gi
		 * 2) Find attributes string <[^ ]*([^>]*)>/i
		 * 
		 * split after first > gives innerHTML+closingTag 
		 * 		split at index length-length of closingTag gives innerHTML
		 * 
		 * leftOf first > gives opening tag
		 */
		//var tagName = fullTagHTML.replace(/<([^ ]*)[\S\s]*/i, '$1');
		//var attrStr = fullTagHTML.replace(/<([^ ]*)([^>]*)/i, "($2)")
		//var content = eval("fullTagHTML.replace(/<([^ ]*)([^>]*)>([\\S\\s]*)<\\/"+tagName+">/i, \"$3\")")
		if (this._rangeType == 'W3C') {
			var random_string = "insert_html_" + Math.round(Math.random()*100000000);
			this._document.execCommand("insertimage",false, random_string);
			var pat = new RegExp("<[^<]*" + random_string + "[^>]*>");
			var current_html = this._document.body.innerHTML = this._document.body.innerHTML.replace(pat, html);
		} else {
			this._range.pasteHTML(html);
		}
	},
	pasteHTML_A: function(tagName,html,attributes) {
		if (this._rangeType == 'W3C') {
			var newElem = document.createElement(tagName);
			if (attributes) {
				for (var i=0;i<attributes.length;i++) {
					newElem.setAttribute(attributes[i].name,attributes[i].value);
				}
			}
			newElem.innerHTML = html;
			this._range.insertNode(newElem);

		} else {
			var str = '<'+tagName;
			if (attributes) {
				for (var i=0;i<attributes.length;i++) {
					str += ' '+attributes[i].name+'="'+attributes[i].value+'"';
				}
			}
			str += '>'+html+'</'+tagName+'>';
			this._range.pasteHTML(str);
		}
	},
	clear: function() {
		if (this._rangeType == 'W3C') {
			this._range.deleteContents();
		} else {
			this._range.pasteHTML('');
		}
	},
	queryCommandValue: function(cmd) {
		if (this._rangeType == 'W3C') {
			return this._document.queryCommandValue(cmd);
		} else {
			return this._range.queryCommandValue(cmd);
		}
	},
	queryCommandEnabled: function(cmd) {
		if (this._rangeType == 'W3C') {
			return this._document.queryCommandEnabled(cmd);
		} else {
			return this._range.queryCommandEnabled(cmd);
		}
	},	
	collapse: function (toStart) {
		this._range.collapse(toStart);
	},
	setEndPoint: function (str) {
		if (this._rangeType == 'W3C') {
			return;
		} else {
			this._range.setEndPoint(str, this._range);
		}
	}
});
