﻿function AbstractRange() {
  this.fatal = false;

  this.startContainer          = null;
  this.startOffset             = null;
  this.endContainer            = null;
  this.endOffset               = null;
  this.collapsed               = true;
  this.commonAncestorContainer = null;

  // compare how
  this.START_TO_START = 0;
  this.START_TO_END   = 1;
  this.END_TO_END     = 2;
  this.END_TO_START   = 3;
}

AbstractRange.prototype = {
  setStart              :function(refNode, offset) {}, // implemented
  setEnd                :function(refNode, offset) {}, // implemented
  setStartBefore        :function(refNode) {}, // not implemented yet
  setStartAfter         :function(refNode) {}, // not implemented yet
  setEndBefore          :function(refNode) {}, // not implemented yet
  setEndAfter           :function(refNode) {}, // not implemented yet
  collapse              :function(toStart) {}, // implemented
  selectNode            :function(refNode) {}, // not implemented yet
  selectNodeContents    :function(refNode) {}, // not implemented yet

  compareBoundaryPoints :function(how, sourceRange) {}, // not implemented yet
  deleteContents        :function() {}, // not implemented yet
  extractContents       :function() {}, // not implemented yet
  cloneContents         :function() {}, // not implemented yet
  insertNode            :function(newNode) {}, // not implemented yet
  surroundContents      :function(newParent) {}, // not implemented yet
  cloneRange            :function() {}, // not implemented yet
  toString              :function() {}, // not implemented yet
  detach                :function() {} // not implemented yet
}

InternetExplorerRange = AbstractRange.extend(
  function() {
    this.className = 'InternetExplorerRange';
  }, {
    setStart:function(refNode, offset) {
      try {
        if (refNode.nodeType == 3 && (offset > refNode.data.length || offset < 0)) {
          throw('Exception... "Index or size is negative or greater than the allowed amount"');
        }
        if (refNode.nodeType == 1 && (offset > refNode.childNodes.length - 1 || refNode.childNodes.length == 0)) {
          throw('Exception... "Index or size is negative or greater than the allowed amount"');
        }
        this.startContainer = refNode;
        this.startOffset    = offset;
        if (this.endContainer == null && this.endOffset == null) {
          this.endContainer = refNode;
          this.endOffset    = offset;
        }
        this.collapsed = this._collapsed();
      } catch (e) {
        this.fatal = true;
        alert(e);
      }
    },
    setEnd:function(refNode, offset) {
      try {
        if (refNode.nodeType == 3 && (offset > refNode.data.length || offset < 0)) {
          throw('Exception... "Index or size is negative or greater than the allowed amount"');
        }
        if (refNode.nodeType == 1 && (offset > refNode.childNodes.length - 1 || refNode.childNodes.length == 0)) {
          throw('Exception... "Index or size is negative or greater than the allowed amount"');
        }
        this.endContainer = refNode;
        this.endOffset    = offset;
        if (this.startContainer == null && this.startOffset == null) {
          this.startContainer = refNode;
          this.startOffset    = offset;
        }
        this.collapsed = this._collapsed();
      } catch (e) {
        this.fatal = true;
        alert(e);
      }
    },
    collapse:function(toStart) {

    },
    _collapsed:function() {
      return (this.startContainer == this.endContainer && this.startOffset == this.endOffset);
    }
  }
);

W3Range = AbstractRange.extend(
  function() {
    this.className = 'W3Range';
    this.range = document.createRange();
  }, {
    setStart:function(refNode, offset) {
      this.range.setStart(refNode, offset);
    },
    setEnd:function(refNode, offset) {
      this.range.setEnd(refNode, offset);
    }
  }
);

function Range() {
  if (document.all) {
    return new InternetExplorerRange();
  } else {
    return new W3Range();
  }
}

function Selection() {
  if (document.all) {
    return new InternetExplorerSelection();
  } else {
    return new W3Selection();
  }
}

function W3Selection() {
  this.ranges = [];
}

W3Selection.prototype = {
  addRange:function(Range) {
    this.ranges[this.ranges.length] = Range;
    window.getSelection().addRange(Range.range);
  },
  setRangeAt:function(Range, offset) {
    this.ranges[offset] = Range;
    if (this.ranges.length == 1) {
      this._addRange();
    }
  },
  removeAllRanges:function() {
    this.ranges.length = 0;
    window.getSelection().removeAllRanges();
  },
  getRangeAt:function(index) {
    return this.ranges[index];
  }
};

function InternetExplorerSelection() {
  this.ranges = [];
}

InternetExplorerSelection.prototype = {
  addRange:function(Range) {
    this.ranges[this.ranges.length] = Range;
    if (this.ranges.length == 1) {
      this._addRange();
    }
  },
  setRangeAt:function(Range, offset) {
    this.ranges[offset] = Range;
    if (this.ranges.length == 1) {
      this._addRange();
    }
  },
  removeAllRanges:function() {
    this.ranges.length = 0;
  },
  getRangeAt:function(index) {

    var r = document.selection.createRange();

    var ReturnRange = new Range();

    var LeftRange = r.duplicate(); LeftRange.collapse(true);
    LeftRange.move('Character', 1);
    LeftRange.move('Character', -1);
    LeftRange.pasteHTML('<b id=\'_range_decomposition_left_temporary\'></b>');

    var placeHolder = document.getElementById('_range_decomposition_left_temporary');
    if (placeHolder.previousSibling && placeHolder.previousSibling.nodeType == 3) {

      offset = placeHolder.previousSibling.data.length;
      placeHolder.nextSibling.data = placeHolder.previousSibling.data + placeHolder.nextSibling.data;
      placeHolder.parentNode.removeChild(placeHolder.previousSibling);

    } else {
      offset = 0;
    }
    refNode = placeHolder.nextSibling;

    placeHolder.parentNode.removeChild(placeHolder);

    ReturnRange.setStart(refNode, offset);

    var RightRange = r.duplicate(); RightRange.collapse(false);

    //RightRange.move('Character', -1);
    //RightRange.move('Character', 1);
    RightRange.pasteHTML('<b id=\'_range_decomposition_right_temporary\'></b>');

    var placeHolder = document.getElementById('_range_decomposition_right_temporary');

    // extension needed for ProcessingInstructions
    while (placeHolder.nextSibling.nodeType == 8) {
      placeHolder = placeHolder.nextSibling;
    }
    if (placeHolder.nextSibling && placeHolder.nextSibling.nodeType == 3) {

      offset = placeHolder.previousSibling.data.length;
      placeHolder.previousSibling.data = placeHolder.previousSibling.data + placeHolder.nextSibling.data;
      placeHolder.parentNode.removeChild(placeHolder.nextSibling);

    } else {
      offset = 0;
    }

    var refNode = placeHolder.previousSibling;

    placeHolder.parentNode.removeChild(placeHolder);

    ReturnRange.setEnd(refNode, offset);
    this.setRangeAt(ReturnRange, index);
    return ReturnRange;
  },
  _addRange:function() {
    var Range = this.ranges[this.ranges.length - 1];

    if (Range.fatal) { return; }
    var WindowsRangeStart = this._selectStart(Range);
    var WindowsRangeEnd   = this._selectEnd(Range);
    WindowsRangeStart.setEndPoint('EndToStart', WindowsRangeEnd);
    WindowsRangeStart.select();
    document.selection._selectedRange = Range;
  },
  _selectStart:function(Range) {
    var WindowsRange = document.body.createTextRange();
    var start  = Range.startContainer, node = start;
    var offset = Range.startOffset;

    if (start.nodeType == 3) {
      // text nodes
      var moveCharacters = offset;
      var moveToNode = null, collapse = true;
      while (node.previousSibling) {
        switch (node.previousSibling.nodeType) {
          case 1:
            // Right candidate node for moving the Range to is found
            moveToNode = node.previousSibling;
            collapse   = false;
            break;
          case 3:
            moveCharacters += node.previousSibling.data.length;
            break;
        }
        // if a right candidate is found, we escape the while
        if (moveToNode != null) {
          break;
        }
        node = node.previousSibling;
      }
      // no right candidate is found, so we select the parent node
      // of the start node (which is an Element node always, since
      // start node is a Text node).
      if (moveToNode == null) {
        moveToNode = start.parentNode;
        collapse   = true;
      }
      WindowsRange.moveToElementText(moveToNode);
      WindowsRange.collapse(collapse);
      WindowsRange.move('Character', moveCharacters);
    } else if (start.nodeType == 1) {
      // elements
      switch (Range.startContainer.childNodes.item(Range.startOffset).nodeType) {
        case 1:
        case 3:          
          Range.setStart(Range.startContainer.childNodes.item(Range.startOffset), 0);
          return this._selectStart(Range);
          break;
        default:
          alert('error');
      }
    }  

    return WindowsRange;
  },
  _selectEnd:function(Range) {
    var WindowsRange = document.body.createTextRange();
    var end  = Range.endContainer, node = end;
    var offset = Range.endOffset;

    if (end.nodeType == 3) {
      // text nodes
      var moveCharacters = end.data.length - offset;
      var moveToNode = null, collapse = false;
      while (node.nextSibling) {
        switch (node.nextSibling.nodeType) {
          case 1:
            // Right candidate node for moving the Range to is found
            moveToNode = node.nextSibling;
            collapse   = true;
            break;
          case 3:
            moveCharacters += node.nextSibling.data.length;
            break;
        }
        // if a right candidate is found, we escape the while
        if (moveToNode != null) {
          break;
        }
        node = node.nextSibling;
      }
      // no right candidate is found, so we select the parent node
      // of the start node (which is an Element node always, since
      // start node is a Text node).
      if (moveToNode == null) {
        moveToNode = end.parentNode;
        collapse   = false;
      }

      // block level elements have a closing space after collapsing
      switch (moveToNode.nodeName.toLowerCase()) {
        case 'p':
        case 'div':
        case 'h1':
        case 'h2':
        case 'h3':
        case 'h4':
        case 'h5':
        case 'h6':
        // need some extension
          moveCharacters++;
      }
//alert(moveCharacters);
      WindowsRange.moveToElementText(moveToNode);
      WindowsRange.collapse(collapse);

      WindowsRange.move('Character', -moveCharacters);

    } else if (end.nodeType == 1) {
      // elements
      switch (Range.endContainer.childNodes.item(Range.endOffset).nodeType) {
        case 3:
          var offset  = 0; //Range.endContainer.childNodes.item(Range.endOffset).data.length;
          var refNode = Range.endContainer.childNodes.item(Range.endOffset);
//alert(refNode.nodeValue);
          Range.setEnd(refNode, offset);
          return this._selectEnd(Range);
          break;
        default:
          alert('error');
      }
    }  

    return WindowsRange;
  }
};