/* ***** BEGIN LICENSE BLOCK *****
 * Licensed under Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * Full Terms at http://mozile.mozdev.org/0.8/LICENSE
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Jorgen Horstink and David Kingma's code.
 *
 * The Initial Developers of the Original Code are Jorgen Horstink and David Kingma.
 * Portions created by the Initial Developer are Copyright (C) 2005-2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *	James A. Overton <james@overton.ca>
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * @fileoverview Adapts Internet Explorer's Selection object to an interface like Mozilla's.
 * <p>History: The original code was written by Jorgen Horstink (http://jorgenhorstink.nl/2006/03/11/w3c-range-in-internet-explorer/).
 * It was extensively modified by David Kingma.
 * This version has been adapted for use with Mozile by James A. Overton.
 * Key changes include wrapping the objects in the Mozile namespace, so as to minimize the impact on other scripts in the same page.
 *
 * @link http://mozile.mozdev.org 
 * @author James A. Overton <james@overton.ca>
 * @version 0.8
 * $Id: InternetExplorerSelection.js,v 1.4 2008/02/20 19:08:00 jameso Exp $
 */

mozile.require("mozile.dom");
mozile.require("mozile.xpath");
mozile.require("mozile.dom.InternetExplorerRange");
mozile.provide("mozile.dom.InternetExplorerSelection");


/**
 * Selection object, based on the mozilla implementation.
 * See http://www.xulplanet.com/references/objref/Selection.html
 * @constructor
 */
mozile.dom.InternetExplorerSelection = function() {
	/**
	 * A reference to IE's native selection object.
	 * @private
	 * @type Selection
	 */
	this._selection = mozile.document.selection;

	/**
	 * The node in wich the selection begins.
	 * @type Node
	 */
	this.anchorNode = null; 

	/**
	 * The number of characters that the selection's anchor is offset within the ancherNode.
	 * @type Integer
	 */
	this.anchorOffset = null; 

	/**
	 * The node in which the selection ends.
	 * @type Node
	 */
	this.focusNode = null; 

	/**
	 * The number of characters that the selection's focus is offset within the focusNode.
	 * @type Integer
	 */
	this.focusOffset = null;

	/**
	 * A boolean indicating whether the selection's start and end points are at the same position.
	 * @type Boolean
	 */
	this.isCollapsed = null;

	/**
	 * The number of ranges in the selection.
	 * @type Integer
	 */
	this.rangeCount = 0;

	/**
	 * The direction of the selection. Can be Left-to-Right or Right-to-Left, using coded integers.
	 * @private
	 * @type Integer
	 */
	this._direction = this._LTR;
	
	// Initialize the properties.
	this._init();
}

/**
 * Code indicating the selection direction. Left-to-right.
 * @private
 * @type Integer
 */
mozile.dom.InternetExplorerSelection.prototype._LTR = 1;

/**
 * Code indicating the selection direction. Right-to-left.
 * @private
 * @type Integer
 */
mozile.dom.InternetExplorerSelection.prototype._RTL = -1;

/**
 * Returns a range object representing one of the ranges currently selected.
 * @private
 * @param {Range} range Optional. A range object to use rather than using the getRangeAt(0).
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype._init = function(range) {
	this._selection = mozile.document.selection;

	if(!range) range = this.getRangeAt(0);
	
	if(!this._direction) this._direction = this._LTR;
	
	if(range && range.startContainer) {
		if(this._direction == this._LTR) {
			this.anchorNode = range.startContainer; 
			this.anchorOffset = range.startOffset; 
			this.focusNode = range.endContainer;
			this.focusOffset = range.endOffset;
		}
		else {
			this.anchorNode = range.endContainer; 
			this.anchorOffset = range.endOffset; 
			this.focusNode = range.startContainer;
			this.focusOffset = range.startOffset;
		}
		this.isCollapsed = range.collapsed;
	}
	else {
		this.anchorNode = null; 
		this.anchorOffset = null; 
		this.focusNode = null;
		this.focusOffset = null;
		this.isCollapsed = true;
	}
	//this.currentRange = range;
	this.rangeCount = 1; //this._selection.createRangeCollection().length;
}

/**
 * Returns a range object representing one of the ranges currently selected
 * @param {Integer} index The index of the desired range.
 * @type Range
 */
mozile.dom.InternetExplorerSelection.prototype.getRangeAt = function (index) {
	// NOTE: Replaced createRangeCollection().item(index) with createRange()
	// to avoid a bug when mozile.document is a different document.

	/*
	range = new mozile.dom.InternetExplorerRange(this._selection.createRange());
	range._init();
	return range;
	*/

	// Attempted optimization:
	var textRange = this._selection.createRange().duplicate();
	var range;
	// Trying to cache range, but it's too slow.
	if(this._lastTextRange && this._lastTextRange.isEqual(textRange)) {
		range = this._lastRange;
	}
	else {
		range = new mozile.dom.InternetExplorerRange(textRange);
		range._init();
		this._lastTextRange = textRange.duplicate();
		this._lastRange = range;
		this._direction = this._LTR;
	}

	return range.cloneRange();
}

/**
 * Collapses the current selection to a single point. 
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.collapse = function (parentNode, offset) {
	var range = new mozile.dom.InternetExplorerRange(
		parentNode.ownerDocument.body.createTextRange());
	range.setStart(parentNode, offset);
	range.setEnd(parentNode, offset);
	range.collapse(false);
	this._direction = this._LTR;
	//alert("Collapsed 1 "+ mozile.util.dumpValues(range.store()));
	// Store the values first, then select the TextRange.
	this._init(range); 
	//alert("Collapsed 2 "+ mozile.util.dumpValues(this.store()));
	this._lastTextRange = range._range.duplicate();
	this._lastRange = range;
	range._range.select();
	//alert("Collapsed 3 "+ mozile.util.dumpValues(this.store()));
}

/**
 * Moves the focus of the selection to a specified point.
 * Somewhat tricky because the direction of the extension has to be accounted for.
 * @param {Node} parentNode The new focusNode.
 * @param {Integer} offset The new focusOffset.
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.extend = function (parentNode, offset) {
	var range = this.getRangeAt(0);
	var direction;

	// Compare offsets in the same node.
	if(parentNode == range.startContainer && 
		range.startOffset <= offset) {
		direction = this._LTR;
	}
	else if(parentNode == range.endContainer && 
		range.endOffset >= offset){
		direction = this._RTL;
	}

	// Compare offsets in the same container.
	// Suppose the startContainer/endContainer is a text node.
	else if(parentNode == range.startContainer.parentNode &&
	  range.startContainer.nodeType == mozile.dom.TEXT_NODE &&
	  mozile.dom.getIndex(range.startContainer) <= offset) {
	  direction = this._LTR;
	}
	else if(parentNode == range.endContainer.parentNode &&
	  range.endContainer.nodeType == mozile.dom.TEXT_NODE &&
	  mozile.dom.getIndex(range.endContainer) >= offset) {
	  direction = this._RTL;
	}

	// Use a TreeWalker to the parentNode relative to the anchorNode.
	else {
		var ancestor = mozile.dom.getCommonAncestor(parentNode, range.commonAncestorContainer)
		var treeWalker = document.createTreeWalker(ancestor, mozile.dom.NodeFilter.SHOW_ALL, null, false);
		treeWalker.currentNode = this.anchorNode;
		while(treeWalker.previousNode()) {
			if(treeWalker.currentNode == parentNode) {
				direction = this._RTL;
				break;
			}
		}
		if(!direction) direction = this._LTR;
	}
	
	// Extend the selection.
	if(direction == this._LTR) range.setEnd(parentNode, offset);
	else if(direction == this._RTL) range.setStart(parentNode, offset);
	else return;
	
	//alert("Extend "+ this._direction +"\nFocus "+ this.focusOffset +"\n"+  mozile.util.dumpValues(range.store()));
	this._direction = direction;
	this._init(range);
	this._lastTextRange = range._range.duplicate();
	this._lastRange = range;
	range._range.select();
}

/**
 * Moves the focus of the selection to the same point as the anchor.
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.collapseToStart = function () {
	var range = this.getRangeAt(0);
	range.collapse(true);
	this._init(range);
	range._range.select();
}

/**
 * Moves the anchor of the selection to the same point as the focus. The focus does not move.
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.collapseToEnd = function () {
	var range = this.getRangeAt(0);
	range.collapse();
	this._init(range);
	range._range.select();
}

/**
 * Adds all the children of the specified node to the selection.
 * @param {Node} parentNode
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.selectAllChildren = function (parentNode) {
	var range = this.getRangeAt(0);
	range.selectNodeContents(parentNode);
	this._init(range);
	range._range.select();
}

/**
 * A range object that will be added to the selection.
 * @param {Range} range The range to add.
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.addRange = function (range) {
	this._direction = this._LTR;
	this._init(range);
	range._range.select();
}

/**
 * Removes a range from the selection.
 * @param {Range} range The range to remove.
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.removeRange = function (range) {
	range.collapse();
	this._init(range);
}

/**
 * Removes all ranges from the selection.
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.removeAllRanges = function () {
	this._selection.empty();
	this._init();
}

/**
 * Deletes the selection's content from the document.
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.deleteFromDocument = function () {
	this._selection.clear();
	this._init();
}

/**
 * NOT IMPLEMENTED. Changes the selection direction from Left-to-Right to Right-to-Left.
 * @param {Boolean} langRTL True indicates Right-to-Left, false indicates Left-to-Right.
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.selectionLanguageChange = function () {}

/**
 * Returns a string currently being represented by the selection object, i.e. the 
 * currently selected text, no markup
 * @type String
 */
mozile.dom.InternetExplorerSelection.prototype.toString = function () {
	var range = this.getRangeAt(0);
	return range.toString();
}

/**
 * NOT IMPLEMENTED. Indicates if a certain node is part of the selection.
 * @param {Node} aNode
 * @param {Boolean} aPartlyContained
 * @type Boolean
 */
mozile.dom.InternetExplorerSelection.prototype.containsNode = function (aNode, aPartlyContained) {
	alert('mozile.dom.InternetExplorerSelection.containsNode() is not implemented yet');
}



/**
 * Store the details about this range in an object which can be used to restore the range.
 * @type Object
 */
mozile.dom.InternetExplorerSelection.prototype.store = mozile.dom.Selection.prototype.store;

/**
 * Takes a stored range object and creates a new range with the same properties.
 * @param {Object} state A state object from Selection.store() or Range.store().
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.restore = mozile.dom.Selection.prototype.restore;

/**
 * Scroll the window to the current selection.
 * @type Void
 */
mozile.dom.InternetExplorerSelection.prototype.scroll = mozile.dom.Selection.prototype.scroll;


/**
 * Restore the give range as the current selection (only allows for a single range).
 * @param {Range} r The range to restore.
 * @type Void
 */
mozile.dom.Selection.restoreSelection = function (r) {
	var range = new mozile.Range();
	try {
		range.setStart(r.startContainer, r.startOffset);
		range.setEnd(r.endContainer, r.endOffset);
	} catch(e) {}

	// Restore the selection.
	var s = new mozile.dom.Selection();
	s.removeAllRanges();
	s.addRange(range);
}




