/**
 * Имя пространства, объединяющего набор функций.
 * @id ra
 */
ra = {
  'event':
  /**
   * Функции работы с событиями.
   * @id ra.event
   */
  {
    'attach': document.addEventListener ?
    /**
     * Подключает обработчик к событию
     * @param {Function} сallback функция, вызываемая при событии.
     * @param {String} eventType тип события.
     * @param {Object} rootEventNode узел, к которому подключаем событие.
     * @return {Function} функция - обработчик события, используется для отключения обработчика от события.
     * @id ra.event.attach
     */
    function(callback, eventType, rootEventNode){
      var handler = function(e){
        callback(e.target, {'x': e.pageX, 'y': e.pageY}, e) === false && e.preventDefault()
      };
      rootEventNode.addEventListener(eventType, handler, false);
      return handler
    }:
    function(callback, eventType, rootEventNode){
      var handler = function(){
        var e = window.event,
        t = document.documentElement,
        o = {'X':'Left','Y':'Top'},
        pos = {}, k, scroll = 'scroll';
        for (k in o) {
          pos[k] = e['client'+k]+t[scroll+o[k]]
        }
        if (t = document.body) for (k in o) {
          pos[k] += t[scroll+o[k]]
        };
        callback(e.srcElement || document, pos, e) === false && (e.returnValue = false)
      };
      rootEventNode.attachEvent('on' + eventType, handler);
      return handler
    },
    'detach': document.addEventListener ?
    /**
     * Отключает обработчик от события
     * @param {Function} handler функция - обработчик.
     * @param {String} eventType тип события.
     * @param {Object} rootEventNode узел, к которому подключено событие.
     * @id ra.event.detach
     */
    function(handler, eventType, rootEventNode){
      rootEventNode.removeEventListener(eventType, handler, false)
    }:
    function(handler, eventType, rootEventNode){
      rootEventNode.detachEvent('on'+eventType, handler)
    },
    'Watcher':
    /**
     * Конструктор "Наблюдателя", ищущего по определенным критериям,
     * в принявшем событие элементе и его родителях, элемент, который хотим обработать.
     * <ul>
     * <li>вызывает <i>функцию обработки внутреннего события</i>, передавая ей найденный элемент и координаты события на странице</li>
     * <li>если событие свершилось вне интересующего элемента, вызывается <i>функция обработки внешнего события</i>.</li>
     * <li>если функция обработки возвращает "ложь", обыкновенная реакция браузера на событие отменяется.</li>
     * </ul>
     * @param {String} eventType тип наблюдаемого события
     * @param {Array} aNodeSelectors массив функций - критериев отбора,<br>
     * определяющих было ли событие на наблюдаемом узле.
     * @param {Function} intoSelectorsMethod метод, вызываемый при событии на наблюдаемом узле.
     * @param {Function} outSelectorsMethod метод, вызываемый при событии вне наблюдаемого узла.
     * @id ra.event.Watcher
     */
    function(eventType, aNodeSelectors, intoSelectorsMethod, outSelectorsMethod){
      var thisObj = this, callback = function(currentNode, pos, e){
        var k = 0, selectedNode = currentNode;
        do {
          if (aNodeSelectors[k](currentNode)) {
            !k && (selectedNode = currentNode);
            if ((++k) == aNodeSelectors.length) {
              if (intoSelectorsMethod) {
                return thisObj[intoSelectorsMethod](selectedNode, pos, e)
              }
              return
            }
          }
        }
        while (currentNode = currentNode.parentNode);
        if (outSelectorsMethod) {
          return thisObj[outSelectorsMethod](selectedNode, pos, e)
        }
        return
      },
      handler;
      (this.attach = function(){
        handler = ra.event.attach(callback, eventType, document)
      })();
      this.detach = function(){
        ra.event.detach(handler, eventType, document);
      }
    },
    'Selector':
    /**
     * Конструктор "Критериев отбора".
     * Создает объект из которого строится функция
     * поиска от верхнего узла к корневому узлу заданной последовательности элементов,
     * запоминая нужные элементы.
     * @id ra.event.Selector
     */
    function(){
      this.order = [];
    }
  },
  'getAttr':
  /**
   * Возвращает значение атрибута указанного узла,<br>
   * корректирует работу IE.
   * @param {HTMLNode} oNode узел, атрибут которого нужно прочесть.
   * @param {String} attrName название атрибута.
   * @return {String} идентификатор узла
   * @id ra.getAttr
   */
  function(oNode, attrName){
    var getAttr='getAttribute', getAttrNode=getAttr+'Node';
    return oNode.nodeType == 1 && oNode[getAttr](attrName, 2)||((oNode[getAttrNode]&&(oNode=oNode[getAttrNode](attrName)))?oNode.nodeValue:'')
  }
};
ra.event.Selector.prototype = {
  /*
   * Кирпичики критериев отбора
   */
  'attrEq':
  /**
   * Текущий элемент должен иметь заданное значение в указанном атрибуте
   * @param {String} name название атрибута
   * @param {String} value значение, которое должно быть у атрибута
   * @id ra.event.Selector.prototype.attrEq
   */
  function(name, value){
    var obj = this;
    obj.order[obj.order.length] = 'attrEq_';
    obj.order[obj.order.length] = arguments;
    return this
  },
  'attrEq_': function(){
    var a = this.order;
    a = a[++a.current];
    return ra.getAttr(obj.currentNode, a[0]) == a[1];
  },
  'attrContain':
  /**
   * Текущий элемент должен содержать значение в указанном атрибуте
   * @param {String} name название атрибута
   * @param {String} value значение, которое должно содержаться в атрибуте
   * @id ra.event.Selector.prototype.attrContain
   */
  function(name, value){
    var obj = this;
    obj.order[obj.order.length] = 'attrContain_';
    obj.order[obj.order.length] = arguments;
    return this
  },
  'attrContain_': function(){
    var a = this.order;
    a = a[++a.current];
    return (' '+ra.getAttr(this.currentNode, a[0])+' ').indexOf(' '+a[1]+' ') != -1
  },
  'hasParentNode':
  /**
   * Название родительского элемента, у текущего, должно иметь заданное значение.
   * @param {String} name - название родительского элемента (elment.nodeName)
   * @id ra.event.Selector.prototype.hasParentNode
   */
  function(name){
    var obj = this;
    obj.order[obj.order.length] = 'hasParentNode_';
    order[order.length] = name;
    return this
  },
  'hasParentNode_': function(){
    var obj = this,
    o = obj.order,
    name = o[++o.current];
    return (obj.currentNode = obj.currentNode.parentNode) &&
    obj.currentNode.nodeName == name
  },
  'store':
  /**
   * Текущий элемент сохраняет в стек найденных элементов.
   * @id ra.event.Selector.prototype.store
   */
  function(){
    var order = this.order;
    order[order.length] = 'store_';
    return this
  },
  'store_': function(){
    var a = this.storedNodes,
    b = a[a.length - 1];
    b[b.length] = this.currentNode;
    return true
  },
  'hasAncestorNode':
  /**
   * Название какого-либо предка, у текущего элемента, должно иметь заданное значение.
   * @param {String} name - название прародительского элемента (elment.nodeName)
   * @id ra.event.Selector.prototype.hasAncestorNode
   */
  function(name){
    var order = this.order;
    order[order.length] = 'hasAncestorNode_';
    order[order.length] = name;
    return this
  },
  'hasAncestorNode_': function(){
    var obj = this;
    if (obj.currentNode = obj.currentNode.parentNode){
      return obj.hasAnyNode_()
    }
    obj.order.current++;
    return false
  },
  'hasAnyNode':
  /**
   * Название у предка или у текущего элемента, должно иметь заданное значение.
   * @param {String} name - название элемента (elment.nodeName)
   * @id ra.event.Selector.prototype.hasAnyNode
   */
  function(name){
    var order = this.order;
    order[order.length] = 'hasAnyNode_';
    order[order.length] = name;
    return this
  },
  'hasAnyNode_': function(){
    var obj = this,
    node = obj.currentNode,
    a = obj.order,
    orderIndex = ++a.current,
    name = a[orderIndex++],
    isNotFound = true,
    i,
    getAll = obj.getAll,
    lastStoredNodes = obj.storedNodes,
    t = lastStoredNodes[lastStoredNodes.length-1];
    lastStoredNodes.length--;
    do {
      if (node.nodeName == name) {
        if (a = obj.getPassed(node, getAll, orderIndex)) {
          for (i = a.length; i--;){
            lastStoredNodes[lastStoredNodes.length] = t.concat(a[i])
          }
          isNotFound = false
        }
      }
    } while ((node = node.parentNode) && (isNotFound || getAll) )
    obj.order.current = obj.order.length
    return !isNotFound && (obj.storedNodes = lastStoredNodes)
  },
  'getPassed':
  /**
   * Возвращает массив найденных совпадений,
   * элементы которого, стек сохраненных элементов.<br>
   * Если совпадения не найдены возвращает ложь. 
   * @param {Object} currentNode элемент от которого начинаем искать совпадения.
   * @param {Boolean} [getAll] если установлен в true, будут искаться все совпадения, иначе поиск прекратится на первом найденном совпадении.
   * @param {Integer} [orderIndex] (для внутреннего использования, <b>не устанавливать</b>)<br>
   * задает позицию текущей последовательности в списке критериев.
   * @id ra.event.Selector.prototype.getPassed
   */
  function(currentNode, getAll, orderIndex){
    this.currentNode = currentNode;
    this.getAll = getAll || false;
    this.order.current = orderIndex || 0;
    (this.storedNodes = [])[0]=[];
    for (
      var o = this.order,l = o.length;
      l > o.current;
      o.current++
    ) {
      if (!this[o[o.current]]()) return false
    }
    return this.storedNodes
  }
}
