/*jslint browser: true, white: true, undef: true, eqeqeq: true,
plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true,
strict: true */
/*global window, $ */
"use strict";


// Rambler.Avia namespace
var r_avia = r_avia || {};


// IE 6-8 does not have Array.indexOf
// From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
// This algorithm is exactly the one specified in ECMA-262, 5th edition,
// assuming Object, TypeError, Number, Math.floor, Math.abs, and Math.max have their original value.
if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
        if (this === void 0 || this === null) {
            throw new TypeError();
        }
        var t = Object(this);
        var len = t.length >>> 0;
        if (len === 0) {
            return -1;
        }
        var n = 0;
        if (arguments.length > 0) {
            n = Number(arguments[1]);
            if (n !== n) { // shortcut for verifying if it's NaN
                n = 0;
            } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
                n = (n > 0 || -1) * Math.floor(Math.abs(n));
            }
        }
        if (n >= len) {
            return -1;
        }
        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
        for (; k < len; k++) {
            if (k in t && t[k] === searchElement) {
                return k;
            }
        }
        return -1;
    }
}


// Alerts
r_avia.alert_start_search = function() {
    r_avia.log.info("Поиск билетов...");
};

r_avia.alert_not_complete_search = function() {
    r_avia.log.info("Больше искать не будем.");
};

r_avia.alert_stop_complete_search = function() {
    r_avia.log.info("Поиск билетов полностью завершен!");
};


// Helpers
r_avia.h = {};

// Constants
r_avia.h.monthNames = 'Январь,Февраль,Март,Апрель,Май,Июнь,Июль,Август,Сентябрь,Октябрь,Ноябрь,Декабрь'.split(',');
r_avia.h.monthNamesR = 'января,февраля,марта,апреля,мая,июня,июля,августа,сентября,октября,ноября,декабря'.split(',');
r_avia.h.monthNamesShort = 'Янв,Фев,Мар,Апр,Май,Июн,Июл,Авг,Сен,Окт,Ноя,Дек'.split(',');

r_avia.h.to_datetime = function(val) {
    var split = val.split(/[\-:\s]+/);
    var d = new Date(split[0], split[1] - 1, split[2], split[3], split[4], split[5]);
    return d.getDate() + " " + r_avia.h.monthNamesShort[d.getMonth()] + ", " + d.getHours() + ":" + d.getMinutes();
};

r_avia.h.to_date = function(val) {
    var split = val.split(/[\-:\s]+/);
    var d = new Date(split[0], split[1] - 1, split[2], split[3], split[4], split[5]);
    return d.getDate() + " " + r_avia.h.monthNamesR[d.getMonth()];
};

r_avia.h.to_time = function(val) {
    return val.substr(11, 5);
};

r_avia.h.to_seconds = function(val) {
    var split = val.split(/[\-:\s]+/);
    var ad = new Date(split[0], split[1] - 1, split[2], split[3], split[4], split[5]);
    return ad.getTime();
};

/*new design*/
r_avia.h.format_cost = function(n) {
    n = n.toString();
    var len = n.length;
    if (len > 3) {
        return n.substring(0, len-3)+'&nbsp;'+n.substring(len-3, len);
    } else {
        return n;
    }
}

r_avia.h.format_date = function(val) {
    var split = val.split(/[\-\s]+/);
    var d = new Date(split[0], split[1] - 1, split[2]);
    return d.getDate() + " " + r_avia.h.monthNamesShort[d.getMonth()];
}

r_avia.h.format_duration = function(val) {
    if (!val) return '';
    var split = val.split(/[:]+/);
    return split[0] + " ч " + split[1] + " м";
}

r_avia.h.format_duration_short = function(val) {
    if (!val) return '';
    var split = val.split(/[:]+/);
    var hours = split[0]*1;
    if (split[1] >= 30) {hours += 1};
    return hours + " ч";
}

r_avia.h.delta_days = function(start, end) {
    var split = start.split(/[\-\s]+/);
    var start_d = new Date(split[0], split[1] - 1, split[2]);
    split = end.split(/[\-\s]+/);
    var end_d = new Date(split[0], split[1] - 1, split[2]);
    var delta = Math.round((end_d.getTime() - start_d.getTime()) / 86400000);
    return delta + r_avia.h.word_endings(delta, " день", " дня", " дней");
}

r_avia.h.word_endings = function(num,str1,str2,str3) {
    var dec = num % 100;
    var b = dec % 10;
    if (dec > 10 && dec < 20) return str3;
    if (b == 1) return str1;
    if (b >= 2 && b <= 4) return str2;
    return str3;
}

r_avia.h.parse_datetime = function(val) {
    var split = val.split(/[\-:\s]+/);
    var d = new Date(split[0], split[1] - 1, split[2], split[3], split[4], split[5]);
    return d;
};

/*end new design*/


/*
 * Arguments: data - any value
 *            max_level - maximum recursion level for inner objects/arrays.
 * Returns  : The textual representation of the array.
 * This will accept some data as the argument and return a
 * text that will be a more readable version of the
 * array/object that is given.
 */
r_avia.h.dump = function(data, max_level, level) {
    var dumped_text = "", padding = "", level = level || 0;

    // The padding given at the beginning of the line.
    for (var j = 0; j < level + 1; j++) padding += "    ";
    if (max_level && level > max_level) {
        return padding + "Maximum Depth Reached: " + level + "\n";
    }

    if(typeof data == 'object') { // Array/Object
        for(var key in data) {
            var value = data[key];
            if(typeof value == 'object') {
                dumped_text += padding + "'" + key + "' ...\n";
                dumped_text += r_avia.h.dump(value, max_level, level + 1);
            } else {
                dumped_text += padding + "'" + key + "' => \"" + value + "\"\n";
            }
        }
    } else { // Stings/Numbers etc.
        dumped_text = "===>" + data + "<===(" + typeof data + ")";
    }
    return dumped_text;
};


r_avia.h.array_filter = function(a, fun) {
    var result = [],
        a_len = a.length;
    for (var i = 0; i < a_len; i++) {
        if (fun(a[i])) {
            result.push(a[i]);
        }
    }
    return result;
};


/**
 * "foo" -> document.getElementsByName("foo")[0]
 * "foo.bar" -> document.forms["foo"]["bar"]
 * @param {string} name
 * @return {Element|undefined}
 */
r_avia.h.find_input_by_name = function(name) {
    var parts = name.split(".");
    if (parts.length > 1) {
        var form = document.forms[parts[0]];
        return form ? form[parts[1]] : undefined;
    }
    return document.getElementsByName(name)[0];
};


// Logging
r_avia.log = {};

r_avia.log.log = function(cat, line) {
   if(r_avia.debug && window.console) console.log(cat.toUpperCase(), line);
};

r_avia.log.debug = function(line) { return r_avia.log.log('debug', line) };
r_avia.log.info  = function(line) { return r_avia.log.log('info',  line) };
r_avia.log.error = function(line) { return r_avia.log.log('error', line) };

"use strict";
/* This module contains code that is run at application start.
 */

r_avia['daterange'] = {
    'maxLengthInDay': 320
};

r_avia['suggest_delay'] = 200; // milliseconds

r_avia['conf'] = {};
r_avia['conf']['ajaxPollRetries'] = 10;
r_avia['conf']['ajaxPollTimeoutFirst'] = 7000; // milliseconds
r_avia['conf']['ajaxPollTimeoutMod'] = 0.95;

"use strict";
(function(NULL, TRUE, FALSE){
  if( window['djp'] ){
    alert('слишком много djp');
    return
  }
  window['djp'] = (
  /**
   * @param {undefined=} UNDEFINED
   * @return {function(*=)}
   */
  function( haveAddEventListener, haveW3CSelection, UNDEFINED ){
    var
    k,
    lastUniqueId = 1,
    checkOn = (
      k = document.createElement("div"),
      k.innerHTML = '<input type="checkbox" />',
      k.getElementsByTagName("input")[0]['value'] === "on"
    ),
    makeToggleAttach = haveAddEventListener ?
    function(node, handler, eventType, useCapture){
      var eventTypes = eventType.split(' ');
      return function(on){
        for(var i = eventTypes.length; i--;){
          node[on ? 'addEventListener' : 'removeEventListener'](
            eventTypes[i], handler, useCapture
          )
        }
      }
    }:
    function(node, handler, eventType){
      var eventTypes = eventType.split(' ');
      return function(on){
        for(var i = eventTypes.length; i--;){
          node[on ? 'attachEvent' : 'detachEvent']('on'+eventTypes[i], handler)
        }
      }
    };
    /**
     * Создает новый метод more, для текущего экземплра DJProxy в цепочке. Вспомогательная функция.
     * @param {DJProxy} pastThis текущий экземпляр DJProxy в цепочке
     * @param {function(DJProxy,*,*,boolean):boolean} chkFunc возвращает true, если элемент проходит проверку в этом звене цепочки. Также может менять элемент этого звена.
     * @param {*=} a необязательный первый передаваемый параметр
     * @param {*=} b необязательный второй передаваемый параметр
     * @return {DJProxy}следующий новый экземпляр DJProxy в цепочке
     */
    function moreMaker(pastThis, chkFunc, a, b){
      var r = new DJProxy(pastThis), isFirst = TRUE;
      /**
       * Возвращает обертку следующего, проходяшего цепочку отбора, элемента.
       * @this {DJProxy}
       * @return {DJProxy}
       */
      r.more = function(){
        while (this.node && !chkFunc(this, a, b, isFirst)){
          this.node = pastThis.more().node;
          isFirst = TRUE;
        }
        isFirst = FALSE;
        return this
      }
      return r.more()
    }
    /**
     * Обертка события.
     * @constructor
     */
    function DJPEvent(e, fToggleAttach){
      var $this = this;
      $this.e = e;
      $this.$ie = haveAddEventListener || {
        $button: e.button,
        'X': e.clientX,
        'Y': e.clientY,
        $propertyName: e.propertyName
      };
      $this.$type = e.type;
      $this.$keyCode = e.keyCode || UNDEFINED;
      $this.$toElement = e.toElement || UNDEFINED;
      $this.$fromElement = e.fromElement || UNDEFINED;
      $this.$relatedTarget = e.relatedTarget || UNDEFINED;
      $this.toggleOn = fToggleAttach;
    }
    DJPEvent.prototype.$which = function (){
      var k;
      return haveAddEventListener ?
      this.e.which :
      (k = this.$ie.$button) !== UNDEFINED ?
      k & 1 ?
      1 : (
        k & 2 ?
        3 : (
          k & 4 ?
          2 :
          0
        )
      ) :
      UNDEFINED;
    };
    DJPEvent.prototype.prevent = function (){
      DJPEvent.prototype.prevent = haveAddEventListener ?
      function (){
        this.e.preventDefault()
      } :
      function (){
        this.e.returnValue = FALSE
      };
      this.prevent()
    };
    DJPEvent.prototype.pos = function (){
      DJPEvent.prototype.pos = haveAddEventListener ?
      function (){
        return {x: this.e.pageX, y: this.e.pageY}
      } :
      function (){
        var
        t = document.documentElement,
        e = this.$ie,
        o = {'X':'Left','Y':'Top'},
        k,
        scroll = 'scroll',
        result = {};
        for (k in o){
          result[k] = e[k]+t[scroll+o[k]]
        }
        if (t = document.body) for (k in o){
          result[k] += t[scroll+o[k]]
        }
        return {x: result['X'], y: result['Y']}
      };
      return this.pos()
    };
    /**
     * Обертка элемента.
     * @constructor
     */
    function DJProxy(p){
      var n=document;
      if(p){
        if (p.constructor != DJProxy) {
          this.node = p.constructor != String ? p : n.getElementById(p)
        } else {
          this.node = p.node
        }
      } else {
        /* если параметр не задан, оборачиваем элемент перед текущим тегом script */
        while(n && n.nodeName!='SCRIPT')
          n=n[n['firebugIgnore']?'previousSibling':'lastChild'];
        while(n && (n.nodeType!=1 || n.nodeName=='SCRIPT'))n=n.previousSibling;
        this.node = n
      }
    }
    function oDJProxy(p){
      return new DJProxy(p)
    }
    DJProxy.prototype = {
      toString: function(){
        var node = this.node;
        return node ? node.nodeName : ''
      },
      /**
       * заполняет элемент текстом, если текст не задан возвращает весь текст в элементе.
       * @this {DJProxy}
       * @param {string=} sValue
       * @return {string|DJProxy}
       */
      text: (typeof document.documentElement.innerText != 'undefined') ?
      function(sValue){
        return sValue === UNDEFINED ?
          this.node.innerText :
          (this.node.innerText = sValue, this)
      } :
      function(sValue){
        return sValue === UNDEFINED ?
          this.node.textContent :
          (this.node.textContent = sValue, this)
      },
      /**
       * Добавляет элементы перед закрытием текущего.
       * @this {DJProxy}
       * @param {string|DJProxy} value текст HTML, добавляемого фрагмента или обретка элемента.
       * @return {DJProxy} обертка последнего добавленного элемента
       */
      append: function(value){
        var
        node = this.node,
        res = value.constructor == DJProxy && value.node,
        rng;

        if(!res){
          if(document.createRange && !node['insertAdjacentHTML']){
            rng = document.createRange();
            rng['selectNodeContents'](node);
            rng.collapse(FALSE);
            res = rng['createContextualFragment'](value);
          } else {
            node['insertAdjacentHTML']('beforeend', value)
          }
        }
        res && node.insertBefore(res, NULL);
        return new DJProxy(node.lastChild)
      },
      /**
       * Добавляет элементы после закрытия текущего.
       * @this {DJProxy}
       * @param {string|DJProxy} value текст HTML, добавляемого фрагмента или обретка элемента.
       * @return {DJProxy} обертка последнего добавленного элемента
       */
      after: function(value){
        var
        node = this.node,
        nextNode = node.nextSibling,
        res = value.constructor == DJProxy && value.node,
        rng;

        if(!res){
          if(document.createRange && !node['insertAdjacentHTML']){
            rng = document.createRange();
            rng['selectNode'](node);
            rng.collapse(FALSE);
            res = rng['createContextualFragment'](value);
          } else {
            node['insertAdjacentHTML']('afterend', value)
          }
        }
        res && node.parentNode.insertBefore(res, nextNode);
        if(nextNode){
          res = new DJProxy( nextNode ).prev()
        } else {
          for(
            res = node.parentNode.lastChild;
            res && res.nodeType != 1;
            res = res.previousSibling
          ){}
        }
        return new DJProxy(res)
      },
      /**
       * Добавляет элементы перед открытием текущего.
       * @this {DJProxy}
       * @param {string|DJProxy} value текст HTML, добавляемого фрагмента или обретка элемента.
       * @return {DJProxy} обертка последнего добавленного элемента
       */
      before: function(value){
        var
        node = this.node,
        res = value.constructor == DJProxy && value.node,
        rng;

        if(!res){
          if(document.createRange && !node['insertAdjacentHTML']){
            rng = document.createRange();
            rng['selectNode'](node);
            rng.collapse(TRUE);
            res = rng['createContextualFragment'](value);
          } else {
            node['insertAdjacentHTML']('beforebegin', value)
          }
        }
        res && node.parentNode.insertBefore(res, node);
        return new DJProxy(
          new DJProxy(node).prev()
        )
      },
      /**
       * Возвращает текущие значения у элемента формы (select, input..)
       * @this {DJProxy}
       * @return {Array}
       */
      getValues: function getValues(){
        var res = [], node = this.node;
        if(node){
          if(node.nodeName == 'SELECT'){
            var
            options = node.options,
            one = node.type === 'select-one',
            i = one ? node.selectedIndex : options.length,
            min = one ? i : 0;
            for(; i >= min; i--){
              if(options[i].selected) res[res.length] = new DJProxy(options[i]).getValues()[0]
            }
          } else {
            res = [node.nodeName == 'OPTION' ?
              (node.attributes.value || {}).specified ? node.value : node.text :
              /radio|checkbox/i.test(node.type) && !checkOn ?
                node.getAttribute('value') === NULL ? "on" : elem.value :
                node.value || ''
            ];
          }
          return res
        }
      },
      /**
       * Возвращает хеш из имен и массива значений у элементов формы
       * @this {DJProxy}
       * @return {Object.<string, Array>}
       */
      serializeToObject: function (){
        function isSubmitable(node){
          return node.name && !node.disabled &&
            (node.checked || /SELECT|TEXTAREA/.test(node.nodeName) ||
              /color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i.test(node.type));
        }
        var
        node = this.node,
        nodes = node.elements || [node],
        i,
        tmp,
        res = {};
        for(i = nodes.length; i--;){
          if(isSubmitable(nodes[i])){
            tmp = new DJProxy(nodes[i]).getValues();
            res[nodes[i].name] = res[nodes[i].name] ?
              res[nodes[i].name].concat(tmp) :
              tmp;
          }
        }
        return res
      },
      /**
       * Возвращает или задает атрибуту элемента значение из второго параметра.
       * @this {DJProxy}
       * @param {string} sAttrName название атрибута
       * @param {string=} sValue новое значение атрибута
       * @return {string|undefined}
       */
      attr: function(sAttrName, sValue){
        var node = this.node;
        return sValue === UNDEFINED ?
          node.nodeType == 1 && node.getAttribute(sAttrName, 2) || (
            node.getAttributeNode && (
              node = node.getAttributeNode(sAttrName)
            ) ? node.nodeValue : ''
          ) :
          (node.setAttribute(sAttrName, sValue), this)
      },
      /**
       * Возвращает true элемент содержит в себе или сам является элементом, пердеданным в аргументе.
       * @this {DJProxy}
       * @param {DJProxy} oDJProxy проверяемый элемент
       * @return {boolean}
       */
      contains: document.documentElement.contains ?
      function(oDJProxy){
        return this.node && oDJProxy.node && (
          this.node == oDJProxy.node || this.node.contains(oDJProxy.node)
        )
      }:
      function (oDJProxy){
        return this.node && oDJProxy.node && (
          this.node == oDJProxy.node || (this.node.compareDocumentPosition(oDJProxy.node) & 16)
        )
      },
      /**
       * Возвращает true если у элемента есть такой стиль.
       * @this {DJProxy}
       * @param {string} sClassName название стиля
       * @return {boolean}
       */
      classIs: function(sClassName){
        return !!( this.node && ( (' '+this.node.className+' ').replace(/\s+/g,' ').indexOf(' '+sClassName+' ') + 1 ) )
      },
      /**
       * Переключает/включает/выключает/заменяет классы стиля у элемента.
       * @this {DJProxy}
       * @param {string} sClassNames названия классов стиля
       * @param {boolean|string|undefined} on включить, если true
       * или выключить если false перечисленные классы. Либо, если строковый тип,
       * удалить названия классов sClassNames и вставить классы из on, если их еще нет.
       * @return {DJProxy}
       */
      toggleClass: function(sClassNames, on){
      	
        var
        node = this.node,
        onIsString = on !== UNDEFINED && on.constructor == String,
        aReplacedClasses = (sClassNames + (onIsString ? ' ' + on : '')).split(/\s+/),
        i = aReplacedClasses.length,
        
        a = (' ' + node.className + ' ').replace(/\s+/g,' ').split(' ' + aReplacedClasses[--i] + ' '),
        c = a.length > 1;
        
        for(;i--;){
          a = (' ' + a.join(' ') + ' ').split(' ' + aReplacedClasses[i] + ' ');
          c |= a.length > 1
        }
        a = a.join(' ').replace(/\s+$/g, ' ');
        if(on !== FALSE){
          node.className = a + (
            onIsString ?
            on : (
              on === UNDEFINED && c ?
              '' :
              sClassNames
            )
          );
        } else if(c){
          node.className = a;
        }
        return this
      },
      /**
       * Возвращает обертку следующего, проходяшего цепочку отбора, элемента.
       * @this {DJProxy}
       * @return {DJProxy}
       */
      more: function(){
        this.node = NULL;
        return this
      },
      /**
       * отбирает элемент с заданным названием узла.
       * @this {DJProxy}
       * @param {string} sNodeName название узла (заглавными буквами http://www.quirksmode.org/dom/w3c_core.html#t20)
       * @return {DJProxy}
       */
      hasNode: function(sNodeName){
        return moreMaker(
          this,
          function(t, sNodeName, b, isFirst){
            return isFirst && t.node.nodeName == sNodeName
          },
          sNodeName
        )
      },
      /**
       * отбирает элемент с подходящим значением атрибута.
       * @this {DJProxy}
       * @param {string} sAttrName название атрибута
       * @param {string|RegExp} expr значение атрибута, или регулярное выражение
       * @return {DJProxy}
       */
      hasAttr: function(sAttrName, expr){
        return moreMaker(
          this,
          function(t, sAttrName, expr, isFirst){
            return isFirst && (expr.test ?
              expr.test(t.attr(sAttrName)) :
              (expr == t.attr(sAttrName)))
          },
          sAttrName, expr
        )
      },
      /**
       * отбирает элемент имеющий заданный класс.
       * @this {DJProxy}
       * @param {string} sValue название класса
       * @return {DJProxy}
       */
      hasClass: function(sValue){
        return moreMaker(
          this,
          function(t, sValue, b, isFirst){
            return isFirst && (' '+t.node.className+' ').replace(/\s+/g,' ').indexOf(' '+sValue+' ') != -1
          },
          sValue
        )
      },
      /**
       * отбирает сначала текущий элемент, а затем родительские.
       * вплоть до элемента beforeNodeNotIs, если он задан
       * @this {DJProxy}
       * @return {DJProxy}
       */
      ancestor: function(beforeNodeNotIs){
        return moreMaker(
          this,
          function(t, a, b, isFirst){
            return isFirst ? t.node : t.node = (
              beforeNodeNotIs && t.node === beforeNodeNotIs ?
              NULL :
              t.node.parentNode
            )
          }
        )
      },
      /**
       * отбирает предидущий элемент, из соседних.
       * @this {DJProxy}
       * @return {DJProxy}
       */
      next: function(){
        return moreMaker(
          this,
          function(t){
            for(
              t.node = t.node.nextSibling;
              t.node && t.node.nodeType != 1;
              t.node = t.node.nextSibling
            ){};
            return t.node
          }
        )
      },
      /**
       * отбирает следующий элемент, из соседних.
       * @this {DJProxy}
       * @return {DJProxy}
       */
      prev: function(){
        return moreMaker(
          this,
          function(t){
            for(
              t.node = t.node.previousSibling;
              t.node && t.node.nodeType != 1;
              t.node = t.node.previousSibling
            ){};
            return t.node
          }
        )
      },
      /**
       * отбирает сначала первый дочерний элемент.
       * @this {DJProxy}
       * @return {DJProxy}
       */
      child: function(){
        return moreMaker(
          this,
          function(t,a,b,isFirst){
            for(
              t.node = isFirst ? t.node.firstChild : t.node.nextSibling;
              t.node && t.node.nodeType != 1;
              t.node = t.node.nextSibling
            ){};
            return t.node
          }
        )
      },
      /**
       * подключает к элементу обработчик события.
       * @this {DJProxy}
       * @param {string} eventType тип события
       * @param {function(DJProxy, {X: Number, Y: Number, which: Number, prevent: function(), toggleOn: function(boolean),keyCode: Number, relatedTarget: Element, toElement: Element, fromElement: Element})} callback функция вызываемая при событии
       * @param {boolean=} useCapture если установлен, то по возможности сработает на этапе просачивания события.
       * @return {function(boolean)} функция повторно отключающая/подклчающая callback к этому событию.
       */
      attach: haveAddEventListener ?
      function(eventType, callback, useCapture){
        var
        handler = function(e){
          if((e.type!='click' && e.type!='mousedown') || !e.button) {
            var r = new DJProxy(e.target);
            callback(r,  new DJPEvent(e, fToggleAttach))
          }
        },
        fToggleAttach = makeToggleAttach(this.node, handler, eventType, useCapture);
        fToggleAttach(TRUE);
        return fToggleAttach
      } :
      function(eventType, callback, useCapture){
        var handler = function(){
          var e = window.event,
          r = new DJProxy(e.srcElement || document);
          callback(r, new DJPEvent(e, fToggleAttach))
        },
        fToggleAttach = makeToggleAttach(this.node, handler, eventType);
        fToggleAttach(TRUE);
        return fToggleAttach
      },
      getUniqueId: function(){
        var node = this.node;

        return node.uniqueID||(node.uniqueID=lastUniqueId++)
      }
    };
    DJProxy.prototype.constructor = DJProxy;

    var getXMLHttpRequest = !window.XMLHttpRequest ?
    function() {
      return new ActiveXObject('Microsoft.XMLHTTP');
    } :
    function() {
      return new XMLHttpRequest();
    };
    var toStringMethod = ({}).toString;
    oDJProxy.isString = function(obj){
      return toStringMethod.call(obj)==='[object String]';
    };
    oDJProxy.isArray = Array.isArray || function(obj){
      return toStringMethod.call(obj)==='[object Array]';
    };
    /**
     * создает строку запроса из обьекта
     * @param {Object.<String, Array> | Object.<String, String>} obj текст кода шаблона со вставками js кода
     * @return {String}
     */
    oDJProxy.objToQueryString = function(obj){
      var k, res=[], a, i;
      
      for(k in obj){
        a = [];
        if(oDJProxy.isArray(obj[k])){
          for(
            i=obj[k].length;
            i--;
          ){
            a[i] = encodeURIComponent(obj[k][i])
          }
        } else {
          a[0] = encodeURIComponent(obj[k])
        }
        res[res.length] =
        encodeURIComponent(k) + '=' +
        a.join('&' + encodeURIComponent(k) + '=');
      }
      return res.join('&')
    };
    oDJProxy.send = function(url, callback, postData){
      var
      request = getXMLHttpRequest(),
      t = /^(\w+:)?\/\/([^\/?#]+)/.exec(url),
      h={
        'X-Requested-With': [
          !t || (t[1] == location.protocol && t[2] == location.host),
          'XMLHttpRequest'
        ]/*,
        'Connection': [request['overrideMimeType'],'close']*/
      }, k, isDone = 0;
      
      setTimeout(
        function(){isDone || request.abort()},
        oDJProxy.send.timeOut
      );
      if(postData){
        if(!oDJProxy.isString(postData)){
          postData = oDJProxy.objToQueryString(postData);
        }
        h['Content-type'] = [1,'application/x-www-form-urlencoded']
      }
      request['open']( postData !== UNDEFINED ? 'POST': 'GET', url, TRUE);
      request['onreadystatechange'] = (
        function(){
          var notready = 1; //fix duble execute
          return function() {
            if(request['readyState'] == 4 && notready) {
              notready = 0;
              isDone = 1;
              callback(request['responseText'], request)
            }
          }
        }
      )(this);
      t = oDJProxy.send.requestHeaders;
      for (k in h)
        if(t[k] === UNDEFINED && h[k][0])
          t[k] = h[k][1]
      for (k in t) {
        request['setRequestHeader'](k, t[k])
      }
      request['send'](postData !== UNDEFINED ? postData : NULL);
      return request;
    };
    oDJProxy.send.requestHeaders = {};
    oDJProxy.send.timeOut = 30000;
    oDJProxy.txt2json = function(s){
      return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
          s.replace(
            /(\\*)"[^"]*?(\\*)"/g,
            function(a,b,c){
              return (b.length%2 ^ c.length%2) ? '"' : ''
            }
          ).replace(/"[^"]*"/g,'')
        ) || !s
      ) && (
        ( new Function(['return(',s,')'].join('')) )()
      );
    };
    
    oDJProxy.getJSON = function(url, callback, postData){
      var storedRequestHeaders = oDJProxy.send.requestHeaders,
          r;
      oDJProxy.send.requestHeaders['Accept'] = 'application/json';
      r = oDJProxy.send(
        url,
        function(s, r){
          if(r.status >= 200 && r.status < 300){
            var s=r.responseText,res=oDJProxy.txt2json(s);
          }
          callback(res, r)
        },
        postData
      );
      oDJProxy.send.requestHeaders = storedRequestHeaders;
      return r;
    };
    /**
     * Генерирует из строки функцию-шаблонизатор
     * @param {String} str текст кода шаблона со вставками js кода
     * @return {function(Object):String}
     */
    oDJProxy.tmpl = function(str){
      var
      b=0,
      e=0,
      f = [
        "var p=[];\np[p.length]='",
        str.split("\t").join(" ")
        .split("\r").join("\n")
        .split("<%").join("\t")
        .replace(
          /(^|\%\>)([^\t]*)/g,
          function(a,b,c){
            return b + c.split("\n").join("\\\n")
          }
        )
        .replace(/((?:^|%>)[^\t]*)'/g, "$1\r")
        .replace(/\t=(.*?)%>/g, "';\np[p.length]=$1;\np[p.length]='")
        //.split("\t").join("';\n")
        .replace(/\t/g,function(){e++;return "';\n"})
        //.split("%>").join("\np[p.length]='")
        .replace(/\%\>/g,function(){b++;return "\np[p.length]='"})
        .split("\r").join("\\'"),
        "';\nreturn p.join('');"
      ].join('');
      
      if(b!=e){
        e = 'Не хватает "' + (b < e ? '%>' : '<%') + '"\n' + f;
        if(console && console.error){
          console.error(e)
        } else {
          alert(e)
        }
        f = "return ''";
      }
      return new Function('c', f)
    };
    return oDJProxy
  })(document.addEventListener, window['getSelection']);
   /**
     * возвращает координаты элемента, относительно документа.
     * @this {DJProxy}
     * @return {{top:Number,left:Number}}
     */
  window['djp'](NULL).constructor.prototype.offset = document.documentElement.getBoundingClientRect?
  function(){
    if(this.node){
      var doc = this.node.ownerDocument,
      rootNode = doc.documentElement,
      box = this.node.getBoundingClientRect(),
      result={'Left':box['left'],'Top':box['top']},
      k, t;
      for (k in result){
        t='scroll'+k;
        result[k] += Math.max( rootNode[t], doc.body[t] ) - rootNode['client'+k]
      }
      return {
        top: result['Top'],
        left: result['Left']
      }
    }
  }:
  function(){
    if(this.node){
      var
      elem = this.node,
      doc = elem.ownerDocument,
      rootNode = doc.documentElement,
      body = doc.body,
      getStyle=doc.defaultView.getComputedStyle,
      results={top:0, left:0},
      parent = elem,
      offsetChild = elem,
      offsetParent = elem,
      userAgent = navigator.userAgent.toLowerCase(),
      safari = /webkit/.test( userAgent ),
      mozilla = /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent ),
      safari2 = safari && parseInt((userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1]) < 522 && !/adobeair/i.test(userAgent),
      fixed,
      add = function(l, t, o) {
        o.left += parseInt(l, 10) || 0;
        o.top += parseInt(t, 10) || 0;
      },
      border = function(elem, o) {
        var style=elem.ownerDocument.defaultView.getComputedStyle(elem,NULL);
        add( style['borderLeftWidth'], style['borderTopWidth'], o)
      };
      t = getStyle(elem, NULL)['borderLeftWidth']
      // Initial element offsets
      add( elem.offsetLeft, elem.offsetTop, results );
      // Get parent offsets
      while ( offsetParent = offsetParent.offsetParent ) {
        // Add offsetParent offsets
        add( offsetParent.offsetLeft, offsetParent.offsetTop, results );
        // Mozilla and Safari > 2 does not include the border on offset parents
        // However Mozilla adds the border for table or table cells
        if ( mozilla && !/^T(ABLE|D|H)$/.test(offsetParent.nodeName) || safari && !safari2 )
          border( offsetParent, results );
        // Add the document scroll offsets if position is fixed on any offsetParent
        if ( !fixed && getStyle(offsetParent, NULL)['position'] == "fixed" )
          fixed = TRUE;
        // Set offsetChild to previous offsetParent unless it is the body element
        offsetParent!=body && (offsetChild=offsetParent)
      }
      // Get parent scroll offsets
      while ( (parent = parent.parentNode) && parent != body && parent != rootNode) {
        // Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
        if ( !/^inline|table.*$/i.test(getStyle(parent, NULL)['display']) )
          // Subtract parent scroll offsets
          add( -parent.scrollLeft, -parent.scrollTop, results );
        // Mozilla does not add the border for a parent that has overflow != visible
        if ( mozilla && getStyle(parent, NULL)['overflow'] != "visible" )
          border( parent, results );
      }
      // Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
      // Mozilla doubles body offsets with a non-absolutely positioned offsetChild
      if ( (safari2 && (fixed || getStyle(offsetChild, NULL)['position'] == "absolute")) ||
        (mozilla && getStyle(offsetChild, NULL)['position'] != "absolute") )
          add( -body.offsetLeft, -body.offsetTop, results );
      // Add the document scroll offsets if position is fixed
      if ( fixed )
        add(Math.max(doc.documentElement.scrollLeft, body.scrollLeft, results),
          Math.max(doc.documentElement.scrollTop,  body.scrollTop));
      // Return an object with top and left properties
      return {
        top: results.top,
        left: results.left
      }
    }
  };
})(null, true, false);

"use strict";
(function(UNDEF){
  var timer,
      aWatchers = [],
      lastHashTextValue = '',
      hashJSON = {};
  djp.$hash = {
    /**
     * в переспективе сжать json в корокий ключ, по которому его можно будет получить
     * @param {Object} obj json, на который возвращаем ключ
     * @return {string}
     */
    encode: function(obj){
      return '#' + (
        encodeURIComponent( toJSON(obj) )
        .replace(/%7B/ig, '%2F')
        .replace(/%7D/ig, '%25')
        .replace(/%22/ig, '%26')
      );
    },
    /**
     * в переспективе получить json по ключу
     * @param {string} key текст json-а на который возвращаем ключ
     * @return {Object}
     */
    decode: function(key){
      return djp.txt2json(
        decodeURIComponent(
          key
        ).replace(/\//ig, '{')
        .replace(/\%/ig, '}')
        .replace(/\&/ig, '"')
      );
    },
    /**
     * Из json-а в location.hash, возвращает ветку, по указаному в path пути.
     * Если val, записывает в ветку. При неудачном чтении возвращает undefined.
     * @param {Array.<string>} path масив ключей от корня к ветке
     * @param {Object=} val json кусок, устанавливаемого значения в ветке
     * @return {Object|undefined}
     */
    store: function (path, val){
      var res,
          i,
          l,
          nowHashTextValue = location.href.split('#').slice(1).join('#');
      if(lastHashTextValue != nowHashTextValue){
        lastHashTextValue = nowHashTextValue;
      }
      if( path && path.length ){
        hashJSON = djp.$hash.decode( nowHashTextValue );
        if(hashJSON.constructor !== Object){
          hashJSON = {};
        }
        if(val === UNDEF){
          for(
            i = 0,
            res = hashJSON,
            l = path ? path.length : 0;
            i < l && res !== UNDEF;
            i++
          ){
            res = path[i] in res ?
            res[ path[i] ] :
            UNDEF;
          }
        } else {
          //создать часть ветки если ее еще нет
          for(
            i = 0,
            l = path.length,
            res = hashJSON;
            i < l - 1;
            i++
          ){
            res = path[i] in res ?
            res[ path[i] ] : (
              res[ path[i] ] = {}
            );
          }
          res[ path[i] ] = val;
          //сохранить весь json в хеш, адреса страницы
          location.href = djp.$hash.encode(hashJSON);
        }
      } else {
        if(val === UNDEF){
          res = decodeURIComponent(nowHashTextValue);
        } else {
          location.href = '#' + encodeURIComponent(val);
        }
      }
      return res;
    },
    /**
     * Прикрепляет функцию к изменениям в ветке по указаному пути.
     * После прикрепления функции сразу будет перердано текущее значение.
     * @param {Array.<string>} path масив ключей от корня к ветке
     * @param {function(Object)|undefined} func
     */
    watcher: function (path, func){
      aWatchers[aWatchers.length] = [ path, func, UNDEF ];
      func( djp.$hash.store(path) );
    }
  };
  (function checkWatchers(){
    var i,
        branch;
    for(i = aWatchers.length; i--;){
      branch = djp.$hash.store( aWatchers[i][0] );
      if( toJSON( aWatchers[i][2] ) !== toJSON( branch ) ){
        aWatchers[i][1]( aWatchers[i][2] = branch );
      }
    }
    //TODO потом када будет аджаксом, лучше запускать вертелку таймаута тока при прикреплении наблюдателя
    timer = setTimeout(checkWatchers, 250);
  })();
  function toJSON (v, indentSpace){
    var r, t, m = {
      '\b':'\\b',
      '\t':'\\t',
      '\n':'\\n',
      '\f':'\\f',
      '\r':'\\r',
      '"' :'\\"',
      '\\':'\\\\'
    };
    if (v == null) {
      r = 'null'
    } else if ((t = v.constructor) == Array) {
      r = arrayToJSON(v, indentSpace)
    } else if (t == Date) {
      r = t.getTime()
    } else if ((t = typeof v) == 'object') {
      r = objectToJSON(v, indentSpace)
    } else if (t == 'boolean') {
      r = v
    } else if (t == 'number'){
      r = isFinite(v) ? v : 'null'
    } else {
      r = ['"',v.toString().replace(
        /[\x00-\x1f\\"]/g,
        function(a){
          var r;
          if(typeof(r=m[a])=='undefined'){
            r = '\\u' + ('000' + a.charCodeAt(0).toString(16)).slice(-4);
          }
          return r
        }
      ),'"'].join('');
    }
    return r.toString()
  };
  function arrayToJSON (v, indentSpace){
    var i,l,r=[];
    for (i=0,l=v.length; i<l; i++){
      r[i]=toJSON(v[i], indentSpace !== UNDEF ? '  '+indentSpace : UNDEF);
    }
    return (
      indentSpace !== UNDEF ? [
        '[\n  ', indentSpace, r.join(',\n  '+indentSpace), '\n' ,indentSpace, ']'
      ] : [
        '[', r.join(','), ']'
      ]
    ).join('')
  };
  function objectToJSON (v, indentSpace){
    var k,r=[];
    for (k in v){
      r[r.length]=[
        toJSON(k, indentSpace !== UNDEF ? '  '+indentSpace : UNDEF ),
        ':',
        toJSON(v[k], indentSpace !== UNDEF ? '  '+indentSpace : UNDEF )
      ].join('');
    }
    return (
      indentSpace !== UNDEF ? [
        '{\n  ',indentSpace,r.join(',\n  '+indentSpace),'\n',indentSpace,'}'
      ] : [
        '{', r.join(','), '}'
      ]
    ).join('')
  };
  djp.toJSON = toJSON;
})();

"use strict";

r_avia.parts = {};

r_avia.parts.searchFormIsNotFill = function(){
  if (!document.forms['geoForm']) {return;}
  var
  formElements = document.forms['geoForm'].elements,
  errorNumber = 0,
  i,
  a,
  n;

  if( formElements['f'] && !formElements['f'].value ){
    errorNumber |= 1;
  }
  if( formElements['t'] && !formElements['t'].value ){
    errorNumber |= 2;
  }
  if( formElements['df'] && !formElements['df'].value){
    errorNumber |= 4;
  }
  if( formElements['dt'] && !formElements['dt'].value){
    for(
      a = formElements[0].form.getElementsByTagName('LI'),
      i = a.length;
      i--;
    ){
      n = djp(a[i]);
      if(
        n.classIs('js-checkpaks-any')
        && n.classIs('b-hor-chooser__item-selected')
        && n.ancestor().hasClass('b-ticket-search__swap').node
      ){
        errorNumber |= 8;
      }
    }
  }
  //подкрашиваем кнопку сабмита
  for(
    i = formElements.length;
    i--;
  ){
    if( formElements[i]['type'] == 'submit' ){
      djp(formElements[i]).toggleClass('m-button-disabled', !!errorNumber);
    }
  }
  return errorNumber
};
(function(UNDEF){
  var searchButton = djp('searchButton'),
    searchButtonContainer = djp(searchButton).ancestor().hasClass('sbmtBtn'),
    textSearch = "Найти", textSearchInProcess = "Ищем билеты", activeText = textSearch,
    switchOn, showOn, animateTimer, searchInProcess;
    r_avia.parts.submitButton = {};

    function enableSubmitButton(){
      /*Делаем кнопку кликабельной*/
      searchButton.toggleClass('b-search-disable',false);
    }
    function disableSubmitButton(){
      /*Делаем кнопку некликабельной*/
      searchButton.toggleClass('b-search-disable',true);
    }
    function showSubmitButton(){
      /*Показываем контэйнер кнопки*/
      showOn = true;
      searchButtonContainer.toggleClass('hidden' , false);
      $t(searchButton.node).tween({
        opacity:1,
        time:0.4
      });
    }
    function hideSubmitButton(fnc){
      /*Прячим контэйнер кнопки, параметр - функция, которую надо выполнить по окончанию*/
      showOn = false;
      $t(searchButton.node).tween({
        opacity:0,
        time:0.4,
        onComplete: function(){
          /*Прерываем, если его уже надо показать*/
          if(!showOn) {
            searchButtonContainer.toggleClass('hidden' , true);
          };
          if (fnc) {
            fnc();
          };
        }
      });
    }
    function startAnimate(){
      /*Включаем анимацию*/
      var backgroundPositionX = 0;
      animateTimer = setTimeout(function animate(){
        searchButton.node.style.backgroundPosition = backgroundPositionX+'px -34px';
        backgroundPositionX = backgroundPositionX + 1;
        animateTimer = setTimeout(animate,100);
      },100);
    };
    function stopAnimate(){
      /*Выключаем анимацию*/
      clearTimeout(animateTimer);
      searchButton.node.style.backgroundPosition = '0px 0px';
    };
    r_avia.parts.submitButton.switchOn = function(){
      /*Включаем кнопку, например при изменении входных данных*/
      if (!switchOn) {
        switchOn = true;
        searchButton.node.value = textSearch;
        showSubmitButton();
        enableSubmitButton();
        stopAnimate();
      }
    };
    r_avia.parts.submitButton.switchOff = function(){
      /*Выключаем кнопку, например если данные вернуль в исходное состояние*/
      switchOn = false;
      var fnc = function(){
        searchButton.node.value = activeText;
        disableSubmitButton();
      }
      if (!searchInProcess) {
        hideSubmitButton(fnc);
      } else {
        startAnimate();
        fnc();
      }
    };
    r_avia.parts.submitButton.startSearch = function(){
      /*Запускаем гипножабу при поиске*/
      searchInProcess = true;
      searchButton.node.value = activeText = textSearchInProcess;
      disableSubmitButton();
      startAnimate();
      showSubmitButton();
    };
    r_avia.parts.submitButton.stopSearch = function(){
      /*Останавливаем гипножабу и при необходимости прячем кнопку по окончанию поиска*/
      var fnc = function(){
        stopAnimate();
        searchButton.node.value = activeText = textSearch;
      };
      if (!switchOn) {
        hideSubmitButton(fnc);
      } else {
        fnc();
      };
      searchInProcess = false;
    }
})();
//tooltip всплывающая подсказка типа мона ли уже нажать и искать билет
(function(UNDEF){
  var lastOvered, timer, fClose, i;

  djp(document).attach(
    'mouseover',
    function(oDJP, oEvent){
      timer !== UNDEF && clearTimeout(timer);
      timer = setTimeout(
        function(){
          var
          errorNumber = r_avia.parts.searchFormIsNotFill(),
          butonishe = oDJP.ancestor().hasClass('b-search-tickets');

          if(
            butonishe.node
          ){
            if(
              lastOvered === UNDEF
              || lastOvered.node !== butonishe.node
            ){
              fClose && fClose();
              if(errorNumber > 0){
                lastOvered = djp(butonishe);
                fClose = djp.openBox.alignLeftInside(
                  lastOvered,
                  {
                    openCallback: function(boxNode, recalc, target){
                      boxNode.append(
                          '<div class="b-micro-popup m-form-error"><div class="b-micro-popup__cn"></div>' + (
                                (errorNumber & 1) > 0 ? '<div>Введите город отправления</div>' :
                                (errorNumber & 2) > 0 ? '<div>Введите город или страну назначения</div>' :
                                (errorNumber & 4) > 0 ? '<div>Выберите даты перелета</div>' :
                                (errorNumber & 8) > 0 ? '<div>Выберите даты обратного вылета</div>' :
                                ''
                          ) + '</div>'
                      );
                    },
                    closeCallback: function(boxNode){
                      fClose = UNDEF;
                      lastOvered = UNDEF;
                    }
                  }
                );
              }
            }
          } else if(fClose){
            fClose();
          }
        },
        150
      );
    }
  );
  //запоминание значений в полях форм у фаирфокса вопу
  for(i = document.forms.length; i--;){
    document.forms[i].reset();
  }
})();
//глобальная отключалка выбора текста на странице - карамба
(function(win){
  djp(document).attach(
    'mousedown keydown selectstart',
    function(oDJP, oEvent){
      var t;
  
      if(
        !djp(oDJP).ancestor().hasNode('INPUT').node
        && !djp(oDJP).ancestor().hasNode('TEXTAREA').node
        && !djp(oDJP).ancestor().hasNode('BUTTON').node
        && !djp(oDJP).ancestor().hasNode('SELECT').node
      ){
        if(
          oEvent.$type !== 'keydown'
          || (
            oEvent.e.ctrlKey && oEvent.$keyCode == 'A'.charCodeAt(0)
          )
        ){
          //чобы чудило хром не отменял клик по скроллу
          t = oEvent.pos();
          if(
            oEvent.$type !== 'mousedown' || !(
              oDJP + '' == 'HTML' && (
                t.x > oDJP.node.clientWidth
                || t.y > oDJP.node.clientHeight
              )
            )
          ){
            oEvent.prevent();
          }
        }
      }
    }
  );
})(this);

djp(document).attach(
    'mouseover mouseout touchstart touchend',
    function(oDJP,oEvent){
        var modeButton = oDJP.ancestor().more().hasClass('b-hor-chooser__item'),
            descriptionBubble = djp(modeButton).ancestor().hasClass('b-ticket-search-form').child().hasClass(
                modeButton.classIs('m-two-ways')?'m-bothway-desc':'m-oneway-desc'
            );

        if(modeButton.node && modeButton.ancestor().more().classIs('b-ticket-search__swap') ){
            descriptionBubble.toggleClass('hidden')
        }
    }
);

"use strict";
djp.date2txt = function(dt) {
  var
  i,
  a='FullYear,Month,Date'.split(',');

  for (i=3; i--;)
    a[i]=dt['get'+a[i]]();
  for (a[1]++,i=3; i--;)
    a[i]=(a[i]+'').replace(/^(\d)$/,'0$1');
  return a.slice(0,3).join('-')
};

"use strict";
djp.group = function (src, aGroupKeys){
  var
  aResult = [],
  oExistKeyTree = {},
  oCurrentSubTree,
  key,
  i,
  j,
  noSkip,
  l,
  len,
  p,
  UNDEF;
  
  for(i = 0, len = src.length; i < len; i++){
    for(
      oCurrentSubTree = oExistKeyTree,
      noSkip = 1,
      j = aGroupKeys.length;
      noSkip && j--;
    ){
      key = djp.isString( aGroupKeys[j] ) ? (
        aGroupKeys[j] in src[i] ?
        src[i][ aGroupKeys[j] ] :
        UNDEF
      ) :
      aGroupKeys[j](src[i]);
      if( key !== UNDEF ){
        oCurrentSubTree = key in oCurrentSubTree ?
        oCurrentSubTree[key] : (
          oCurrentSubTree[key] = j ?
          {} :
          aResult.length
        );
      } else {
      /* отсекаем запись, в которой нет хотябы одной цепочки ключей группировки */
        noSkip = 0;
      }
    }
    if(noSkip){
      key = aResult.length !== oCurrentSubTree ?
      aResult[oCurrentSubTree] : (
        aResult[oCurrentSubTree] = []
      );
      key[key.length] = src[i];
    }
  }
  return aResult
}

"use strict";
/**
 * отслеживает величину сдвига нажатой мыши или "пальца", начавшиеся в елементе.
 * @param {function(targetDJP, oEvent, posX)} itsMovableNode если функция вернет true, будем следить за перемещением.
 * @param {function(delta, posX)} onDeltaChange вызывается при перемещении.
 * @param {function(isMoved)} onToggleMove вызывается в начале перемещения по элементу и при прекращении.
 */
djp.makeMovable = (function(UNDEF){
  var
  aMovable = [],
  docDJP = djp(document),
  hasTouch = false,
  scrollTimer,
  mouseIsDown = false,
  mousewheelEventType;
  
  function makeDynamicMove(oMovable){
    (function period(){
      var delta;
    
      oMovable.timer !== UNDEF && clearTimeout(oMovable.timer);
      if(
        oMovable.catchTime !== UNDEF &&
        (new Date()).getTime() - oMovable.catchTime < 300 &&
        oMovable.msPerPix !== UNDEF &&
        oMovable.msPerPix < 1500
      ){
        delta = Math.ceil( Math.abs( 40 / oMovable.msPerPix ) );
        if(oMovable.msPerPix < 0){
          delta = -delta;
        }
        oMovable.onDeltaChange(
          delta,
          oMovable.catchAt - delta
        );
        oMovable.catchAt -= delta;
        oMovable.msPerPix *= 1.05;
        oMovable.msPerPix -= (oMovable.msPerPix < 0 ? 1 : -1) * 0.1;
        oMovable.timer = setTimeout(period, oMovable.msPerPix / delta);
      } else {
        if(hasTouch){
          oMovable.touchIdentifier = UNDEF;
        }
        oMovable.msPerPix = UNDEF;
        oMovable.catchAt = null;
        oMovable.onToggleMove && oMovable.onToggleMove(false);
      }
    })();
  }
  docDJP.attach(
    'mousedown touchstart',
    function(oDJP, oEvent){
      var
      b = false,
      i,
      j,
      posX,
      t;
      
      if(oEvent.$type == 'touchstart'){
        hasTouch = true;
      }
      if( oEvent.$type == 'mousedown' ^ hasTouch ){
        if(!hasTouch){
          posX = oEvent.pos().x;
          if(posX < 0){
            posX = 0;
          }
        }
        for(i = aMovable.length; i--;){
          if(hasTouch){
            t = oEvent.e['changedTouches'];
            for(
              j = t['length'];
              j--;
            ){
              posX = t[j]['pageX'];
              if(posX < 0){
                posX = 0;
              }
              if(
                aMovable[i].itsMovableNode(
                  djp(t[j]['target']),
                  oEvent,
                  posX
                )
              ){
                if(aMovable[i].msPerPix){
                  aMovable[i].msPerPix = UNDEF;
                  makeDynamicMove(aMovable[i]);
                }
                aMovable[i].touchIdentifier = t[j]['identifier'];
                aMovable[i].catchAt = posX;
                aMovable[i].catchTime = (new Date()).getTime();
                aMovable[i].onToggleMove && aMovable[i].onToggleMove(true);
                b = true;
              }
            }
          } else if(
            aMovable[i].itsMovableNode(
              djp(oDJP),
              oEvent,
              posX
            )
          ){
            if(aMovable[i].msPerPix){
              aMovable[i].msPerPix = UNDEF;
              makeDynamicMove(aMovable[i]);
            }
            aMovable[i].catchAt = posX;
            aMovable[i].catchTime = (new Date()).getTime();
            aMovable[i].onToggleMove && aMovable[i].onToggleMove(true);
            mouseIsDown = true;
            b = true;
          }
        }
        if(b){
          oEvent.prevent();
        }
      }
    }
  );
  docDJP.attach(
    'mouseup touchend touchcancel',
    function(oDJP, oEvent){
      var i, j, t;
      
      if( oEvent.$type == 'mouseup' ^ hasTouch ){
        for(i = aMovable.length; i--;){
          if(hasTouch){
            t = oEvent.e['changedTouches'];
            for(
              j = t['length'];
              j--;
            ){
              if( t[j]['identifier'] == aMovable[i].touchIdentifier ){
                if( aMovable[i].noInertia ){
                  aMovable[i].msPerPix = UNDEF;
                }
                makeDynamicMove(aMovable[i]);
              }
            }
          } else {
            mouseIsDown = false;
            if( aMovable[i].noInertia ){
              aMovable[i].msPerPix = UNDEF;
            }
            makeDynamicMove(aMovable[i]);
          }
        }
      }
    }
  );
  docDJP.attach(
    'mousemove touchmove',
    function(oDJP, oEvent){
      var b;
      
      function doMove(){
        var i, j, delta, posX, t, k, d;
        
        for(i = aMovable.length; i--;){
          if(hasTouch){
            t = oEvent.e['changedTouches'];
          } else {
            t = [
              {'pageX': oEvent.pos().x}
            ];
          }
          for(
            j = t['length'];
            j--;
          ){
            if(
              (
                t[j]['identifier'] === UNDEF &&
                aMovable[i].catchAt !== null &&
                mouseIsDown
              ) || (
                t[j]['identifier'] !== UNDEF &&
                t[j]['identifier'] == aMovable[i].touchIdentifier
              )
            ){
              posX = t[j]['pageX'];
              if(posX < 0){
                posX = 0;
              }
              b = true;
              aMovable[i].onDeltaChange(
                delta = aMovable[i].catchAt - posX,
                posX
              );
              aMovable[i].catchAt -= delta;
              
              k = (new Date()).getTime();
              d = k - aMovable[i].catchTime;
              aMovable[i].catchTime = k;
              if(aMovable[i].msPerPix !== UNDEF && d < 1000 ){
                aMovable[i].msPerPix = (aMovable[i].msPerPix + d / delta) / 2;
              } else {
                aMovable[i].msPerPix = d / delta;
              }
            }
          }
        }
        scrollTimer = UNDEF;
      }
      b = false;
      if(oEvent.$type == 'touchmove'){
        doMove();
        if(
          b &&
          oEvent.e['touches']['length'] == 1
        ){
          oEvent.prevent();
        }
      } else if(scrollTimer === UNDEF && oEvent.$type != 'touchmove' && !hasTouch){
        scrollTimer = setTimeout( doMove, 10 );
      }
    }
  );
  docDJP.attach(
    'mousewheel DOMMouseScroll',
    function(oDJP, oEvent){
      var i, posX, delta = -oEvent.e.wheelDelta || oEvent.e.detail, b = false;

      if(mousewheelEventType === UNDEF){
        mousewheelEventType = oEvent.$type;
      }
      if(oEvent.$type == mousewheelEventType){
        posX = oEvent.pos().x;
        delta = delta > 0 ? 60 : -60;
        if(posX < 0){
          posX = 0;
        }
        for(i = aMovable.length; i--;){
          if(
            aMovable[i].itsMovableNode(
              djp(oDJP.node.nodeName == '#text' ? oDJP.node.parentNode : oDJP ),
              oEvent,
              posX
            )
          ){
            b = true;
            aMovable[i].catchTime = (new Date()).getTime();
            aMovable[i].onToggleMove && aMovable[i].onToggleMove(true);
            aMovable[i].onDeltaChange(
              delta,
              posX - delta
            );
            aMovable[i].onToggleMove && aMovable[i].onToggleMove(false);
          }
        }
        b && oEvent.prevent();
      }
    }
  );
  return function (itsMovableNode, onDeltaChange, onToggleMove, noInertia){
    aMovable[aMovable.length] = {
      itsMovableNode: itsMovableNode,
      onDeltaChange: onDeltaChange,
      onToggleMove: onToggleMove,
      noInertia : !!noInertia,
      catchAt: null
    };
  }
})();

"use strict";
(function(djp, doc, TRUE, FALSE, UNDEF){
  /** @const */ var DELIM = '|';
  var toggleValue = function(targetNode, buttonNode, state) {
    var value = buttonNode.getAttribute('data-value');
    if (state === UNDEF) {
      state = (DELIM + targetNode.value + DELIM).indexOf(DELIM + value + DELIM) === -1;
    }
    if (!value) {
      targetNode.value = '';
    } else {
      var parts = targetNode.value.split(DELIM);
      if (state) {
        parts.push(value);
      } else {
        /* Array.indexOf does not work in IE6 */
        var i = parts.indexOf(value);
        if (i !== -1) { parts[i] = "" }
      }
      parts = r_avia.h.array_filter(parts, function(item) { return item !== "" })
      parts.sort();
      targetNode.value = parts.join(DELIM);
    }
  },
  aCheckPaks = [],
  hasTouch = false;

  djp.clickTouch = function (on, callback){
    djp(document).attach(
      'click touchend',
      function(oDJP, oEvent){
        var a, j, res;
        if(oEvent.$type != 'click'){
          hasTouch = true;
        }
        if( oEvent.$type == 'click' ^ hasTouch){
          if(hasTouch){
            a = oEvent.e['changedTouches'];
          } else {
            a = [{'target': oDJP.node}];
          }
          for(
            j = a['length'];
            j--;
          ){
            if( res = on( djp(a[j]['target']) ) ){
              callback(res, oEvent);
            }
          }
        }
      }
    );
  };
  djp.checkpaks = function(on, callback){
    aCheckPaks[aCheckPaks.length] = {on: on, callback: callback};
  };
  djp.clickTouch(
    function(oDJP){
      var
      res = [
        djp(oDJP).ancestor().hasAttr('class', /(?:\s+|^)js-checkpaks-(?:item|any)(?:\s+|$)/)
      ];

      res[1] = res[0].ancestor().hasClass('js-checkpaks');
      return res[0].node && res;
    },
    function(res, oEvent){
      var
      buttonDJP = djp(res[0]),
      containerDJP = djp(res[1]),
      buttonAnyDJP,
      isAllItemUncheck = TRUE,
      targetName, targetNode,
      i, l,
      bitPos,
      bitMask = 0;

      targetName = containerDJP.attr('data-target');
      if (targetName) {
        targetNode = r_avia.h.find_input_by_name(targetName);
      }
      if( buttonDJP.classIs('js-checkpaks-any') ){
        isAllItemUncheck = buttonDJP.classIs('b-hor-chooser__item-selected');
        buttonDJP.toggleClass('b-hor-chooser__item-selected', !isAllItemUncheck);
        targetNode && toggleValue(targetNode, buttonDJP.node, !isAllItemUncheck);
        for(
          bitPos = 0,
          buttonDJP = containerDJP.node.getElementsByTagName('*'),
          i = buttonDJP.length;
          i--;
        ){
          targetNode = djp(buttonDJP[i]);
          if(targetNode.classIs('js-checkpaks-item')){
            djp(buttonDJP[i]).toggleClass('b-hor-chooser__item-selected', isAllItemUncheck);
            bitPos++
          }
        }
        if(isAllItemUncheck){
          bitMask = ~(-1<<bitPos);
        }
      } else {
        buttonDJP.toggleClass('b-hor-chooser__item-selected');
        targetNode && toggleValue(targetNode, buttonDJP.node);
        buttonAnyDJP = false;
        for(
          buttonDJP = containerDJP.node.getElementsByTagName('*'),
          bitPos = 0,
          i = 0, l = buttonDJP.length;
          i < l;
          i++
        ){
          if( djp(buttonDJP[i]).classIs('js-checkpaks-any') ){
            buttonAnyDJP = djp(buttonDJP[i]);
          }
          if( djp(buttonDJP[i]).classIs('js-checkpaks-item') ){
            if(isAllItemUncheck){
              isAllItemUncheck = !djp(buttonDJP[i]).classIs('b-hor-chooser__item-selected');
            }
            if( djp(buttonDJP[i]).classIs('b-hor-chooser__item-selected') ){
              bitMask |= 1<<bitPos
            }
            bitPos++;
          }
        }
        buttonAnyDJP.toggleClass('b-hor-chooser__item-selected', isAllItemUncheck);
        if (isAllItemUncheck) {
          targetNode && toggleValue(targetNode, buttonAnyDJP.node, !buttonAnyDJP.classIs('b-hor-chooser__item-selected'));
        }
      }
      oEvent.prevent();
      for(i = aCheckPaks.length; i--;){
        if( aCheckPaks[i].on( djp(res[0]) ) ){
          aCheckPaks[i].callback(bitMask, djp(res[0]), oEvent);
        }
      }
    }
  );

  // При загрузке выделить кнопки в соответствии с значениями в инпутах
  var choosers = doc.getElementsByTagName('dd');
  for(var i = choosers.length; i--;) {
    if (djp(choosers[i]).classIs('js-checkpaks')) {
      var chooser = choosers[i], targetName = chooser.getAttribute('data-target'), targetNode;
      if(targetName) {
        targetNode = r_avia.h.find_input_by_name(targetName);
        if(targetNode && targetNode.value) {
          var selectedValues = targetNode.value.split(DELIM);
          for(var buttonDJP = djp(chooser).child().child(); buttonDJP.node; buttonDJP.more()) {
            var
            buttonValue = buttonDJP.node.getAttribute('data-value'),
            /* Array.indexOf does not work in IE6 */
            isSelected = selectedValues.indexOf(buttonValue) !== -1;

            buttonDJP.toggleClass('b-hor-chooser__item-selected', isSelected);
          }
        }
      }
    }
  }
})(djp, document, true, false);

"use strict";
(function(djp, errorTemplate){
  var TRUE = true, NULL = null, timer,
      boxCSSText = ['position:absolute;padding:0;margin:0;z-index:98;','top:0;left:0;visibility:hidden;'],
      doc = document,
      rootNode = doc.documentElement,
      currentBoxes = [];
  errorTemplate = djp.tmpl(errorTemplate);
  /**
   * отображает блок поверх страницы в заданной позиции.
   * @param {DJProxy=} target эелемент относительно которого будет позиционироваться блок
   * @param {{
   *   openCallback: function(boxNode, recalc, target),
   *   closeCallback: (function(boxNode)|undefined),
   *   closeClass: (string|undefined),
   *   pos: ({x:integer, y:integer}|undefined)
   * }} options настройки
   * @return {function()} close
   */
  djp.openBox = function(target, options){
    var
    boxNode,
    recalc;

    options = options || {};
    recalc = options.recalc ||
    /* Показывает блок слева от вызвавшего его элемента или если нет места справа.
     * верхнаяя граница блока выравнена по центру от вызываемого элемента.
     */
    function(target, boxNode){
      var
      offset = forRecalc(target, boxNode);
      offset.top += target.node.offsetHeight >> 1;

      boxNode.node.style.cssText = boxCSSText[0] + [
        'top:', offset.top, 'px;',
        'left:', offset.left, 'px;',
        'width:', offset.width, 'px;'
      ].join('');
    };
    options.closeClass = options.closeClass || 'closeButton';
    function fClose (){
      var tmp, i, l;
      if(boxNode){
        for(
          i = 0,
          l = currentBoxes.length;
          i < l
          && currentBoxes[i].node !== boxNode.node;
        ){
          i++;
        }
        for(;l > i;){
          currentBoxes[--l].onCloseToggler();
          options.closeCallback && options.closeCallback(
            djp(currentBoxes[l].node)
          );
          (tmp = currentBoxes[l].node.parentNode).parentNode.removeChild(tmp);
        }
        currentBoxes.length = l;
        boxNode = NULL;
      }
    }
    if(!boxNode){
      boxNode = djp(doc.body).append(
        '<div style="margin:-1px 0 0;text-align:left;">' + (
          options.shadowClass ? (
            '<div class="' + options.shadowClass +'"></div>'
          ) :
          ''
        ) + '<div style="' + boxCSSText.join('') + '"></div>'
      ).child();
      options.shadowClass && boxNode.more();
      currentBoxes[currentBoxes.length] = {
        node: boxNode.node,
        target: target,
        recalc: recalc,
        onCloseToggler: djp(doc).attach(
          'keyup ' + (rootNode['uniqueID'] ? 'mouseup' : 'mousedown'),
          function (oTargetDJProxy, oEvent){
            if(
              currentBoxes[currentBoxes.length - 1].node === boxNode.node
            ){
              if(
                oEvent.$type == 'keyup'
                && oEvent.$keyCode == 27
              ){
                fClose();
              } else if(
                (
                  boxNode.contains(
                    djp(oTargetDJProxy).ancestor().hasClass(options.closeClass)
                  ) && (
                    oEvent.$type != 'keyup' ||
                    oEvent.$keyCode == 32 ||
                    oEvent.$keyCode == 13
                  )
                ) || (
                  !boxNode.contains(
                    oTargetDJProxy
                  ) && oEvent.$type != 'keyup'
                )
              ){
                fClose();
                oEvent.prevent()
              }
            }
          }
        )
      };
      options.openCallback(boxNode, recalc, target);
      recalc(target, boxNode, boxCSSText);
    }
    return fClose
  };
  djp(window).attach(
    'resize scroll',
    function(){
      typeof timer != 'undefined' && clearTimeout(timer);
      timer = setTimeout(
        function(){
          var i;
          for(i = currentBoxes.length; i--;){
            currentBoxes[i].node
            && currentBoxes[i].recalc(
              currentBoxes[i].target,
              djp(currentBoxes[i].node),
              boxCSSText
            );
          }
        },
        5
      )
    }
  );
  function forRecalc(target, boxNode){
    var offset = target.offset(), isToLeft = TRUE;
    boxNode.node.style.cssText = boxCSSText.join('') + 'width:';
    offset.width = boxNode.node.offsetWidth
    offset.left -= offset.left < offset.width ? (
      isToLeft=!isToLeft,
      -target.node.offsetWidth
    ) :
    offset.width;
    boxNode.ancestor().more().toggleClass('toLeft', isToLeft);
    return offset
  }
  /* Показывает блок поцентру окна просмотра */
  djp.openBox.centre = function(target, options){
    options = options || {};
    options.recalc = function(target, boxNode){
      var k, o = {'Width':'Left','Height':'Top'}, t, a = [];
      boxNode.node.style.cssText = boxCSSText.join('');
      for (k in o){
        a[a.length] = o[k].toLowerCase() + ':' + (
          (
            t = (
              typeof window['inner'+k] == 'undefined' ?
              rootNode['client'+k] || doc.body['client'+k] :
              window['inner'+k]
            ) - boxNode.node['offset'+k]
          ) < 0 ?
            0 :
            ((t>>1) + Math.max(doc.body[t='scroll'+o[k]], rootNode[t]))
        ) + 'px;'
      }
      boxNode.node.style.cssText = boxCSSText[0] + a.join('');
    };
    return djp.openBox(target, options)
  };
  /* Показывает блок вырвненным по нижней границе с вызвавшим его элементом и слева.
   * Если сверху нет места чтобы блок влез целиком,
   * выравниваются верхняя граница блока с нижней границей элемента.
   * Если нет места слева , блок будет справа от вызвавшего его элемента.
   */
  djp.openBox.alignLeftInside = function(target, options){
    options = options || {};
    options.recalc = function(target, boxNode){
      var
      isBelow = TRUE,
      offset = forRecalc(target, boxNode),
      tHeight = target.node.offsetHeight,
      bHeight = boxNode.node.offsetHeight;
      offset.top -= offset.top -
      Math.max(doc.body.scrollTop, rootNode.scrollTop) < bHeight - tHeight ?
      -tHeight : (
        isBelow = !isBelow,
        -tHeight + bHeight
      );
      boxNode.toggleClass('below', isBelow);
      boxNode.node.style.cssText = boxCSSText[0] + [
        'top:', offset.top, 'px;',
        'left:', offset.left, 'px;',
        'width:', offset.width, 'px;'
      ].join('');
    };
    return djp.openBox(target, options)
  };
  /* 3 Показывает блок слева от вызвавшего его элемента или если нет места справа.
   * верхнаяя граница блока выровнена по центру от вызываемого элемента.
   */
  djp.openBox.centreAside = function(target, options){
    options = options || {};
    options.recalc = function(target, boxNode){
      var
      offset = forRecalc(target, boxNode);
      offset.top += (target.node.offsetHeight - boxNode.node.offsetHeight) >> 1;
      boxNode.node.style.cssText = boxCSSText[0] + [
        'top:', offset.top, 'px;',
        'left:', offset.left, 'px;',
        'width:', offset.width, 'px;'
      ].join('');
    };
    return djp.openBox(target, options)
  };
  /* Показывает блок над или если нет места то под элементом, вызвавшем его.
   */
  djp.openBox.overOrBelow = function(target, options){
    options = options || {};
    options.recalc = function(target, boxNode){
      var
      offset = target.offset(),
      isBelow = TRUE,
      tHeight,
      bHeight,
      visibleWidth = (typeof window['innerWidth'] == 'undefined' ?
      rootNode['clientWidth'] || doc.body['clientWidth'] :
      window['innerWidth'])- 40,
      tmp,
      tmp2;
      boxNode.node.style.cssText = [
        boxCSSText[0],
        'left:0;top:0;',
        'px;width:;'
      ].join('');
      tmp = visibleWidth - boxNode.node.offsetWidth;
      offset.width = boxNode.node.offsetWidth;
      if (tmp < 0){
        boxNode.node.style.width = (offset.width += tmp) + 'px'
      }
      tHeight = target.node.offsetHeight;
      bHeight = boxNode.node.offsetHeight;
      offset.top -= offset.top -
      Math.max(doc.body.scrollTop, rootNode.scrollTop) < bHeight ?
      -tHeight : (
        isBelow = !isBelow,
        bHeight
      );
      if(options.pos){
        tmp = Math.max(doc.body.scrollLeft, rootNode.scrollLeft);
        tmp2 = options.pos.x + offset.width;
        offset.left = tmp2 - tmp > visibleWidth ?
        tmp + visibleWidth - offset.width :
        options.pos.x
      }
      boxNode.toggleClass('below', isBelow);
      boxNode.node.style.cssText = boxCSSText[0] + [
        'top:', offset.top, 'px;',
        'left:', offset.left, 'px;',
        'width:', offset.width, 'px;'
      ].join('');
    };
    return djp.openBox(target, options)
  };
  /* Показывает блок под элементом, вызвавшем его.
   */
  djp.openBox.below = function(target, options){
    options = options || {};
    options.recalc = function(target, boxNode){
      var
      offset = target.offset(),
      visibleWidth = (typeof window['innerWidth'] == 'undefined' ?
      rootNode['clientWidth'] || doc.body['clientWidth'] :
      window['innerWidth'])- 40,
      tmp,
      tmp2;
      boxNode.node.style.cssText = [
        boxCSSText[0],
        'left:0;top:0;',
        'px;width:;'
      ].join('');
      tmp = visibleWidth - boxNode.node.offsetWidth;
      offset.width = boxNode.node.offsetWidth;
      if (tmp < 0){
        boxNode.node.style.width = (offset.width += tmp) + 'px'
      }
      offset.top -= - target.node.offsetHeight;
      if(options.pos){
        tmp = Math.max(doc.body.scrollLeft, rootNode.scrollLeft);
        tmp2 = options.pos.x + offset.width;
        offset.left = tmp2 - tmp > visibleWidth ?
        tmp + visibleWidth - offset.width :
        options.pos.x
      }
      boxNode.toggleClass('below', TRUE);
      boxNode.node.style.cssText = boxCSSText[0] + [
        'top:', offset.top, 'px;',
        'left:', offset.left, 'px;',
        'width:', offset.width, 'px;'
      ].join('');
    };
    return djp.openBox(target, options)
  };
  djp.alert = function(message){
    /*Модальное окно с ошибкой*/
    djp.openBox.centre(
      NULL,
      {
        openCallback: function(boxNode, recalc, target){
          djp('pageFader').toggleClass('hidden', false);
          boxNode.append(errorTemplate({'message':message}));
        },
        closeCallback: function(boxNode){
          djp('pageFader').toggleClass('hidden', true);
        },
        closeClass: 'js-close'
      }
    );
  }
})(djp,
//шаблон окошка ошибки
'<div class="b-popup b-error-popup">\n\
    <i class="b-close js-close"></i>\n\
    <h2 class="b-popup__header">&nbsp;</h2>\n\
    <p class="b-error-popup__msg"><%= c.message %></p>\n\
    <span class="g-button-medium m-button-grey b-error-popup__ok js-close">ОК</span>\n\
</div>'
);

/**
 * @projectDescription
 * Класс для пакетной анимации кучи объектов и их свойств.
 * Базируется на коде
 * caurina.transitions.Tweener (http://code.google.com/p/tweener/)
 * и JSTweener (http://coderepos.org/share/wiki/JSTweener)<br><br>
 * @author Sergey Chikuyonok (sc@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 * @link http://code.google.com/p/jtweener/
 * @version 0.2.1
 *
 * TODO включить в документацию анимационные функции и функции Безье
 */

/**
 * Класс для пакетной анимации кучи объектов и их свойств.
 * В большинстве случаев для создания анимации нужно всего лишь
 * вызвать метод <code>jTweener.addTween()</code>
 */
var jTweener = function() {
    var looping = false;
    var frameRate = 60;

    var userAgent = navigator.userAgent.toLowerCase();

    /** @type {Boolean} Говорит, что текущий браузер — Internet Explorer */
    var is_msie = /msie/.test(userAgent) && !/msie 9.0/.test(userAgent) && !/opera/.test(userAgent);

    /**
     * Хэш всех анимируемых в данный момент объектов, разбитые по нэймспэйсам.
     * Ключом хэша является нэймспэйс, а значением — массив анимируемых объектов
     */
    var objects = {};

    /** Действия для нэймспэйсов */
    var nsActions = {};

    /**
     * Стандартные значения анимации, который будут подставляться в каждый
     * <code>addTween()</code>, если они не указаны
     */
    var defaultOptions = {
        time: 1,
        transition: 'easeoutexpo',
        namespace: 'default',
        delay: 0,
        prefix: {},
        suffix: {},
        onStart: undefined,
        onStartParams: undefined,
        onUpdate: undefined,
        onUpdateParams: undefined,
        onComplete: undefined,
        onCompleteParams: undefined
    };

    /**
     * Названия параметров, при анимации которых будет менятся цвет.
     */
    var color_properties = ['backgroundColor', 'borderBottomColor',
        'borderLeftColor', 'borderRightColor', 'borderTopColor',
        'color', 'outlineColor', 'borderColor'];

    /**
     * Регулярное выражение для разбора значения, до которого нужно анимировать.
     * Используется для разбора значений типа '+=30', '-=20'
     */
    var re_value = /^\s*([+\-])=\s*(\-?\d+)/;

    var inited = false;
    var easingFunctionsLowerCase = {};

    /**
     * Первичная инициализация объекта, вызывается только один раз при создании
     * первой анимации.
     */
    function init() {
        for (var key in jTweener.easingFunctions) {
            easingFunctionsLowerCase[key.toLowerCase()] = jTweener.easingFunctions[key];
        }
        inited = true;
    }
    ;

    /**
     * Вспомогательный метод для callback-функций
     * @param {Function} func Функция, котрую нужно вызвать
     * @param {Array} [params] Агрументы функции
     * @param {Object} [context] Контекст выполнения функции
     */
    function callback(func, params, context) {
        if (typeof func == 'function') {
            func.apply(context || window, params || []);
        }
    }

    /**
     * Возвращает значение CSS-свойства <b>name</b> элемента <b>elem</b>
     * @author John Resig (http://ejohn.org)
     * @param {Element} elem Элемент, у которого нужно получить значение CSS-свойства
     * @param {String} name Название CSS-свойства
     * @return {String}
     */
    function getStyle(elem, name) {
        // If the property exists in style[], then it's been set
        // recently (and is current)
        if (elem.style[name]) {
            return elem.style[name];
        }
        //Otherwise, try to use IE's method
        else if (is_msie) {
            var cs = elem.currentStyle;
            if (name == 'opacity') {
                // если хотим полцчить opacity — наверняка хотим анимировать это свойство,
                // поэтому принудительно ставлю zoom = 1
                elem.style.zoom = 1;

                /**
                 * взял код с jQuery
                 * @author John Resig
                 */
                return cs.filter && cs.filter.indexOf("opacity=") >= 0 ?
                       parseFloat(cs.filter.match(/opacity=([^)]*)/)[1]) / 100 :
                       1;
            } else {
                return elem.currentStyle[name];
            }
        }
        // Or the W3C's method, if it exists
        else if (document.defaultView && document.defaultView.getComputedStyle) {
                //It uses the traditional 'text-align' style of rule writing,
                //instead of textAlign
                name = name.replace(/([A-Z])/g, "-$1").toLowerCase();
                // Get the style object and get the value of the property (if it exists)
                var s = document.defaultView.getComputedStyle(elem, "");
                return s && s.getPropertyValue(name);
            }
            //Otherwise, we're using some other browser
            else {
                return null;
            }
    }

    /**
     * Проверяет, является ли переданный элемент массивом, и если нет,
     * то создает новый массив, элементом которого является переданный аргумент
     * @param {Object}
            * @return {Array}
     */
    function toArray(obj) {
        return (!(obj instanceof Array) && !obj.jquery) ? [obj] : obj;
    }

    /**
     * Проверяет, является ли переданный объект html-нодом
     * @return {Boolean}
     */
    function isNode(obj) {
        return obj.nodeType ? true : false;
    }

    /**
     * Проверяет, является ли анимируемое свойство цветовым
     * @param {String} prop Название свойства
     * @return {Boolean}
     */
    function isColorProperty(prop) {
        for (var i = 0; i < color_properties.length; i++) {
            if (color_properties[i] == prop) {
                return true;
            }
        }
        return false;
    }

    /**
     * Проверяет, является ли переданный объект функцией
     * @param {Object} obj
     * @return {Boolean}
     */
    function isFunction(obj) {
        return (typeof obj == 'function');
    }

    /**
     * Получить значение свойства <b>key</b> объекта <b>obj</b>.
     * Если объект является html-элементом, достается css-свойство.
     * @param {Object, Element} obj
     * @param {String} key
     * @return {String}
     */
    function getValue(obj, key) {
        var val = 0;
        if (isNode(obj)) { //это html-элемент
            val = getStyle(obj, key);
        } else if (isFunction(obj[key])) { // используем свойство как getter
            val = obj[key]();
        } else { //это обычный объект
            val = obj[key];
        }

        return val;
    }

    /**
     * Получить числовое значение свойства <b>key</b> объекта <b>obj</b>.
     * Аналогичен <code>getValue()</code>, только возвращает число.
     * Если объект является html-элементом, достается css-свойство.
     * @param {Object|Element} obj
     * @param {String} key
     * @return {Number}
     */
    function getNumericValue(obj, key) {
        return parseFloat(getValue(obj, key)) || 0;
    }

    /**
     * Запускает функции нэймспэйса
     * @param {String} ns
     * @param {String} action_name
     */
    function runNSAction(ns, action_name) {
        if (nsActions[ns] && nsActions[ns][action_name]) {
            var actions = nsActions[ns][action_name];
            for (var i = 0; i < actions.length; i++) {
                callback(actions[i].func, actions[i].params);
            }
        }
    }

    /**
     * Ставит новое значение анимируемому свойству объекта.
     * Перед установкой проверяется, является ли это свойство цветовым,
     * getter/setter или opacity. В зависимости от этого выбираются разные
     * стратегии установки значения
     * @param {Object} o Анимационный объект, возвращаемый <code>prepareOptions()</code>
     * @param {String} property Анимируемое свойство
     * @param {Number} val Новое значение свойства
     */
    function setProperty(o, property, val) {
        var new_value = (o.suffix[property]) ? val + o.suffix[property] : val;
        if (isFunction(o.target[property])) {
            // используем свойство объекта как setter
            o.target[property].call(o.rawTarget, new_value);
        } else if (o.targetPropeties[property].func) {
            // используем анимируемое свойство как setter
            o.targetPropeties[property].func.call(o.rawTarget, val);
        } else if (isColorProperty(property)) {
            // анимируем цвет
            var tP = o.targetPropeties[property];
            o.target[property] = jTweener.Utils.Color.blend(tP.start_color, tP.end_color, val) + '';
        } else {
            // обычное или CSS-свойство
            // FIXME:For IE. A Few times IE (style.width||style.height) = value is throw error...
            try {
                if (is_msie && property == 'opacity' && isNode(o.rawTarget)) {
                    // устанавливаем opacity для IE

                    // для фильтров, установленных через CSS, нужно использовать currentStyle
                    var flt = o.target.filter || o.rawTarget.currentStyle.filter;

                    o.target.filter = (flt || "").replace(/alpha\([^)]*\)/, "") +
                                      (parseFloat(val).toString() == "NaN" ? "" : "alpha(opacity=" + val * 100 + ")");
                } else {
                    o.target[property] = new_value;
                }
            } catch(e) {
            }
        }
    }


    /**
     * Основной метод, который работает с анимациями. Все анимации отрабатываются
     * и останавливаются именно в нем.
     */
    function eventLoop() {
        var now = (new Date() - 0);

        // Количество namespaces в объектах. Если равно нулю после завершения
        // основного цикла, значит, нужно прекращать анимацию
        var ns_len = 0;

        for (var ns in objects) {
            var ns_obj = objects[ns];
            ns_len++;

            for (var i = 0; i < ns_obj.length; i++) {
                var o = ns_obj[i];
                var t = now - o.startTime;
                var d = o.endTime - o.startTime;

                if (t >= d) { //завершаем анимацию
                    for (var property in o.targetPropeties) {
                        var tP = o.targetPropeties[property];
                        setProperty(o, property, tP.b + tP.c);
                    }
                    ns_obj.splice(i, 1);

                    callback(o.onUpdate, o.onUpdateParams, o.rawTarget);
                    callback(o.onComplete, o.onCompleteParams, o.rawTarget);
                } else {
                    for (var property in o.targetPropeties) {
                        var tP = o.targetPropeties[property];
                        setProperty(o, property, o.easing(t, tP.b, tP.c, d));
                    }

                    callback(o.onUpdate, o.onUpdateParams, o.rawTarget);
                }
            }

            runNSAction(ns, 'onUpdate');

            if (!ns_obj.length) {
                ns_obj = null;
                delete objects[ns];
                ns_len--;
                runNSAction(ns, 'onComplete');
            }
        }

        if (ns_len > 0) {
            //еще есть объекты для анимирования
            setTimeout(eventLoop, 1000 / frameRate);
        } else {
            //объектов больше нет, останавливаемся
            looping = false;
        }
    }

    /**
     * Останавливает все анимации для объекта <b>obj</b> путем удаления
     * соответствующих объектов из хэша <code>objects</code>. Возвращает
     * количество удаленных анимаций у объекта
     * @param {Object} obj Объект, для которого нужно остановить все анимации
     * @param {String} [ns] Удалять анимации только из этого нэймспэйса
     * @return {Number}
     */
    function stopAnimation(obj, ns) {
        var how_many = 0;

        if (obj && isNode(obj)) {
            obj = obj.style;
        }

        function rm(items) {
            for (var i = items.length - 1; i >= 0; i--) {
                if (items[i].target == obj) {
                    items.splice(i, 1);
                    how_many++;
                }
            }
        }
        ;

        if (!obj && ns) { // удаляем все анимации из нэймспэйса
            objects[ns] = [];
        } else if (ns && objects[ns]) { // если указали нэймспэйс, удаляем анимации только из него
            rm(objects[ns]);
        } else {
            for (var n in objects) {
                rm(objects[n]);
            }
        }

        return how_many;
    }

    /**
     * Метод извлекает системные свойства анимации из объекта (такие как
     * <b>time</b>, <b>transition</b>, <b>onStart</b> и т.д), переданного
     * пользоватем, <b>модифицируя аргумент <code>options</code></b>. Если
     * системные свойства не были найдены — подставятся значения по умолчанию.
     * @param {Object} options Параметры анимации, переданные пользователем
     * @return {Object} Хэш системных свойств анимации
     */
    function extractSystemOptions(options) {
        var result = {};
        for (var key in defaultOptions) {
            result[key] = options[key] || defaultOptions[key];
            delete options[key];
        }

        if (isFunction(result.transition)) {
            result.easing = result.transition;
        } else {
            result.easing = easingFunctionsLowerCase[result.transition.toLowerCase()];
        }

        delete options.easing;

        return result;
    }

    /**
     * Создание копии объекта
     * @param {Object} obj Объект, который нужно скопировать
     * @return {Object}
     */
    function copyObject(obj) {
        var result = {};
        for (var a in obj) if (obj.hasOwnProperty(a)) {
            result[a] = obj[a];
        }

        return result;
    }

    /**
     * Преобразует переданные пользователем опции в вид, пригодный для анимации
     * @param {Object} obj Объект, для которого нужно подготовить опции
     * @param {Object} options Опции, пришедшие от пользователя
     * @return {Object}
     */
    function prepareOptions(obj, options) {
        // создаю копию объекта, так как эти опции могут использоваться
        // для нескольких объектов, а мне нужно модифицировать объект
        options = copyObject(options);

        var is_node = isNode(obj);
        var o = extractSystemOptions(options);
        o.rawTarget = obj;
        o.target = (is_node) ? obj.style : obj;
        o.targetPropeties = {};

        /** @type {Array} */
        var m;
        // прохожусь по всем свойствам, которые нужно анимировать
        for (var key in options) {
            // TODO: удалить суффиксы и префиксы из параметров, данные брать из значения
            if (!o.prefix[key])
                o.prefix[key] = '';

            if (!o.suffix[key])
                o.suffix[key] = (is_node && key != 'opacity') ? 'px' : '';

            var option_value = options[key];

            if (option_value === null)
                continue;

            // если анимируем html-элемент, нужно преобразовать название css-свойства:
            // 'background-color' => 'backgroundColor'
            if (is_node) {
                key = key.replace(/\-(\w)/g, function(/* String */ str, /* String */ p) {
                    return p.toUpperCase();
                });
            }

            if (isColorProperty(key)) {
                // хотим анимировать цвет
                o.targetPropeties[key] = {
                    b: 0, //base — начальное значение
                    c: 1, //change — изменение значения
                    start_color: jTweener.Utils.getRGB(getValue(obj, key)),
                    end_color: jTweener.Utils.getRGB(option_value)
                };
            } else if (isFunction(option_value)) {
                /*
                 * Анимируемый параметр является функцией — это особая нотация,
                 * которая подразумевает, что пользователь хочет запустить
                 * процентную анимацию (значение от 0 до 1)
                 */
                o.targetPropeties[key] = {
                    func: option_value,
                    b: 0, //base — начальное значение
                    c: 1 //change — изменение значения
                };
            } else {
                var base_value = getNumericValue(obj, key);
                var end_value = option_value;

                if ((m = re_value.exec(end_value))) {
                    end_value = base_value + (m[1] == '-' ? -1 : 1) * parseFloat(m[2]);
                } else {
                    end_value = parseFloat(end_value);
                }

                o.targetPropeties[key] = {
                    b: base_value, //base — начальное значение
                    c: end_value - base_value //change — изменение значения
                };
            }
        }

        return o;
    }

    /**
     * Создание новой анимации. Параметры такие же, как
     * и в <code>jTweener.addTween</code> с одним исключением: <b>obj</b> —
     * именно тот объект, который нужно анимировать (а не массив объектов)
     * @param {Object} obj Объект, свойства которого нужно анимировать.
     * Если нужно анимировать css-свойства элемента, передать <code>obj.style</code>
     * @param {Object} options Параметры анимации
     */
    function createTween(obj, options) {
        if (!inited)
            init();

        var delay = options.delay || defaultOptions.delay;

        setTimeout(function() {
            var o = prepareOptions(obj, options);
            o.startTime = (new Date() - 0);
            o.endTime = o.time * 1000 + o.startTime;

            callback(o.onStart, o.onStartParams, o.rawTarget);

            if (!objects[o.namespace]) {
                objects[o.namespace] = [];
            }

            objects[o.namespace].push(o);
            if (!looping) {
                looping = true;
                eventLoop();
            }
        }, delay * 1000);
    }

    return {
        /**
         * @param {Object, Array} obj Объект или массив объектов, свойства которых нужно анимировать.
         * @param {Object} options Параметры анимации
         */
        addTween: function(obj, options) {
            obj = toArray(obj);

            for (var i = 0; i < obj.length; i++) {
                createTween(obj[i], options);
            }
        },

        /**
         * @param {Object} options Параметры анимации
         * @see jTweener#addTween
         * @return {Object} Внутренний объект, созданный при добавлении анимации. Его можно использовать в <code>jTweener.removeTween()</code>
         */
        addPercent: function(options) {
            var anim_obj = {};
            if (arguments.length == 2) {
                // передали анимируемые объекты и параметры анимации
                anim_obj = arguments[0];
                options = arguments[1];
            }

            createTween(anim_obj, options);
            return anim_obj;
        },

        /**
         * @param {Object} actions Действия
         * @param {String} [ns] Нэймспэйс, которому нужно добавить действия (по умолчанию: 'default')
         */
        addNSAction: function(actions, ns) {
            ns = ns || defaultOptions.namespace;
            if (!nsActions[ns]) {
                nsActions[ns] = {};
            }

            var nsa = nsActions[ns];

            for (var a in actions) if (a.indexOf('Params') == -1) {
                if (!nsa[a]) {
                    nsa[a] = [];
                }

                nsa[a].push({func: actions[a], params: actions[a + 'Params']});
            }
        },

        removeNSActions: function() {
            switch (arguments.length) {
                case 0: //удаляем все действия
                    nsActions = {};
                    break;
                default:
                    var ns = arguments[0];
                    var actions = [].splice.call(arguments, 1);
                    if (nsActions[ns]) {
                        if (actions && actions.length) { //указали действия — удаляем их
                            var nsa = nsActions[ns];
                            for (var i = 0; i < actions.length; i++) {
                                delete nsa[actions[i]];
                            }
                        } else { //удаляем все действия из нэймспэйса
                            delete nsActions[ns]
                        }
                    }
            }
        },

        removeTween: function() {
            switch (arguments.length) {
                case 0:
                    objects = {};
                    break;
                default:
                    var ns, items;

                    if (arguments.length == 1) {
                        if (typeof arguments[0] == 'string') {
                            ns = arguments[0];
                        } else {
                            items = arguments[0];
                        }
                    } else {
                        ns = arguments[0];
                        items = arguments[1];
                    }

                    if (items && (items instanceof Array || items.jquery)) {
                        for (var i = 0; i < items.length; i++) {
                            stopAnimation(items[i], ns);
                        }
                    } else {
                        stopAnimation(items, ns);
                    }
            }

        }
    };
}();

/**
 * Утилиты для анимаций
 */
jTweener.Utils = {
    bezier2: function(t, p0, p1, p2) {
        return (1 - t) * (1 - t) * p0 + 2 * t * (1 - t) * p1 + t * t * p2;
    },
    /**
     * @param {Number} t Время
     * @param {Number} p0 Начальная точка
     * @param {Number} p1 Контрольная точка 1
     * @param {Number} p2 Контрольная точка 2
     * @param {Number} p3 Конечная точка
     */
    bezier3: function(t, p0, p1, p2, p3) {
        return Math.pow(1 - t, 3) * p0 + 3 * t * Math.pow(1 - t, 2) * p1 + 3 * t * t * (1 - t) * p2 + t * t * t * p3;
    },

    /**
     * Объединяет несколько объектов-хэшей в один
     * @param {Object} ... Объекты, которые нужно объединить
     * @return {Object}
     */
    mergeObjects: function() {
        var result = {};
        for (var i = 0; i < arguments.length; i++) {
            var obj = arguments[i];
            if (!obj)
                continue;

            for (var a in obj)
                result[a] = obj[a];
        }

        return result;
    },

    // Color Conversion functions from highlightFade
    // By Blair Mitchelmore
    // http://jquery.offput.ca/highlightFade/

    // Parse strings looking for color tuples [255,255,255]
    /**
     * Разбирает строку и возвращает объект, содержащий цветовые компоненты.
     * Понимает следующие форматы:<br>
     * <b>rgb(120,50,255)</b><br>
     * <b>rgb(88%,100%,39%)</b><br>
     * <b>#a0b1c2</b><br>
     * <b>#fff</b><br>
     * Если строка не может быть разобрана — возвращается черный цвет
     * @author Blair Mitchelmore (http://jquery.offput.ca/highlightFade/)
     * @param {String} color Строка с цветом.
     * @return jTweener.Util.Color
     */
    getRGB: function(color) {
        var result;

        // Если на передели объект Color, значит, делать ничего не нужно
        if (color && color.constructor == jTweener.Utils.Color)
            return color;

        // Look for rgb(num,num,num)
        if (result = /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/.exec(color))
            return new jTweener.Utils.Color(
                    parseInt(result[1], 10),
                    parseInt(result[2], 10),
                    parseInt(result[3], 10)
                    );

        // Look for rgb(num%,num%,num%)
        if (result = /rgb\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*\)/.exec(color))
            return new jTweener.Utils.Color(
                    parseFloat(result[1], 10) * 2.55,
                    parseFloat(result[2], 10) * 2.55,
                    parseFloat(result[3], 10) * 2.55
                    );

        // Look for #a0b1c2
        if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))
            return new jTweener.Utils.Color(
                    parseInt(result[1], 16),
                    parseInt(result[2], 16),
                    parseInt(result[3], 16)
                    );

        // Look for #fff
        if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))
            return new jTweener.Utils.Color(
                    parseInt(result[1] + result[1], 16),
                    parseInt(result[2] + result[2], 16),
                    parseInt(result[3] + result[3], 16)
                    );

        // Otherwise, return black color
        return new jTweener.Utils.Color(0, 0, 0);
    }
};

/**
 * @class Вспомогательный класс для работы с цветом
 * @param {Number} r Красная компонента (0—255)
 * @param {Number} g Зеленая компонента (0—255)
 * @param {Number} b Синяя компонента (0—255)
 */
jTweener.Utils.Color = function(r, g, b) {
    this.r = Math.max(Math.min(Math.round(r), 255), 0);
    this.g = Math.max(Math.min(Math.round(g), 255), 0);
    this.b = Math.max(Math.min(Math.round(b), 255), 0);
};

/**
 * Смешивает два цвета в указанной пропорции.
 * @param {jTweener.Utils.Color} color1 Первичный цвет
 * @param {jTweener.Utils.Color} color2 Вторичный цвет
 * @param {Number} ratio Пропорция, в которой нужно смешать цвет (0 — чистый первичный цвет, 1 — чистый вторичный цвет).
 * @return {jTweener.Utils.Color}
 */
jTweener.Utils.Color.blend = function(color1, color2, ratio) {
    ratio = ratio || 0;
    return new jTweener.Utils.Color(
            color1.r + (color2.r - color1.r) * ratio,
            color1.g + (color2.g - color1.g) * ratio,
            color1.b + (color2.b - color1.b) * ratio
            );
};

jTweener.Utils.Color.prototype = {
    /** Красная компонента цвета */
    r: 0,

    /** Зеленая компонента цвета */
    g: 0,

    /** Синяя компонента цвета */
    b: 0,
    /**
     * Возвращает цвет в формате RGB, например, 'rgb(129,250,0)'
     * @return {String}
     */
    toString: function() {
        return 'rgb(' + this.r + ',' + this.g + ',' + this.b + ')';
    }
};



/**
 * Фасад для jTweener. Предоставляет из себя более удобную и компактную 
 * структуру для создания анимации 
 * @author Sergey Chikuyonok (sc@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 * @include "/jTweener/src/jTweener.js"
 * 
 * TODO добавить функцию rnd(min, max)
 */

(function(/* jTweener */ jTweener){
    // если фасад уже инициализирован, либо отсутствует jTweener — ничего не делаю
    if (window.$t || !jTweener)
        return;
    
    /**
     * Проверяет, является ли переданный объект функцией
     * @param {Object} obj
     * @return {Boolean}
     */
    function isFunction(obj){
        return (typeof obj == 'function');
    }
    
    /**
     * Объединяет несколько объектов-хэшей в один
     * @param {Object} ... Объекты, которые нужно объединить
     * @return {Object}
     */
    function merge(){
        return jTweener.Utils.mergeObjects.apply(this, arguments);
    }   
    
    /** Префикс для свойств процентных анимаций 
     * (см. <code>jTweenerObj.prototype.percent</code>) 
     */
    var prc_prefix = '__jto';
    
    /**
     * Создает вспомогательный объект для создания анимаций через 
     * <code>jTweener</code>. Этот объет удобен тем, что хранит в себе
     * ссылки на анимируемые объекты, что сокращает объем кода, необходимый
     * для работы с <code>jTweener</code>. В том числе удобен тем, что
     * позволяет хранить и передавть несколько хэшей с параметрами анимаций,
     * что позволяет, например, создать хэш со стандартными параметрами, которые
     * будут применяться при анимации разных объектов.
     * @return {jTweenerObj}
     */
    var $t = function(obj, options){
        return new jTweenerObj(obj, Array.prototype.slice.call(arguments, 1));
    }
    
    /**
     * Вспомогательный класс (фасад) для работы с jTweener
     * @param {Element|Object|Array|jQuery} Объект или набор объектов, которые нужно анимировать
     * @param {Object|Array} [options] Хэш (или массив хэшей) опций, которые будут применяться ко всем анимациям по умолчанию. Можно указать несколько аргументов с опициями
     */
    function jTweenerObj(obj, options){
        this.obj = obj;
        this.options = {};
        if (options instanceof Array) {
            this.addOptions.apply(this, options);
        } else {
            this.addOptions(options);
        }
    }
    
    jTweenerObj.prototype = {
        /**
         * Звпуск анимации. Принимает неограниченное количество аргументов,
         * каждый из которых является набором параметров анимаций. Все эти опции
         * (включая те, которые были указаны при создании объекта) объединяются
         * в один набор в том порядке, в котором они были указаны.
         * @param {Object} [options] Один или несколько наборов параметров анимации
         * @return {jTweenerObj}
         */
        tween: function(){
            var options;
            if (arguments.length) {
                options = Array.prototype.slice.call(arguments, 0);
                options.unshift(this.options);
                options = merge.apply(this, options);
            } else {
                options = this.options;
            }
            
            jTweener.addTween(this.obj, options);
            return this;
        },
        
        /**
         * Создает особый вид анимации — процентную (см. <code>jTweener.addPercent</code>).
         * От обычной она отличется тем, что все анимируемые свойства должны быть 
         * функциями, принимающими один аргумент. Значение этого аргумента 
         * изменяется от 0 до 1. Для удобства вместо хэшей с параметрами можно 
         * отдавать функции
         * @example
         * var default_options = {time: 2, value: function(v){ console.log(v); }};<br>
         * var obj = document.getElementById('test_obj');<br>
         * $t(obj).percent(default_options, function(v){ console.log('same value is' + v); });
         * @return {jTweenerObj}
         */
        percent: function(){
            var args = [];
            // собираем все аргументы в один массив, чтобы из него создать один хэш
            for (var i = 0; i < arguments.length; i++) {
                if (isFunction(arguments[i])) {
                    var obj = {};
                    obj[prc_prefix + i] = arguments[i];
                    args.push(obj);
                } else {
                    args.push(arguments[i]);
                }
            }
            
            jTweener.addPercent(this.obj, merge.apply(this, args));
            return this;
        },
        
        /**
         * Останавливает анимацию на всех объектах текущей выборки
         * @return {jTweenerObj}
         */
        stop: function(){
            jTweener.removeTween(this.obj);
            return this;
        },
        
        /**
         * Добавляет параметры анимации к тем параметрам, которые будут 
         * применятся по умолчанию при запуске любой анимации
         * @param {Object} options Один или несколько хэшей с опциями
         * @return {jTweenerObj}
         */
        addOptions: function(){
            var opt = Array.prototype.slice.call(arguments, 0);
            opt.unshift(this.options);
            this.options = merge.apply(this, opt);
            return this;
        },
        
        /**
         * Удаляет все параметры анимации, которые были установлены по умолчанию
         * @return {jTweenerObj}
         */
        clearOptions: function(){
            this.options = {};
            return this;
        },
        
        /**
         * Удаляет указанные параметры из параметров анимации по умолчанию.
         * Принимает неограниченное количество строковых параметров.
         * @param {String} option Одно или несколько названий параметров, которые нужно удалить
         * @return {jTweenerObj}
         */
        removeOptions: function(){
            for (var i = 0; i < arguments.length; i++) {
                delete this.options[String(arguments[i])];
            }
            return this;
        }
    };
    
    // выношу наружу внутренние элементы
    window.$t = $t;
    
})(jTweener);

/**
 * jTweener.easingFunctions is Tweener's easing functions 
 * (Penner's Easing Equations) porting to JavaScript.
 * http://code.google.com/p/tweener/
 * @author Robert Penner
 */

jTweener.easingFunctions = {
    easeNone: function(t, b, c, d) {
        return c*t/d + b;
    },
    easeInQuad: function(t, b, c, d) {
        return c*(t/=d)*t + b;
    },
    easeOutQuad: function(t, b, c, d) {
        return -c *(t/=d)*(t-2) + b;
    },
    easeInOutQuad: function(t, b, c, d) {
        if((t/=d/2) < 1) return c/2*t*t + b;
        return -c/2 *((--t)*(t-2) - 1) + b;
    },
    easeInCubic: function(t, b, c, d) {
        return c*(t/=d)*t*t + b;
    },
    easeOutCubic: function(t, b, c, d) {
        return c*((t=t/d-1)*t*t + 1) + b;
    },
    easeInOutCubic: function(t, b, c, d) {
        if((t/=d/2) < 1) return c/2*t*t*t + b;
        return c/2*((t-=2)*t*t + 2) + b;
    },
    
    easeInExpo: function(t, b, c, d) {
        return(t==0) ? b : c * Math.pow(2, 10 *(t/d - 1)) + b - c * 0.001;
    },
    easeOutExpo: function(t, b, c, d) {
        return(t==d) ? b+c : c * 1.001 *(-Math.pow(2, -10 * t/d) + 1) + b;
    },
    easeInOutExpo: function(t, b, c, d) {
        if(t==0) return b;
        if(t==d) return b+c;
        if((t/=d/2) < 1) return c/2 * Math.pow(2, 10 *(t - 1)) + b - c * 0.0005;
        return c/2 * 1.0005 *(-Math.pow(2, -10 * --t) + 2) + b;
    },
    
    easeInElastic: function(t, b, c, d, a, p) {
        var s;
        if(t==0) return b;  if((t/=d)==1) return b+c;  if(!p) p=d*.3;
        if(!a || a < Math.abs(c)) { a=c; s=p/4; } else s = p/(2*Math.PI) * Math.asin(c/a);
        return -(a*Math.pow(2,10*(t-=1)) * Math.sin((t*d-s)*(2*Math.PI)/p )) + b;
    },
    easeOutElastic: function(t, b, c, d, a, p) {
        var s;
        if(t==0) return b;  if((t/=d)==1) return b+c;  if(!p) p=d*.3;
        if(!a || a < Math.abs(c)) { a=c; s=p/4; } else s = p/(2*Math.PI) * Math.asin(c/a);
        return(a*Math.pow(2,-10*t) * Math.sin((t*d-s)*(2*Math.PI)/p ) + c + b);
    },
    easeInOutElastic: function(t, b, c, d, a, p) {
        var s;
        if(t==0) return b;  if((t/=d/2)==2) return b+c;  if(!p) p=d*(.3*1.5);
        if(!a || a < Math.abs(c)) { a=c; s=p/4; }       else s = p/(2*Math.PI) * Math.asin(c/a);
        if(t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin((t*d-s)*(2*Math.PI)/p )) + b;
        return a*Math.pow(2,-10*(t-=1)) * Math.sin((t*d-s)*(2*Math.PI)/p )*.5 + c + b;
    },
    easeInBack: function(t, b, c, d, s) {
        if(s == undefined) s = 1.70158;
        return c*(t/=d)*t*((s+1)*t - s) + b;
    },
    easeOutBack: function(t, b, c, d, s) {
        if(s == undefined) s = 1.70158;
        return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
    },
    easeInOutBack: function(t, b, c, d, s) {
        if(s == undefined) s = 1.70158;
        if((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
        return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
    },
    easeInBounce: function(t, b, c, d) {
        return c - jTweener.easingFunctions.easeOutBounce(d-t, 0, c, d) + b;
    },
    easeOutBounce: function(t, b, c, d) {
        if((t/=d) <(1/2.75)) {
            return c*(7.5625*t*t) + b;
        } else if(t <(2/2.75)) {
            return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
        } else if(t <(2.5/2.75)) {
            return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
        } else {
            return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
        }
    },
    easeInOutBounce: function(t, b, c, d) {
        if(t < d/2) return jTweener.easingFunctions.easeInBounce(t*2, 0, c, d) * .5 + b;
        else return jTweener.easingFunctions.easeOutBounce(t*2-d, 0, c, d) * .5 + c*.5 + b;
    }
};
jTweener.easingFunctions.linear = jTweener.easingFunctions.easeNone;
"use strict";
// Rambler.Avia namespace
var r_avia = r_avia || {};
r_avia.anytime = {};
r_avia.anytime.beginAjaxUpdate = (function() {
    var anytimeResult = document.getElementById('js-anytime-result'),
        errorBlock = djp(anytimeResult).next().hasClass('b-search-error'),
        animationFacade = $t(anytimeResult),
        animationTimeout,
        ajaxUpdateTimeout;
    return function(){
        clearTimeout(ajaxUpdateTimeout);
        ajaxUpdateTimeout = setTimeout(function(){
            var url_query = window.location.search;
            if (url_query.search(/min_days=[0-9]+/) === -1 || url_query.search(/max_days=[0-9]+/) === -1) {
                return;
            }
            // Extract form fields, build get_tickets URL
            var form = document.forms['geoForm'], args = {};
            for (var key in form) {
                var input = form[key];
                if (input && input.name && input.value) {
                    args[input.name] = input.value;
                }
            }
            var url = r_avia.urls.get_anytime_tickets + "?" + djp.objToQueryString(args);
            r_avia.anytime.xhr !== undefined && r_avia.anytime.xhr.abort();
            clearTimeout(animationTimeout);
            animationFacade.tween(
                {
                    opacity:0.5,
                    time:0.4
                }
            );
            r_avia.anytime.xhr = djp.getJSON(url,
                function(data) {
                    var a, i, t;
                    djp('scrollContainer').toggleClass('hidden', true);
                    animationTimeout = setTimeout(
                        function(){
                            animationFacade.tween(
                                {
                                    opacity:1,
                                    time:0.3
                                }
                            );
                        },400
                    );
                    if (data && !data['error']) {
                        r_avia.log.info("Получили " + data.tickets.length + " билетов.");
                        if (data.tickets.length || args['stops'] || args['week_days']) {
                            djp(document.forms.geoForm).next().hasClass('b-search-params').child().toggleClass('hidden', false);
                        }
                        if (data.tickets.length) {
                            _tracker.track('events.anytime.ok');
                            t = 0;
                            if("stops" in args){
                              for(
                                a = args["stops"].split("|"), i = a.length;
                                i--;
                              ){
                                t |= (1 << a[i]);
                              }
                            }
                            t = {
                              'data': data,
                              'hash' : djp.$hash.encode({
                                'filters': {
                                  'transfers': t
                                }
                              })
                            };
                            r_avia.anytime.resultContainerNode.innerHTML = r_avia.anytime.template( t );
                            djp(anytimeResult).toggleClass('hidden', false);
                            djp(errorBlock).toggleClass('hidden', true);
                            // drawAirlinesFilter items format:
                            // {'S7': {textName: "Sibir 7", minPrice: 100500}}
                            var airlineFilterData = {};
                            for (var i = data.tickets.length; i--;) {
                                var ticket = data.tickets[i], airline = ticket.airline,
                                    existing = airlineFilterData[airline];
                                if (!existing || ticket.cost < existing.minPrice) {
                                    airlineFilterData[airline] = {
                                        textName: data.classifier.airlines[airline],
                                        minPrice: ticket.cost
                                    };
                                }
                            }
                            r_avia.tickets.drawAirlinesFilter(airlineFilterData);
                        } else {
                            _tracker.track('events.anytime.nil');
                            var i = 0, stops = args["stops"] ? args["stops"].split("|") : [],
                              stopsTexts = ["без пересадок", "с одной пересадкой", "с двумя пересадками"],
                              weekDays = args["week_days"] ? args["week_days"].split("|") : [], lastWeekDay,
                              weekDaysTexts = ["понедельник", "вторник", "среду", "четверг", "пятницу", "субботу", "воскресенье"];
                            for (i=0;i<stops.length;i++) {
                                stops[i] = stopsTexts[stops[i]-0];
                            }
                            for (i=0;i<weekDays.length;i++) {
                                weekDays[i] = weekDaysTexts[weekDays[i]-1];
                            }
                            if (weekDays.length > 1) {
                              lastWeekDay = weekDays.slice(-1)[0];
                              weekDays = weekDays.slice(0, -1);
                            }
                            djp(anytimeResult).toggleClass('hidden', true);
                            djp(errorBlock).child().hasClass('js-error-info').node.innerHTML =
                              (weekDays.length > 0 ? ((weekDays[0] == weekDaysTexts[1] ? " во " : " в ") + weekDays.join(", ") + (lastWeekDay ? " или " + lastWeekDay : "")) : "") +
                              (stops.length > 0 && stops.length < 3 ? " " + stops.join(" или ") : "");
                            djp(errorBlock).toggleClass('hidden', false);
                        }
                    } else {
                        _tracker.track('events.anytime.error');
                        r_avia.log.error(
                            data && data['error'] ?
                            data['error'] :
                            'Билеты получить не удалось.'
                        );
                    }
                    r_avia.anytime.xhr = undefined;
                }
            );
        },100)
    }
})();
(function(templateSource) {
    // Is this /anytime page?
    var containerNode = document.getElementById('js-anytime-result');
    if (!containerNode) {
        return;
    }
    /** @const */ var DELIM = '|';
    r_avia.anytime.resultContainerNode = containerNode;
    r_avia.anytime.template = djp.tmpl(templateSource);
    var form = document.forms['geoForm'];
    // Week days and Stops filters click
    djp.checkpaks(
        function(oDJP) {
            return oDJP.ancestor().hasClass('b-hor-chooser').node;
        },
        function(bitMask, oDJP, oEvent) {
            r_avia.anytime.beginAjaxUpdate();
        }
    );
    // Airlines "Apply" button
    djp.clickTouch(
        function(oDJP) {
            var res = djp(oDJP).ancestor().hasClass('b-offer__buy').ancestor().hasClass('b-avia-companies-dropdown__content');
            return res.node && res;
        },
        function(res, oEvent) {
            var inputs = res.node.getElementsByTagName('INPUT'),
                selected = [];
            for (var i = 0, len = inputs.length; i < len; i++) {
                var input = inputs[i];
                if (input.value && input.checked) {
                    selected.push(input.value);
                }
            }
            form['airlines'].value = selected.join(DELIM);
            r_avia.anytime.beginAjaxUpdate();
        }
    );
    r_avia.anytime.beginAjaxUpdate();
})(
/* Шаблон выдачи anytime */
'<%\n\
var current_month = null,\n\
    ranges = [].concat(c.data.decades, c.data.months);\n\
\n\
for (var i = 0; i < ranges.length; i++) {\n\
    var range_begin = ranges[i][0], range_end = ranges[i][1],\n\
        ticket = null,\n\
        is_decade = i < c.data.decades.length,\n\
        is_past = range_end < c.data.today,\n\
        range_begin_parts = range_begin.split("-"),\n\
        month = parseInt(range_begin_parts[1], 10),\n\
        day = parseInt(range_begin_parts[2], 10),\n\
        part = day <= 10 ? "Начало месяца" : day > 20 ? "Конец месяца" : "Середина месяца",\n\
        search_url = null;\n\
\n\
    for (var j = 0; j < c.data.tickets.length; j++) {\n\
\n\
        var ticket_direct = c.data.tickets[j].direct_departure;\n\
\n\
        if (ticket_direct >= range_begin && ticket_direct <= range_end) {\n\
            ticket = c.data.tickets[j];\n\
            if (!is_past) {\n\
                search_url = r_avia.urls.main_search(\n\
                    c.data.form.f, c.data.form.t,\n\
                    ticket.direct_departure, ticket.back_departure\n\
                ) + c.hash;\n\
            }\n\
            break;\n\
        }\n\
    }\n\
\n\
    if (current_month && current_month != month) { %>\n\
        </div>\n\
        <% if (i == 6) { %> <br/> <% }\n\
    } if (!current_month || current_month != month) { %>\n\
        <div class="b-offer-group <%= i > 5?"m-offer-group-single":"" %>">\n\
            <div class="b-offer-group__month"><%= month == current_month ? "" : r_avia.h.monthNames[month - 1] %></div>\n\
    <% } %>\n\
\n\
    <%\n\
        var offerClassName = "b-offer";\n\
\n\
        /* Если индекс предложения больше пяти или первый в группе - то ставим класс первого */\n\
        if( i > 5 || i < 5 && i % 3 === 0){\n\
            offerClassName += " m-offer-first";\n\
        }\n\
\n\
        /* если предложение последнее в группе */\n\
        if(i && i < 6 && (i+1) % 3 === 0){\n\
            offerClassName += " m-last-offer";\n\
        }\n\
\n\
        if(is_past){\n\
            offerClassName += " m-offer-disabled";\n\
        }\n\
\n\
        if(ticket === null ){\n\
            offerClassName += " m-empty-offer";\n\
        }\n\
\n\
    %>\n\
\n\
    <div class="<%= offerClassName %>">\n\
        <% if (is_decade) { %>\n\
            <p class="b-offer-month-part"><%= part %></p>\n\
        <% }\n\
\n\
        if (search_url !== null) { %>\n\
            <a href="<%= search_url %>" class="b-offer__mainlink">\n\
        <% } %>\n\
\n\
            <div class="b-offer-frame">\n\
                <div class="b-offer-frame__inner">\n\
                    <% if (ticket === null) { %>\n\
                        <p class="b-offer__empty-msg">\n\
                            На этот\n\
                            <% if(is_decade) { %>\n\
                                период\n\
                            <% } else { %>\n\
                                месяц\n\
                            <% } %>\n\
                            <br /> данных нет\n\
                        </p>\n\
                    <% } else { %>\n\
                        <p class="b-offer__price">от <strong class="b-price-value"><%= r_avia.h.format_cost(ticket.cost) %></strong> р.</p>\n\
\n\
                        <p class="b-offer__city-name"><%= c.data.form.t.length === 2 ? c.data.classifier.cities[ticket.city_to] : "&nbsp;" %> </p>\n\
\n\
                        <p class="b-offer__duration"><%= ticket.days !== null ? ticket.days + r_avia.h.word_endings(ticket.days, " день", " дня", " дней"): "&nbsp;" %></p>\n\
                        <div class="b-travel-options">\n\
                            <p class="b-travel-options__transfers">\n\
                                <% if (ticket.stops) { %>\n\
                                    <%= ticket.stops + " " + r_avia.h.word_endings(ticket.stops, "пересадка", "пересадки", "пересадок") %>\n\
                                <% } else { %>\n\
                                    Без пересадок\n\
                                <% } %>\n\
                            </p>\n\
                            <p class="b-travel-options__company"><%= c.data.classifier.airlines[ticket.airline] %>&nbsp;</p>\n\
                        </div>\n\
                    <% } %>\n\
                </div>\n\
            </div>\n\
\n\
        <% if (search_url !== null) { %>\n\
            </a>\n\
        <% } %>\n\
    </div>\n\
<%\n\
    current_month = month;\n\
} %>\n\
\n\
</div>'
);

"use strict";
(function(UNDEF){
  var
  containerDJP = djp(document.forms['geoForm']).child().child().child().hasClass('b-slide-control'),
  pixPerDay = 27,
  t,
  a,
  i,
  b,
  j;
  
  /* Пропустить весь код, если на странице нет нужных элементов */
  if(!containerDJP.node) return;
  
  (function(){
    var
    formDJP = djp(containerDJP).ancestor().hasNode('FORM'),
    formElems = formDJP.node.elements,
    minDays = formElems['min_days'],
    maxDays = formElems['max_days'];
    
    if(!maxDays.value){
      maxDays.value = 10;
    }
    if(!minDays.value){
      minDays.value = 1;
    }
    setNewValue (minDays.value, true, true);
    setNewValue (maxDays.value, false, true);
  })();
  function setNewValue (newNumber, isMin, forse){
    var
    formElems = djp(containerDJP).ancestor().hasNode('FORM').node.elements,
    minDays = formElems['min_days'],
    maxDays = formElems['max_days'],
    values,
    t,
    i,
    p,
    s;
    
    if( forse || (isMin ? minDays : maxDays).value != newNumber ){
      values = [
        (isMin ? maxDays : minDays).value,
        newNumber
      ];
      if(
        (
          isMin && newNumber <= maxDays.value - 0
        ) || (
          !isMin && newNumber < minDays.value - 0
        )
      ){
        values.reverse();
      }
      for(i=2;i--;){
        if(values[i]-0 < 1) values[i] = 1;
        if(values[i]-0 > 21) values[i] = 21;
      }
   
      minDays.value = values[0];
      maxDays.value = values[1];
      t = djp(containerDJP).child().hasNode('DIV');
      for(
        p = djp(t).child().hasNode('DIV'),
        i=2;
        i--;
        p.more()
      ){
        s = '<strong class="b-slide__indicator__val">' + values[i] + '</strong>';
        djp(p).child().hasNode('DIV').node.innerHTML = i ? (
          values[0] == values[1] ? (
            'ровно ' + s
          ) : (
            'до ' + s
          )
        ) : (
          values[0] == values[1] ? (
            ''
          ) : (
            'от ' + s
          )
        )
      }
      t.node.style.cssText =
      'margin-left:' + (
        values[0] * pixPerDay - 17
      ) + 'px;width:' + (
        (values[1] - values[0]) * pixPerDay
      ) + 'px;';
    }
  }
  //таскание краев диапазона дней пребывания.
  (function(){
    var
    isMinValue,
    startPos;
    
    djp.makeMovable(
      function(oDJP, oEvent, posX){
        var
        b = false,
        formElems,
        minDays,
        maxDays;
        
        if( !{'mousewheel':1,'DOMMouseScroll':1}[oEvent.$type] ){
          b = djp(oDJP).ancestor().hasClass('b-slide-control');
          if( b.ancestor().hasClass('b-slider').node ){
            formElems = djp(b).ancestor().hasNode('FORM').node.elements;
            minDays = formElems['min_days'];
            maxDays = formElems['max_days'];
            startPos = b.offset().left;
            isMinValue =
            Math.abs( startPos - (-minDays.value) * pixPerDay - posX ) <
            Math.abs( startPos - (-maxDays.value) * pixPerDay - posX );
          }
          b = !!b.node;
        }
        return b
      },
      function(delta, posX){
        setNewValue( Math.ceil( (posX - startPos) / pixPerDay ), isMinValue );
      },
      function(b){
        //djp(document.body).toggleClass('jsWResize', b);
      },
      true
    );
  })();
  djp.checkpaks(
    function(oDJP){
      return oDJP.ancestor().hasClass('b-ticket-search__swap')
      .ancestor().hasNode('FORM').hasAttr('action', /\/anytime$/).node;
    },
    function(bitMask, oDJP, oEvent){
      var formDJP = oDJP.ancestor().hasNode('FORM').hasAttr('action', /\/anytime$/);

      formDJP.node.elements['one_way'].value = !!bitMask ? '1' : '';
      formDJP.child().hasClass('b-slider').toggleClass('m-slider-disabled', !!bitMask);
    }
  );
  for(
    a = document.forms,
    i = a.length;
    i--;
  ){
    if( /\/anytime$/.test(a[i]['action']) ){
      djp(a[i]).child().hasClass('b-slider').toggleClass(
        'm-slider-disabled',
        !!a[i].elements['one_way'].value
      );
      if(!!a[i].elements['one_way'].value){
        for(
          b = a[i].getElementsByTagName('UL'),
          j = b.length;
          j--;
        ){
          if( djp(b[j]).classIs('b-ticket-search__swap js-checkpaks') ){
            t = djp(b[j]).child().hasClass('b-hor-chooser__item');
            t.toggleClass('b-hor-chooser__item-selected');
            t.more().toggleClass('b-hor-chooser__item-selected');
          }
        }
      }
    }
  }
})();

"use strict";
r_avia.form = {};
(function(
  datesFrameTemplate,
  UNDEF
){
  var
  containerDJP = djp(document.forms['geoForm']).child().hasClass('dateRangeContainer'),
  pixPerDay = 32,
  msPerDay = 86400000, /* 24 hours * 60 minutes * 60 seconds * 1000 milliseconds */
  msPerPix = msPerDay / pixPerDay,
  maxDate = new Date( (new Date).getTime() + msPerDay * r_avia['daterange']['maxLengthInDay'] ),
  asMonth = r_avia.h.monthNames,
  SizeFactorFrom, 
  SizeFactorTo, 
  dateRangesTo,
  dateRangesFrom,
  dateRangesTmp,
  firstDrawingDate = new Date,
  todayDateOffset = firstDrawingDate.getTimezoneOffset(),
  scrollMultipler = 1,
  containerScrollLeft,
  containerScrollLeftMin,
  scrollTimer,
  isScrolledByMonth, 
  offScreenDates = [[],[]],
  isOneWay = true,
  selectionBoxIsMoved = false,
  isGecko = !window.opera && navigator.product == 'Gecko' && navigator.userAgent.indexOf('WebKit') == -1,
  setContainerScrollLeft,
  inputToDate = function(node){
    var s = node.value.split('-');
    (node = s.length == 3 && new Date( s[0], --s[1], s[2], 12 )) &&
      node.getTimezoneOffset() != todayDateOffset && node.setTime(node.getTime()-(node.getTimezoneOffset()-todayDateOffset)*60000);
    return node;
  },
  containerScrollBy, containerPermanetlyScrollBy = (function(){
    var timer, delay, delta, fn;
    
    function period(){
      containerScrollBy(delta);
      fn && fn(-delta);
      timer !== UNDEF && clearTimeout(timer);
      timer = setTimeout(period, delay);
    }
    return function(pixPer40Ms, resizeSelectionBox){
      delta = pixPer40Ms;
      if(pixPer40Ms){
        fn = resizeSelectionBox;
        pixPer40Ms = Math.abs(1 / pixPer40Ms);
        delay = 40;
        if(pixPer40Ms > 1){
          delay *= pixPer40Ms;
          delta = delta > 0 ? 1 : -1;
        }
        period();
      } else if(timer !== UNDEF){
         clearTimeout(timer);
         timer = UNDEF;
      }
    }
  })(),
  toggleMicroPopUps = function(dates, bordersState){
    /*Показываем/скрываем красную всплывашку, говорящую о том, что за границами есть выбранная дата*/
    var result = [], showDates = [],
      microPopUp = [djp(containerDJP).next().hasClass('js-red-popup-l'), djp(containerDJP).next().hasClass('js-red-popup-r')];
    offScreenDates = [[],[]];
    for (var i=bordersState.length;i--;) {
        var show = false, sd;
        for (var j=bordersState[i].length;j--;) {
            show = show || bordersState[i][j];
            showDates[j] = bordersState[i][j]?dates[j]:null;
            offScreenDates[i][j] = showDates[j];
        }
        if (show) {
          var res = [];
          for (j=0;j<3;j+=2) {
            if (sd = showDates[j] || showDates[j+1]) {
              res[j/2] = showDates[j] && showDates[j+1]
                ? (showDates[j].getTime() == showDates[j+1].getTime()
                  ? [showDates[j].getDate()+" "+r_avia.h.monthNamesR[showDates[j].getMonth()]]
                  : (showDates[j].getMonth() == showDates[j+1].getMonth()
                    ?[showDates[j].getDate()+" &ndash; "+showDates[j+1].getDate()+" "+r_avia.h.monthNamesR[showDates[j].getMonth()]]
                    :[showDates[j].getDate()+" "+r_avia.h.monthNamesR[showDates[j].getMonth()]+" &ndash; "+showDates[j+1].getDate()+" "+r_avia.h.monthNamesR[showDates[j+1].getMonth()]]
                  )
                )
                : [sd.getDate()+" "+r_avia.h.monthNamesR[sd.getMonth()]];
            }
          }
          microPopUp[i].child().toggleClass('hidden', !res[0]);
          microPopUp[i].child().child().more().node.innerHTML = res[0]||'';
          microPopUp[i].child().more().toggleClass('hidden', !res[1]);
          microPopUp[i].child().more().child().more().node.innerHTML = res[1]||'';
        }
        microPopUp[i].toggleClass('hidden', !show);
    }
  },
  
  revers = -1,
  scroll = 0,
  searchButton = djp('searchButton'),
  lastFormValues = {f:'', t:'', df: '', dfr: '', dt: '', dtr: ''},
  formElems;
  if (searchButton.node) {
    formElems = searchButton.ancestor().hasNode('FORM').node.elements;
    for (var k in lastFormValues) {
        lastFormValues[k] = formElems[k].value;
    }
  }

  r_avia.form.toggleSearchButton = function () {
    if (searchButton.node) {
      var changed = false;
      for (var k in lastFormValues) {
        if (lastFormValues[k] != formElems[k].value) {
          changed = true;
        }
      }
      if(changed){
        r_avia.parts.submitButton.switchOn();
      } else {
        r_avia.parts.submitButton.switchOff();
      }
    }
  };

  /* Пропустить весь код, если на странице нет нужных элементов */
  if(!containerDJP.node) return;
  
  setContainerScrollLeft = (function(){
    var prevArrowClass, nextArrowClass, lastPrevArrowClass, lastNextArrowClass, oDJP, d, tmp, cOff, aOff,
      prevArrow = djp(containerDJP).next().hasClass('scrollPrev').node,
      nextArrow = djp(containerDJP).next().hasClass('scrollNext').node,
      leftMonth, lastLeftMonth, rightMonth, lastRightMonth,
      bordersState = [[],[]],
      lastDates = [],
      lastBordersState = [
        [ //левее левой границы
            //границы слева направо
            false,false,false,false
        ],[ //правее правой
            false,false,false,false
        ]
      ];
    
    return function(x){
      var changeBorderState = false, locChangeBorderState = false,
      formElems = djp(containerDJP).ancestor().hasNode('FORM').node.elements,
      dates = [
        inputToDate(formElems['df']),
        inputToDate(formElems['dfr']),
        inputToDate(formElems['dt']),
        inputToDate(formElems['dtr'])
      ];
      /*Сенсоры ухода влево (true, если дата ушла за границу)*/
      /*первая дата прямого вылета*/
      bordersState[0][0] = dates[0] &&
        x >= ( dates[0].getTime() - firstDrawingDate.getTime() ) / msPerPix;
      /*вторая дата прямого вылета*/
      bordersState[0][1] = dates[1] &&
        x - pixPerDay >= ( dates[1].getTime() - firstDrawingDate.getTime() ) / msPerPix;
      /*первая дата обратного вылета*/
      bordersState[0][2] = dates[2] &&
        x >= ( dates[2].getTime() - firstDrawingDate.getTime() ) / msPerPix;
      /*вторая дата обратного вылета*/
      bordersState[0][3] = dates[3] &&
        x - pixPerDay >= ( dates[3].getTime() - firstDrawingDate.getTime() ) / msPerPix;
      /*Сенсоры ухода вправо (true, если дата ушла за границу)*/
      /*первая дата прямого вылета*/
      bordersState[1][0] = dates[0] &&
        x + containerDJP.node.offsetWidth <= (
          dates[0].getTime() - firstDrawingDate.getTime()
        ) / msPerPix;
      /*вторая дата прямого вылета*/
      bordersState[1][1] = dates[1] &&
        x + containerDJP.node.offsetWidth - pixPerDay <= (
          dates[1].getTime() - firstDrawingDate.getTime()
        ) / msPerPix;
      /*первая дата обратного вылета*/
      bordersState[1][2] = dates[2] &&
        x + containerDJP.node.offsetWidth <= (
          dates[2].getTime() - firstDrawingDate.getTime()
        ) / msPerPix;
      /*вторая дата обратного вылета*/
      bordersState[1][3] = dates[3] &&
        x + containerDJP.node.offsetWidth - pixPerDay <= (
          dates[3].getTime() - firstDrawingDate.getTime()
        ) / msPerPix;

      prevArrowClass = x <= containerScrollLeftMin ? (
        x = containerScrollLeftMin,
        'dsbl'
      ) : (
        bordersState[0][0]
      ) ?
      'more' :
      '';
      nextArrowClass = x >= (
        maxDate.getTime() - firstDrawingDate.getTime()
      ) / msPerPix - containerDJP.node.offsetWidth ?
      'dsbl' : (
        bordersState[1][3] || bordersState[1][1]
      ) ?
      'more' :
      '';
      /*Отслеживаем, изменился ли хоть один сенсор перехода даты через границу и изменились ли даты за границей*/
      for (var i=bordersState.length;i--;) {
        for (var j=bordersState[i].length;j--;) {
          changeBorderState = changeBorderState || (lastBordersState[i][j] != bordersState[i][j]) || (bordersState[i][j] && lastDates[j]!=dates[j].getTime());
        }
      }

      if (changeBorderState) {
        toggleMicroPopUps(dates, bordersState);
      }
      /*Стрелки управления движением ползунка*/
      if(lastPrevArrowClass != prevArrowClass){
        lastPrevArrowClass = prevArrowClass;
        djp(prevArrow).toggleClass('more dsbl', prevArrowClass);
      }
      if(lastNextArrowClass != nextArrowClass){
        lastNextArrowClass = nextArrowClass;
        djp(nextArrow).toggleClass('more dsbl', nextArrowClass);
      }
//      установка текущего месяца и года
      cOff = containerDJP.offset().left;
      d = djp(prevArrow).offset().left + prevArrow.offsetWidth + x - cOff;
      for (
        oDJP = djp(containerDJP).child().child().hasClass("monthBlock");
        oDJP.node && (oDJP.node.offsetLeft + oDJP.node.offsetWidth <= d);
        oDJP.more()
      ) {}
      if (lastLeftMonth != (leftMonth = new Date(firstDrawingDate.getTime() + d * msPerPix - msPerDay/2)).getMonth()) {
        djp(prevArrow).prev().hasNode("SPAN").child().node.innerHTML =
            asMonth[lastLeftMonth=leftMonth.getMonth()]+
            ' '+leftMonth.getFullYear();
      }
      if (oDJP.node) {
        d = djp(prevArrow).prev().hasNode("SPAN").child();
        aOff = d.offset().left;
        if (
          (
            !(tmp=djp(oDJP.node).child().hasClass("mothName").node) ||
            tmp.offsetLeft + oDJP.node.offsetLeft + cOff - x <= aOff
          ) && oDJP.node.offsetLeft + cOff - x + oDJP.node.offsetWidth > aOff + d.node.offsetWidth
        ) {
          tmp && (tmp.style.visibility = "hidden");
          tmp = d.node.style.visibility!="visible";
          d.node.style.visibility = "visible";
          if (tmp) {
            tmp = djp(oDJP.node).next().child().hasClass("mothName").node;
            tmp && (tmp.innerHTML = asMonth[tmp=(leftMonth.getMonth()+1)%12] + (tmp? '' : ' '+(leftMonth.getFullYear()+1)));
          }
        } else {
          if (tmp) {
            tmp.style.visibility = ((tmp=(tmp.style.visibility!="visible")) || 1) && "visible";
            if (tmp) {
              (tmp=djp(oDJP.node).child().hasClass("mothName").node).innerHTML = asMonth[leftMonth.getMonth()] + ' ' + leftMonth.getFullYear();
              if (tmp.offsetLeft + oDJP.node.offsetLeft + cOff - x <= aOff) {
                tmp = djp(oDJP.node).next().child().hasClass("mothName").node;
                tmp && (tmp.innerHTML = asMonth[tmp=(leftMonth.getMonth()+1)%12] + ' ' + (leftMonth.getFullYear() + (tmp? 0 : 1)));
              }
            }
          } else if(d.node.style.visibility!="hidden") {
            tmp = djp(oDJP.node).next().child().hasClass("mothName").node;
            tmp && (tmp.innerHTML = asMonth[tmp=(leftMonth.getMonth()+1)%12] + ' ' + (leftMonth.getFullYear() + (tmp? 0 : 1)));
          }
          d.node.style.visibility = "hidden";
        }
      }
      d = x + containerDJP.node.offsetWidth;
      for (;oDJP.more().node && (oDJP.node.offsetLeft + oDJP.node.offsetWidth <= d);) {
        djp(oDJP.node).child().hasClass("mothName").node.style.visibility = "visible"
      }
      if (oDJP.node) {
        if (
          (tmp=oDJP.child().hasClass("mothName").node).offsetLeft + oDJP.node.offsetLeft + tmp.offsetWidth <=
            x + (djp(nextArrow).offset().left||(cOff + containerDJP.node.offsetWidth)) - cOff
        ) {
          tmp.style.visibility = "visible";
        } else {
          tmp.style.visibility = "hidden";
        }
      }
      for (var i=bordersState.length;i--;) {
        for (var j=bordersState[i].length;j--;) {
          lastBordersState[i][j] = bordersState[i][j];
          lastDates[j] = dates[j] && dates[j].getTime();
        }
      }
      containerScrollLeft = x;
    }
  })();
  
  function drawMonths (){
    var
    res=[],
    l,
    b = true,
    dateCounter = new Date( firstDrawingDate.getTime() ),
    asDays = 'Вс,Пн,Вт,Ср,Чт,Пт,Сб'.split(','),
    sreenPixWidth = containerDJP.node.offsetWidth,
    todayTime = new Date;
    
    todayTime.setHours(12,0,0,0);
    todayTime = todayTime.getTime();
    if(containerScrollLeft < containerScrollLeftMin){
      containerScrollLeft = containerScrollLeftMin;
    }
    for(
      l = 0;
      l < 10 ||
      b || dateCounter.getDate() != 1;
      dateCounter.setDate(dateCounter.getDate() + 1), l++
    ){
      b = l * pixPerDay - containerScrollLeft < sreenPixWidth * scrollMultipler;
      if(dateCounter.getDate() == 1){
        res[res.length] =
        '</div><div class="monthBlock" unselectable="on"><div class="monthBlock-border"></div><div class="mothName">' +
        asMonth[dateCounter.getMonth()] + (dateCounter.getMonth()? '' : ' ' + dateCounter.getFullYear()) +
        '</div>'
      }
      res[res.length] = '<b unselectable="on"' + (
        dateCounter.getTime() < todayTime ? ' class="dayPast'+({0:1, 6:1}[dateCounter.getDay()] ? ' dayOff' : '')+'">' :
        {0:1, 6:1}[dateCounter.getDay()] ? ' class="dayOff">' : '>'
      ) + dateCounter.getDate() + '<span unselectable="on">' + asDays[dateCounter.getDay()] + '</span></b>'
    }
    res[0] = [
      '<div style="width:',
      (l * pixPerDay),
      'px;height:100%"><div class="monthBlock" unselectable="on">'/*<div class="mothName">',
      asMonth[firstDrawingDate.getMonth()],
      ' ',
      dateCounter.getFullYear(),
      ' г.</div>'*/
    ].join('');
    res[res.length] = '</div>';
    res[res.length] = '</div>';
    containerDJP.node.innerHTML = res.join('');
    setContainerScrollLeft(containerDJP.node.scrollLeft = containerScrollLeft);
    setNewDates(dateRangesFrom, true);
    setNewDates(dateRangesTo, false);
  }

  firstDrawingDate.setDate(firstDrawingDate.getDate()-1);
  maxDate.setHours(12,0,0,0);
  containerScrollLeftMin = containerScrollLeft = (firstDrawingDate.getDate() - 1) * pixPerDay;
  firstDrawingDate.setDate(1);
  firstDrawingDate.setHours(12,0,0,0);
  datesFrameTemplate = djp.tmpl(datesFrameTemplate);

  
  (function(){
    var
    formDJP = djp(containerDJP).ancestor().hasNode('FORM'),
    formElems = formDJP.node.elements,
    a, b, todayTime = new Date, c = false;
    
    formDJP.attach(
      'submit',
      function(oDJP, oEvent){
        var i, formElements = oDJP.ancestor().hasNode('FORM').node.elements;

        for( i = formElements.length; i--; ){
          if( formElements[i]['type'] == 'submit' ){
            djp(formElements[i]).classIs('m-button-disabled') && oEvent.prevent();
            break
          }
        }
      }
    );
    //при загрузке стираем серую подложку
    for(b = formElems.length; b--;){
      if(djp(formElems[b]).classIs('js-suggest-name') && formElems[b].value){
        djp(formElems[b]).prev().node.value = ''
      }
    }
    //при загрузке установить диапазоны на линейку
    todayTime.setHours(12,0,0,0);
    todayTime = todayTime.getTime();
    if(
      ( a = inputToDate(formElems['df']) ) &&
      ( b = inputToDate(formElems['dfr']) )
    ){
      dateRangesFrom = [a, b];
      c = true
    }
    if(
      (a = inputToDate(formElems['dt']) ) &&
      (b = inputToDate(formElems['dtr']) )
    ){
      dateRangesTo = [a, b];
    } else if( c ) {
      isOneWay = true;
    }
      //сброс диапазоноы если есть прошедшая дата
      for(b = false,a = 2;  dateRangesTo && a--;){
        if( dateRangesTo[a].getTime() < todayTime ) b = true
      }
      for(a = 2; dateRangesFrom && a--;){
        if( dateRangesFrom[a].getTime() < todayTime ) b = true
      }
      if(b){
        dateRangesTo = dateRangesFrom = UNDEF;
        isOneWay = false;
      } else if (dateRangesFrom){
        //центрируем первую выбранную дату
        if ((c =
          (
            dateRangesFrom[0].getTime() + ((dateRangesTo? dateRangesTo : dateRangesFrom)[1].getTime() - dateRangesFrom[0].getTime() + msPerDay) / 2
            -firstDrawingDate.getTime()
          ) / msPerPix - containerDJP.node.offsetWidth/2
        ) < containerScrollLeftMin) {
          firstDrawingDate.setTime((new Date).getTime() - Math.round((containerScrollLeftMin - c) * msPerPix));
          containerScrollLeftMin = containerScrollLeft = (firstDrawingDate.getDate() - 1) * pixPerDay;
          firstDrawingDate.setDate(1);
          firstDrawingDate.setHours(12,0,0,0);
        }
        c -= Math.max(0,c + djp(containerDJP).node.offsetWidth - (maxDate.getTime() - firstDrawingDate.getTime()) / msPerPix);
        setContainerScrollLeft(c);
      }
//    }
    if(isOneWay){
      for(
        a = djp(
          djp( formElems['f'] ).ancestor().hasClass('b-search-field')
        ).next().child().hasClass('b-hor-chooser__item');
        a.node;
        a.more()
      ){
        a.toggleClass('b-hor-chooser__item-selected')
      }
    }
    drawMonths();
  })();
  function setNewDates (newDateRange, isFrom){
    var
    formElems = djp(containerDJP).ancestor().hasNode('FORM').node.elements,
    beginDateFrom = formElems['df'],
    endDateFrom = formElems['dfr'],
    beginDateTo = formElems['dt'],
    endDateTo = formElems['dtr'],
    todayTime = new Date,
    oneDayFromAndTo,
    datesIsNear,
    minTime,
    oDJP,
    l,
    d,
    w,
    b;
  
    if (typeof isFrom != "boolean") {
      beginDateTo = {value:djp.date2txt(isFrom[0])};
      endDateTo = {value:djp.date2txt(isFrom[1])};
      isFrom = true;
    }
    todayTime.setHours(12,0,0,0);
    minTime = todayTime.getTime();
    minTime -= (isFrom ? 0 : -msPerDay);
    
    for(
      oDJP = djp(containerDJP).child().child().child().hasNode('B')
      .hasClass( isFrom ? 'selectFrom' : 'selectTo' );
      oDJP.node;
      oDJP.more()
    ){
      /(\D|^)1\D*\</.test(oDJP.node.innerHTML) && (b=djp(oDJP).prev().hasClass('monthBlock-border').node) && (b.style.cssText = "");
      oDJP.toggleClass( isFrom ? 'selectFrom' : 'selectTo', false );
    }
    if(newDateRange && !((l = newDateRange[1].getTime() - newDateRange[0].getTime())<0 && !(newDateRange = UNDEF))){
      //прошедшая дата заменяется на сегодняшную
      if(newDateRange[0].getTime() < minTime){
        newDateRange[0] = new Date(minTime);
      }
      if(newDateRange[1].getTime() < minTime){
        newDateRange[1] = new Date(minTime);
      };
      //дата больше максимальной, заменяется на максимальную
      if( maxDate.getTime() < newDateRange[0].getTime() ){
        newDateRange[0] = new Date( maxDate.getTime() );
      }
      if( maxDate.getTime() < newDateRange[1].getTime() ){
        newDateRange[1] = new Date( maxDate.getTime() );
      }
      //при изменении диапазона прямых рейсов
      if(isFrom){
        w = inputToDate(beginDateTo);
        //конечная дата диапазона прямых рейсов должна быть меньше начальной даты диапазона обратных
        if(
          w && (
            d = newDateRange[1].getTime() - w.getTime() + msPerDay
          ) > 0
        ){
          if (!l && inputToDate(endDateTo).getTime() == w.getTime()) {
            newDateRange = [w,w];
          } else {
            b = inputToDate(beginDateFrom);
            newDateRange = [
              new Date(
                w.getTime() - msPerDay - l + (
                  b && b.getTime() == newDateRange[0].getTime() ? d : 0
                )
              ),
              new Date( w.getTime() - msPerDay )
            ];
          }
        }
      } else {
        //начальная дата диапазона обратных рейсов не меньше конечной даты диапазона прямых рейсов
        w = inputToDate(endDateFrom);
        if(
          w && (
            d = newDateRange[0].getTime() - w.getTime() - msPerDay
          ) < 0
        ){
          if ((!l || -d>msPerDay) && inputToDate(beginDateFrom).getTime() == w.getTime()) {
            newDateRange = [w,w];
          } else {
            b = inputToDate(endDateTo);
            newDateRange = [
              new Date( w.getTime() + msPerDay ),
              new Date(
                w.getTime() + msPerDay + l + (
                  b && b.getTime() == newDateRange[1].getTime() ? d : 0
                )
              )
            ];
          }
        }
      }
      if (newDateRange[0] > newDateRange[1]) newDateRange.reverse();
      // не даем увеличить рамку шире 7ми дней
      if(
        newDateRange[1].getTime() - newDateRange[0].getTime() > 6 * msPerDay
      ){
        if(
          (
            isFrom ? inputToDate(beginDateFrom) : inputToDate(beginDateTo)
          ).getTime() != newDateRange[0].getTime()
        ){
          newDateRange[1] = new Date( newDateRange[0].getTime() + 6 * msPerDay );
        } else {
          newDateRange[0] = new Date( newDateRange[1].getTime() - 6 * msPerDay );
        }
      }
      for(
        l = Math.floor(
          ( newDateRange[0].getTime() - firstDrawingDate.getTime() ) / msPerDay
        ),
        oDJP = djp(containerDJP).child().child().child().hasNode('B');
        oDJP.node && l--;
        oDJP.more()
      ){}
      d = [oDJP.node];
      
      for(
        l =  Math.floor(
          ( newDateRange[1].getTime() - newDateRange[0].getTime() ) / msPerDay + 1
        );
        oDJP.node && l--;
        oDJP.more()
      ){
        /(\D|^)1\D*\</.test(oDJP.node.innerHTML) && (b=djp(oDJP).prev().hasClass('monthBlock-border').node) && (b.style.cssText = "display:none;");
        oDJP.toggleClass( isFrom ? 'selectFrom' : 'selectTo', true );
      }
      d[1] = oDJP.node;
      
      l = d[0]?
        d[0].offsetLeft + d[0].parentNode.offsetLeft :
        Math.floor(
          (( newDateRange[0].getTime() - firstDrawingDate.getTime() ) / msPerPix ) / 2
        ) * 2;
      w = d[1]?
        d[1].offsetLeft + d[1].parentNode.offsetLeft - l :
        Math.floor(
          (( newDateRange[1].getTime() - newDateRange[0].getTime() ) / msPerPix ) / 2
        ) * 2 + pixPerDay;

      for(
        oDJP = djp(containerDJP).child().child().hasClass('dateRangeControl');
        oDJP.node && (oDJP.classIs('from') != isFrom);
      ){
        oDJP.more()
      }

      datesIsNear = isFrom?
          (datesIsNear = inputToDate(beginDateTo)) && (datesIsNear.getTime() - newDateRange[1].getTime() <= msPerDay) :
          (datesIsNear = inputToDate(endDateFrom)) && (newDateRange[0].getTime() - datesIsNear.getTime() <= msPerDay);

      oneDayFromAndTo = datesIsNear && (isFrom?
        inputToDate(beginDateTo).getTime() == inputToDate(endDateTo).getTime() &&
        newDateRange[0].getTime() ==  newDateRange[1].getTime() &&
        inputToDate(beginDateTo).getTime() == newDateRange[0].getTime() :
        inputToDate(beginDateFrom).getTime() == inputToDate(endDateFrom).getTime() &&
        newDateRange[0].getTime() ==  newDateRange[1].getTime() &&
        inputToDate(beginDateFrom).getTime() == newDateRange[0].getTime()
      );

      if(oDJP.node){
        oDJP.node.style.cssText = 'left:' + l + 'px;width:' + w + 'px';//l-16 w+30
        isFrom && (d = djp(containerDJP).child().child().hasClass('dateRangeControl to')).node && d.before(oDJP);
      } else {
        isFrom && (d = djp(containerDJP).child().child().hasClass('dateRangeControl to')).node? 
            d.before(djp(datesFrameTemplate({'isFrom':isFrom, 'left':l, 'width':w}))) : 
            djp(containerDJP).child().append(datesFrameTemplate({'isFrom':isFrom, 'left':l, 'width':w}));//l-16 w+30
      }

      for(
        oDJP = djp(containerDJP).child().child().hasClass('dateRangeControl');
        oDJP.node && oDJP.toggleClass('m-near-control m-oneday-from-to',oneDayFromAndTo? 'm-oneday-from-to' : datesIsNear && 'm-near-control');
        oDJP.more()
      ){
        (oDJP.classIs('from') != isFrom) || oDJP.toggleClass('only-one-day',!(newDateRange[1].getTime() - newDateRange[0].getTime()));
      }

      isFrom? dateRangesFrom = [newDateRange[0],newDateRange[1]] : dateRangesTo = [newDateRange[0],newDateRange[1]];

    } else isFrom? dateRangesFrom = newDateRange : dateRangesTo = newDateRange;

    oDJP = djp(containerDJP).child().child().hasClass('dateRangeControl middleControl');
    b = [(b = djp(containerDJP).child().child().hasClass('dateRangeControl')).node, b.more().node];
      
    //добавляем инструмент для перетаскивания обеих дат
    if (!datesIsNear && dateRangesTo && b[0] && b[1]) {
      (b[1].offsetLeft < b[0].offsetLeft) && b.reverse();
      l = b[0].offsetLeft + b[0].offsetWidth;
      w = b[1].offsetLeft - l;
      b = w + b[0].offsetWidth + b[1].offsetWidth >= containerDJP.node.offsetWidth;
      if (oDJP.node) {
        oDJP.node.style.cssText = 'left:' + l + 'px;' + 'width:' + w + 'px;';
        oDJP.toggleClass('dsbl', b);
      } else {
        djp(containerDJP).child().append(
          '<div class="dateRangeControl middleControl'+(b? ' dsbl' : '')+
            '" style="left:' + l + 'px;width:' + w + 'px;"></div>');
      }
    } else oDJP.node && oDJP.node.parentNode.removeChild(oDJP.node);

    w = (isFrom ?  beginDateFrom : beginDateTo);
    if(newDateRange){
      w.disabled = false;
      w.value = djp.date2txt(newDateRange[0])
    } else {
      w.value = '';
      w.disabled = true;
    }
    w = (isFrom ?  endDateFrom : endDateTo);
    if(newDateRange){
      w.disabled = false;
      w.value = djp.date2txt(newDateRange[1])
    } else {
      w.value = '';
      w.disabled = true;
      for(
        oDJP = djp(containerDJP).child().child().hasClass('dateRangeControl');
        oDJP.node && oDJP.toggleClass('m-near-control m-oneday-from-to',false) && (oDJP.classIs('from') != isFrom);
      ){
        oDJP.more()
      }
      if(oDJP.node){
        oDJP.node.parentNode.removeChild(oDJP.node)
        r_avia.form.toggleSearchButton();
      }
    }
    //включить/выключить кнопку поиска
    r_avia.parts.searchFormIsNotFill();
  }
  
  //управление рамкой диапазона дат.
  (function(){
    var
    currMoveMethod,
    currOvered,
    currODJp = {},
    currPos,
    outTime,
    leftScrollLimit,
    rightScrollLimit,
    scrollDiff = 60,
    moveDir,
    className,
    minPosX,
    maxPosX;
    
    containerScrollBy = function (delta){
      var
      offWidth = containerDJP.node.offsetWidth,
      maxLeftScroll = containerDJP.node.firstChild.offsetWidth -
      offWidth,
      tmp = containerScrollLeft - delta + offWidth - (
        maxDate.getTime() - firstDrawingDate.getTime()
      ) / msPerPix;
      
      if(tmp > 0){
        currPos -= tmp;
        delta += tmp;
      }
      containerScrollLeft -= delta;
      if(containerScrollLeft < containerScrollLeftMin){
        containerScrollLeft = containerScrollLeftMin;
      }
      if( containerScrollLeft > maxLeftScroll ){
        drawMonths();
        if (currODJp.node) {
          for(
            tmp = djp(containerDJP).child().child().hasClass('dateRangeControl');
            tmp.node && ((tmp.classIs("from") != currODJp.classIs("from")) || (tmp.classIs("middleControl") != currODJp.classIs("middleControl")));
          ){
              tmp.more()
          }
          currODJp = tmp.toggleClass('m-hovered-control', true);
        }
      } else {
        setContainerScrollLeft(containerDJP.node.scrollLeft = containerScrollLeft);
      }
    }
    function resizeSelectionBox(addDelta){
      var res;
      
      if( addDelta > 0 || containerScrollLeft > containerScrollLeftMin ){
        currPos += addDelta;
      }
      if( res = Math.round(currPos / pixPerDay) * pixPerDay ){
        currPos += currMoveMethod(-res);
        currPos -= res;
        setNewDates(className == 'from' ? dateRangesFrom : dateRangesTo, className == 'from');
        r_avia.form.toggleSearchButton();
        setContainerScrollLeft(containerScrollLeft);
      }
    }
    function toggleHover(oDJP,set,timeout){
      if (timeout) {
        outTime = setTimeout(function(){ timeout = false; toggleHover(oDJP,set) }, 500);
      } else {
        outTime && (outTime = clearTimeout(outTime) && false);
        oDJP.toggleClass('m-hovered-control', set);
        for(
          var tmp = djp(containerDJP).child().child().hasClass('dateRangeControl');
          tmp.node && (tmp.node === oDJP.node);
        ){
          tmp.more()
        }
        tmp.node && tmp.toggleClass('m-unhovered-control', set);
        return oDJP;
      }
    }
    djp(document).attach(
      'mousemove',
      function(oDJP, oEvent){
        var tmp;
        if ((tmp = djp(oDJP).ancestor().hasClass('dateRangeControl')).node && (tmp.classIs('from')? dateRangesFrom : dateRangesTo)) {
          if (tmp.node !== currOvered) {
            currODJp.node?
              currOvered = tmp.node :
              (
                currOvered && toggleHover(djp(currOvered), false),
                (currOvered = toggleHover(tmp, true).node)
              );
          }
        } else currOvered && (currOvered = currODJp.node? false : !toggleHover(djp(currOvered), false));
      }
    );
    djp.makeMovable(
      function(oDJP, oEvent, posX){
        var
        t,
        tmp,
        fns =[
          function (res){
            var
            r = 0,
            tmp,
            todayTime = new Date;
            
            if(tmp = className == 'from' ? dateRangesFrom : dateRangesTo){
              r = tmp[0].getTime() - res * msPerPix;
              todayTime.setHours(12,0,0,0);
              todayTime = todayTime.getTime();
              if( r > tmp[1].getTime() ){
                r = ( r - tmp[0].getTime() ) / msPerPix;
              } else if( r < todayTime ){
                r = (r - todayTime) / msPerPix;
                tmp[0] = new Date(todayTime);
              } else if( tmp[1].getTime()-r > 6*msPerDay ){
                r = ( r - tmp[1].getTime() + 6*msPerDay ) / msPerPix;
              } else {
                tmp[0] = new Date(r);
                r = 0;
                rightScrollLimit += res;
              }
            }
            return r
          },
          function (res){
            var r = 0, tmp;
            
            if(tmp = className == 'from' ? dateRangesFrom : dateRangesTo){
              r = tmp[1].getTime() - res * msPerPix;
              if( r < tmp[0].getTime() ){
                r = ( r - tmp[0].getTime() ) / msPerPix;
              } else if( r - tmp[0].getTime() > 6*msPerDay ){
                r = ( r - tmp[1].getTime() ) / msPerPix;
              } else {
                tmp[1] = new Date(r);
                leftScrollLimit -= res;
                r = 0;
              }
            }
            return r
          },
          function (res){
            var
            r = 0,
            tmp,
            i,
            todayTime = new Date;
              
            if(tmp = className == 'from' ? dateRangesFrom : dateRangesTo){
              todayTime.setHours(12,0,0,0);
              todayTime = todayTime.getTime();
              for(i=2; i--;){
                tmp[i] = new Date( tmp[i].getTime() - res * msPerPix )
              }
              r =  tmp[0].getTime() - todayTime;
              if(r < 0){
                for(i=2; i--;){
                  tmp[i] = new Date( tmp[i].getTime() - r )
                }
                r =  r / msPerPix;
              } else {
                r = 0;
              }
            }
            return r;
          },
          function (res){
            var
            r = 0,
            tmp,
            i,
            j,
            todayTime = new Date;
              
            if(dateRangesFrom && dateRangesTo){
              tmp = [dateRangesFrom, dateRangesTo];
              todayTime.setHours(12,0,0,0);
              todayTime = todayTime.getTime();
              for (i=2; i--;){
                for(j=2; j--;){
                  tmp[i][j] = new Date( tmp[i][j].getTime() - res * msPerPix )
                }
              }
              r =  tmp[0][0].getTime() - todayTime;
              if(r < 0){
                for (i=2; i--;){
                  for(j=2; j--;){
                    tmp[i][j] = new Date( tmp[i][j].getTime() - r )
                  }
                }
                r =  r / msPerPix;
              } else {
                r = 0;
              }
            }
            setNewDates(dateRangesFrom,dateRangesTo);
            return r
          }
        ],
        formElems;
        
        currPos = 0;
        currODJp = {};
        if(
          oEvent.$type.search(/(wheel|scroll)/i)==-1 &&
          (tmp = djp(oDJP).ancestor().hasClass('dateRangeControl')).node &&
          (tmp.classIs('from')? dateRangesFrom : dateRangesTo)
        ){
          minPosX = containerDJP.offset().left;
          maxPosX = minPosX + containerDJP.node.offsetWidth;
          formElems = djp(containerDJP).ancestor().hasNode('FORM').node.elements;
          if( tmp.classIs('from') ){
            className = 'from';
            dateRangesFrom = [
              inputToDate(formElems['df']),
              inputToDate(formElems['dfr'])
            ];
          } else {
            className = '';
            dateRangesTo = [
              inputToDate(formElems['dt']),
              inputToDate(formElems['dtr'])
            ];
          }
          currMoveMethod = fns[
            t = djp(oDJP).ancestor().hasClass('beginSideEdge').node ?
              0 : djp(oDJP).ancestor().hasClass('endSideEdge').node ?
                1 : djp(oDJP).ancestor().hasClass('middleControl').node ?
                  3 :
                  2
          ];

          currODJp = tmp;
          moveDir = {};
          
          if (tmp.classIs('middleControl')) {
            if (tmp.classIs('dsbl')) return (currODJp={}) && false;
            tmp = djp(containerDJP).child().child().hasClass('dateRangeControl');
            leftScrollLimit = posX - tmp.hasClass('from').offset().left;
            rightScrollLimit = tmp.hasClass('to').node.offsetWidth - (posX - tmp.offset().left);
            scrollDiff = leftScrollLimit+rightScrollLimit>containerDJP.node.offsetWidth-400?
              (containerDJP.node.offsetWidth - (leftScrollLimit+rightScrollLimit))/2 :
              200;
          } else {
            leftScrollLimit = posX - tmp.offset().left;
            rightScrollLimit = tmp.node.offsetWidth - leftScrollLimit;
            scrollDiff = 60;
          }
         
          if (!dateRangesTo && (tmp = djp(containerDJP).child().child().hasClass('dateRangeControl to').node)) {
            tmp.parentNode.removeChild(tmp);
          }
          return true
        }
      },
      function(delta, posX){
        /* промотка при изменениии диапазона
         */
        var dt = posX - minPosX -(
          posX + rightScrollLimit < maxPosX - scrollDiff ? leftScrollLimit : 0
         );
        moveDir.dir || (moveDir = {state:true,dir:delta});
        scrolled = true;
        if((moveDir.state? delta>0 : true) && dt < scrollDiff ) {
          containerPermanetlyScrollBy( (scrollDiff - (dt < 0 ? 0 : dt)) / 2, resizeSelectionBox )
        } else {
          dt = posX - maxPosX + (
            posX - leftScrollLimit > minPosX + scrollDiff ? rightScrollLimit : 0
          );
          if((moveDir.state? delta<0 : true) &&  dt > -scrollDiff ){
            containerPermanetlyScrollBy( (-scrollDiff - (dt > 0 ? 0 : dt)) / 2, resizeSelectionBox )
          } else {
            containerPermanetlyScrollBy(0);
            resizeSelectionBox(0);
          }
        }
        moveDir.dir && delta && (moveDir.dir>0 != delta>0) && (moveDir = {dir:1,state:false});
        currPos -= delta * scrollMultipler;
      },
      function(b){
        selectionBoxIsMoved = b;
        containerDJP.toggleClass('jsWResize', b);
        if(b){
          currODJp.node && toggleHover(currODJp,true);
        } else {
          if (currODJp.node){
            currOvered ?
            currOvered !== currODJp.node && (
              toggleHover(currODJp, false),
              toggleHover( djp(currOvered), true )
            ) :
            toggleHover(currODJp, false, true);
          }
          currODJp = {};
          isScrolledByMonth || containerPermanetlyScrollBy(0);
        }
      },
      true
    );


    var hasTouch = false,
      scrolled = false;
    
    //таскание видимой области линейки дат.
    djp.makeMovable(
      function(oDJP,oEvent){
      var tmp = oDJP.node;
      return (oDJP.ancestor().hasClass('dateRangeContainer').node && oEvent.$type.search(/(wheel|scroll)/i)>0) ||
        oDJP.ancestor().hasClass('monthBlock').node ||
        ((!dateRangesTo || !dateRangesFrom) && djp(tmp).ancestor().hasClass('dateRangeControl '+(!dateRangesFrom? 'from' : 'to')).node) ||
        djp(tmp).ancestor().hasClass('dateRangeControl middleControl dsbl').node ||
        djp(tmp).ancestor().hasClass("static-monthName").node
      },
      function(delta){
        Math.abs(delta)>1 && (scrolled = true);
        containerScrollBy(delta * scrollMultipler * revers);
      },
      function(b){
        containerDJP.toggleClass('jsWResize', b);
        if(b){
          scrolled = false;
        }
      }
    );

    //вставляет диапазон вылетов туда, а затем и обратно
    djp.clickTouch(function(oDJP){
      return !scrolled && (!dateRangesFrom || (!dateRangesTo && !isOneWay) ) && oDJP.ancestor().hasClass('dateRangeContainer').node
    }, function(oDOM,oEvent){
      scrolled = false;
      var
      msDelta,
      sF,
      t;

      if(!oEvent.$type.indexOf('touch')){
        hasTouch = true;
      }
      msDelta = Math.floor(
        (
          (
            hasTouch ?
            oEvent.e['changedTouches'][0]['pageX'] :
            oEvent.pos().x
          ) -
          containerDJP.offset().left +
          containerDJP.node.scrollLeft
        ) / pixPerDay
      ) * pixPerDay * msPerPix;
      t = new Date( firstDrawingDate.getTime() + msDelta );
      t.setHours(12,0,0,0);
      sF = !dateRangesFrom? SizeFactorFrom : SizeFactorTo;
      t = dateRangesTmp || [
        new Date( t.getTime() - sF[0]*msPerDay),
        new Date( t.getTime() + sF[1]*msPerDay )
      ];

      for(
        oDOM = djp(containerDJP).child().child().hasClass('dateRangeControl');
        oDOM.node;
        oDOM.more()
      ){
        (msDelta = djp(oDOM).child().child().hasClass("b-daterange-label").node) && (msDelta.style.display="block");
      }

      if(!dateRangesFrom){
        dateRangesFrom = t;
      } else {
        dateRangesTo = t;
      }
      setNewDates( t, t === dateRangesFrom );
      r_avia.form.toggleSearchButton();
    });
  })();

  //подсветка, куда вставится рамка диапазона дат.

  (function(){
    var scrollTimer, 
        sizeFactorFrom = 1,
        sizeFactorTo = 1,
        arr = [
          djp(containerDJP).next().hasClass('scrollPrev').node, 
          djp(containerDJP).next().hasClass('scrollNext').node
        ], 
        getDayLimit = function(px){
          var msDelta = Math.floor(
            (
              (
                px -
                containerDJP.offset().left
              ) +
              containerDJP.node.scrollLeft
            ) / pixPerDay
          ) * pixPerDay * msPerPix;
          msDelta = new Date( firstDrawingDate.getTime() + msDelta );
          msDelta.setHours(12,0,0,0);
          return msDelta;
        }, 
        hideRange = function(){
          for(
            var txt = [],
            t = djp(containerDJP).child().child().hasClass('dateRangeControl');
            t.node;
            t.more()
          ){
            if(
              t.classIs('from')? !dateRangesFrom : !dateRangesTo
            ){
              txt[txt.length] = t.node
            }
          }
          for( t = txt.length; t--; ){
            txt[t].parentNode.removeChild(txt[t])
          }
        };

    SizeFactorFrom = [Math.floor((sizeFactorFrom - 1) / 2),  Math.ceil((sizeFactorFrom - 1) / 2)];
    SizeFactorTo = [Math.floor((sizeFactorTo - 1) / 2),  Math.ceil((sizeFactorTo - 1) / 2)];
    
    djp(document).attach(
      'mousemove',
      function(oDJP, oEvent){
        if(scrollTimer === UNDEF){
          scrollTimer = setTimeout(
            function(){
              var
              sF, 
              msDelta,
              txt,
              t,
              tmp = oDJP.node,
              todayTime;
              
              if(
                !selectionBoxIsMoved && (
                  !dateRangesFrom || ( !dateRangesTo && !isOneWay )
                )
              ){
                if(
                  oDJP.ancestor().hasClass('monthBlock').node ||
                  djp(tmp).ancestor().hasClass('dateRangeControl '+(!dateRangesFrom? 'from' : 'to')).node
                ){
                  sF = !dateRangesFrom? SizeFactorFrom : SizeFactorTo;
                  t = getDayLimit(oEvent.pos().x - (pixPerDay/2)*Math.abs(sF[1]-sF[0])/((sF[1]-sF[0])||1));

                  todayTime = new Date;
                  todayTime.setHours(12,0,0,0);

                  t = [
                    new Date(
                      Math.max(
                        getDayLimit(djp(arr[0]).offset().left + arr[0].offsetWidth + pixPerDay),
                        todayTime.getTime(), 
                        t.getTime() - sF[0]*msPerDay
                      )
                    ).getTime(), 
                    new Date(
                      Math.min(
                        getDayLimit(djp(arr[1]).offset().left - pixPerDay),
                        t.getTime() + sF[1]*msPerDay
                      )
                    )
                  ];

                  if (dateRangesFrom && dateRangesFrom[1].getTime() + msPerDay > t[0]) {
                    t = [dateRangesFrom[1].getTime() + msPerDay, dateRangesFrom[1].getTime() + msPerDay + (sF[0]+sF[1])*msPerDay];
                  }

                  if (dateRangesTo && dateRangesTo[0].getTime() - msPerDay < t[1]) {
                    t = [dateRangesTo[0].getTime() - msPerDay - (sF[0]+sF[1])*msPerDay, dateRangesTo[0].getTime() - msPerDay];
                  }

                  t = [
                    new Date(
                      Math.max(
                        getDayLimit(djp(arr[0]).offset().left + arr[0].offsetWidth + pixPerDay),
                        todayTime.getTime(), 
                        t[0]
                      )
                    ).getTime(), 
                    new Date(
                      Math.min(
                        getDayLimit(djp(arr[1]).offset().left - pixPerDay),
                        t[1]
                      )
                    )
                  ];

                  if ( t[1] - t[0] < 0) hideRange();
                  else {
                    dateRangesTmp = [
                      new Date( t[0] ),
                      new Date( t[1] )
                    ];
                    txt = [
                      Math.floor(
                        (( dateRangesTmp[0].getTime() - firstDrawingDate.getTime() ) / msPerPix ) / 2
                      ) * 2,
                      Math.floor(
                        (( dateRangesTmp[1].getTime() - dateRangesTmp[0].getTime() ) / msPerPix ) / 2
                      ) * 2 + pixPerDay
                    ];

                    if(
                      (
                        t = djp(containerDJP)
                        .child().child()
                        .hasClass(
                          'dateRangeControl ' +
                          (!dateRangesFrom ? 'from' : 'to')
                        )
                      ).node
                    ){
                      t.node.style.cssText = 'left:' + txt[0] + 'px;width:' + txt[1] + 'px';
                    } else {
                      t = djp(containerDJP).child().append(datesFrameTemplate({'isFrom':!dateRangesFrom, 'left':txt[0], 'width':txt[1]}));
                    }

                    t = (t=t.child().child().hasClass("b-daterange-label")).node? t.node.style : {};
                    
                    if (
                      (tmp = djp(containerDJP).next().child().hasClass("b-scroll-label")).node &&
                      tmp.offset().left + tmp.node.offsetWidth > txt[0]+10 - containerScrollLeft
                    ) t.display = "none";
                    else {
                      for (
                        tmp = djp(containerDJP).child().child().hasClass("monthBlock").child().hasClass("mothName");
                        tmp.node && (tmp.node.offsetLeft + tmp.node.parentNode.offsetLeft + tmp.node.offsetWidth < txt[0]+10);
                        tmp.more()
                      ) {};
                      t.display = (tmp.node && (tmp.node.offsetLeft + tmp.node.parentNode.offsetLeft < txt[0]+txt[1]-10))? "none" : "block";
                    }
                  }
                } else {
                  hideRange()
                }
              }
              scrollTimer = UNDEF;
            },
            30
          );
        }
      }
    );
  })();

  //Билет в одну сторону
  djp.clickTouch(
    function(oDJP){
      return oDJP.ancestor().hasClass('b-ticket-search__swap').node;
    },
    function(oDOM, oEvent){
      //таймаут, чтобы дать время отработать переключателю
      setTimeout(
        function(){
          isOneWay = !djp(
            oDOM
          ).child().hasClass('js-checkpaks-any')
          .classIs('b-hor-chooser__item-selected');
          setNewDates( dateRangesTo = UNDEF, false );
          setContainerScrollLeft(containerScrollLeft);
        }, 0
      );
    }
  );
  //fix сбрасывания скролла в FF4
  if(isGecko && navigator.userAgent.indexOf('Firefox/4.') != -1){
    djp(document).attach(
      'DOMContentLoaded',
      function(){
        if(containerScrollLeft < containerScrollLeftMin){
          containerScrollLeft = containerScrollLeftMin;
        }
        setContainerScrollLeft(containerDJP.node.scrollLeft = containerScrollLeft);
      }
    );
  }
  //переключалка класса стиля focusOff на focusOn када фокус на нем или его детях
  (function(doc){
    var lastDJP;
    
    djp(doc).attach(
      'click keyup',
      function(oDJP, oEvent){
        var tmp;
        
        if( lastDJP && lastDJP.node ){
          lastDJP.toggleClass('focusOn', 'focusOff');
        }
        lastDJP = djp(tmp = doc.activeElement).ancestor().hasClass('focusOff');
        lastDJP.node && (
          doc.hasFocus ? doc.hasFocus() : tmp.readOnly === false
        ) && lastDJP.toggleClass('focusOff', 'focusOn');
      }
    );
  })(document);
  //Стрелки промотки
  (function(doc){
    var hasTouch = false,
      upEvent, 
      monthStepLimit, 
      curLimit,
      setCurLimit = function(i,n,range,range2,Off,Off2){
        if (dateRangesTo && dateRangesFrom) {
          curLimit = [dateRangesFrom[0].getTime(), dateRangesTo[1].getTime(), 1];
          if ((curLimit[1] - curLimit[0]) / msPerPix >= containerDJP.node.offsetWidth) {
            curLimit = Off?
              [range2[0].getTime(), range2[1].getTime(), 1] :
              Off2?
                [range[n].getTime(), range[n].getTime() + i*2*msPerDay, 1+i] :
                [range[0].getTime(), range[1].getTime(), 1];
          }
        } else curLimit = [(dateRangesFrom||dateRangesTo)[0].getTime(), (dateRangesFrom||dateRangesTo)[1].getTime(), 1];
        curLimit = Math.max(
          (curLimit[0] + (curLimit[1] - curLimit[0] + msPerDay)/2- firstDrawingDate.getTime()) / msPerPix - curLimit[2]*containerDJP.node.offsetWidth/2,
          containerScrollLeftMin
        );
        curLimit -= Math.max(0,curLimit + djp(containerDJP).node.offsetWidth - (maxDate.getTime() - firstDrawingDate.getTime()) / msPerPix);
        containerScrollBy(i*(i*(containerScrollLeft-curLimit) - Math.ceil(i*(containerScrollLeft-curLimit)/60)*60));
      }, 
      setMonthStepLimit = function(i){
        monthStepLimit = new Date(firstDrawingDate.getTime() + 
            (containerScrollLeft + containerDJP.node.offsetWidth/2 + i*containerDJP.node.offsetWidth/2) * msPerPix// - i*pixPerDay
          );
        monthStepLimit.setMonth(monthStepLimit.getMonth() + Math.max(i,0));
        monthStepLimit.setDate(Math.max(i,0));
        monthStepLimit.setHours(12,0,0,0);
        monthStepLimit = Math.max(
          (monthStepLimit.getTime() - firstDrawingDate.getTime()) / msPerPix - Math.max(i,0) * containerDJP.node.offsetWidth + i*5*pixPerDay, 
          containerScrollLeftMin
        );
        monthStepLimit -= Math.max(0,monthStepLimit + containerDJP.node.offsetWidth - (maxDate.getTime() - firstDrawingDate.getTime()) / msPerPix);
        containerScrollBy(i*(i*(containerScrollLeft-monthStepLimit) - Math.ceil(i*(containerScrollLeft-monthStepLimit)/120)*120));
        monthStepLimit *= i;
      };
    
    djp(doc).attach(
      'mousedown touchstart',
      function(oDJP, oEvent){
        var
        tmp,
        geoFormDJP,
        i = 1;
        
        if(oEvent.$type == 'touchstart'){
          hasTouch = true;
        }
        if(oEvent.$type == 'mousedown' ^ hasTouch){
          tmp = hasTouch ? oEvent.e['changedTouches'][0]['target'] : oDJP;
          geoFormDJP = djp(tmp).ancestor().hasClass('scrollPrev');
          if(
            !geoFormDJP.node
          ){
            i = -1;
            geoFormDJP = djp(tmp).ancestor().hasClass('scrollNext');
          }
          geoFormDJP = geoFormDJP.ancestor().hasClass('geoForm');
          
          geoFormDJP.node && (djp(tmp).ancestor().hasClass('b-calendar-fast-arr').node?
            (
              setMonthStepLimit(-i),
              (isScrolledByMonth = containerPermanetlyScrollBy(120 * i,  
                function(delta){
                  if (-i*containerScrollLeft>=monthStepLimit) isScrolledByMonth = containerPermanetlyScrollBy(0) && false;
                }
              ) || true)
            ) : 
            (
              (upEvent = true),
              containerPermanetlyScrollBy(60 * i)
            )
          );
        }
      }
    );
    djp(doc).attach(
      'mouseup touchend', 
      function(oDJP, oEvent){
        if (upEvent) {
          isScrolledByMonth || containerPermanetlyScrollBy(0);
          upEvent = false;
        }
      }
    )
    djp(doc).attach(
      'click',
      function(oDJP, oEvent){
          if (djp(oDJP).ancestor().hasClass('js-red-popup-l').node) {
            setCurLimit(-1,0,dateRangesFrom,dateRangesTo,offScreenDates[0][2],offScreenDates[1][3]);
            containerPermanetlyScrollBy(60,function(delta){
              if (containerScrollLeft<=curLimit) containerPermanetlyScrollBy(0);
            });
          } else if (djp(oDJP).ancestor().hasClass('js-red-popup-r').node) {
            setCurLimit(1,1,dateRangesTo,dateRangesFrom,offScreenDates[1][1],offScreenDates[0][0]);
            containerPermanetlyScrollBy(-60,function(delta){
              if (containerScrollLeft>=curLimit) containerPermanetlyScrollBy(0);
            });
          }
      }
    );
  })(document);
  
  //фикс изменения масштаба (Ctrl+Scroll)
  djp(window).attach(
      'resize',
      function(oDJP, oEvent){
        containerScrollBy(0);
        var d, toDJP, DateRange, l, w, fromNode, toNode;
        for(
          oDJP = djp(containerDJP).child().child().hasClass('dateRangeControl');
          oDJP.node && (DateRange = oDJP.classIs('from')? ((fromNode = oDJP.node),dateRangesFrom) : ((toNode = oDJP.node),dateRangesTo));
          oDJP.more()
        ){
          for(
            l = Math.floor(
              ( DateRange[0].getTime() - firstDrawingDate.getTime() ) / msPerDay
            ),
            toDJP = djp(containerDJP).child().child().child().hasNode('B');
            toDJP.node && l--;
            toDJP.more()
          ){}
          d = [toDJP.node];
          for(
            l =  Math.floor(
              ( DateRange[1].getTime() - DateRange[0].getTime() ) / msPerDay + 1
            );
            toDJP.node && l--;
            toDJP.more()
          ){}
          d[1] = toDJP.node;
          l = d[0]?
            d[0].offsetLeft + d[0].parentNode.offsetLeft :
            Math.floor(
              (( DateRange[0].getTime() - firstDrawingDate.getTime() ) / msPerPix ) / 2
            ) * 2;
          w = d[1]?
            d[1].offsetLeft + d[1].parentNode.offsetLeft - l :
            Math.floor(
              (( DateRange[1].getTime() - DateRange[0].getTime() ) / msPerPix ) / 2
            ) * 2 + pixPerDay;


          oDJP.node.style.cssText = 'left:' + l + 'px;width:' + w + 'px';//l-16 w+30
        }

        oDJP = djp(containerDJP).child().child().hasClass('dateRangeControl middleControl');
      
        if (oDJP.node) {
          l = fromNode.offsetLeft + fromNode.offsetWidth;
          w = toNode.offsetLeft - l;
          oDJP.node.style.cssText = 'left:' + l + 'px;' + 'width:' + w + 'px;';
        };
      }
  );
})(
//шаблон окошка дат
'<div class="dateRangeControl<%= c.isFrom ? " from" : " to" %>" style="left:<%= c.left %>px;width:<%= c.width %>px">\n\
    <div class="beginSideEdge"></div>\n\
    <div class="core">\n\
        <div class="dateRangeControl_bdb"></div>\n\
        \n\
        <span class="b-daterange-label">\n\
            <i class="b-daterange-flight-icon"></i>\n\
            <%= c.isFrom ? "Туда" : "Обратно" %>\n\
        </span>\n\
    </div>\n\
    <div class="endSideEdge"></div>\n\
</div>'
);

"use strict";
r_avia.cart = {};

(function(UNDEF){
  /* Предыдущий хеш */
  var hash = '',
      checkHash = true,
      cartCode = djp(document.forms['cartCode']).hasClass('b-cart-code-form'),
      openCartButton = djp(document.body).child().child().hasClass('b-footer').child().child().hasClass('b-cart__show'), xhr;
  //Костылек, дабы не было ругани на страницах, где нет корзины
  if (!openCartButton.node) {return};
  /*
    alert_obj - объект, в который мы должны поместить ошибку.
    Если этот объект есть, то билеты подгружаем до смены хэша,
      а после смены хэша ничего не делаем.
  */
  r_avia.cart.show_tickets = function(cart_id, comparison, alert_obj) {

      var args = {};
      cart_id = (cart_id + '').replace(/^\s+/, "").replace(/\s+$/, "");

      if (cart_id) {
        args['id'] = cart_id
      } else if (alert_obj) {
        djp(alert_obj).child().node.innerHTML = "Введите номер";
        djp(alert_obj).toggleClass('b-micro-popup-hidden', false);
        setTimeout(function(){djp(alert_obj).toggleClass('b-micro-popup-hidden', true)}, 5000);
        return;
      }
     
      xhr !== UNDEF && xhr.abort();

      xhr = djp.getJSON(
          r_avia.urls.get_cart + "?" + djp.objToQueryString(args),
          function(data) {
              var currentCart;
              
              xhr = UNDEF;
              if (data && !data['error']) {

                  if(data['foreigner']){
                      currentCart = r_avia.cart.carts.someone;
                  } else {
                      currentCart = r_avia.cart.carts.my;
                  }
                  
                  /* Проверяем - если текущая корзина уже открыта - нет смысла снова ее анимировать */
                  if(!currentCart.isOpened){
                      /* Принудительно закрываем другую корзину */
                      
                      if(data['foreigner']){
                          r_avia.cart.carts.my.hide();
                      } else {
                          r_avia.cart.carts.someone.hide();
                      }
                      
                      currentCart.show();
                  }

                  openCartButton.toggleClass( 'm-cart-opened', !(data['foreigner']) );

                  r_avia.cart.set_current_cart(currentCart);

                  r_avia.tickets.classifier_update(data['classifier']);
                  
                  
                  r_avia.cart.update_cart_foreigner(data['foreigner']);
                  
                  if(!data['foreigner']){
                      r_avia.cart.update_tickets_count(data['tickets'].length);
                  }
                  
                  r_avia.cart.tickets_update(data['tickets']);
                  
                  r_avia.cart.update_cart_id(data['cart_id']);

                  if (comparison) {
                      r_avia.cart.render_comparison();
                  } else {
                      r_avia.cart.render_tickets();
                  }

                  currentCart.tweenHeight();
                  
                    checkHash = true;

                    djp.$hash.store(
                      ['cart'],
                      {
                        'mode': comparison ? 'table' : 'block',
                        'code': data['cart_id']
                      }
                    );
                    
                    currentCart.cartLinkBaloon.child().hasClass('b-cart-link-popup__input')
                    .node.value = location.protocol + '//' + location.host
                    + '/' + djp.$hash.encode(
                      {'cart': djp.$hash.store(['cart']) }
                    );
                  
              } else {
                  r_avia.cart.empty();
                  var error = data && data['error'] ? data['error'] :
                    "К сожалению, произошла ошибка.";
                  if (alert_obj) {
                    djp(alert_obj).child().node.innerHTML = error;
                    djp(alert_obj).toggleClass('b-micro-popup-hidden', false);
                    setTimeout(function(){djp(alert_obj).toggleClass('b-micro-popup-hidden', true)}, 5000);
                  } else {
                    djp.alert( error );
                  }
              }
          }
      );
  };
  /*Закрываем корзину*/
  djp(document).attach('click',function(el,evt){
      if(djp(el).ancestor().hasClass('b-cart__show').node){
        /* Если висит класс что кнопка disabled - ничего не делаем*/
        if(djp(el).ancestor().hasClass('m-open-disabled').node){
          evt.prevent();
          return
        }
        if(djp(el).ancestor().hasClass('m-cart-opened').node){
          r_avia.cart.carts.my.hide();
          evt.prevent()
      }
    }
  });
  
  /* Элемент формы поиска - будем фейдить */
  var searchFormAnimator = $t(djp(document.forms['geoForm']).child().child().child().hasClass('b-ticket-search-form').node);
  
  /**
   * Конструктор корзины
   * @constructor
   * @param {Element} element Контейнер корзины и фейда
   */
  
  function Cart(element){
      var self = this;

      this.container = element;
      this.isUserCart = false;
      
      /* Элемент корзины */
      this.cartElement = djp(element).child().hasClass('b-cart');
      
      this.cartContent  = djp(this.cartElement).child().hasClass('b-cart__content');
      
      /* Фейд корзины */
      this.fader = djp(element).child().hasClass('b-page-fader');
      
      
      djp(this.cartElement).child().child().hasClass('b-cart__close').attach('click',function(elem,evt){
          self.hide();
          evt.prevent();
      })
      
      djp(this.cartElement).child().child().hasClass('b-cart__logo-dummy').attach('click',function(elem,evt){
          self.hide();
          evt.prevent();
      })
      
      
      var shareContainer = djp(this.cartElement).child().child().child().hasClass('b-cart__share'),
          baloon = shareContainer.child().hasClass('b-cart-link-popup');
      
      this.cartLinkBaloon = baloon;
      
      shareContainer.child().attach('click',function(elem,evt){
          evt.prevent();
          baloon.toggleClass('m-cart-link-popup-show');
          
          djp(self.cartLinkBaloon).child().hasClass('b-cart-link-popup__input').node.select();
      });
      
      djp(baloon).child().hasClass('b-close').attach('click',function(){
          baloon.toggleClass('m-cart-link-popup-show');
      })
  }
  
  Cart.prototype = {
      show:function(){
          this.isOpened = true;

          /* Присваеваем стартовую позицию - top*/
          this.cartElement.node.style.top = document.body.clientHeight + 'px';
          
          this.container.toggleClass( 'hidden' , false );
          
          /* Поднимаем наверх */
          $t(this.cartElement.node).tween({
              top:108,
              time:1.4
          });
          
          /* Вытягиваем контейнер по высоте */
          
          
          /* Фейдим форму*/
          
          searchFormAnimator.tween({
              opacity: 0,
              time:0.45
          });
          
          var self = this;
          
          
      },
      hide: function(){
          var self;

          if(!this.isOpened){
              return false
          }
          self = this;

          r_avia.cart.set_current_cart(UNDEF);
          djp.$hash.store(['cart'], '');
          
          this.isOpened = false;
          
          /* Скрываем ссылку на корзину */
          this.cartLinkBaloon.toggleClass('m-cart-link-popup-show',false);
          
          /* Опускаем корзину вниз */
          $t(this.cartElement.node).tween({
              top: document.body.clientHeight+100,
              time: 1.3
          });
          
          searchFormAnimator.tween({
              opacity: 1,
              time:0.45
          });
          
          /* убиваем высоту контейнера */
          
          $t(this.container.node).tween({
              height: document.body.clientHeight,
              time: 0.4,
              onComplete: function(){
                  /* Скрываем контейнер */
                  self.container.toggleClass( 'hidden' , true );
                  if(self.isUserCart){
                      
                      openCartButton.toggleClass('m-cart-opened', false );
                  }
              }
          });
          
      },
      tweenHeight : function(){
          var height = this.cartContent.node.offsetHeight > document.body.clientHeight ? this.cartContent.node.offsetHeight:document.body.clientHeight;
          
          height += 210;
          
          $t(this.container.node).tween({
              height:height,
              time:0.8
          });
      }
  };
  
  r_avia.cart.carts = {}
  
  /* Пользовательская корзина */
  r_avia.cart.carts.my = new Cart( djp(document.body).child().hasClass('b-cart-container') );
  r_avia.cart.carts.my.isUserCart = true;
  
  /* Чужая корзина */
  r_avia.cart.carts.someone = new Cart( djp(document.body).child().hasClass('b-cart-container-someone') );
  
  /* Следим за изменение хэша и открываем/закрываем нужную корзину */
  djp.$hash.watcher(
    ['cart'],
    function (obj){
      if(checkHash && obj){
        r_avia.cart.show_tickets( obj['code'], obj['mode'] == 'table' );
      }
      checkHash = true;
    }
  );

  if (cartCode.node) {
    /* заменить полную ссылку на код */
    djp('cart-code').attach(
      'change',
      function(oTarget, oEvent){
        var res = /^\s*(\d+)\s*$/.exec(oTarget.node.value);
        if(res){
          res = res[1];
        } else {
          res = djp.$hash.decode( oTarget.node.value.split('#').slice(1).join('#') );
          if(
            res
            && 'cart' in res
            && 'code' in res['cart']
            && /^\d+$/.test( res['cart']['code'] )
          ){
            res = res['cart']['code'];
          } else {
            res = '';
          }
        }
        oTarget.node.value = res;
      }
    );
    cartCode.attach("submit", function(elem,evt){
        r_avia.cart.show_tickets(
          djp('cart-code').node.value,
          true,
          djp('cart-code').prev().node
        );
        evt.prevent();
        return;
    });
  }
})();

/*jslint browser: true, white: true, undef: true, eqeqeq: true,
plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true,
strict: true */
/*global window, $ */
"use strict";
r_avia.tickets = {};
(function(
  blockTemplate,
  fullTicketTemplate,
  cartTemplate,
  comparisonTemplate,
  airlineFilterTemplate,
  UNDEF
){
  var
  k,
  classifier = {},
  transshipmentsFilter = djp.$hash.store(['filters', 'transfers']),
  excludeAirlinesFilter = {},
  dayFilterMin,
  dayFilterMax,
  dayFilterFrom,
  dayFilterTo,
  dayFilterPixPerDay,
  timer,
  xhr,
  container,
  scrollContainer,
  cartContainer = djp(document.body).child().child().hasClass('b-footer')
  .child().child().hasClass('b-cart__content'),
  cartShowContainer = djp('ticketCountId').ancestor().hasClass('b-cart__show'),
  cartCodeContainer ,
  monthNames = 'янв,фев,мар,апр,май,июн,июл,авг,сен,окт,ноя,дек'.split(','),
  dayFullNames = 'воскресенье,понедельник,вторник,среда,четверг,пятница,суббота'.split(','),
  serverError = "К сожалению, произошла ошибка. Попробуйте повторить поиск.",
  aTicketsBlocks = [],
  blocksProperties = {},
  cartForeigner = false,
  cartTickets = [],
  cartTicketsKeys = {},
  tiketsBlockWidth = 320,
  comparisonContanerScroll = 0,
  containerPermanetlyScrollBy = (function(){
    var timer, delay, delta;
    function period(){
      containerScrollBy(delta);
      timer !== UNDEF && clearTimeout(timer);
      timer = setTimeout(period, delay);
    }
    return function(pixPer40Ms){
      delta = pixPer40Ms;
      if(pixPer40Ms){
        pixPer40Ms = Math.abs(1 / pixPer40Ms);
        delay = 40;
        if(pixPer40Ms > 1){
          delay *= pixPer40Ms;
          delta = delta > 0 ? 1 : -1;
        }
        period();
      } else if(timer !== UNDEF){
         clearTimeout(timer);
         timer = UNDEF;
      }
    }
  })(),
  containerVirtualScrollLeft = 0,
  containerVirtualScrollRight = 0,
  containerScrollBy = (function(){
    var
    timer,
    lastTime = (new Date).getTime();
    return function(delta){
      /* Если нет элемента - дальше не идем */
      if(!container.node){
          return
      }
      var x = container.node.scrollLeft + containerVirtualScrollLeft;

      function setScrollButton(){
        djp(container).next().hasClass('m-scroll-left').toggleClass(
          'hidden',
          container.node.scrollLeft < 1 - containerVirtualScrollLeft
        );
        djp(container).next().hasClass('m-scroll-right').toggleClass(
          'hidden',
          container.node.scrollLeft > container.node.scrollWidth
          - container.node.offsetWidth - 14 + containerVirtualScrollRight
        );
      }
      //для ускорения, отключаем отрисовку элементов за пределом прокрутки
      function draw(){
        var
        visibleWidth = container.node.offsetWidth,
        l,
        scrollButtonWidth,
        scrollButtonLeft,
        j,a,l,i,n,m,t,b,k,
        selectedTimeIndex,
        collapseTitleIndex,
        hasExpand,
        more,
        indexKeys,
        currBlockDJP,
        deleteBlocks = [],
        removesBlocks = [],
        appendsBlocks = [],
        insertIndex,
        аirlinesFilterItemsHash = {},
        durac,
        maxIndex = Math.ceil( (x + visibleWidth * 2) / tiketsBlockWidth ),
        minIndex = (
          visibleWidth > x ?
          0 :
          Math.floor( (x - visibleWidth) / tiketsBlockWidth )
        ),
        lastInsertIndex = minIndex;

        
        dayFilterFrom = UNDEF;
        dayFilterTo = UNDEF;
        //создаем плоский массив развернутых и свернутых билетов
        //и хеш хранящий позиции в этом массиве по идентификатору
        for(
          indexKeys = {},
          a = [],
          i = 0,
          l = aTicketsBlocks.length;
          i < l;
          i++
        ){
          for(
            j = 0,
            collapseTitleIndex = false,
            hasExpand = false,
            n = aTicketsBlocks[i].length;
            j < n;
            j++
          ){
            for(
              selectedTimeIndex = false,
              t = aTicketsBlocks[i][j].length;
              t--;
            ){
              if( !( aTicketsBlocks[i][j][t]['id'] in blocksProperties ) ){
                blocksProperties[
                  aTicketsBlocks[i][j][t]['id']
                ] = {};
              }
              blocksProperties[
                aTicketsBlocks[i][j][t]['id']
              ].groupIndex = i;
              blocksProperties[
                aTicketsBlocks[i][j][t]['id']
              ].subgroupIndex = j;
              blocksProperties[
                aTicketsBlocks[i][j][t]['id']
              ].timeIndex = t;
              //Если, после обновления, в этой группе, остался хотя бы один билет
              //из ранее развернутой группы.
              if(
                blocksProperties[
                  aTicketsBlocks[i][j][t]['id']
                ].expand === true
              ){
                hasExpand = true;
              }
              if(
                selectedTimeIndex === false &&
                blocksProperties[
                  aTicketsBlocks[i][j][t]['id']
                ].selectedTime
              ){
                selectedTimeIndex = t;
              }
            }
            aTicketsBlocks[i][j].selectedTimeIndex = selectedTimeIndex || 0;
          }
          for(
            j = hasExpand ? 0 : collapseTitleIndex || 0,
            t = hasExpand ? aTicketsBlocks[i].length : ( j + 1 );
            j < t;
            j++
          ){
            m = aTicketsBlocks[i][j][
              aTicketsBlocks[i][j].selectedTimeIndex
            ];
            blocksProperties[ m['id'] ].expand = hasExpand;
            //фильтруем выдачу по выбранным пересадкам
            for(
              b = !transshipmentsFilter,
              k = 0;
              !b && ( transshipmentsFilter >> k ) > 0;
              k++
            ){
              if(
                transshipmentsFilter & (1<<k)
                && (
                  m['direct']['transshipments'].length == k
                  && !(
                    m['back'] && ('duration' in m['back'])
                    && m['back']['transshipments'].length > k
                  ) || (
                    m['back'] && ('duration' in m['back'])
                    && m['back']['transshipments'].length == k
                    && (
                      m['direct']['transshipments'].length <= k
                    )
                  )
                )
              ){
                b = true
              }
            }
            if( b /* билет прошел фильтр по количеству пересадок */){
              durac = m['back'] && ('duration' in m['back'])
              && Ticket.prototype['stayPeriod'].call(m);
              if(
                durac !== false && (
                  dayFilterFrom === UNDEF ||
                  dayFilterFrom > durac
                )
              ){
                dayFilterFrom = durac;
              }
              if(
                durac !== false && (
                  dayFilterTo === UNDEF ||
                  dayFilterTo < durac
                )
              ){
                dayFilterTo = durac;
              }
              if( /* время пребывания у билета между выбранными значениями */
                durac === false || (
                  (
                    dayFilterMin === UNDEF ||
                    durac >= dayFilterMin
                  ) && (
                    dayFilterMax === UNDEF ||
                    durac <= dayFilterMax
                  )
                )
              ) {
                //формируем список у фильтра авиалиний
                if(
                  !( m['direct']['airline'] in аirlinesFilterItemsHash )
                  || аirlinesFilterItemsHash[ m['direct']['airline'] ].minPrice > m['cost']
                ){
                  аirlinesFilterItemsHash[ m['direct']['airline'] ] = {
                    textName: classifier['airlines'][ m['direct']['airline'] ],
                    minPrice: m['cost']
                  }
                }
                if( !( m['direct']['airline'] in excludeAirlinesFilter ) ){
                  indexKeys[ m['id'] ] = a.length;
                  a[a.length] = new Block( m );
                }
              }
            }
          }
        }
        drawSliderFilter();
        r_avia.tickets.drawAirlinesFilter(аirlinesFilterItemsHash);
        for(
          currBlockDJP = djp(container).child().child().hasClass('js-tiketsBlock');
          currBlockDJP.node;
          currBlockDJP.more()
        ){
          insertIndex = indexKeys[ currBlockDJP.attr('data-ticket-id') ];
          if(insertIndex !== UNDEF){
            if( insertIndex > maxIndex || insertIndex < minIndex ){
              deleteBlocks[deleteBlocks.length] = djp(currBlockDJP);
            } else {
              for(;lastInsertIndex < insertIndex; lastInsertIndex++){
                appendsBlocks[appendsBlocks.length] = currBlockDJP.before( a[lastInsertIndex] + '' );
              }
              lastInsertIndex = insertIndex + 1;
            }
          } else {
            removesBlocks[removesBlocks.length] = djp(currBlockDJP);
          }
        }
        for(i = deleteBlocks.length; i--;){
          deleteBlocks[i].node.parentNode.removeChild(deleteBlocks[i].node);
        }
        l = Math.min(a.length, maxIndex);
        lastInsertIndex < l && djp(container).child().append(
          a.slice(lastInsertIndex, l).join('')
        );
        //отрисовываем удаление блоков
        for(i = removesBlocks.length; i--;){
          removesBlocks[i].node.parentNode.removeChild(removesBlocks[i].node);
        }
        i = a.length - maxIndex;
        i < 0 && (i = 0);
        containerVirtualScrollRight = i * tiketsBlockWidth;
        containerVirtualScrollLeft = minIndex * tiketsBlockWidth;
        //перемещаем скролбар
        l = scrollContainer.node.offsetWidth;
        scrollButtonWidth = Math.round(
          visibleWidth * l / a.length / tiketsBlockWidth
        );
        if(scrollButtonWidth < 50){
          scrollButtonWidth = 50;
        }
        if(scrollButtonWidth > l){
          scrollButtonWidth = 0;
          scrollButtonLeft = 0;
        } else {
          scrollButtonLeft = Math.round(
            x  * (l - scrollButtonWidth) / (
              a.length * tiketsBlockWidth - visibleWidth
            )
          );
        }
        if(scrollButtonLeft < 0){
          scrollButtonLeft = 0;
        }
        if(scrollButtonLeft > l - scrollButtonWidth){
          scrollButtonLeft = l - scrollButtonWidth;
        }
        djp(scrollContainer).child().
        hasClass('b-scroller__button').node.style.cssText = scrollButtonWidth ?
        [
          'margin-left:', scrollButtonLeft,
          'px; width:', scrollButtonWidth,
          'px'
        ].join('') :
        'width:50px;visibility:hidden';
        lastTime = (new Date).getTime();
        container.node.scrollLeft = x - containerVirtualScrollLeft;
        scrollContainer.toggleClass('hidden',!(a.length));
        setScrollButton();
      }
      if(delta !== UNDEF){
        x -= delta;
      }
      timer !== UNDEF && clearTimeout(timer);
      if( (new Date).getTime() - lastTime > 500 ){
        draw();
      } else {
        timer = setTimeout(draw, 20);
      }
      container.node.scrollLeft = x - containerVirtualScrollLeft;
      setScrollButton();
    }
  })();

  container = djp(
    document.forms['geoForm']
  ).next().hasClass('js-tickets');
  scrollContainer = djp( djp(container).next() );

  blockTemplate = djp.tmpl(blockTemplate);
  fullTicketTemplate = djp.tmpl(fullTicketTemplate);
  cartTemplate = djp.tmpl(cartTemplate);
  comparisonTemplate = djp.tmpl(comparisonTemplate);
  //клик и таскание по полосе прокрутки, проматывает билеты
  (function(startPos){
    function setScroll(delta, posX){
      var
      scrollButtonWidth = djp(scrollContainer).child().
      hasClass('b-scroller__button').node.offsetWidth,
      l = scrollContainer.node.offsetWidth - scrollButtonWidth,
      newScrollLeft,
      w = djp(container).child().node.offsetWidth + containerVirtualScrollLeft
      + containerVirtualScrollRight - container.node.offsetWidth;

      posX -= startPos + scrollButtonWidth / 2;
      if(posX < 0){
        posX = 0;
      }
      if(posX > l){
        posX = l;
      }
      newScrollLeft = Math.round( posX * w / l );
      /*
      if(newScrollLeft < 0){
        newScrollLeft = 0
      }
      if(newScrollLeft > w){
        newScrollLeft = w
      }
      */
      containerScrollBy(
        container.node.scrollLeft - newScrollLeft + containerVirtualScrollLeft
      );
    }
    djp.makeMovable(
      function(oDJP, oEvent, posX){
        var b = !{'mousewheel':1,'DOMMouseScroll':1}[oEvent.$type] && !!(
          aTicketsBlocks.length &&
          scrollContainer.node &&
          scrollContainer.contains(oDJP)
        );
        if( b ){
          startPos = scrollContainer.offset().left;
          setScroll(0, posX)
        }
        return b;
      },
      setScroll,
      function(b){
        //djp(document.body).toggleClass('jsWResize', b);
      },
      true
    );
  })();
  r_avia.tickets.drawAirlinesFilter = function(itemsHash){
    var
    k,
    i,
    t,
    aOptions = [],
    b = true,
    selectedAirlines = [],
    minPrice;

    for(k in itemsHash){
      for(i = aOptions.length; i--;){
        if( aOptions[i]['name'] >= itemsHash[k].textName ){
          break
        }
      }
      if(minPrice === UNDEF || minPrice > itemsHash[k].minPrice){
        minPrice =  itemsHash[k].minPrice;
      }
      t = aOptions.slice(++i);
      aOptions.length = i;
      if(b && k in excludeAirlinesFilter){
        b = false;
      }
      aOptions[i] = {
        'name': itemsHash[k].textName,
        'value': k,
        'selected': !(k in excludeAirlinesFilter),
        'minPrice': itemsHash[k].minPrice
      };
      if( !(k in excludeAirlinesFilter) ){
        selectedAirlines[selectedAirlines.length] = itemsHash[k].textName;
      }
      aOptions = aOptions.concat(t);
    }
    aOptions['selectedAll'] = b;
    aOptions['minPrice'] = minPrice;
    if(!b && selectedAirlines.length > 0 ){
      aOptions['val'] = selectedAirlines[0];
      if(selectedAirlines.length > 1){
        aOptions['val'] += ' и еще ' + (selectedAirlines.length - 1);
      }
    } else if(b){
      aOptions['val'] = 'Все авиакомпании';
    }
    setTimeout(
      function(){
        var t, i, k, b;

        for(
          t = document.getElementsByTagName('DL'),
          i = t.length;
          i--;
        ){
          k = djp(t[i]).hasClass('js-param-airlines').child().hasNode('DD');
          b = k.child().child().hasClass('b-avia-companies-dropdown__content');
          if( k.node && b.classIs('hidden') ){
            k.node.innerHTML = airlineFilterTemplate(aOptions);
          }
        }
      },
      5
    );
  }
  function drawSliderFilter(){
    var
    k,
    i,
    t,
    a,
    iMin = dayFilterMin === UNDEF || dayFilterFrom > dayFilterMin ? dayFilterFrom : dayFilterMin,
    iMax = dayFilterMax === UNDEF || dayFilterTo < dayFilterMax ? dayFilterTo : dayFilterMax;

    for(
      t = document.forms['geoForm'].getElementsByTagName('DL'),
      i = t.length;
      i--;
    ){
      a = djp(t[i]).hasClass('js-param-slider');
      k =  a.child().hasNode('DD').child();
      if( k.node ){
        if(iMin === UNDEF){
          a.node.style.display = 'none';
        } else {
          a.node.style.display = '';
          dayFilterPixPerDay = 212 / (dayFilterTo - dayFilterFrom);
          djp(k).child().hasClass('m-slide-indicator-from').child()
          .hasNode('STRONG').node.innerHTML = iMin;
          djp(k).child().hasClass('m-slide-indicator-to').child()
          .hasNode('STRONG').node.innerHTML = iMax + r_avia.h.word_endings(iMax, " дня", " дней", " дней");
          k = djp(k).child().hasClass('b-slide-control__bar').node;
          k.style.cssText = 'margin-left:' + Math.round( (iMin - dayFilterFrom) * dayFilterPixPerDay )
          + 'px; width:' + Math.round(
            (iMax - iMin) * dayFilterPixPerDay + 13
          ) + 'px';
        }
      }
    }
  }
  function Ticket(data){
    var t = this;
    t['id'] = data['id'];
    t['cost'] = data['cost'];
    t['direct'] = data['direct'];
    t['more'] = null;
    if( data['back'] && 'duration' in data['back'] ){
      t['back'] = data['back'];
    }
    t['isCart'] = true;
  }
  Ticket.prototype.toString = function(){
    return blockTemplate(this)
  };
  Ticket.prototype['tiketDate'] = function(){
    return this['direct']['departure']['datetime'].split(' ')[0];
  };
  Ticket.prototype['formatCost'] = function(){
    var res = [], s = this['cost'] + '';
    do {
      res[res.length] = s.slice(-3);
    } while (s = s.slice(0,-3));
    return res.reverse().join(' ');
  };
  Ticket.prototype['stayPeriod'] = function(){
    var
    res = false,
    directDepartureDate,
    backDepartureDate,
    msPerDay = (new Date(0)).setUTCDate(2);
    if(this['back']){
      directDepartureDate = this['direct']['departure']['datetime'].split(/\D+/);
      directDepartureDate[1]--;
      backDepartureDate = this['back']['departure']['datetime'].split(/\D+/);
      backDepartureDate[1]--;
      res = Math.floor( Date.UTC.apply(this, backDepartureDate.slice(0,3)) / msPerDay ) -
      Math.floor( Date.UTC.apply(this, directDepartureDate.slice(0,3)) / msPerDay );
      if(res == 0){
        res++;
      }
    }
    this['stayPeriod'] = function(){
      return res
    };
    return res
  };
  Ticket.prototype['getPlaceName'] = function(isBack, isArrival){
    return classifier['cities'][
      this[isBack ? 'back' : 'direct'
    ][
      isArrival ? 'arrival' : 'departure'
    ]['city'] ];
  };
  Ticket.prototype['getAitportNameWithIATA'] = function(isBack, isArrival){
    var iata = this[isBack ? 'back' : 'direct'][isArrival ? 'arrival' : 'departure']['airport'];
    return classifier['airports'][iata]+" ("+iata+")";
  };
  Ticket.prototype['getTime'] = function(isBack, isArrival){
    return this[
      isBack ? 'back' : 'direct'
    ][
      isArrival ? 'arrival' : 'departure'
    ]['datetime'].split(/\D+/).slice(3,-1).join(':')
  };
  Ticket.prototype['getMounth'] = function(isBack, isArrival){
    return this[
      isBack ? 'back' : 'direct'
    ][
      isArrival ? 'arrival' : 'departure'
    ]['datetime'].split(/\D+/)[1]-1
  };
  Ticket.prototype['getMounthName'] = function(isBack, isArrival){
    return monthNames[
      this['getMounth'](isBack, isArrival)
    ]
  };
  Ticket.prototype['getMounthFullName'] = function(isBack, isArrival){
    return r_avia.h.monthNamesR[
      this['getMounth'](isBack, isArrival)
    ]
  };
  Ticket.prototype['getDate'] = function(isBack, isArrival){
    return this[
      isBack ? 'back' : 'direct'
    ][
      isArrival ? 'arrival' : 'departure'
    ]['datetime'].split(/\D+/)[2]-0
  };
  Ticket.prototype['getDay'] = function(isBack, isArrival){
    var datetime = this[isBack ? 'back' : 'direct'][isArrival ? 'arrival' : 'departure']['datetime'].split(/\D+/);
    datetime[1]--;
    return dayFullNames[(new Date(Date.UTC.apply(this, datetime))).getUTCDay()]
  };
  Ticket.prototype['getTransshipmentsCount'] = function(isBack){
      return this[isBack ? 'back' : 'direct']['transshipments'].length;
  };
  Ticket.prototype['getTransshipmentsInfo'] = function(isBack){
    var resText = 'Без пересадок',
      transshipments = this[isBack ? 'back' : 'direct']['transshipments'],
      tCount = transshipments.length;
    if (tCount == 1) {
        var res = transshipments[0].duration.split(":"),
          time = [(res[0] != "0" ? (res[0] + " ч") : ""), (res[1] != "00" ? (res[1] + " мин.") : "")].join(" "),
          city = classifier['cities'][transshipments[0].city],
          trInfo = city + ", " + time;
        if (trInfo.length > 21) {
          trInfo = city.substring(0, 18 - time.length) + '&hellip; ' + time;
        }
        resText = 'Пересадка: ' + trInfo ;
    } else if (tCount > 1) {
        resText =  tCount
          + r_avia.h.word_endings(tCount, ' пересадка', ' пересадки', ' пересадок');
    }
    return resText;
  };
  Ticket.prototype['getTransshipmentsAirlines'] = function(isBack){
    var resText = '',
      transshipments = this[isBack ? 'back' : 'direct']['transshipments'];
    for (var i=0;i<transshipments.length;i++) {
      resText += classifier['airlines'][transshipments[i].airline];
      if (i < transshipments.length - 1) {
        resText += "<br/>";
      }
    };
    return resText;
  };
  Ticket.prototype['getAirlineName'] = function(isBack){
    return classifier['airlines'][this[isBack ? 'back' : 'direct'].airline];
  };
  Ticket.prototype['getDurationArray'] = function(isBack){
    var res = this[
      isBack ? 'back' : 'direct'
    ]['duration'];
    if(res){
      res = res.split(/\D+/).slice(0,2);
    }
    if(res && res[0]-0 == 0){
      res = res.slice(1);
    }
    /*если у минут нужно будет всегда уберать первый 0, то перенести следующую строку в if(res)*/
    res[0] = res[0]-0;
    return res
  };
  Ticket.prototype['getDurationShort'] = function(isBack){
    var res = this.getDurationArray(isBack);
    return res ? (
      res.join(' ч ') + ' мин.'
    ) : ''
  };
  Ticket.prototype['getDurationMedium'] = function(isBack){
    var res = this.getDurationArray(isBack);
    return res ? (
      'В пути ' + res.join(' ч ') + ' мин.'
    ) : '&nbsp;'
  };
  Ticket.prototype['getDurationFull'] = function(isBack){
    var res = this.getDurationArray(isBack);
    if(res){
        res[1] = parseInt(res[1],10);
    }
    return res ? (
      'Общее время в пути: ' + res.join(' ч ') + ' мин.'
    ) : '&nbsp;'
  };
  Ticket.prototype['isInCart'] = function(){
    return true;
  };
  function Block(data){
    var
    t = blocksProperties[ data['id'] ],
    s,
    i,
    isBack,
    departureTime,
    arrivalTime,
    l,
    m,
    storeTimes,
    widthPerItem = 61,
    addWidth = 106;

    function forSort(a, b){
      var
      f = a[isBack ? 'back' : 'direct']['departure']['datetime'].split(/\D+/),
      s = b[isBack ? 'back' : 'direct']['departure']['datetime'].split(/\D+/),
      r;

      f[1]--;
      s[1]--;
      r = Date.UTC.apply(this, f) - Date.UTC.apply(this, s);
      if(r === 0){
        f = a[isBack ? 'back' : 'direct']['arrival']['datetime'].split(/\D+/);
        s = b[isBack ? 'back' : 'direct']['arrival']['datetime'].split(/\D+/);
        f[1]--;
        s[1]--;
        r = Date.UTC.apply(this, f) - Date.UTC.apply(this, s);
      }
      return r;
    }
    Ticket.call(this, data);
    this['more'] = !t.expand && (
      aTicketsBlocks[ t.groupIndex ].length - 1
    )
    this['isCart'] = false;
    storeTimes = [];
    m = 'back' in this ? 2 : 1;
    for(isBack = 0; isBack < m; isBack++ ){
      storeTimes[isBack] = [];
      s = aTicketsBlocks[t.groupIndex][t.subgroupIndex].slice(0);
      s.sort( forSort );
      for(
        i = 0, l = s.length;
        i < l;
        i++
      ){
        departureTime = s[i][
          isBack ? 'back' : 'direct'
        ]['departure']['datetime'].split(/\D+/).slice(3,-1).join(':');
        arrivalTime = s[i][
          isBack ? 'back' : 'direct'
        ]['arrival']['datetime'].split(/\D+/).slice(3,-1).join(':');
        if(
          storeTimes[isBack].length == 0 ||
          storeTimes[isBack][storeTimes[isBack].length - 1]['departureTime'] != departureTime ||
          storeTimes[isBack][storeTimes[isBack].length - 1]['arrivalTime'] != arrivalTime
        ){
          storeTimes[isBack][storeTimes[isBack].length] = {
            'departureTime': departureTime,
            'arrivalTime': arrivalTime,
            'id': s[i]['id']
          };
        }
        if(
          storeTimes[isBack][storeTimes[isBack].length - 1]['departureTime'] === departureTime
          && storeTimes[isBack][storeTimes[isBack].length - 1]['arrivalTime'] === arrivalTime
          && data['id'] === s[i]['id']
        ){
          storeTimes[isBack][storeTimes[isBack].length - 1] = {
            'departureTime': departureTime,
            'arrivalTime': arrivalTime,
            'id': s[i]['id']
          };
        }
      }
    }
    this['storeTimes'] = storeTimes;
    this['timeExpand'] = t.timeExpand;
    if(t.timeExpand !== UNDEF){
      l = storeTimes[t.timeExpand].length;
      this['width'] = l < 7 ? 306 : (
        l < 13 ?
        Math.ceil(l / 2) * widthPerItem + addWidth :
        Math.ceil(l / 3) * widthPerItem + addWidth
      )
    }
  }

  Block.prototype['isInCart'] = function(){
    return this['id'] in cartTicketsKeys;
  };
  Block.prototype.toString = function(){
    return blockTemplate(this)
  };
  for(k in Ticket.prototype){
    if( !(k in Block.prototype) ){
      Block.prototype[k] = Ticket.prototype[k];
    }
  }
  function FullTicket(data){
    Ticket.call(this, data);
    /*Совместимость с объектом Ticket*/
    for(var i = 0; i < 2; i++) {
      var k=['direct','back'][i];
      if (this[k]) {
        var segments = this[k]['segments'];
        this[k]['airline'] = segments[0]['airline'];
        this[k]['departure'] = {
          'datetime':segments[0]['departure_datetime'],
          'city':segments[0]['departure_city'],
          'airport':segments[0]['departure_airport']
        }
        this[k]['arrival'] = {
          'datetime':segments[segments.length-1]['arrival_datetime'],
          'city':segments[segments.length-1]['arrival_city'],
          'airport':segments[segments.length-1]['arrival_airport']
        }
        var a = segments[0]['arrival_datetime'].split(/\D+/);
        a[1]--;
        var arrival_datetime = Date.UTC.apply(this, a);
        this[k]['transshipments'] = [];
        for (var j=1;j<segments.length;j++) {
            var d = segments[j]['departure_datetime'].split(/\D+/);
            d[1]--;
            var departure_datetime = Date.UTC.apply(this, d);
            var duration = (departure_datetime - arrival_datetime) / 60000;
            this[k]['transshipments'][j-1] = {
                'city': segments[j]['departure_city'],
                'airport': segments[j]['departure_airport'],
                'duration': Math.floor(duration / 60) + ":" + (duration % 60)
            };
            a = segments[j]['arrival_datetime'].split(/\D+/);
            a[1]--;
            arrival_datetime = Date.UTC.apply(this, a);
        }
      }
    }
    this['isCart'] = false;

    if(data.openFromMyCart){
        this['openFromMyCart'] = true;
    }
  }
  FullTicket.prototype['getAirportNameForSegment'] = function(isBack, isArrival, seg){
    var segment = this[isBack ? 'back' : 'direct']['segments'][seg],
      airport_name = classifier['airports'][segment[(isArrival ? 'arrival' : 'departure') + '_airport']],
      city_name = classifier['cities'][segment[(isArrival ? 'arrival' : 'departure') + '_city']];
    return city_name +
      (airport_name == city_name ? "" : ", " + airport_name) +
      " (" + segment[(isArrival ? 'arrival' : 'departure') + '_airport'] + ")";
  };
  FullTicket.prototype['getAirlineNameForSegment'] = function(isBack, seg){
    var segment = this[isBack ? 'back' : 'direct']['segments'][seg];
    return classifier['airlines'][segment['airline']];
  };
  FullTicket.prototype['getPlaneNameForSegment'] = function(isBack, seg){
    var segment = this[isBack ? 'back' : 'direct']['segments'][seg];
    return classifier['planes'][segment['plane']];
  };
  FullTicket.prototype['getMounthNameForSegment'] = function(isBack, isArrival, seg){
    return monthNames[
      this[
        isBack ? 'back' : 'direct'
      ]['segments'][seg][(isArrival ? 'arrival' : 'departure' ) + '_datetime'].split(/\D+/)[1]-1
    ]
  };
  FullTicket.prototype['getMounthFullNameForSegment'] = function(isBack, isArrival, seg){
    return r_avia.h.monthNames[
      this[
        isBack ? 'back' : 'direct'
      ]['segments'][seg][(isArrival ? 'arrival' : 'departure' ) + '_datetime'].split(/\D+/)[1]-1
    ]
  };
  FullTicket.prototype['getMounthFullNameRForSegment'] = function(isBack, isArrival, seg){
    return r_avia.h.monthNamesR[
      this[
        isBack ? 'back' : 'direct'
      ]['segments'][seg][(isArrival ? 'arrival' : 'departure' ) + '_datetime'].split(/\D+/)[1]-1
    ]
  };
  FullTicket.prototype['getDateForSegment'] = function(isBack, isArrival, seg){
    return this[
      isBack ? 'back' : 'direct'
    ]['segments'][seg][(isArrival ? 'arrival' : 'departure' ) + '_datetime'].split(/\D+/)[2]-0
  };
  FullTicket.prototype['getDurationArrayForSegment'] = function(isBack, seg){
    var res = this[
      isBack ? 'back' : 'direct'
    ]['segments'][seg]['duration'];
    if(res){
      res = res.split(/\D+/).slice(0,2);
    }
    if(res && res[0]-0 == 0){
      res = res.slice(1);
    }
    /*если у минут нужно будет всегда уберать первый 0, то перенести следующую строку в if(res)*/
    res[0] = res[0]-0;
    return res
  };
  FullTicket.prototype['getPlaneWithDurationForSegment'] = function(isBack, seg){
    var res = [], duration = this.getDurationArrayForSegment(isBack, seg),
      name = this.getPlaneNameForSegment(isBack, seg);
    if (name) {
        res.push(name);
    }
    if (duration) {
        res.push('в пути ' + duration.join(' ч ') + ' мин.')
    }
    return res ? res.join(', ') : '';
  };
  FullTicket.prototype['getFlightNumberForSegment'] = function(isBack, seg){
    var seg = this[
      isBack ? 'back' : 'direct'
    ]['segments'][seg];
    return seg['airline'] + seg['flight_number'];
  };
  FullTicket.prototype['getSummaryText'] = function(isBack){
    var segments = this[isBack ? 'back' : 'direct']['segments'],
      transshipmentsLen = segments.length - 1,
      summaryString = "",
      days = this.getDate(isBack,1,transshipmentsLen) - this.getDate(isBack,0,0);
      /*TODO прилет на следующий день, кажется надо не так отслеживать*/
    if (transshipmentsLen > 0) {
        summaryString += transshipmentsLen + r_avia.h.word_endings( transshipmentsLen, " пересадка", " пересадки", " пересадок" );
    } else {
        summaryString += "Без пересадок";
    }
    if (days > 0) {
        if (days == 1) {
            summaryString += ", прилет на следующий день";
        } else if (days == 2) {
            summaryString += ", прилет через день";
        };
        summaryString += ", " + this.getDate(isBack,1,transshipmentsLen) + " " + this.getMounthFullName(isBack,1,transshipmentsLen);
    }
    return summaryString;
  };
  FullTicket.prototype['getTransshipmentTimeForSegment'] = function(isBack,seg){
    var segments = this[isBack ? 'back' : 'direct']['segments'],
      a = segments[seg]['arrival_datetime'].split(/\D+/),
      d = segments[seg + 1]['departure_datetime'].split(/\D+/);
    a[1]--;
    d[1]--;
    var firstDateTime = Date.UTC.apply(this, a),
      secondDateTime = Date.UTC.apply(this, d),
      deltaTime = (secondDateTime - firstDateTime) / 60000,
      res = [Math.floor( deltaTime / 60 ), deltaTime % 60];
    return ["Пересадка", (res[0] ? (res[0] + " ч") : ""), (res[1] ? (res[1] + " мин") : "")].join(" ");
  };
  FullTicket.prototype['isInCart'] = function(){
    return this['id'] in cartTicketsKeys;
  };
  FullTicket.prototype.toString = function(){
    return fullTicketTemplate(this)
  };
  for(k in Ticket.prototype){
    if( !(k in FullTicket.prototype) ){
      FullTicket.prototype[k] = Ticket.prototype[k];
    }
  }
  /*begin Описание объекта билета в корзине*/
  function CartTicket(data){
    FullTicket.call(this, data);
    this['isCart'] = true;
    this['isForeignerCart'] = cartForeigner;
  }
  CartTicket.prototype['getTransshipmentsCityWithDuration'] = function(isBack){
    var resText = '',
      transshipments = this[isBack ? 'back' : 'direct']['transshipments'];
    for (var i=0;i<transshipments.length;i++) {
      var duration = transshipments[i].duration.split(":");
      resText += classifier['cities'][transshipments[i].city] + " "
        + [(duration[0] != "0" ? (duration[0] + " ч") : ""), (duration[1] != "00" ? (duration[1] + " мин.") : "")].join(" ");
      if (i < transshipments.length - 1) {
        resText += "<br/>";
      }
    };
    return resText||'&mdash;';
  };
  CartTicket.prototype['getAirlinesSummary'] = function(isBack){
    var segments = this[isBack ? 'back' : 'direct']['segments'],
      airlineHash = {}, airlines = [];
    for (var i=0; i<segments.length; i++) {
      airlineHash[segments[i]['airline']] = 1;
    }
    for (var a in airlineHash) {
        airlines.push(classifier['airlines'][a]);
    }
    return airlines.join('<br>');
  };
  CartTicket.prototype['getPlanesSummary'] = function(isBack){
    var segments = this[isBack ? 'back' : 'direct']['segments'],
      planeHash = {}, planes = [];
    /*Удаляем дубликаты по названиям, так как разным кодам может сответствовать одно название самолета*/
    for (var i=0; i<segments.length; i++) {
      if (segments[i]['plane']) {
        planeHash[classifier['planes'][segments[i]['plane']]] = 1;
      }
    }
    for (var p in planeHash) {
        planes.push(p);
    }
    return planes.join('<br>');
  };
  CartTicket.prototype.toString = function(){
    return blockTemplate(this)
  };
  for(k in FullTicket.prototype){
    if( !(k in CartTicket.prototype) ){
      CartTicket.prototype[k] = FullTicket.prototype[k];
    }
  }
  /*end Описание объекта билета в корзине*/
  function updateClassifier(data){
    var p, k;
    for(p in data){
      if(!classifier[p]){
        classifier[p] = {};
      }
      for(k in data[p]){
        classifier[p][k] = data[p][k];
      }
    }
  }
  r_avia.tickets.classifier_update = updateClassifier;
  function updateTickets(tickets){
    var a, i, j, indexKeys, t, tiketsId2Remove;

    /**
     * Возвращает следующую, самую младшую, запись из многомерного массива
     * @param {Array} k массив индексов, от корневого до самого младшего, текущей записи. изменяется после выполнения.
     * @param {Array} a многомерный массив записей
     * @return {Object|undefined} следующая запись
     */
    function nextByKeys(k, a){
      var tmp = a, i, l, lastAccessible, resUndef;
      
      for(
        lastAccessible = [],
        i = 0,
        l = k.length;
        !resUndef;
      ){
        lastAccessible[i] = tmp.length - 1 > k[i] ?
        [tmp, k[i] + 1] :
        [];
        if(i < l - 1){
          tmp = tmp[k[i++]];
        } else {
          if(tmp.length > k[i]){
            resUndef = tmp[ k[i]++ ];
          } else {
            for(; lastAccessible[i].length < 2 && i > 0; i--){
              k[i] = 0;
            }
            if( lastAccessible[i].length > 1 ){
              tmp = lastAccessible[i][0];
              k[i] = lastAccessible[i][1];
            } else {
              break;
            }
          }
        }
      }
      return resUndef
    }
    if(tickets && tickets.length){
      //если билет с таким id есть, стереть его в полосе выдачи
      for(
        tiketsId2Remove = {},
        i = tickets.length;
        i--;
      ){
        t = tickets[i]['id'];
        if( t in blocksProperties ){
          tiketsId2Remove[ t ] = 1;
          t = djp(container).child().child().hasAttr( 'data-ticket-id', t ).node;
          t && t.parentNode.removeChild(t);
        }
      }
      a = tickets.slice(0);
      indexKeys = [0,0,0];
      for(
        ; aTicketsBlocks.length && (
          t = nextByKeys( indexKeys, aTicketsBlocks )
        );
      ){
        //если пришел новый билет с таким id, прошлый не сохранять
        if( !tiketsId2Remove[ t['id'] ] ){
          a[a.length] = t;
        } else {
          for( j = a.length; j > 0 && a[--j]['id'] != t['id']; ){}
          if( t['cost'] < a[j]['cost'] ){
            //старый, с таким же id билет, не перезаписываем новым, но более дорогим билетом
            a = a.slice( 0, j ).concat( a.slice( j + 1 ) );
            a[a.length] = t;
            r_avia.log.log(
              'warning',
              'старый, с таким же id:' + t['id']
              + ' билет, не перезаписываем новым, но более дорогим билетом '
            );
          } else {
            r_avia.log.info(
              'заменяем старый билет с таким же id:' + t['id']
              + ' за ' + t['cost'] + 'р. на билет за '
              + a[j]['cost'] + 'р.'
            );
          }
        }
      }
      //сортируем по цене
       a.sort(
        function(a, b){
          return a['cost'] - b['cost']
        }
      );
      //отобразить количество найденных вариантов
      for(
        t = document.forms['geoForm'].getElementsByTagName('H4'),
        i = t.length;
        i--;
      ){
        if( djp(t[i]).classIs('b-expand-params__expand') ){
          djp(t[i]).next().text(
            r_avia.h.word_endings(
              a.length,
              "Найден",
              "Найдено",
              "Найдено"
            ) + ' ' + a.length + ' '
            + r_avia.h.word_endings(
              a.length,
              "вариант перелета",
              "варианта перелета",
              "вариантов перелета"
            )
          );
        }
      }
    }
    //группируем по cost, direct.airline, back.airline, direct.arrival.airport, direct.departure.airport
    a = tickets && tickets.length ?
    djp.group(
      a,
      [
        'cost',
        function(v){
          return v['direct']['airline']
        },
        function(v){
          return v['direct']['departure']['airport']
        }
      ].concat(
        tickets[0]['back'] && tickets[0]['back']['departure'] ? [
          function(v){
            return v['back']['airline']
          },
          function(v){
            return v['back']['departure']['airport']
          }
        ] :
        []
      )
    ) :
    [];
    for(
      i = a.length;
      i--;
    ){
      //разбиваем каждую группу на подгруппы по дате вылета туда и если есть, дате вылета обратно
      a[i] = djp.group(
        a[i],
        [
          function(v){
            return v['direct']['departure']['datetime'].split(' ')[0];
          }
        ].concat(
          tickets[0]['back'] && tickets[0]['back']['departure'] ? [
            function(v){
              return v['back']['departure']['datetime'].split(' ')[0];
            }
          ] :
          []
        )
      );
      for(j = a[i].length; j--;){
        //сортируем внутри каждой подгруппы по времени вылета, прилета, и для обратного рейса если он есть.
        a[i][j].sort(
          function(a, b){
            var
            f = a['direct']['departure']['datetime'].split(/\D+/),
            s = b['direct']['departure']['datetime'].split(/\D+/),
            r;

            f[1]--;
            s[1]--;
            r = Date.UTC.apply(this, f) - Date.UTC.apply(this, s);
            if(r === 0){
              f = a['direct']['arrival']['datetime'].split(/\D+/);
              s = b['direct']['arrival']['datetime'].split(/\D+/);
              f[1]--;
              s[1]--;
              r = Date.UTC.apply(this, f) - Date.UTC.apply(this, s);
            }
            if(r === 0 && a['back'] && 'duration' in a['back']){
              f = a['back']['departure']['datetime'].split(/\D+/);
              s = b['back']['departure']['datetime'].split(/\D+/);
              f[1]--;
              s[1]--;
              r = Date.UTC.apply(this, f) - Date.UTC.apply(this, s);
            }
            if(r === 0 && a['back'] && 'duration' in a['back']){
              f = a['back']['arrival']['datetime'].split(/\D+/);
              s = b['back']['arrival']['datetime'].split(/\D+/);
              f[1]--;
              s[1]--;
              r = Date.UTC.apply(this, f) - Date.UTC.apply(this, s);
            }
            return r;
          }
        );
      }
    }
    if(a.length){
      aTicketsBlocks = a;
    }
    containerScrollBy();
  }
  /* Initiates series of getJSON requests for tickets. On successful fetching,
   * r_avia.tickets.update is called with new tickets.
   * After each request, next timeout is calculated as
   * options.after *= r_avia.conf.ajaxPollTimeoutMod;
   *
   * Options
   *
   * data       : passed unchanged to $.getJSON
   * on_success : function (data) called on each successful fetch of tickets
   * retries    : integer. Repeat requests this many times or until response.more !== true.
   * after      : float. Repeat request after this many seconds.
   * */
  r_avia.tickets.begin_ajax_update = function(options){
    var days_dir = Math.floor(
          (
            r_avia.h.to_seconds(options.data.dfr + ' 12:00:00')
            - r_avia.h.to_seconds(options.data.df + ' 12:00:00')
          ) / 86400000
        ) + 1,
        days_bak = options.data.dt ?
        Math.floor(
          (
            r_avia.h.to_seconds(options.data.dtr + ' 12:00:00')
            - r_avia.h.to_seconds(options.data.dt + ' 12:00:00')
          ) / 86400000
        ) + 1 :
        0;
    timer !== UNDEF && clearTimeout(timer);
    xhr !== UNDEF && xhr.abort();
    // Defaults.
    if(options.retryNum === UNDEF){
      options.retryNum = 1;
    }
    if(options.retries === UNDEF){
      options.retries = r_avia.conf.ajaxPollRetries;
      /*Статистика выбора диапазонов*/
      _tracker.track('events.search.do.'+days_dir+'_'+days_bak);
      r_avia.parts.submitButton.startSearch();
    }
    if(options.after === UNDEF){
      options.after = r_avia.conf.ajaxPollTimeoutFirst;
    }
    options.data['serial'] = options.retryNum;

    xhr = djp.getJSON(
      r_avia.urls.get_tickets + '?' + djp.objToQueryString(options.data),
      function(data) {
        var t, n;

        options.retryNum++;

        if (data && !data['error']) {
          options.on_success && options.on_success(data);
          options.retries--;
          options.after *= r_avia.conf.ajaxPollTimeoutMod;
          /*Классификатор аэропортов, городов, авиакомпаний*/
          updateClassifier(data['classifier']);
          (
            options.retries == 0 || (
              data['tickets'] && data['tickets'].length
            )
          ) && updateTickets(data['tickets']);
          /*показываем кнопку фильтров*/
          if (data['tickets'].length/* && djp('searchButton').ancestor().more().classIs('hidden') */) {
            setTimeout(function(){
                //TipsAnimator.stop();
            },5000);
            /* Показываем ползунок */
            djp('scrollContainer').toggleClass('b-invisible',false);
            
            t = djp(document.forms['geoForm'].getElementsByTagName('DL')[0]).ancestor().more().child();
            t.toggleClass('hidden', false);
            if( !djp(t).child().classIs('m-expand-params__expand-on') ){
              //влазит ли по высоте с раскрытыми фильтрами?
              t.child().toggleClass('m-expand-params__expand-on', true);
              for(
                n = djp(t).next().hasClass('b-search-param');
                n.node;
                n.more()
              ){
                n.toggleClass('hidden', false);
              }
              if(
                Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
                > Math.max(document.body.clientHeight, document.documentElement.clientHeight)
              ){
                t.child().toggleClass('m-expand-params__expand-on', false);
                for(
                  n = djp(t).next().hasClass('b-search-param');
                  n.node;
                  n.more()
                ){
                  n.toggleClass('hidden', true);
                }
              }
            }
          }
          //r_avia.tickets.update_filters(data['filters']);
          _tracker.track('events.search.state'+options.retryNum+'.'+days_dir+'_'+days_bak+'.'+(aTicketsBlocks.length?'ok':'nil')+'-'+'events.search.get'+options.retryNum+'.'+days_dir+'_'+days_bak+'.'+(data['tickets'].length?'ok':'nil'));
          if (data['more'] && options.retries > 0) {
            timer = setTimeout(
              function() {
                r_avia.tickets.begin_ajax_update(options);
              },
              options.after
            );
          } else {
            if (aTicketsBlocks.length) {
              /*По завершению запросов отобразили (ok) билеты*/
              _tracker.track('events.search.result.'+days_dir+'_'+days_bak+'.ok');
            } else {
              /*По завершению запросов не отобразили (nil) билеты*/
              _tracker.track('events.search.result.'+days_dir+'_'+days_bak+'.nil');
              container.toggleClass('hidden', true);
              container.next().hasClass('b-search-error').toggleClass('hidden', false);
            }
            r_avia.parts.submitButton.stopSearch();
            r_avia.alert_stop_complete_search();
            //TipsAnimator.stop();
          }
        } else {
          /*Ошибка получения билетов*/
          _tracker.track('events.search.result.'+days_dir+'_'+days_bak+'.error');
          r_avia.parts.submitButton.stopSearch();
          if(data && data['error']){
            r_avia.log.error(data['error']);
          }
        }
        xhr = UNDEF;
      }
    );
  };
  djp.makeMovable(
    function(oDJP, oEvent, posX){
      return container.contains(oDJP);
    },
    function(delta, posX){
      containerScrollBy(-delta);
    },
    function(b){
      if(container.node){
        container.toggleClass('jsWResize', b);
        if(!b){
          containerPermanetlyScrollBy(0);
        }
      }
    }
  );
  /* Клик на кнопку со звездой - добавление или удаление билета в корзину */
  djp.clickTouch(
    function(oDJP){
      var res;

      return djp(oDJP).ancestor().hasClass('b-offer__addtocart').node && (
        res = djp(oDJP).ancestor().hasAttr('data-ticket-id', /.+/)
      ).node && res;
    },
    function(ticketDJP){
      var dummyOffset = ticketDJP.offset(),
          cartOffset = cartShowContainer.offset(),
          dummyTweener,
          isToAdd = !ticketDJP.classIs('m-offer__addtocart-added'),
          /* флаг - открыта чужая корзина */
          isInForeignCart = (r_avia.cart.current_cart == r_avia.cart.carts.someone);
      
      if(isToAdd){
        /*регистрируем событие нажатия на кнопку добавления билета в корзину*/
        isToAdd && _tracker.track('events.ticket.to_cart.click');
        
        dummyTweener = $t(djp(document.body).append(
          '<div class="b-offer-flightdummy" style="width:'
          +ticketDJP.node.offsetWidth+'px;height:'
          +ticketDJP.node.offsetHeight+'px;left:'
          +dummyOffset.left+'px;top:'
          +dummyOffset.top+'px;position:absolute;"></div>'
        ).node);
        dummyTweener.tween({
          width:0,
          height:0,
          opacity:0,
          left:cartOffset.left,
          top:cartOffset.top,
          time:0.3,
          transition:'easeInOutCubic'
        });
        $t(
            cartShowContainer.node
        ).tween({
          paddingBottom:20,
          time:0.4,
          transition:'easeInOutCubic'
        }).tween({
          paddingBottom:0,
          time:0.2,
          transition:'easeInOutCubic',
          delay:0.5
        });
        djp.getJSON(
          r_avia.urls.add_to_cart,
          function(data) {
            var a, ticketInCartElement , ticketInResultElement, ticketId;
            
            if (data && !data['error']) {
              ticketId = ticketDJP.attr('data-ticket-id') ;
              
              _tracker.track('events.ticket.to_cart.ok');
              a = data['tickets'];
              updateCardTicketsId(data['cart_id'], isInForeignCart);
              updateCardUrlHash(data['cart_id']);
              
              updateClassifier( a );
              updateCartTikets( a );
              updateTicketsCount( a.length );
              
              /* добавляем билет в индекс своих билетов билет */
              cartTicketsKeys[ ticketDJP.attr('data-ticket-id') ] = true;
              
              /* Если мы в списке билетов - удаляем элемент билета 
               * Стираем билет, чтобы он перерисовался заново. Чтобы перерисовалась звездочка корзины.
               **/
              if(djp(ticketDJP).ancestor().hasClass('b-offers-place').node){
                  ticketDJP.node.parentNode.removeChild(ticketDJP.node);
              }
              
              /* Если кликнули подробнее в билетах не своей корзины */
              if(isInForeignCart && ticketDJP.classIs('m-offer-full')){
                  ticketInCartElement = djp(r_avia.cart.carts.someone.container).child().child().hasClass('b-cart__content').child().child().child().hasAttr('data-ticket-id',ticketId);
                  
                  if(ticketInCartElement.node){
                      ticketInCartElement.toggleClass('m-offer__addtocart-added',true);
                  }
              }
              
              ticketInResultElement = djp(container).child().child().hasAttr('data-ticket-id', ticketId);
              
              if (ticketInResultElement.node) {
                  ticketInResultElement.toggleClass('m-offer__addtocart-added', true);
              }
              
              ticketDJP.toggleClass('m-offer__addtocart-added');

              containerScrollBy();
            } else {
              /*регистрируем ошибку добавления билета в корзину*/
              _tracker.track('events.ticket.to_cart.error');
              djp.alert(
                data && data['error'] ?
                data['error'] : serverError
              );
            }
          },
          djp.objToQueryString({
            'tid': ticketDJP.attr('data-ticket-id'),
            'date_direct': ticketDJP.attr('data-ticket-date')
          })
        );
      } else {
        ticketDJP.toggleClass('m-offer__addtocart-added', false);
        removeTicketFromCart(ticketDJP.attr('data-ticket-id'), isInForeignCart);
      }
    }
  );
  /*Обновление переменной, показывающей что открыта чужая корзины*/
  function updateCardForeignerInfo(val){
    cartForeigner = val;
  }
  r_avia.cart.update_cart_foreigner = updateCardForeignerInfo;
  /**
   * Обновление кода корзины
   * 
   * @param id {Number} Новый id корзины
   * @param updateMyCart {Bool} Флаг обновления пользовательской корзины. Если false - id меняется в текущей-открытой корзине
   **/
  function updateCardTicketsId(id,updateMyCart){
    /* если нужно апдейтить свою корзину */
    if(updateMyCart){
        djp(r_avia.cart.carts.my.cartElement).child().child().hasClass('b-cart__header').child().hasClass('b-cart__code').child().child().hasClass('b-cart__code__val').text(id || '-');
        return
    }
    
    cartCodeContainer.text(id||'-');
  }
  r_avia.cart.update_cart_id = updateCardTicketsId;
  /*Обновление ссылки перехода в корзину*/
  function updateCardUrlHash(id){
    var res = id ? '#{"cart": {"mode": "table", "code": ' + id + '}}' : '#';
    cartShowContainer.node.href = res;
    return res;
  }
  r_avia.cart.update_cart_url_hash = updateCardUrlHash;
  /* Очистка содержимого корзины */
  function emptyCartContainer(id){
    updateCardTicketsId(null);
    cartContainer.node.innerHTML = "";
  }
  r_avia.cart.empty = emptyCartContainer;
  /*Рисуем обычную корзину*/
  function drawCartTikets(){
    var cartMode = djp(cartCodeContainer).ancestor().hasClass('b-cart__header').child().hasClass('b-hor-chooser').child();
    cartMode.toggleClass('b-hor-chooser__item-selected', true);
    cartMode.more().toggleClass('b-hor-chooser__item-selected', false);
    cartContainer.node.innerHTML = cartTemplate(cartTickets.join(''));
    cartContainer.toggleClass('m-compare-table', false);
  }
  r_avia.cart.render_tickets = drawCartTikets;
  /*Рисуем таблицу сравнения в корзине*/
  function drawCartComparison(){
    var cartMode = djp(cartCodeContainer).ancestor().hasClass('b-cart__header').child().hasClass('b-hor-chooser').child();

    cartMode.toggleClass('b-hor-chooser__item-selected', false);
    cartMode.more().toggleClass('b-hor-chooser__item-selected', true);
    if (cartContainer.node) {
      cartContainer.node.innerHTML = comparisonTemplate(cartTickets);
    }
    cartContainer.toggleClass('m-compare-table', true);
  }
  r_avia.cart.render_comparison = drawCartComparison;
  
  /* Устанавливает текущую корзину */
  function setCurrentCart(cartObject){
      r_avia.cart.current_cart = cartObject;
      
      if(!cartObject){
          return false
      }
      
      cartContainer = djp(cartObject.cartElement).child().hasClass('b-cart__content');
      cartCodeContainer = djp(cartObject.cartElement).child().child().hasClass('b-cart__header').child().hasClass('b-cart__code').child().child().hasClass('b-cart__code__val');
  }
  
  r_avia.cart.set_current_cart = setCurrentCart;
  if (r_avia.cart.carts) {
    /* Корзина по умолчанию на старте - пользовательская */
    r_avia.cart.set_current_cart(r_avia.cart.carts.my);
  }

  /*Обновление билетов в корзине*/
  function updateCartTikets(tickets){
    var i;

    cartTickets = [];
    /* если апдейтим билеты пришедшие не из "своей корзины" - не ломаем индекс */
    if(!cartForeigner){
        cartTicketsKeys = {};
    }
    
    for(i = tickets.length; i--;){
      if( 'cost' in tickets[i] ){
        cartTickets[i] = new CartTicket( tickets[i] );
      }
      
      /* Если апдейтим(рисуем) не свою корзину - не добавляем в индекс билеты */
      if(!cartForeigner){
          cartTicketsKeys[ tickets[i]['id'] ] = i;
      }

    }
  }
  r_avia.cart.tickets_update = updateCartTikets;
  
  function updateTicketsCount(count){
      var msg = count === 0?'Не выбрано ни одного билета':r_avia.h.word_endings(count, 'выбран', 'выбрано', 'выбрано' ) + ' ' + count + ' ' + r_avia.h.word_endings(count, 'билет', 'билета', 'билетов' ),
          ticketCountElement = djp('ticketCountId');

      ticketCountElement.text( msg );
      ticketCountElement.ancestor().hasClass('b-cart__show').toggleClass('m-open-disabled',(count === 0))
      
  }
  
  r_avia.cart.update_tickets_count = updateTicketsCount;
  
  /*Переключение в режим сравнения*/
  djp.clickTouch(
    function(oDJP){
      return djp(oDJP).ancestor().child().hasClass('m-compare-mode').node && oDJP
    },
    function(oDJP){
      djp.$hash.store(
        ['cart'],
        {
          'mode': 'table',
          'code':cartCodeContainer.text().replace('-','')
        }
      );
    }
  );
  /*Переключение в режим билетов*/
  djp.clickTouch(
    function(oDJP){
      return djp(oDJP).ancestor().child().hasClass('m-ticket-mode').node && oDJP
    },
    function(oDJP){
      djp.$hash.store(
        ['cart'],
        {
          'mode': 'block',
          'code':cartCodeContainer.text().replace('-','')
        }
      );
    }
  );
  /*показать билет подробно*/
  djp.clickTouch(
    function(oDJP){
      var ticketDJP = djp(oDJP).ancestor().hasClass('b-offer__details-link')
      .ancestor().hasAttr('data-ticket-id', /.+/);

      return ticketDJP.node && ticketDJP;
    },
    function(ticketDJP, oEvent){
      oEvent.prevent();
      /*регистрируем событие нажатия на ссылку деталей билета*/
      _tracker.track('events.ticket.details.click');
      xhr !== UNDEF && xhr.abort();
      xhr = djp.getJSON(
        r_avia.urls.get_full_ticket + '?' +
        djp.objToQueryString({
          'tid': ticketDJP.attr('data-ticket-id'),
          'date_direct' : ticketDJP.attr('data-ticket-date')
        }),
        function(data){
          var node;
          if (data && !data['error']){
            if (data['tickets'].length) {
              /*регистрируем событие получения билета*/
              _tracker.track('events.ticket.details.ok');
            } else {
              /*регистрируем событие получения пустого ответа*/
              _tracker.track('events.ticket.details.nil');
            }
            updateClassifier(data['classifier']);
            /* Если открываем "подробнее" из своей корзины - зажигаем флаг,  чтобы скрывать кнопку "добавить к сравнению" */
            if(ticketDJP.classIs('m-offer-in-my-cart')){
                data['tickets'][0].openFromMyCart = true;
            }
            
            djp.openBox.centre(
              null,
              {
                openCallback: function(boxNode, recalc, target){
                  boxNode.append( new FullTicket( data['tickets'][0] ) );
                },
                closeCallback: function() {
                },
                closeClass: 'b-offer__close',
                shadowClass: 'b-page-fader'
              }
            );
          } else {
            /*регистрируем ошибку*/
           _tracker.track('events.ticket.details.error');
            djp.alert(
              data && data['error'] ?
              data['error'] : serverError
             );
          }
          xhr = UNDEF;
        }
      );
    }
  );
  /**
   * удаление билета из корзины
   * 
   * @param id {String} ID билета для удаления
   * @param isInForeignCart {Bool} Удаляем из чужой корзины
   */
  function removeTicketFromCart(id, isInForeignCart){
    djp.getJSON(
      r_avia.urls.del_from_cart,
      function(data){
        var node, ticketInResult;

        if (data && !data['error']){
          /*регистрируем удаление билета из корзины*/
          _tracker.track('events.cart_ticket.delete.ok');
          updateCartTikets(data['tickets']);
          updateTicketsCount(data['tickets'].length)
          updateCardTicketsId(data['cart_id'], isInForeignCart);
          updateCardUrlHash(data['cart_id']);
          
          
          
          /* если находимся в своей корзине */
          if(!isInForeignCart){
              node = djp(cartContainer).child().child().child().hasAttr('data-ticket-id', id).node;
              if(node){
                node.parentNode.removeChild(node);
              }
          }
          
          ticketInResult = djp(container).child().child().hasAttr('data-ticket-id', id);
          
          if (ticketInResult.node) {
              ticketInResult.toggleClass('m-offer__addtocart-added', false);
          }
          
          /* Если находимся - в чужой корзине - снимаем звездочку */
          if(isInForeignCart){
              djp(cartContainer).child().child().child().hasAttr('data-ticket-id', id).toggleClass('m-offer__addtocart-added', false);
          } else {
              if(data['cart_id']){
                  djp.$hash.store( ['cart', 'code'], data['cart_id'] );
              }
          }
          
          /* Удаляем билет из индекса своих билетов */
          delete cartTicketsKeys[id];
          
        } else {
          /*регистрируем ошибку удаления билета из корзины*/
          _tracker.track('events.cart_ticket.delete.error');
          djp.alert(
            data && data['error'] ?
            data['error'] : serverError
          );
        }
      },
      djp.objToQueryString({
        'tid': id
      })
    );
  }
  djp.clickTouch(
    function(oDJP){
      var id = djp(oDJP).ancestor().hasClass('b-offer__close')
      .ancestor().hasAttr('data-ticket-id', /.+/);

      return id.node && djp(oDJP).ancestor().hasClass('b-cart__content').node && id
    },
    function(id, oEvent){
      /*регистрируем событие клика по крестику удаления билета из корзины*/
      _tracker.track('events.cart_ticket.delete.click');
      oEvent.prevent();
      removeTicketFromCart(id.attr('data-ticket-id'));
    }
  );
  //удаление билета из сравнения
  djp.clickTouch(
    function(oDJP){
      var id = djp(oDJP).ancestor().hasClass('b-compare__remove-offer')
      .ancestor().hasAttr('data-ticket-id', /.+/);

      return id.node && djp(oDJP).ancestor().hasClass('b-cart__content').node && id
    },
    function(id, oEvent){
      /*регистрируем событие клика по крестику удаления билета из сравнения*/
      _tracker.track('events.comparison_ticket.delete.click');
      oEvent.prevent();
      id = id.attr('data-ticket-id');
      djp.getJSON(
        r_avia.urls.del_from_cart,
        function(data){
          var node, ticketInResult;
          if (data && !data['error']){
            /*регистрируем удаление билета из корзины*/
            _tracker.track('events.comparison_ticket.delete.ok');
            updateCartTikets(data['tickets']);
            updateCardTicketsId(data['cart_id']);
            updateCardUrlHash(data['cart_id']);
            /*Сохраняем положение таблицы при обновлении*/
            comparisonContanerScroll = cartContainer.child().hasClass('b-comapre-table-wrapper').node.scrollLeft;
            /*Пока без причин удаления*/
            //node = djp(cartContainer).child().child().child().hasAttr('data-ticket-id', id).node;
            //if(node){
            //  node.parentNode.removeChild(node);
            //}
            /*При переходе прыгает горизонтальный скролл*/
            ticketInResult = djp(container).child().child().hasAttr('data-ticket-id', id);
            if (ticketInResult.node) {
                ticketInResult.toggleClass('m-offer__addtocart-added', false);
            }
            if(data['cart_id']){
              djp.$hash.store( ['cart', 'code'], data['cart_id'] );
            } else {
              djp.$hash.store( ['cart'], '' );
              updateTicketsCount(0);
              r_avia.cart.carts.my.hide();
            }
          } else {
            /*регистрируем ошибку удаления билета из корзины*/
            _tracker.track('events.comparison_ticket.delete.error');
            djp.alert(
              data && data['error'] ?
              data['error'] : serverError
            );
          }
        },
        djp.objToQueryString({
          'tid': id
        })
      );
    }
  );
  //рассыпать билеты по одному из стопки
  djp.clickTouch(
    function(oDJP){
      var ticketDJP = djp(oDJP).ancestor().hasNode('A')
      .ancestor().hasClass('b-offer__info')
      .ancestor().hasAttr('data-ticket-id', /.+/);

      return container.node && container.contains(oDJP) && ticketDJP.node && ticketDJP;
    },
    function(ticketDJP, oEvent){
      oEvent.prevent();
      //помечаем билет раскрытым
      blocksProperties[
       ticketDJP.attr('data-ticket-id')
      ].expand = true;
      //чтобы перерисовать раскрываемую стопку билетов
      ticketDJP.node.parentNode.removeChild(ticketDJP.node);
      containerScrollBy();
    }
  );
  (function(lastID){
    function replaceBy(old, newID){
      var
      t = blocksProperties[newID],
      res;
      
      t = aTicketsBlocks[t.groupIndex][t.subgroupIndex];
      res = old.after( new Block( t[t.selectedTimeIndex] ) );
      old.node.parentNode.removeChild(old.node);
      return res;
    }
    function restoreBeforeHovered(id){
      var
      t = blocksProperties[id],
      a = aTicketsBlocks[t.groupIndex][t.subgroupIndex],
      i,
      res = UNDEF;

      for(i = a.length; i--;){
        if( blocksProperties[ a[i]['id'] ].selectedBeforeHovered ){
          res = a[i]['id'];
        }
        blocksProperties[ a[i]['id'] ].selectedBeforeHovered = UNDEF;
      }
      return res
    }
    function saveBeforeHovered(id){
      var
      t = blocksProperties[id],
      a = aTicketsBlocks[t.groupIndex][t.subgroupIndex],
      i;

      for(i = a.length; i--;){
        blocksProperties[ a[i]['id'] ].selectedBeforeHovered = a[i]['id'] == id;
      }
    }
    function setSelectedTime(id, isBack){
      var
      t = blocksProperties[id],
      a = aTicketsBlocks[t.groupIndex][t.subgroupIndex],
      filteredByNewTimeTickets = [],
      newTime,
      lastTime,
      i,
      newID = id,
      currDiff;

      if(isBack !== UNDEF){
        lastTime = a[a.selectedTimeIndex][
          isBack ? 'direct' : 'back'
        ]['departure']['datetime'];
        newTime = a[t.timeIndex][
          isBack ? 'back' : 'direct'
        ]['departure']['datetime'];
        for(i = a.length; i--;){
          if(
            a[i][ isBack ? 'back' : 'direct' ]['departure']['datetime'] == newTime
          ){
            filteredByNewTimeTickets[filteredByNewTimeTickets.length] = a[i]
          }
        }
        lastTime = lastTime.split(/\D+/);
        lastTime[1]--;
        lastTime = Date.UTC.apply(this, lastTime);
        t = UNDEF;
        for(i = filteredByNewTimeTickets.length; i--;){
          newTime = filteredByNewTimeTickets[i][
            isBack ? 'direct' : 'back'
          ]['departure']['datetime'].split(/\D+/);
          newTime[1]--;
          newTime = Date.UTC.apply(this, newTime);
          currDiff = Math.abs( newTime - lastTime );
          if(
            t === UNDEF ||
            t > currDiff
          ){
            newID = currDiff !== 0 ?
            filteredByNewTimeTickets[i]['id'] :
            id;
            t = currDiff
          }
        }
      }
      for(i = a.length; i--;){
        if(
          blocksProperties[ a[i]['id'] ].selectedTime
          = a[i]['id'] == newID
        ){
          if(
            blocksProperties[
              a[a.selectedTimeIndex]['id']
            ].expand
          ){
            blocksProperties[
              a[a.selectedTimeIndex]['id']
            ].expand = false;
            blocksProperties[
              id
            ].expand = true;
          }
          a.selectedTimeIndex = i;
        }
      }
    }
    function setExpandTime(id, val){
      var
      t = blocksProperties[id],
      a = aTicketsBlocks[t.groupIndex][t.subgroupIndex],
      i;

      for(i = a.length; i--;){
        blocksProperties[ a[i]['id'] ].timeExpand = val;
      }
    }
    //скрыть выбор времени у билета
    djp.clickTouch(
      function(oDJP){
        var
        ticketDJP = djp(oDJP).ancestor().hasClass('m-offer-ticket__expand-collapse');

        ticketDJP = djp(ticketDJP).ancestor().hasAttr('data-ticket-id', /.+/);
        return container.node && container.contains(oDJP) && ticketDJP.node && ticketDJP;
      },
      function(ticketDJP, oEvent){
        var id = ticketDJP.attr('data-ticket-id');
        
        setExpandTime(id, UNDEF);
        replaceBy(ticketDJP, id );
      }
    );
    //показать выбор времени у билета
    djp.clickTouch(
      function(oDJP){
        var res = [
          djp(oDJP).ancestor().hasClass('b-offer-ticket__expand')
        ];
        res[1] = res[0].node && res[0].ancestor().hasAttr('data-ticket-id', /.+/);

        return container.node && container.contains(oDJP) && res[0].node && res;
      },
      function(res, oEvent){
        var
        t = res[1].attr('data-ticket-id'),
        o = djp(res[0]).ancestor().hasClass('m-ticket-back'),
        nod;

        if( o.node ){
          o = 1;
        } else {
          o = 0;
        };
        saveBeforeHovered(t);
        setExpandTime(t, o);
        nod = replaceBy(res[1], t );
        if(o === 1){
          nod = nod.child().child().child()
          .hasClass('b-offer-tickets-wrapper').node;
          t = nod.scrollHeight - nod.clientHeight - 9;
          if(t > 0 ){
            nod.scrollTop = t;
          }
        }
      }
    );
    //выбрать время вылета
    djp.clickTouch(
      function(oDJP){
        var
        res = [djp(oDJP).ancestor().hasAttr('data-other-id', /.+/)];

        res[1] = res[0].node && res[0].ancestor().hasAttr('data-ticket-id', /.+/);
        return container.node && container.contains(oDJP) && res[0].node && res;
      },
      function(res, oEvent){
        var id = res[0].attr('data-other-id'), isBack;

        //стираем запомненное
        restoreBeforeHovered(id);
        setExpandTime(id, UNDEF);
        if(
          djp(res[0]).ancestor().hasClass('m-ticket-back').node
        ){
          isBack = 1;
        } else if(
          djp(res[0]).ancestor().hasClass('b-offer-ticket')
          .next().hasClass('m-ticket-back').node
        ){
          isBack = 0;
        } else {
          isBack = UNDEF;
        }
        setSelectedTime(id, isBack);
        //чтобы перерисовать раскрываемую стопку билетов
        replaceBy(res[1], id );
      }
    );
    /*подсказки*/
    (function(){
      var hasTouch = false, bubblePopUp = {};
      djp(document).attach(
        'mousemove touchstart touchend mousewheel DOMMouseScroll',
        function(oDJP, oEvent){
          var bubbleSensor,
              bubbleElement,
              sensorOffset,
              a, j, o,
              uniqueId;
          if( oEvent.$type == 'touchstart' ){
            hasTouch = true;
          }
          if( !({'touchstart':1, 'touchend':1}[oEvent.$type]) ^ hasTouch ){
            if(hasTouch){
              a = oEvent.e['changedTouches'];
            } else {
              a = [{'target': oDJP.node}];
            }
            for(
              j = a['length'];
              j--;
            ){
              bubbleSensor = djp(a[j]['target']).ancestor().hasClass('b-offer-ticket__logo');
              for(uniqueId in bubblePopUp){
                o = {
                  'mousemove' : !bubbleSensor.node || bubblePopUp[uniqueId].node != bubbleSensor.node,
                  'touchend' : bubblePopUp[uniqueId].touchIdentifier !== UNDEF
                  && bubblePopUp[uniqueId].touchIdentifier === a[j]['identifier'],
                  'mousewheel': true,
                  'DOMMouseScroll' : true
                };
                if( o[oEvent.$type] ){
                  //спрятать подсказки под отжатым пальцем точскрина или ставшим не под курсором мыши елементом
                  bubbleElement = djp( bubblePopUp[uniqueId].node ).ancestor().hasClass(
                    'b-offer-frame__inner'
                  ).child().hasClass(
                    djp( bubblePopUp[uniqueId].node ).ancestor().hasClass('m-ticket-back').node ?
                    'b-airline-name-back' :
                    'b-airline-name-to'
                  );
                  if(bubbleElement.node){
                    djp( bubblePopUp[uniqueId].node ).ancestor().hasClass('b-offer')
                    .toggleClass('m-offer-up', false);
                    bubbleElement.toggleClass(
                      'b-micro-popup-hidden',
                      true
                    );
                    delete bubblePopUp[uniqueId]
                  }
                }
              }
              uniqueId = bubbleSensor.node && !({
                'touchend':1,
                'mousewheel':1,'DOMMouseScroll':1
              }[oEvent.$type]) ?
              bubbleSensor.getUniqueId() :
              UNDEF;
              if ( uniqueId !== UNDEF && !(uniqueId in bubblePopUp) ){
                /* Выбираем нужный бабл */
                bubbleElement = djp(bubbleSensor).ancestor().hasClass(
                  'b-offer-frame__inner'
                ).child().hasClass(
                  djp(bubbleSensor).ancestor().hasClass('m-ticket-back').node ?
                  'b-airline-name-back' :
                  'b-airline-name-to'
                )
                if(bubbleElement.node){
                  bubbleElement.toggleClass(
                    'b-micro-popup-hidden',
                    false
                  );
                  bubblePopUp[ uniqueId ] = {
                    node: bubbleSensor.node,
                    touchIdentifier: 'identifier' in a[j] ? a[j]['identifier'] : UNDEF
                  };
                  sensorOffset = bubbleSensor.child().node;
                  bubbleElement.node.style.cssText = //'width:'
                  /*+ bubbleElement.node.offsetWidth + */ 'top:'
                  + (sensorOffset.offsetTop - bubbleElement.node.offsetHeight - 9)
                  + 'px; left:' + (
                    sensorOffset.offsetLeft - (
                      (bubbleElement.node.offsetWidth - sensorOffset.offsetWidth) >> 1
                    )
                  ) + 'px;width:' + (
                    bubbleElement.node.offsetWidth
                    - 18
                  ) + 'px';
                }
              }
            }
          }
        }
      )
    })();
    //когда курсор над вариантом времени, подрисовывать времена, даты пересадки, короче все.
    djp(document).attach(
      'mouseover',
      function(oTarget, oEvent){
        var
        otherTime = djp(oTarget).ancestor().hasAttr('data-other-id', /.+/),
        ticketDJP = otherTime.ancestor().hasAttr('data-ticket-id', /.+/),
        newID = false,
        nod,
        isBack,
        t;

        if( otherTime.node ){
          newID = otherTime.attr('data-other-id');
          if(newID === lastID){
            newID = false
          } else {
            lastID = newID;
          }
        } else if( lastID !== UNDEF && blocksProperties[ lastID ].timeExpand !== UNDEF ){
          newID = restoreBeforeHovered(lastID);
          if(newID !== UNDEF){
            ticketDJP = djp(container).child().child().hasAttr( 'data-ticket-id', lastID );
            saveBeforeHovered(newID);
          } else {
            newID = false;
          }
          lastID = UNDEF;
        }
        if( newID !== false ){
          if(
            djp(otherTime).ancestor().hasClass('m-ticket-back').node
          ){
            isBack = 1;
          } else if(
            djp(otherTime).ancestor().hasClass('b-offer-ticket')
            .next().hasClass('m-ticket-back').node
          ){
            isBack = 0;
          } else {
            isBack = UNDEF;
          }
          setSelectedTime( newID, isBack );
          nod = replaceBy( ticketDJP, newID );
          if( blocksProperties[ newID ].timeExpand === 1 ){
            nod = nod.child().child().child()
            .hasClass('b-offer-tickets-wrapper').node;
            t = nod.scrollHeight - nod.clientHeight - 9;
            if(t > 0 ){
              nod.scrollTop = t;
            }
          }
        }
      }
    )
  })();
  //клик по кнопкам фильтра пересадок
  djp.checkpaks(
    function(oDJP){
      return oDJP.ancestor().hasClass('b-param-transfers').node;
    },
    function(bitMask, oDJP, oEvent){
      transshipmentsFilter = bitMask;
      r_avia.log.info(bitMask.toString(2));
      //перерисовать стопки билетов и отматать в начало, фильтр жо новыя
      containerScrollBy(
        container.node.scrollLeft + containerVirtualScrollLeft
      );
    }
  );
  //таскание краев фильтра длительности пребывания.
  (function(){
    var
    startPos,
    isMinValue;
    
    function move(posX){
      var
      t = Math.round( (posX - startPos) / dayFilterPixPerDay ) + dayFilterFrom,
      b = false;

      if( t < dayFilterFrom ){
        t = dayFilterFrom;
      }
      if( t > dayFilterTo ){
        t = dayFilterTo;
      }
      if(isMinValue && dayFilterMin !== t){
        b = true;
        dayFilterMin = t;
      } else if( !isMinValue && dayFilterMax !== t ){
        b = true;
        dayFilterMax = t;
      }
      if( dayFilterMax !== UNDEF && t > dayFilterMax ){
        dayFilterMax = t;
      }
      if( dayFilterMin !== UNDEF && t < dayFilterMin ){
        dayFilterMin = t;
      }
      b && //перерисовать стопки билетов и отматать в начало, фильтр жо новыя
      containerScrollBy(
        container.node.scrollLeft + containerVirtualScrollLeft
      );
    }
    djp.makeMovable(
      function(oDJP, oEvent, posX){
        var b = !{'mousewheel':1,'DOMMouseScroll':1}[oEvent.$type],
            iMin = dayFilterMin === UNDEF || dayFilterFrom > dayFilterMin ? dayFilterFrom : dayFilterMin,
            iMax = dayFilterMax === UNDEF || dayFilterTo < dayFilterMax ? dayFilterTo : (
              dayFilterMax < dayFilterFrom ? dayFilterFrom : dayFilterMax
            );
        if(b){
          b = djp(oDJP).ancestor().hasClass('m-slide-control-mini');
          if(b.ancestor().hasClass('js-param-slider').node){
            startPos = b.offset().left;
            iMin =  posX - startPos - (iMin - dayFilterFrom) * dayFilterPixPerDay;
            iMax = Math.abs( posX - startPos - (iMax - dayFilterFrom) * dayFilterPixPerDay );
            isMinValue = Math.abs(iMin) < iMax ?
            true : (
              Math.abs(iMin) > iMax ?
              false : (
                iMin < 0
              )
            );
            move(posX);
            b = true;
          } else {
            b = false;
          }
        }
        return b
      },
      function(delta, posX){
        move(posX);
      },
      function(b){
        //djp(document.body).toggleClass('jsWResize', b);
      },
      true
    );
  })();
  //раскрыть/свернуть список фильтра по авиакомпаниям
  (function(){
    function hiddeCompaniesFilter (oDJP){
      var t, i, k;
      if( !djp(oDJP).ancestor().hasClass('b-avia-companies-dropdown').node ){
        for(
          t = document.forms['geoForm'].getElementsByTagName('DL'),
          i = t.length;
          i--;
        ){
          k = djp(t[i]).hasClass('js-param-airlines').child().hasNode('DD')
          .child().child().hasClass('b-avia-companies-dropdown__content');
          if( k.node ){
            k.toggleClass('hidden', true);
          }
        }
      }
    }
    djp.clickTouch(
      function(oDJP){
        var res = djp(oDJP).ancestor().hasClass('b-avia-companies-dropdown__val');
  
        if( !res.node ){
          res = false;
          //схлопываем раскрытые варианты фильтра по авиакомпаниям, при клике мимо фильтра
          hiddeCompaniesFilter(oDJP);
        }
        return res;
      },
      function(res, oEvent){
        oEvent.prevent();
        res.next().toggleClass('hidden');
      }
    );
    djp(document).attach(
      'mousedown',
      function(oDJP, oEvent){
        !djp(oDJP).ancestor().hasClass('b-avia-companies-dropdown__val').node
        && hiddeCompaniesFilter(oDJP);
      }
    );
  })();
  //включить/выключить все авиакомпании из списока фильтра
  djp.clickTouch(
    function(oDJP){
      var
      res = [djp(oDJP).ancestor().hasNode('INPUT').hasAttr('value', '')];

      res[1] = res[0].node
      && res[0].ancestor().hasClass('b-avia-companies-dropdown__content');
      return res[0].node && res;
    },
    function(res, oEvent){
      setTimeout(
        function(){
          var
          i,
          b = !res[0].node.checked,
          a = res[1].node.getElementsByTagName('INPUT');
    
          for( i = a.length; i--; ){
            a[i].checked = b;
          }
        },
        20
      );
      oEvent.prevent();
    }
  );
  //применить фильтр по авиакомпаниям
  djp.clickTouch(
    function(oDJP){
      var
      res = djp(oDJP).ancestor().hasClass('b-offer__buy')
      .ancestor().hasClass('b-avia-companies-dropdown__content');

      return res.node && res;
    },
    function(res, oEvent){
      var
      unselectedValues = [],
      valuesCount,
      i,
      inputs = res.node.getElementsByTagName('INPUT');

      for(
        valuesCount = 0,
        i = inputs.length;
        i--;
      ){
        if(inputs[i].value){
          valuesCount++;
          delete excludeAirlinesFilter[inputs[i].value];
          if(!inputs[i].checked){
            unselectedValues[unselectedValues.length] = inputs[i].value;
          }
        }
      }
      i = unselectedValues.length;
      if( i !=  valuesCount){
        while(i--){
          excludeAirlinesFilter[ unselectedValues[i] ] = true;
        }
      }
      res.toggleClass('hidden', true);
      if(container.node) {
        containerScrollBy(
          container.node.scrollLeft + containerVirtualScrollLeft
        );
      }
    }
  );
  //кнопки промотки найденных билетов
  (function(){
    var hasTouch = false;

    djp(document).attach(
      'mousedown touchstart',
      function(oDJP, oEvent){
        var a, j, res;
        if(oEvent.$type != 'mousedown'){
          hasTouch = true;
        }
        if( container.node && oEvent.$type == 'mousedown' ^ hasTouch){
          if(hasTouch){
            a = oEvent.e['changedTouches'];
          } else {
            a = [{'target': oDJP.node}];
          }
          for(
            j = a['length'];
            j--;
          ){
            if( djp(a[j]['target']).ancestor().hasClass('b-offers-scroll-btn m-scroll-left').node ){
              containerPermanetlyScrollBy(60);
            }
            if( djp(a[j]['target']).ancestor().hasClass('b-offers-scroll-btn m-scroll-right').node ){
              containerPermanetlyScrollBy(-60);
            }
          }
        }
      }
    );
  })();
  /*первоначальная инфа о корзине*/
  (function(){
    if (cartShowContainer.node) {
      djp.getJSON(
        r_avia.urls.cart_info,
        function(data){
          if (data && !data['error']){
            updateCartTikets(data['tickets']);
            updateTicketsCount(data['tickets'].length);
            updateCardTicketsId(data['cart_id']);
            updateCardUrlHash(data['cart_id']);
          } else {
            r_avia.log.error(
              data && data['error'] ?
              data['error'] :
              'не подгрузилась инфа о корзине'
            );
          }
        }
      );
    }
  })();
  
  /* Если кликнули по выключенной кнопке - не сабмитим форму */
  var searchButtonDJP = djp('searchButton');
  if(searchButtonDJP.node){
    searchButtonDJP.attach('click',function(el,evt){
      if(djp(el).classIs('b-search-disable')){
        evt.prevent();
      }
    });
  }
  /* Перекрасить фильтр пересадок в соотведствии с полученному из черепашки */
  (function(){
    var a, i, n;
    if( !!transshipmentsFilter && container.node){
      for(
        a = document.forms['geoForm'].getElementsByTagName('DL'),
        i = a.length;
        i--;
      ){
        n = djp( a[i] );
        if( n.classIs('b-param-transfers') && n.classIs('js-checkpaks') ){
          i = 0
        } else {
          n = false
        }
      }
      if(n){
        djp(n).child().child().child()
        .hasClass('js-checkpaks-any').toggleClass(
          'b-hor-chooser__item-selected', false
        );
        for(
          i = 0,
          n = n.child().child().child().hasClass('js-checkpaks-item');
          !!n.node;
          n.more(), i++
        ){
          n.toggleClass(
            'b-hor-chooser__item-selected',
            !!( transshipmentsFilter & (1<<i) )
          );
        }
      }
    }
  })();
})(
/* шаблон стопки сгруппированных билетов */
'\n\
<div\n\
  data-ticket-id="<%= c.id %>"\n\
  data-ticket-date="<%= c.tiketDate() %>"\n\
  class="js-tiketsBlock b-offer m-big-offer<%= c.isInCart() ? " m-offer__addtocart-added" : "" %> <%= c.isCart && !c.isForeignerCart ? "m-offer-in-my-cart" : "" %> <%\n\
    if( c.width ) {%> m-offer-expanded " style="width:<%= c.width %>px<%\n\
    } %>"\n\
>\n\
    <div class="b-offer-frame">\n\
        <div class="b-offer-frame__inner">\n\
            <p class="b-offer__price"><strong class="b-price-value"><%= c.formatCost() %></strong> <span class="b-price-value__sign">р.</span></p>\n\
            <div class="b-offer__info">\n\
                <% //Чартер %><% if( c.stayPeriod() ){\n\
                %><span class="b-offer__duration"><%= c.stayPeriod() + r_avia.h.word_endings( c.stayPeriod(), " день", " дня", " дней" ) %></span>\n\
                <% } if(c.isCart){\n\
                %><% if (!c.isForeignerCart) { %><i class="b-offer__close"></i><% } %>\n\
                <% } if(c.more){\n\
                %><br /><a href="#">и еще <%= c.more + r_avia.h.word_endings(c.more, " вариант", " варианта", " вариантов") %> </a><% } %>\n\
            </div>\n\
            \n\
            <span class="b-micro-popup b-micro-popup-hidden b-airline-name-to">\n\
                <%= c.getAirlineName(0) %><i class="b-micro-popup__cn"></i>\n\
            </span>\n\
            \n\
            <% if("back" in c) {%>\n\
                <span class="b-micro-popup b-micro-popup-hidden b-airline-name-back">\n\
                    <%= c.getAirlineName(1) %><i class="b-micro-popup__cn"></i>\n\
                </span>\n\
            <% } %>\n\
            <div class="b-offer-tickets-wrapper">\n\
            <% var len = ("back" in c ? 2 : 1);\n\
              for(var i = 0; i < len; i++){ %>\n\
              <table cellspacing="0" class="b-offer-ticket <%= i == 1?"m-ticket-back":"" %>  <%= (c[ i ? "back" : "direct" ].airline).toLowerCase() %>">\n\
                <tr>\n\
                  <td class="b-offer-ticket__first m-offer-from">\n\
                      <span class="b-line-filler"><span class="b-offer-ticket__time"><%= c.getTime(i,0) %></span>\n\
                      <% if(c.storeTimes && c.storeTimes[i].length > 1){ %>\n\
                      <i class="b-offer-ticket__expand<%= i === c.timeExpand ? " m-offer-ticket__expand-collapse" : "" %>"></i>\n\
                      <% } %>\n\
                      </span>\n\
                      <span class="b-offer-ticket__direction"><%= c.getPlaceName(i,0) %></span>\n\
                  </td>\n\
                  <td class="m-offer-to">\n\
                      <span class="b-offer-ticket__time"><%= c.getTime(i,1) %></span>\n\
                      <% if( c.getDate(i,0) != c.getDate(i,1) || c.getMounth(i,0) != c.getMounth(i,1) ){ %>\n\
                      &nbsp;<span class="b-red-text"><%= c.getDate(i,1) %> <%= c.getMounthName(i,1) %></span>\n\
                      <% } %>\n\
                      <span class="b-offer-ticket__direction"><%= c.getPlaceName(i,1) %></span>\n\
                  </td>\n\
                  <td class="b-offer-ticket__date">\n\
                      <%= c.getMounthName(i,0) %><br /><span class="b-offer-ticket__date__num"><%= c.getDate(i,0) %></span>\n\
                  </td>\n\
                </tr>\n\
                <% if(i === c.timeExpand) {%>\n\
                <tr class="b-offer-ticket__time-selector">\n\
                    <td class="b-offer-ticket__first" colspan="2">\n\
                      <div style="position:relative;">\n\
                        <div class="b-offer-ticket__time-selector__cn"></div>\n\
                        <% for(var j = 0, n = c.storeTimes[i].length; j < n; j++){ %>\n\
                          <span class="g-button-small m-button-grey" data-other-id="<%= c.storeTimes[i][j].id %>"><%= c.storeTimes[i][j].departureTime %></span>\n\
                        <% } %>\n\
                      </div>\n\
                    </td>\n\
                    <td></td>\n\
                </tr>\n\
                <% } %>\n\
                <tr>\n\
                  <td colspan="2" class="b-offer-ticket__first">\n\
                      <div class="b-offer-ticket__changes"><%= c.getTransshipmentsInfo(i) %></div>\n\
                      <span class="b-offer-ticket__travel-time"><%= c.getDurationMedium(i) %></span>\n\
                  </td>\n\
                  <td class="b-offer-ticket__logo">\n\
                      <span class="b-offer-ticket__airline__logo" style="position:relative"></span>\n\
                  </td>\n\
                </tr>\n\
              </table>\n\
            <% } %>\n\
            </div>\n\
            <div class="b-offer__buttons">\n\
                <span class="g-button-medium m-button-grey b-offer__buy">Купить</span>\n\
                \n\
                <% if(!c.isCart || c.isForeignerCart){%>\n\
                    <span class="g-button-medium b-offer__addtocart m-button-grey"><i class="b-offer__addtocart__icon"></i></span>\n\
                <% } %>\n\
                \n\
                <a href="#" class="b-offer__details-link">Подробности</a>\n\
            </div>\n\
            <div class="b-offer-fader"></div>\n\
        </div>\n\
    </div>\n\
</div>',
//шаблон развернутого билета
'<div\n\
  class="b-offer m-big-offer m-offer-full<%= c.isInCart() ? " m-offer__addtocart-added" : "" %>"\n\
  data-ticket-id="<%= c.id %>"\n\
  data-ticket-date="<%= c.tiketDate() %>"\n\
>\n\
    <div class="b-offer-frame">\n\
        <div class="b-offer-frame__inner">\n\
            <i class="b-close b-offer__close"></i>\n\
            <p class="b-offer__price"><strong class="b-price-value"><%= c.formatCost() %></strong>р.</p>\n\
            <div class="b-offer__info">\n\
                <% if( "back" in c ){%><span class="b-offer__duration"><%= c.stayPeriod() + r_avia.h.word_endings( c.stayPeriod(), " день", " дня", " дней" ) %></span><% } %>\n\
            </div>\n\
            <% var isDeparture = 0, isArrival = 1, len = ("back" in c ? 2 : 1); %>\n\
            <% for(var i = 0; i < len; i++){ %>\n\
            <table class="b-offer-ticket <%= i ? "m-ticket-back" : "" %>">\n\
                <% for( var segs = c[ ["direct","back"][i] ].segments, segPos = 0, segLen = segs.length; segPos < segLen; segPos++){ %>\n\
\n\
                <tr class="<%= (c[ ["direct","back"][i] ].segments[segPos].airline).toLowerCase() %>">\n\
                    <td class="b-offer-ticket__first m-offer-from">\n\
                        <span class="b-line-filler"><span class="b-offer-ticket__time"><%= segs[segPos].departure_datetime.slice(11, -3) %></span>\n\
                        <% if(c.getDateForSegment(i,isDeparture,0) != c.getDateForSegment(i,isDeparture,segPos)){ %>\n\
                            &nbsp;<span class="b-red-text"><%= c.getDateForSegment(i,isDeparture,segPos) %> <%= c.getMounthFullNameRForSegment(i,isDeparture,segPos) %></span>\n\
                        <% } %>\n\
                        </span>\n\
                        <span class="b-offer-ticket__direction"><%= c.getAirportNameForSegment(i,isDeparture,segPos) %></span>\n\
                    </td>\n\
                    <td class="m-offer-to">\n\
                        <span class="b-offer-ticket__time"><%= segs[segPos].arrival_datetime.slice(11, -3) %></span>\n\
                        <% if(c.getDateForSegment(i,isDeparture,0) != c.getDateForSegment(i,isArrival,segPos)){ %>\n\
                            &nbsp;<span class="b-red-text"><%= c.getDateForSegment(i,isArrival,segPos) %> <%= c.getMounthFullNameRForSegment(i,isArrival,segPos) %></span>\n\
                        <% } %>\n\
                        <span class="b-offer-ticket__direction"><%= c.getAirportNameForSegment(i,isArrival,segPos) %></span>\n\
                    </td>\n\
                    <td class="b-offer-ticket__airline <%= (segs[segPos].airline).toLowerCase() %>">\n\
                        <span class="b-offer-ticket__airline__logo"></span>\n\
\n\
                        <%= c.getAirlineNameForSegment(i,segPos) %>\n\
                        <span class="b-offer-ticket__info">\n\
                            Рейс <%= c.getFlightNumberForSegment(i,segPos) %><br>\n\
                            <%= c.getPlaneWithDurationForSegment(i,segPos) %>\n\
                        </span>\n\
                    </td>\n\
                    <% if(segPos == 0){ %>\n\
                    <td class="b-offer-ticket__date" rowspan="<%= 1 + segLen * 2 %>">\n\
                        <%= c.getMounthFullNameForSegment(i,isDeparture,segPos) %><br />\n\
                        <span class="b-offer-ticket__date__num"><%= c.getDateForSegment(i,isDeparture,segPos) %></span>\n\
                        <span class="b-offer-ticket__date__day"><%= c.getDay(i,isDeparture) %></span>\n\
                    </td>\n\
                    <% } %>\n\
                </tr>\n\
                <tr>\n\
                    <td colspan="3" class="b-offer-ticket__horline <%= segLen - 1 != segPos ? "b-offer-ticket__change" : "" %>">\n\
                        <div class="b-offer-ticket__line"></div>\n\
                        <% if(segLen - 1 != segPos){ %><span class="b-offer-ticket__change__text"><%= c.getTransshipmentTimeForSegment(i,segPos) %></span><% } %>\n\
                    </td>\n\
                </tr>\n\
                <% } %>\n\
                <tr class="b-offer-ticket__summary">\n\
                    <td colspan="2" class="b-offer-ticket__summary__text">\n\
                        <%= c.getSummaryText(i) %>\n\
                    </td>\n\
                    <td><%= c.getDurationMedium(i) %></td>\n\
                </tr>\n\
            </table>\n\
            <% } %>\n\
            <div class="b-offer__buttons">\n\
                <span class="g-button-medium m-button-grey b-offer__buy">Купить</span>\n\
                <% if(!c.openFromMyCart){ %>\n\
                    <span class="g-button-medium m-button-grey b-offer__addtocart"><i class="b-offer__addtocart__icon"></i> Добавить в корзину</span>\n\
                <% } %>\n\
            </div>\n\
            <div class="b-offer-fader"></div>\n\
        </div>\n\
    </div>\n\
</div>\n\
\n\
',
//шаблон обычной корзины
'<div class="w-fix">\n\
    <div class="b-offer-list" style="width:1000px;">\n\
        <!-- tickets list -->\n\
        <%= c %>\n\
    </div>\n\
</div>',
//шаблон сравнения
'<% function removeReason(){ %>\n\
    <div class="b-for-removed g-hidden">\n\
        <span class="b-cancel-remove">Отметить</span>\n\
        <p class="b-why-remove">Вы удалили билет. <br />Почему он вам не понравился?</p>\n\
        <ul class="b-why-remove-variants">\n\
            <li class="b-why-remove-variants__variant"><input type="radio" name="why"/><label>Слишком рано</label></li>\n\
            <li class="b-why-remove-variants__variant"><input type="radio" name="why"/><label>Слишком поздно</label></li>\n\
            <li class="b-why-remove-variants__variant"><input type="radio" name="why"/><label>Слишком рано</label></li>\n\
            <li class="b-why-remove-variants__variant"><input type="radio" name="why"/><label>Слишком дорогой</label></li>\n\
            <li class="b-why-remove-variants__variant"><input type="radio" name="why"/><label>Слишком долгая пересадка</label></li>\n\
            <li class="b-why-remove-variants__variant"><input type="radio" name="why"/><label>Не нравится авиакомпания</label></li>\n\
        </ul>\n\
    </div>\n\
<% } %>\n\
<% function direction(isBack){ %>\n\
    <% if (!isBack) { %>\n\
        <tr class="b-compare__main-row m-nohover">\n\
            <th>&nbsp;</th>\n\
            <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td>\n\
               <div class="b-offer-summary" data-ticket-id="<%= t.id %>" data-ticket-date="<%= t.tiketDate() %>">\n\
                    <% if (!c[i].isForeignerCart) { %><i class="b-compare__remove-offer"></i><% } %>\n\
                    <span class="g-button-medium m-button-grey b-offer__buy">Купить</span>\n\
                    <strong class="b-compare__price"><span class="b-compare__price__val"><%= t.formatCost() %></span> <span class="b-compare__price__cur">р.</span></strong>\n\
                    <div class="b-compare__long"><% if( t.stayPeriod() ){ %><%= t.stayPeriod() + r_avia.h.word_endings( t.stayPeriod(), " день", " дня", " дней" ) %><% } %></div>\n\
                </div>\n\
                <% removeReason() %>\n\
            </td>\n\
            <% } %>\n\
        </tr>\n\
    <% } %>\n\
\n\
    <tr class="b-compare__empty m-nohover b-direction-empty-spacer">\n\
        <th></th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td></td>\n\
        <% } %>\n\
    </tr>\n\
    <tr>\n\
        <th>\n\
            <i class="b-plane-icon<% if (isBack) { %> m-plane-icon-back<% } %>"></i>\n\
            Перелет <%= isBack?"обратно":"туда" %>\n\
        </th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td class="b-table-flight-name">\n\
                <% if (t[isBack ? "back" : "direct"]) { %>\n\
                    <span class="b-table-flight-name__departure"><%= t.getPlaceName(isBack,0) %></span> &mdash; <%= t.getPlaceName(isBack,1) %>\n\
                <% } else { %>\n\
                    &nbsp;\n\
                <% } %>\n\
            </td>\n\
        <% } %>\n\
    </tr>\n\
    <tr>\n\
        <th>Аэропорт вылета</th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td>\n\
                <% if (t[isBack ? "back" : "direct"]) { %>\n\
                    <%= t.getAitportNameWithIATA(isBack,0) %>\n\
                <% } else { %>\n\
                    &nbsp;\n\
                <% } %>\n\
            </td>\n\
        <% } %>\n\
    </tr>\n\
    <tr>\n\
        <th>Дата вылета</th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td>\n\
                <% if (t[isBack ? "back" : "direct"]) { %>\n\
                    <%= t.getDate(isBack,0) %> <%= t.getMounthFullName(isBack,0) %>, <%= t.getDay(isBack,0) %>\n\
                <% } else { %>\n\
                    &nbsp;\n\
                <% } %>\n\
            </td>\n\
        <% } %>\n\
    </tr>\n\
    <tr>\n\
        <th>Время вылета</th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td>\n\
                <% if (t[isBack ? "back" : "direct"]) { %>\n\
                    <%= t.getTime(isBack,0) %>\n\
                <% } else { %>\n\
                    &nbsp;\n\
                <% } %>\n\
            </td>\n\
        <% } %>\n\
    </tr>\n\
    <tr>\n\
        <th>Время прилета</th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td>\n\
                <% if (t[isBack ? "back" : "direct"]) { %>\n\
                    <%= t.getTime(isBack,1) %>\n\
                    <% if(t.getDate(isBack,0) != t.getDate(isBack,1)){ %>\n\
                        <span class="b-red-comment"><%= t.getDate(isBack,1) %> <%= t.getMounthFullName(isBack,1) %></span>\n\
                    <% } %>\n\
                <% } else { %>\n\
                    &nbsp;\n\
                <% } %>\n\
            </td>\n\
        <% } %>\n\
    </tr>\n\
\n\
    <tr>\n\
        <th>Время в пути</th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td>\n\
                <% if (t[isBack ? "back" : "direct"]) { %>\n\
                    <%= t.getDurationShort(isBack) || "<span class=\\"b-color-gray\\">нет данных</span>" %>\n\
                <% } else { %>\n\
                    &nbsp;\n\
                <% } %>\n\
            </td>\n\
        <% } %>\n\
    </tr>\n\
    <tr>\n\
        <th>Пересадки</th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td>\n\
                <% if (t[isBack ? "back" : "direct"]) { %>\n\
                    <%= t.getTransshipmentsCount(isBack) || "&mdash;" %>\n\
                <% } else { %>\n\
                    &nbsp;\n\
                <% } %>\n\
            </td>\n\
        <% } %>\n\
    </tr>\n\
    <tr>\n\
        <th>Города пересадок</th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td>\n\
                <% if (t[isBack ? "back" : "direct"]) { %>\n\
                    <%= t.getTransshipmentsCityWithDuration(isBack) %>\n\
                <% } else { %>\n\
                    &nbsp;\n\
                <% } %>\n\
            </td>\n\
        <% } %>\n\
    </tr>\n\
    <tr>\n\
        <th>Авиакомпании</th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td>\n\
                <% if (t[isBack ? "back" : "direct"]) { %>\n\
                    <%= t.getAirlinesSummary(isBack) %>\n\
                <% } else { %>\n\
                    &nbsp;\n\
                <% } %>\n\
            </td>\n\
        <% } %>\n\
    </tr>\n\
    <tr>\n\
        <th>Тип самолета</th>\n\
        <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
            <td>\n\
                <%  var airplaneModel = t[isBack ? "back" : "direct"]?t.getPlanesSummary(isBack):undefined; %>\n\
                 <% if(airplaneModel){ %>\n\
                     <%= airplaneModel %>\n\
                 <% } else { %>\n\
                     <span class="b-color-gray">нет данных</span>\n\
                 <% } %>\n\
            </td>\n\
        <% } %>\n\
    </tr>\n\
<% } %>\n\
<% if (c.length > 0) { %>\n\
    <div class="b-comapre-table-wrapper">\n\
        <table class="b-compare-table" rules="groups" style="width:<%= 142 + 230 * c.length %>px">\n\
            <colgroup class="b-first-colgroup"></colgroup>\n\
            <colgroup span="1" class="m-first-col-shadow"></colgroup>\n\
            <% for (var i=0;i<c.length-1;i++) { var t = c[i]; %>\n\
                <colgroup span="1"></colgroup>\n\
            <% } %>\n\
\n\
            <tbody class="b-compare__offers">\n\
                <% direction(0) %>\n\
                <tr class="b-compare__spacer m-nohover">\n\
                    <th>&nbsp;</th>\n\
                    <% for (var i=0;i<c.length;i++) { var t = c[i]; %>\n\
                        <td></td>\n\
                    <% } %>\n\
                </tr>\n\
                <% direction(1) %>\n\
            </tbody>\n\
        </table>\n\
    </div>\n\
<% } %>',
//шаблон фильтра авиакомпаний
djp.tmpl('<div class="b-avia-companies-dropdown">\n\
    <div class="b-avia-companies-dropdown__val"><%= "val" in c ? c.val : "&nbsp;" %><i class="b-avia-companies-dropdown__arr"></i></div>\n\
    <div class="b-avia-companies-dropdown__content hidden">\n\
        <ul class="b-avia-companies-list">\n\
          <li class="b-avia-companies-list__item">\n\
                <span class="b-avia-companies-list__price">от <%= c.minPrice %> р.</span>\n\
                <label>\n\
                    <input\n\
                       type="checkbox"\n\
                       name=""\n\
                       value=""\n\
                       <%= c.selectedAll ? " checked" : "" %>\n\
                       class="b-avia-companies-list__checkbox"\n\
                    />\n\
                    <span class="b-avia-companies-list__item-label">Все авиакомпании</span>\n\
                </label>\n\
            </li>\n\
          <% for(var i=c.length; i--;){ %>\n\
            <li class="b-avia-companies-list__item">\n\
                <span class="b-avia-companies-list__price">от <%= c[i].minPrice %> р.</span>\n\
                <label>\n\
                    <input\n\
                       type="checkbox"\n\
                       name=""\n\
                       value="<%= c[i].value %>"\n\
                       <%= c[i].selected ? " checked" : "" %>\n\
                       class="b-avia-companies-list__checkbox"\n\
                    />\n\
                    <span class="b-avia-companies-list__item-label"><%= c[i].name %></span>\n\
                </label>\n\
            </li>\n\
          <% } %>\n\
        </ul>\n\
        <span class="g-button-small m-button-grey b-offer__buy">Применить</span>\n\
    </div>\n\
</div>')
);

"use strict";
(function(djp, template, TRUE, FALSE, NULL, UNDEFINED){
  var
  doc = document, oSellers = {
    params: {
      'adults':1,
      'children':0,
      'infants':0
    },
    Cart: false
  },
  lastBoxNode,
  tableElement,
  popupBoxNode,
  timer,
  preloaders;

  template = djp.tmpl(template);;

  preloaders = (function(){
      var preloadersElements,
          animationTimeout;
      
      function animatePreloaders(to){
          var i;
          
          to = to || 0;
          
          for(i = 0 ; i < preloadersElements.length; i++){
              preloadersElements[i].style.backgroundPosition = to + 'px -34px';
          }
          
          to = to + 1;
          
          animationTimeout = setTimeout(function(){animatePreloaders(to)},100);
      }
      
      return {
          start:function(){
              if(!animationTimeout){
                  animatePreloaders()
              }
          },
          rebuildElements: function(){
              var rowElement,
                  rowsCnt;
              
              preloadersElements  = [];
              
              for(rowsCnt = 0; rowsCnt < tableElement.rows.length; rowsCnt++){
                  rowElement = djp(tableElement.rows[rowsCnt]);
                  
                  if(rowElement.classIs('b-buy-table__row_loaded')){
                      preloadersElements.push( rowElement.child().child().hasClass('b-buy-table-preloader').node );
                  }
              }
          },
          stop: function(){
              clearTimeout(animationTimeout);
              animationTimeout = undefined;
          }
      }
     
  })();
  
  function sendRe(options){
    var k, url;
    timer !== UNDEFINED && clearTimeout(timer);
    timer = UNDEFINED;

    if(options === undefined){
        options = {
            retries: r_avia.conf.ajaxPollRetries,
            after: r_avia.conf.ajaxPollTimeoutFirst,
            retryNum: 0
        };
    }

    oSellers.params['serial'] = options.retryNum + 1;
    url = r_avia['urls']['ticket_refine'] + '?' + djp.objToQueryString(oSellers.params);

    djp.getJSON(
      url,
      function(json, r){
        options.retries--;
        options.retryNum++;
        options.after *= r_avia.conf.ajaxPollTimeoutMod;
        
        var o, source={'params': oSellers.params},
          track='events.'+(oSellers.Cart?'cart_':'')+'ticket.buy.response'
            +oSellers.params.adults+oSellers.params.children+oSellers.params.infants+'.'+options.retryNum;
          if(json && !json['error']){
              
          source['json'] = json;
          source['retries'] = options.retries;
          source['retryNum'] = options.retryNum;
          source['more'] = options.retries > 0 && json['more'];
          
          if (source['json'].sellers.length) {
            /*регистрируем событие наличие возможности купить на данном шаге*/
            _tracker.track(track+'.ok');
          } else {
            /*регистрируем событие отстутствия возможности купить на данном шаге*/
            _tracker.track(track+'.nil');
          }
          if(lastBoxNode && options.retryNum > 1){
            var fragment = document.createElement('div');
                fragment.innerHTML = template(source);
                
            var newBoxElement = djp(fragment).child();
          
            lastBoxNode.node.parentNode.replaceChild( newBoxElement.node , lastBoxNode.node);
            
            lastBoxNode = newBoxElement ;
            
            tableElement = djp(newBoxElement).child().hasClass('b-buy-table').node;
            if(tableElement && source['more']){
                preloaders.rebuildElements();
                preloaders.start();
            }
            
          } else if(options.retryNum === 1){
            oSellers.fClose && oSellers.fClose();
            oSellers.fClose = djp.openBox.centre(
              djp(doc),
              {
                openCallback: function(boxNode, recalc, target){
                  popupBoxNode = boxNode;
                  boxNode.append(template(source));
                  tableElement = djp(popupBoxNode).child().child().child().hasClass('b-buy-table').node;
                  
                  if(source['more']){
                      preloaders.rebuildElements();
                      preloaders.start();
                  }
                  
                  o = djp(boxNode).child().hasNode('FORM');
                  lastBoxNode = djp(o.child().hasClass('b-buy-popup__container'));
                  o.attach(
                    'submit',
                    resetForm
                  );
                },
                closeCallback: function(boxNode){
                  oSellers.fClose = NULL;
                  lastBoxNode = NULL;
                  timer !== UNDEFINED && clearTimeout(timer);
                  timer = UNDEFINED;
                },
                closeClass: 'b-close',
                shadowClass: 'b-page-fader'
              }
            );
          }
          if(lastBoxNode && source['more']){
            timer = setTimeout(function(){
                sendRe(options);
            }, options.after);
          } else {
              preloaders.stop();
          }
        } else {
          /*регистрируем ошибку*/
          _tracker.track(track+'.error');
          djp.alert(
            json && json['error'] ?
            json['error'] :
            'К сожалению, произошла ошибка. Закройте окно перехода на покупку и попробуйте нажать кнопку "Купить" еще раз.'
          );
        }
      }
    );
  }
  function resetForm(oTarget, oEvent){
    var
    formDJP = oTarget.ancestor().hasNode('FORM'),
    hash = formDJP.serializeToObject(),
    k;
    
    oEvent.prevent();
    for( k in hash ){
      oSellers.params[k] = hash[k][0]
    }
    /*регистрируем нажатие на кнопку "Пересчитать" с учетом количества пассажиров в окне покупки*/
    var track = 'events.'+(oSellers.Cart?'cart':'')+'ticket.buy.refine.'
      +oSellers.params.adults+oSellers.params.children+oSellers.params.infants;
    _tracker.track(track);
    sendRe();
  }
  djp.clickTouch(
    function(oDJP){
      var oTID = djp(oDJP)
          .ancestor().hasClass('b-offer__buy')
          .ancestor().hasAttr('data-ticket-id', /.+/);
      return oTID.node && oTID;
    },
    function(oTID, oEvent){
      oSellers.Cart = djp(oTID).ancestor().hasClass('b-cart').node ? true : false;
      oSellers.params['tid'] = oTID.attr('data-ticket-id');
      oSellers.params['date_direct'] = oTID.attr('data-ticket-date');
      /*регистрируем нажатие на кнопку купить в выдаче или в корзине*/
      var track = 'events.'+(oSellers.Cart?'cart':'')+'ticket.buy.click';
      _tracker.track(track);
      sendRe();
    }
  );
})(
window['djp'],
'<% \n\
    var emptySellersCounter = 0;\n\
    \n\
    for( var t=c.json.sellers, i=0, l=t.length; i < l ; i++){\n\
        t[i].isEmpty = false;\n\
        t[i].isLoaded = false;\n\
        if(t[i].cost === null){\n\
            if(c.retries !== 0){\n\
                t[i].isLoaded = true;\n\
            }\n\
            if(!c.more){\n\
                t[i].isEmpty = true;\n\
                emptySellersCounter++\n\
            }\n\
        }\n\
    }\n\
    \n\
%>\n\
\n\
<% function box(){ %>\n\
    <div class="b-buy-popup__container">\n\
        <div class="b-buy-table-head">Выберите продавца</div>\n\
        <table class="b-buy-table">\n\
            <tbody>\n\
                <% for( var t=c.json.sellers, i=0, l=t.length; i < l ; i++){ %>\n\
                    <%\n\
                        var rowClassName = "";\n\
                        \n\
                        if(t[i].isLoaded){\n\
                            rowClassName = "b-buy-table__row_loaded";\n\
                        }\n\
                        \n\
                        if(t[i].isEmpty){\n\
                            rowClassName = rowClassName+ " b-buy-table__row_none";\n\
                        }\n\
                        \n\
                    %>\n\
                    <tr class="<%=rowClassName %>">\n\
                        <td class="b-buy-table__<%= i == l ? "last" : "first" %>">\n\
                            <h2 class="b-buy-table__partner"><%= t[i].seller_name %></h2>\n\
                            <p class="b-buy-table__paymethods"><% if (t[i].payment_info) { %>Способы оплаты: <%= t[i].payment_info %><% } %></p>\n\
                        </td>\n\
\n\
                        \n\
                        \n\
                        <% if(t[i].isEmpty) {%>\n\
                            <td colspan="2" class="b-buy-table__notfound">Билеты не найдены</td>\n\
                        <% } else {%>\n\
                            <td class="b-buy-table__price">\n\
                                <% if(!t[i].isLoaded && !t[i].isEmpty){%>\n\
                                    <strong class="b-buy-table__price__val"><%= t[i].cost %></strong>Р.\n\
                                <% } %>\n\
                            </td>\n\
                            <td class="b-buy-table__buy">\n\
                                <% if(t[i].isLoaded){%>\n\
                                    <span class="b-buy-table-preloader">Уточняем</span>\n\
                                <%} else {%>\n\
                                    <a href="<%= r_avia.urls.ticket_recheck(c.params.tid, c.params.date_direct, t[i].seller, c.params.adults, c.params.children, c.params.infants) %>" target="_blank">\n\
                                        <span class="g-button-medium m-button-grey">Купить</span>\n\
                                    </a>\n\
                                <%}%>\n\
                            </td>\n\
                        <% } %>\n\
                    </tr>\n\
                <% } %>\n\
            </tbody>\n\
        </table>\n\
        <p class="b-buy-popup__note">Все цены включают налоги и сборы. <br />\n\
            <span class="b-buy-popup__note-micro">Стоимость может измениться в зависимости от наличия выбранного тарифа при оформлении.</span>\n\
        </p>\n\
    </div>\n\
<% } %>\n\
<% function selector(name, label, min, max, selected, desc){ %>\n\
    <dl class="b-buy-popup__param m-buy-popup__param-<%= name %>">\n\
        <dt>\n\
            <span class="b-buy-popup__param__label"><%= label %></span>\n\
            <% if(desc){ %>\n\
                <span class="b-buy-popup__param__description"><%= desc %></span>\n\
            <% } %>\n\
        </dt>\n\
        <dd>\n\
            <select name="<%= name %>">\n\
                <% for(var i=min;i<=max;i++){ %>\n\
                    <option value="<%= i %>"<% if(i == selected) { %> selected<% } %>>\n\
                        <%= i?i:"-" %>\n\
                    </option>\n\
                <% } %>\n\
            </select>\n\
        </dd>\n\
    </dl>\n\
<% } %>\n\
<% if(c.retryNum === 1){ %>\n\
<form class="b-popup b-buy-popup" data-ticket-id="<%= c.params.tid %>">\n\
    <i class="b-close"></i>\n\
    <h2 class="b-popup__header">Купить билет</h2>\n\
    <div class="b-buy-popup__params">\n\
        <p class="b-buy-popup__params_caption">Для уточнения стоимости билета укажите количество пассажиров</p>\n\
        <% selector("adults", "Взрослые", 1, 6, c.params.adults) %>\n\
        <% selector("children", "Дети", 0, 3, c.params.children, "от 2 до 12 лет") %>\n\
        <% selector("infants", "Младенцы", 0, 3, c.params.infants, "младше 2 лет") %>\n\
        <input type="submit" value="Пересчитать" class="g-button-small m-button-grey b-button-count">\n\
    </div>\n\
\n\
    <% box() %>\n\
\n\
    \n\
</form>\n\
<% } else { \n\
        if(c.retries === 0 && emptySellersCounter === c.json.sellers.length){ %>\n\
            <div class="b-search-error">\n\
                <div class="b-search-error__icon"></div>\n\
                К сожалению, для заданного количества пассажиров нет билетов на этот рейс\n\
            </div>\n\
<%      } else { %>\n\
            <% box() %>\n\
<%      }\n\
   }\n\
 %>',
true, false, null);

"use strict";
(function(djp, TRUE, FALSE, NULL, template, UNDEFINED){
  var
  doc = document,
  timer,
  i,
  lastSuggestsValues = {},
  oSuggest = {},
  xhr,
  preventMouseupAfterFocus;

  template = djp.tmpl(template);
  //включить/выключить кнопку поиска
  r_avia.parts.searchFormIsNotFill();

  function fEnter(oDJP, oEvent){
    var oItem = djp(oDJP).ancestor().hasClass('item');

    if( oItem.node ){
      oSuggest.oCurrentItem = oItem;
      oSuggest.fClose();
    }
  }
  function setNewCurrentItem(oDJP, isEnter){

    var
    itemDJP = oDJP.ancestor().hasClass('item'),
    i,
    res,
    iata_code,
    b,
    l;

    if(itemDJP.node){
      oSuggest.oCurrentItem.toggleClass('selected', FALSE);
      ( oSuggest.oCurrentItem = itemDJP ).toggleClass('selected', TRUE);
      iata_code = itemDJP.attr('data-iata');
      for(
        i = 0,
        l = oSuggest.aLocations.length;
        !res && i < l;
        i++
      ){
        if( oSuggest.aLocations[i][0] == iata_code ){
          res = oSuggest.aLocations[i]
        }
      }
      for( i = 4; !res[--i]; ){}

      /* true значит можно показать дописку серым */
      b = !res[i].toUpperCase().indexOf( oSuggest.oInput.node.value.toUpperCase() );
      if(isEnter){
        lastSuggestsValues[oSuggest.oInput.getUniqueId()] =
        oSuggest.oInput.node.value =
        res[i];
      } else if(b){
        lastSuggestsValues[oSuggest.oInput.getUniqueId()] =
        oSuggest.oInput.node.value =
        res[i].slice( 0, oSuggest.oInput.node.value.length );
      }
      
      
      djp(oSuggest.oInput).next().hasClass('b-search-field__country').text( res.isCountry? '' :res[1] );
      
      djp(oSuggest.oInput).prev().hasNode('INPUT').node.value = b ? (
        res[i] + ' ('+ res[0] +')'
      ) :
      '';
    }
  }
  function sendSuggest(oInput){
    var
    newValue = oInput.getValues()[0],
    inputUniqueId = oInput.getUniqueId(),
    elems = oInput.node.form.elements,
    direction = oInput.node.getAttribute('data-direction'),
    otherInput = elems[direction == 'f' ? 't' : 'f'];
    i;

    if(
      !(oSuggest.fClose || newValue) ||
      newValue !== lastSuggestsValues[inputUniqueId]
    ){
      djp(oInput).next().hasNode('INPUT').node.value = '';
      for( i = elems.length; i--; ){
        if( elems[i]['type'] == 'submit' ){
          djp(elems[i]).toggleClass('m-button-disabled', TRUE);
        }
      }
      lastSuggestsValues[inputUniqueId] = newValue;
      xhr !== UNDEFINED && xhr.abort();
      xhr = djp.getJSON(
        r_avia['urls']['location_suggest'](newValue, direction, otherInput.value),
        function(oJSON, r){
          var
          maxWeight = -1,
          row,
          doHaveCity = {},
          tmp,
          i,
          l,
          input_is_empty = oInput.node.value == '',
          textFind = new RegExp(
            input_is_empty ? '(^$)' :
            '(' + (
              oInput.node.value.replace(/[\\\[\]\(\)\/\.\^\$\*\?\+\{\}]/g, '\\$&')
              .replace(/[её]/g,'[её]')
              .replace(/[ий]/g,'[ий]')
            ) + ')',
            'ig'
          );

          xhr = UNDEFINED;
          if(oSuggest.fClose){
            oSuggest.isReplace = TRUE;
            oSuggest.fClose();
          }
          if(oJSON && oJSON['status'] == 'ok'){
            /* oJSON['locations'] == [
             *  [
             *    0: iata_code,
             *    1: "название страны",
             *    2: "название города" || null,
             *    3: "название аэропорта" || null,
             *    4: "iata код города, этого порта" || null
             *    5: "вес"
             *  ],
             *  ..
             * ]
             */
            oSuggest.aLocations = [];
            tmp = 0;
            for(
              i = 0, l = oJSON['locations'].length;
              i < l;
              i++
            ){
              row = oJSON['locations'][i];
              if(!row[3] && row[2]){
                doHaveCity[ row[0] ] = TRUE;
              }
              row['name'] = (row[3] || row[2] || row[1]).replace(textFind, '<' + 'b style="font-weight:bold">$1<' + '/b>');
              row['country'] = !row[3] || ( row[3] && !doHaveCity[row[4]] ) ? row[1] : NULL;
              row['isCountry'] = !row[3] && !row[2];
              row['city'] = row[3] && !doHaveCity[row[4]] ? row[2] : NULL;
              oSuggest.aLocations[i] = row;
              if( maxWeight < row[5] ){
                oSuggest.aLocations[tmp]['current'] = NULL;
                maxWeight = row[5];
                oSuggest.aLocations[tmp = i]['current'] = TRUE;
              }
            }
            oSuggest.oInput = oInput;
            if(oSuggest.aLocations.length){
              oSuggest.fClose = djp.openBox.below(
                oInput,
                {
                  openCallback: function(boxNode, recalc, target){
                    boxNode.append( template(oSuggest.aLocations) );
                    setNewCurrentItem(
                      oSuggest.oCurrentItem = djp(boxNode).child().child().hasClass('selected')
                    );
                    boxNode.attach(
                      'click',
                      fEnter
                    );
                    boxNode.attach(
                      'mouseover',
                      function(oDJP, oEvent){
                        if( oSuggest.oCurrentItem.node ){
                          if( !oSuggest.oCurrentItem.contains(oDJP) ){
                            setNewCurrentItem(oDJP);
                          }
                        }
                      }
                    );
                    recalc(target, boxNode);
                  },
                  closeCallback: function(boxNode){
                    //кладем выбранное значение в скрытое поле формы, и заполняем выбор.
                    var
                    oInput = djp(oSuggest.oInput),
                    oSwitchModesLink = djp('switchModesLink').node,
                    elems, i;

                    if( !oSuggest.isReplace ){
                      i = oSuggest.oCurrentItem.attr('data-iata');
                      //в статистику уходит выбранный аэропорт
                      (new Image).src = r_avia['urls']['location_stat'] + '?' + djp.objToQueryString({
                        's': oInput.node.value,
                        'd': oInput.node.getAttribute('data-direction'),
                        'iata': i
                      });

                      setNewCurrentItem(oSuggest.oCurrentItem, TRUE);
                      //oInput.node.focus();
                      djp(oInput).next().hasNode('INPUT').node.value = i;
                      r_avia.parts.searchFormIsNotFill();
                      elems = oInput.node.form.elements;
                      if (oSwitchModesLink) {
                        oSwitchModesLink.href = oSwitchModesLink.href.split('?')[0]
                          + '?' + djp.objToQueryString({
                            'f': elems['f'].value,
                            't': elems['t'].value
                          });
                      }
                      r_avia.form.toggleSearchButton();
                    }
                    oSuggest = {};
                  },
                  closeClass: 'r--suggest-close'
                }
              );
            }
          } else {
            //жопа сервер ругаитцо мы пропале
          }
          //alert(r.responseText)
        }
      );
    }
  }
  djp(doc.documentElement).attach(
    'input keydown cut paste click focus focusin DOMFocusIn',
    function(oTarget, oEvent){
      var
      oInput = djp(oTarget).ancestor().hasClass('js-suggest-name'),
      oItem,
      inputUniqueId,
      fn,
      alreadyAttach = FALSE;;

      if(oInput.node){
        inputUniqueId = oInput.getUniqueId();
        if(
          lastSuggestsValues[inputUniqueId] === UNDEFINED &&
          djp(oInput).next().hasNode('INPUT').getValues()[0]
        ){
          lastSuggestsValues[inputUniqueId] = oInput.getValues()[0];
        }
        oInput.prev().node.value = '';
        if(
          fn = oSuggest.fClose && oEvent.$type == 'keydown' && {
            13: /* ввод */ fEnter,
            38: /* вверх */ function(oCurrentItem){
              var oItem = djp(oCurrentItem).prev().hasClass('item');

              if(!oItem.node) {
                oItem = djp(
                  djp(oCurrentItem).ancestor().more().node.lastChild
                );
                if( !oItem.classIs('item') ){
                  oItem = oItem.prev().hasClass('item');
                }
              }
              setNewCurrentItem(oItem);
            },
            40: /* вниз */function(oCurrentItem){
              var oItem = djp(oCurrentItem).next().hasClass('item');
              if(!oItem.node){
                oItem = djp(oCurrentItem).ancestor().more().child().hasClass('item');
              }
              setNewCurrentItem(oItem);
            },
            9: /* таб */ fEnter
          }[oEvent.$keyCode]
        ){
          fn(oSuggest.oCurrentItem, oEvent);
          oEvent.$keyCode !== 9 && oEvent.prevent();
        } else {
          if(
            {'focus':1, 'focusin':1, 'DOMFocusIn':1}[oEvent.$type]
            && preventMouseupAfterFocus === UNDEFINED
          ){
            preventMouseupAfterFocus = inputUniqueId;
          }
          timer !== UNDEFINED && clearTimeout(timer);
          timer = setTimeout(
            function(){sendSuggest(oInput)},
            r_avia['suggest_delay']
          );
        }
      }
    },
    TRUE
  );
  djp(doc.documentElement).attach(
    'mouseup',
    function(oTarget, oEvent){
      var
      oInput = djp(oTarget).ancestor().hasClass('js-suggest-name'),
      inputUniqueId;

      if(oInput.node){
        inputUniqueId = oInput.getUniqueId();
        if( inputUniqueId === preventMouseupAfterFocus ){
          oInput.node.select();
          oEvent.prevent();
        }
      }
      preventMouseupAfterFocus = UNDEFINED;
    }
  );
})(window['djp'], true, false, null, '<div class="suggest">\n\
  <%\n\
  for( var i=0, l=c.length; i < l; i++ ){\n\
  %>\n\
  <div data-iata="<%= c[i][0] %>" class="item <%= c[i].current ? \' selected\' : \'\' %>">\n\
    <% if(c[i].country){ %>\n\
      <span class="b-suggest__country"><%=  !c[i].isCountry && c[i].country ? c[i].country : \' \'  %></span>\n\
    <% } %>\n\
    <div class="bigItem">\n\
      \n\
      \n\
      <div class="b-suggest-ellipsis">\n\
          <%= c[i].name %><% if(c[i].country){ %><%= c[i].city ? \', \' + c[i].city : \'\' %><% } %>\n\
      </div>\n\
      <!--div class="b-suggest-aircode">(<%= c[i][0] %>)</div-->\n\
    </div>\n\
  </div>\n\
  <%\n\
  } %>\n\
</div>');

"use strict";
(function(UNDEF){
  var
  oDl,
  dls = document.getElementsByTagName('dl'),
  b;

  for (var i=dls.length;i--;){
      oDl = djp(dls[i]);
      if (oDl.classIs('b-search-param')) {
          var sensor = oDl.prev().child();
          sensor.attach(
            'click',
            function(oTarget, oEvent){
              b = sensor.classIs('m-expand-params__expand-on');
              sensor.toggleClass('m-expand-params__expand-on');
              for (i=dls.length;i--;){
                  oDl = djp(dls[i]);
                  if (oDl.classIs('b-search-param')) {
                      oDl.toggleClass('hidden', b)
                  }
              }
            }
          );
      }
  }
})();

(function (errorTemplate, UNDEF) {
    var xhr, gotoSeller = djp('gotoSeller');
    errorTemplate = djp.tmpl(errorTemplate);
    function showError (error) {
        djp(document.body).child().hasClass('b-page')
          .child().hasClass('js-container').node.innerHTML = errorTemplate(
            {'message': error}
          );
    }
    if (gotoSeller.node) {
        var gId = gotoSeller.attr('data-id'),
          sId = gotoSeller.attr('data-seller-id');
        xhr = djp.getJSON(
          r_avia.urls.gotoseler + '?' +
          djp.objToQueryString({
            'gid': gId
          }),
          function(data) {
            if (data && !data['error'] && data['location']) {
                _tracker.trackByRedirect(data['location'], 'events.recheck.'+gId+'-events.gotoseller.'+sId);
            } else {
                _tracker.track('events.recheck.'+gId+'.error-events.gotoseller.'+sId+'.error');
                showError(
                  data && data['error'] ?
                  data['error'] :
                  "К сожалению, продавец не подтвердил наличие этого билета.<br>Возможно, билет уже продан."
                );
            }
            xhr = UNDEF;
          }
        );
    }
})(
/* шаблон ощибки */
'<div class="b-attention"></div>\n\
<p class="b-msg m-msg-micro"><%= c.message %></p>\n\
<p class="b-msg">\n\
    <a href="#" class="b-msg__link" onclick="window.close();return false;">Вернуться к результатам поиска</a>\n\
</p>'
);


