/* global SB */

;(function () {
  var Smartbox,
    _ready = false,
    readyCallbacks = [],
    userAgent = navigator.userAgent.toLowerCase(),
    SmartboxAPI;

  //private func for applying all ready callbacks
  var onReady = function () {
    _ready = true;

    for ( var i = 0, len = readyCallbacks.length; i < len; i++ ) {
      if (typeof readyCallbacks[i] === 'function') {
        readyCallbacks[i].call(this);
      }
    }
    // no need anymore
    readyCallbacks = null;
  };

  /**
   * Detecting current platform
   * @returns {boolean} true if running on current platform
   */
  function detect ( slug ) {
    return userAgent.indexOf(slug) !== -1;
  }

  var initialise = function() {
    Smartbox.setPlugins();
    Smartbox.getDUID();

    // wait for calling others $()
    setTimeout(function () {
      onReady();
      onReady = null;
    }, 10);
  };

  Smartbox = function ( platform, cb ) {
    if ( typeof platform === 'string' ) {
      Smartbox.readyForPlatform(platform, cb);
    } else if ( typeof platform === 'function' ) {
      // first arg - function
      Smartbox.ready(platform);
    }
  };

  //public smartbox API
  SmartboxAPI = {
    platformName: '',

    userAgent: userAgent,

    createPlatform: function ( platformName, platformApi ) {
      var isCurrent = platformApi.detect && platformApi.detect();

      if ( isCurrent || detect(platformApi.platformUserAgent) ) {
        this.platformName = platformName;
        _.extend(this, platformApi);

        if (typeof platformApi.onDetect === 'function') {
          this.onDetect();
        }
      }
    },

    // calling cb after library initialise
    ready: function ( cb ) {
      if ( _ready ) {
        cb.call(this);
      } else {
        readyCallbacks.push(cb);
      }
    },

    // calling cb after library initialise if platform is current
    readyForPlatform: function ( platform, cb ) {
      var self = this;
      this.ready(function () {
        if ( platform == self.platformName ) {
          cb.call(self);
        }
      });
    },

    utils: {

      /**
       * Show error message
       * @param msg
       */
      error: function ( msg ) {
        $$log(msg, 'error');
        if (msg) {
          $$log('Error :: ' + msg);
        }
      },

      /**
       * Show messages in log
       * all functionality in main.js
       */
      log: {
        log: $.noop,
        state: $.noop,
        show: $.noop,
        hide: $.noop,
        startProfile: $.noop,
        stopProfile: $.noop
      },

      /**
       * Asynchroniosly adding javascript files
       * @param filesArray {Array} array of sources of javascript files
       * @param cb {Function} callback on load javascript files
       */
      addExternalJS: function ( filesArray, cb ) {
        var $externalJsContainer,
          loadedScripts = 0,
          len = filesArray.length,
          el,
          scriptEl;

        function onloadScript () {
          loadedScripts++;

          if ( loadedScripts === len ) {
            cb && cb.call();
          }
        }

        if ( filesArray.length ) {

          $externalJsContainer = document.createDocumentFragment();
          el = document.createElement('script');
          el.type = 'text/javascript';
          el.onload = onloadScript;

          for ( var i = 0; i < len; i++ ) {
            scriptEl = el.cloneNode();
            scriptEl.src = filesArray[i];
            $externalJsContainer.appendChild(scriptEl);
          }

          document.body.appendChild($externalJsContainer);
        } else {

          // if no external js simple call cb
          cb && cb.call(this);
        }
      },

      addExternalCss: function ( filesArray ) {
        var $externalCssContainer;

        if ( filesArray.length ) {
          $externalCssContainer = document.createDocumentFragment();
          _.each(filesArray, function ( src ) {

            var el = document.createElement('link');

            el.rel = 'stylesheet';
            el.href = src;

            $externalCssContainer.appendChild(el);
          });

          document.body.appendChild($externalCssContainer);
        }
      },

      addExternalFiles: function ( cb ) {
        if ( this.externalJs.length ) {
          this.addExternalJS(this.externalJs, cb);
        }
        if ( this.externalCss.length ) {
          this.addExternalCss(this.externalCss);
        }
      }
    }
  };

  Smartbox.config = {
    DUID: 'real'
  };

  _.extend(Smartbox, SmartboxAPI);

  // exporting library to global
  window.SB = Smartbox;

  // initialize library
  window.onload = function () {
    initialise();

    // we don't need initialise func anymore
    initialise = null;
  };
})();

!(function ( window, undefined ) {

  var PlatformApi = {
    externalCss: [],
    externalJs: [],
    keys: {},

    DUID: '',

    platformUserAgent: 'not found',

    /**
     * Get DUID in case of Config
     * @return {string} DUID
     */
    getDUID: function () {
      switch ( SB.config.DUID ) {
        case 'real':
          this.DUID = this.getNativeDUID();
          break;
        case 'mac':
          this.DUID = this.getMac();
          break;
        case 'random':
          this.DUID = this.getRandomDUID();
          break;
        /*case 'local_random':
         this.DUID = this.getLocalRandomDUID();
         break;*/
        default:
          this.DUID = Config.DUIDSettings;
          break;
      }

      return this.DUID;
    },

    getSDI: function () {
      return '';
    },

    /**
     * Returns random DUID for platform
     * @returns {string}
     */
    getRandomDUID: function () {
      return (new Date()).getTime().toString(16) + Math.floor(Math.random() * parseInt("10000", 16)).toString(16);
    },

    /**
     * Returns MAC for platform if exist
     * @returns {string}
     */
    getMac: function () {
      return '';
    },

    /**
     * Returns native DUID for platform if exist
     * @returns {string}
     */
    getNativeDUID: function () {
      return '';
    },

    /**
     * Set custom plugins for platform
     */
    setPlugins: $.noop,

    // TODO: volume for all platforms
    volumeUp: $.noop,
    volumeDown: $.noop,
    getVolume: $.noop,
    exit: $.noop,
    sendReturn: $.noop,
    setData: function ( name, val ) {
      // save data in string format
      localStorage.setItem(name, JSON.stringify(val));
    },

    getData: function ( name ) {
      var result;
      try {
        result = JSON.parse(localStorage.getItem(name));
      } catch (e) {
      }

      return result;
    },

    removeData: function ( name ) {
      localStorage.removeItem(name);
    }
  };

  _.extend(SB, PlatformApi);
})(this);

window.SB = window.SB || {};

// Default layouts, can be extended
window.SB.keyboardPresets = {

  en: function () {
    return [
      'qwertyuiop'.split(''),
      'asdfghjkl'.split('').concat(['backspace{{<i class="backspace_icon"></i>}}']),
      ['shift{{<i class="shift_icon"></i>Shift}}'].concat('zxcvbnm'.split('')).concat(
        ['delall{{<span>Del<br/>all</span>}}']),
      ['lang{{en}}', 'nums{{123}}', 'space{{}}', 'complete{{Complete}}']
    ];
  },

  ru: function () {
    return [
      'йцукенгшщзхъ'.split(''),
      'фывапролджэ'.split('').concat(['backspace{{<i class="backspace_icon"></i>}}']),
      ['shift{{<i class="shift_icon"></i>Shift}}'].concat('ячсмитьбю'.split('')).concat(['delall{{<span>Del<br/>all</span>}}']),
      ['lang{{ru}}', 'nums{{123}}', 'space{{}}', 'complete{{Готово}}']
    ]
  },

  email: function () {
    return [
      '1234567890@'.split(''),
      'qwertyuiop'.split('').concat(['backspace{{<i class="backspace_icon"></i>}}']),
      'asdfghjkl_'.split('').concat(['delall{{<span>Del<br/>all</span>}}']),
      'zxcvbnm-.'.split('').concat('complete{{OK}}')
    ];
  },

  num: function () {
    return [
      '123'.split(''),
      '456'.split(''),
      '789'.split(''),
      ['backspace{{<i class="backspace_icon"></i>}}', '0', 'complete{{OK}}']
    ]
  },

  fullnum: function () {
    return [
      '1234567890'.split(''),
      '-/:;()$"'.split('').concat(['&amp;', 'backspace{{<i class="backspace_icon"></i>}}']),
      ['nums{{ABC}}'].concat("@.,?!'+".split('')),
      ['space{{}}', 'complete{{OK}}']
    ]
  },

  fulltext_ru: ['ru','en'],
  fulltext_en: ['en'],
  fulltext_ru_nums: ['ru', 'en', 'fullnum'],
  fulltext_en_nums: ['en', 'fullnum']
};
(function (window) {
  "use strict";
  /*globals _, ViewModel,$,Events,document, Observable, Computed, Lang, nav*/
  // Нельзя использовать 'icon' - по каким-то причинам не отображается в tizen
  var icons = ['info','red', 'green', 'yellow', 'blue', 'rew', 'play', 'pause', 'stop', 'ff', 'tools', 'left',
               'right', 'up', 'down', 'leftright', 'updown', 'move', 'number', 'enter', 'ret'],

    notClickableKeys= ['leftright', 'left', 'right', 'up', 'down', 'updown', 'move', 'number'],
    _isInited,
    LegendKey,
    savedLegend = [],
    Legend;

  function isClickable( key ) {
    return (notClickableKeys.indexOf(key) === -1)
  }

  function renderKey( key ) {
    var clickableClass = isClickable(key) ? ' legend-clickable' : '';
    return '<div class="legend-item legend-item-' + key + clickableClass + '" data-key="' + key + '">' +
             '<i class="leg-icon leg-icon-' + key + '"></i>' +
             '<span class="legend-item-title"></span>' +
           '</div>';
  }

  function _renderLegend() {
    var legendEl,
      wrap,
      allKeysHtml = '';

    for (var i = 0, len = icons.length; i<len; i++) {
      allKeysHtml += renderKey(icons[i]);
    }

    legendEl = document.createElement('div');
    wrap = document.createElement('div');

    legendEl.className = 'legend';
    legendEl.id = 'legend';
    wrap.className = 'legend-wrap';
    wrap.innerHTML = allKeysHtml;
    legendEl.appendChild(wrap);

    return $(legendEl);
  }

  Legend = function() {
    var self = this;
    this.$el = _renderLegend();
    this.keys = {};

    var initKey = function ( key ) {
      var $keyEl;
      if ( !self.keys[key] ) {
        $keyEl = self.$el.find('.legend-item-' + key);
        self.keys[key] = new LegendKey($keyEl);
      }
    };

    for ( var i = 0; i < icons.length; i++ ) {
      initKey(icons[i]);
    }

    this.addKey = function ( keyName, isClickable ) {
      var keyHtml;

      if (typeof isClickable === 'undefined') {
        isClickable = true;
      }

      if (!isClickable) {
        notClickableKeys.push(keyName);
      }

      keyHtml = renderKey(keyName);

      this.$el.find('.legend-wrap').append(keyHtml);
      initKey(keyName);
    };

    this.show = function () {
      this.$el.show();
    };

    this.hide = function () {
      this.$el.hide();
    };

    this.clear = function () {
      for ( var key in this.keys ) {
        this.keys[key]('');
      }
    };

    this.save = function () {
      for ( var key in this.keys ) {
        savedLegend[key] = this.keys[key]();
      }
    };

    this.restore = function () {
      _.each(icons, function ( key ) {
        Legend[key](savedLegend[key]);
      });

      for ( var key in savedLegend ) {
        this.keys[key](savedLegend[key]);
      }

      savedLegend = [];
    };
  };

  LegendKey = function ($el) {
    this.$el = $el;
    this.$text = $el.find('.legend-item-title');

    return _.bind(this.setText, this);
  };

  LegendKey.prototype.text = '';
  LegendKey.prototype.isShown = false;
  LegendKey.prototype.setText = function (text) {

    if (typeof text === 'undefined') {
      return this.text;
    } else if (text !== this.text) {
      text = text || '';

      if (!text && this.isShown) {
        this.$el[0].style.display = 'none';
        this.$el.removeClass('legend-item-visible');
        this.isShown = false;
      } else if (text && !this.isShown) {
        this.$el[0].style.display = '';
        this.$el.addClass('legend-item-visible');
        this.isShown = true;
      }

      this.text = text;
      this.$text.html(text);
    }
  };


  window.$$legend = new Legend();

  $(function () {
    $$legend.$el.appendTo(document.body);
    $$legend.$el.on('click', '.legend-clickable', function () {
      var key = $(this).attr('data-key'),
        ev, commonEvent;

      if (key === 'ret') {
        key = 'return';
      } else if (key === 'rew') {
        key = 'rw';
      }

      ev = $.Event("nav_key:" + key);
      commonEvent = $.Event("nav_key");
      commonEvent.keyName = ev.keyName = key;

      $$nav.current().trigger(ev).trigger(commonEvent);
    });
  });
})(this);
(function ( window, undefined ) {

  var profiles = {},
    logs = {},
    logNames = [],
    curPanelIndex = 0,
  // максимум логов на странице
    maxLogCount = 20,
    $logWrap,
    $logRow,
    Log,
    LogPanel;

  // append log wrapper to body
  $logWrap = $('<div></div>', {
    id: 'log'
  });

  $(function () {
    $logWrap.appendTo(document.body);
  });

  $logRow = $('<div></div>', {
    'class': 'log-row'
  });

  /**
   * LogPanel constructor
   * @param logName {String} name of log panel
   * @constructor
   */
  LogPanel = function ( logName ) {
    this.name = logName;
    this.logs = 0;
    this.states = {};

    var $wrapper = $logWrap.find('#log_' + this.name);

    this.$content = $wrapper.find('.log_content');
    this.$state = $wrapper.find('.log_states');

    // no need anymore
    $wrapper = null;
  };

  _.extend(LogPanel.prototype, {
    log: function log ( msg ) {
      var logRow = $logRow.clone(),
        $rows, length;
      this.logs++;
      msg = _.escape(msg);

      logRow.html(msg).appendTo(this.$content);
      if ( this.logs > maxLogCount ) {
        $rows = this.$content.find(".log-row");
        length = $rows.length;
        $rows.slice(0, length - maxLogCount).remove();
        this.logs = $rows.length;
      }
    },

    state: function state ( value, stateName ) {
      var state = this.states[stateName] || this.createState(stateName);
      state.textContent = stateName + ': ' + value;
    },

    createState: function ( stateName ) {
      var $state = document.createElement('div');
      $state.id = '#log_' + this.name + '_' + stateName;
      this.states[stateName] = $state;
      this.$state.append($state);

      return $state;
    }
  });

  var logPanelTemplate = '<div class="log_pane" id="log_<%=name%>">' +
                           '<div class="log_name"><br/>--------- [<%=name%>] ---------</div>' +
                           '<div class="log_content_wrap">' +
                            '<div class="log_content"></div>' +
                           '</div>' +
                           '<div class="log_states"></div>' +
                         '</div>';

  Log = {

    create: function ( logName ) {
      var logHtml = logPanelTemplate.replace(/<%=name%>/g, logName);
      $logWrap.append(logHtml);
      logs[logName] = new LogPanel(logName);
      logNames.push(logName);
      return logs[logName];
    },

    getPanel: function ( logName ) {
      logName = logName || 'default';
      return (logs[logName] || this.create(logName));
    }
  };

  /**
   * Public log API
   */
  window.SB.utils.log = {
    log: function ( msg, logName ) {
      Log.getPanel(logName).log(msg, logName);
    },

    state: function ( msg, state, logName ) {
      Log.getPanel(logName).state(msg, state);
    },

    show: function ( logName ) {
      logName = logName || logNames[curPanelIndex];

      if ( !logName ) {
        curPanelIndex = 0;
        this.hide();
      } else {
        curPanelIndex++;
        $logWrap.show();
        $('.log_pane').hide();
        $('#log_' + logName).show();
      }
    },

    hide: function () {
      $logWrap.hide();
    },

    startProfile: function ( profileName ) {
      if ( profileName ) {
        profiles[profileName] = (new Date()).getTime();
      }
    },

    stopProfile: function ( profileName ) {
      if ( profiles[profileName] ) {
        this.log(profileName + ': ' + ((new Date()).getTime() - profiles[profileName]) + 'ms', 'profiler');
        delete profiles[profileName];
      }
    }
  };
  window.$$log = SB.utils.log.log;
  window.$$error = SB.utils.error;

})(this);

$(function () {
  var logKey = SB.config.logKey || 'tools';
  $(document.body).on('nav_key:' + logKey, function () {
    SB.utils.log.show();
  });
});


!(function ( window, undefined ) {

  var $body = null,
    nav, invertedKeys = {};

  SB.ready(function () {
    var keys = SB.keys;
    for (var key in keys) {
      invertedKeys[keys[key]] = key.toLowerCase();
    }
  });

  function Navigation () {


    // for methods save и restore
    var savedNavs = [],

    // object for store throttled color keys  methods
      throttledMethods = {},

    // current el in focus
      navCur = null,

    // arrays
      numsKeys = ['n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9'],
      colorKeys = ['green', 'red', 'yellow', 'blue'],

    // pause counter
      paused = 0;

    function onKeyDown ( e ) {
      var key,
        data = {},
        keyCode = e.keyCode;

      if ( paused || !navCur ) {
        return;
      }

      key = invertedKeys[keyCode];
      if ( key ) {
        if ( colorKeys.indexOf(key) > -1 ) {
          throttleEvent(key);
        } else {
          if ( numsKeys.indexOf(key) > -1 ) {
            data.num = key[1];
            key = 'num';
          }

          triggerKeyEvent(key, data);
        }
      }
    }

    /**
     * 'nav_key:' event trigger
     * @param key key name
     * @param data event data
     */
    function triggerKeyEvent ( key, data ) {
      var ev,
        commonEvent;
      if ( navCur ) {
        ev = $.Event("nav_key:" + key, data || {});
        commonEvent = $.Event("nav_key");

        $$log('EventTrigger:' + key + '; navCur: ' + (navCur[0] && navCur[0].className), 'keys')

        ev.keyName = key;
        commonEvent.keyName = key;
        navCur.trigger(ev);
        //первый trigger мог уже сменить текщий элемент
        navCur && navCur.trigger(commonEvent);
      }
    }

    function throttleEvent ( key ) {
      var keyMethod = throttledMethods[key];

      // lazy init
      if ( !keyMethod ) {
        keyMethod = throttledMethods[key] = _.throttle(function () {
          triggerKeyEvent(key);
        }, 800, {
          leading: true
        });
      }

      keyMethod(key);
    }

    /**
     * trigger click on current element
     */
    function onClick () {
      navCur && navCur.click();
    }

    return {

      // nav els selector
      area_selector: '.nav-item',

      /**
       * Current el class
       * @type {string}
       */
      higlight_class: 'focus',

      /**
       * navigation container
       * @type {jQuery}
       */
      $container: null,

      /**
       * Current looping type
       * false/hbox/vbox
       * @type {boolean|string}
       */
      loopType: null,

      /**
       * Phantom els selector
       * @type {string}
       */
      phantom_selector: '[data-nav-phantom]',

      /**
       * Returns current navigation state
       * @returns {boolean}
       */
      isPaused: function () {
        return !!paused;
      },

      /**
       * Stop navigation. Increase pause counter
       * @returns {Navigation}
       */
      pause: function () {
        paused++;
        return this;
      },

      /**
       * Resume navigation if force or pause counter is zero
       * @param force {Boolean} force navigation resume
       * @returns {Navigation}
       */
      resume: function ( force ) {
        paused--;
        if ( paused < 0 || force ) {
          paused = 0;
        }
        return this;
      },

      /**
       * Save current navigation state
       * @returns {Navigation}
       */
      save: function () {

        savedNavs.push({
          navCur: navCur,
          area_selector: this.area_selector,
          higlight_class: this.higlight_class,
          $container: this.$container
        });
        return this;
      },

      /**
       * Restore navigation state
       * @returns {Navigation}
       */
      restore: function () {
        if ( savedNavs.length ) {
          this.off();
          var foo = savedNavs.pop();
          this.area_selector = foo.area_selector;
          this.higlight_class = foo.higlight_class;
          this.on(foo.$container, foo.navCur);
        }

        return this;
      },

      /**
       * Setting focus on element
       * @param element {*} - HTMLElement, selector or Jquery object
       * @param originEvent {string} - event source(nav_key, mousemove, voice etc.)
       * @return {Navigation}
       */
      current: function ( element, originEvent ) {
        if ( !element ) {
          return navCur;
        }

        originEvent = originEvent || 'nav_key';

        var $el = $(element);
        if ( $el.is(this.phantom_selector) ) {
          $el = $($($el.attr('data-nav-phantom'))[0]);
        }
        if ( $el.length > 1 ) {
          throw new Error('Focused element must be only one!');
        }
        if ( !$el.length ) {
          return this;
        }
        var old = navCur;
        if ( navCur ) {
          navCur.removeClass(this.higlight_class).trigger('nav_blur', [originEvent, $el]);
        }

        navCur = $el;

        $el.addClass(this.higlight_class).trigger('nav_focus', [originEvent, old]);
        return this;
      },

      /**
       * Turn on navigation in container, turn off previous navigation
       * @param container - HTMLElement, selector or Jquery object (body by default)
       * @param cur - HTMLElement, selector or Jquery object(first nav el by default)
       * @return {Navigation}
       */
      on: function ( container, cur ) {

        var self = this,
          $navTypeEls;

        $body = $body || $(document.body);

        this.off();

        this.$container = container ? $(container) : $body;

        if ( SB.platform != 'philips' ) {
          this.$container.on('mouseenter.nav', this.area_selector, function ( e ) {
            if ( !$(this).is(self.phantom_selector) ) {
              self.current(this, 'mouseenter');
            }
          });
        }

        $navTypeEls = this.$container.find('[data-nav_type]');

        if ( this.$container.attr('data-nav_type') ) {
          $navTypeEls = $navTypeEls.add(this.$container);
        }

        $navTypeEls.each(function () {
          var $el = $(this);
          var navType = $el.attr("data-nav_type");
          $el.removeAttr('data-nav_type');
          //self.setLoop($el);
          var loop = $el.attr("data-nav_loop");

          self.siblingsTypeNav($el, navType, loop);
        });

        $body
          .bind('keydown.navigation', onKeyDown)
          .bind('nav_key:enter.navigation', onClick);

        if ( !cur ) {
          cur = this.$container.find(this.area_selector).filter(':visible')[0];
        }
        this.current(cur);
        return this;
      },

      siblingsTypeNav: function ( $container, type, loop ) {
        var self = this;
        $container.on('nav_key:left nav_key:right nav_key:up nav_key:down', this.area_selector,
          function ( e ) {
            var last = 'last',
              cur = self.current(),
              next,
              fn;

            //check if direction concur with declared
            if ( (type == 'hbox' && e.keyName == 'left') ||
                 (type == 'vbox' && e.keyName == 'up') ) {
              fn = 'prev';
            } else if ( (type == 'hbox' && e.keyName == 'right') ||
                        (type == 'vbox' && e.keyName == 'down') ) {
              fn = 'next';
            }

            if ( fn == 'next' ) {
              last = 'first';
            }

            if ( fn ) {
              next = cur[fn](self.area_selector);

              while ( next.length && !next.is(':visible') ) {
                next = next[fn](self.area_selector);
              }

              if ( !next.length && loop ) {
                next = $container.find(self.area_selector).filter(':visible')[last]();
              }

              if ( next.length ) {
                nav.current(next);
                return false;
              }
            }
          });
      },

      /**
       * Turn off navigation from container, disable navigation from current element
       * @return {Navigation}
       */
      off: function () {
        if ( navCur ) {
          navCur.removeClass(this.higlight_class).trigger('nav_blur');
        }
        this.$container && this.$container.off('mouseenter.nav').off('.loop');
        $body.unbind('.navigation');
        navCur = null;
        return this;
      },

      /**
       * Find first nav el & set navigation on them
       */
      findSome: function () {
        var cur;

        if ( !(navCur && navCur.is(':visible')) ) {
          cur = this.$container.find(this.area_selector).filter(':visible').eq(0);
          this.current(cur);
        }

        return this;
      },

      /**
       * Find closest to $el element by dir direction
       * @param $el {jQuery} - source element
       * @param dir {string} - direction up, right, down, left
       * @param navs {jQuery} - object, contains elements to search
       * @returns {*}
       */
      findNav: function ( $el, dir, navs ) {
        var user_defined = this.checkUserDefined($el, dir);

        if ( user_defined ) {
          if (user_defined === 'none') {
            return false;
          } else {
            return user_defined;
          }
        }

        var objBounds = $el[0].getBoundingClientRect(),
          arr = [],
          curBounds = null,
          cond1, cond2, i , l;

        for ( i = 0, l = navs.length; i < l; i++ ) {
          curBounds = navs[i].getBoundingClientRect();

          if ( curBounds.left == objBounds.left &&
               curBounds.top == objBounds.top ) {
            continue;
          }

          switch ( dir ) {
            case 'left':
              cond1 = objBounds.left > curBounds.left;
              break;
            case 'right':
              cond1 = objBounds.right < curBounds.right;
              break;
            case 'up':
              cond1 = objBounds.top > curBounds.top;
              break;
            case 'down':
              cond1 = objBounds.bottom < curBounds.bottom;
              break;
            default:
              break;
          }

          if ( cond1 ) {
            arr.push({
              'obj': navs[i],
              'bounds': curBounds
            });
          }
        }

        var min_dy = 9999999, min_dx = 9999999, min_d = 9999999, max_intersection = 0;
        var dy = 0, dx = 0, d = 0;

        function isIntersects ( b1, b2, dir ) {
          var temp = null;
          switch ( dir ) {
            case 'left':
            case 'right':
              if ( b1.top > b2.top ) {
                temp = b2;
                b2 = b1;
                b1 = temp;
              }
              if ( b1.bottom > b2.top ) {
                if ( b1.top > b2.right ) {
                  return b2.top - b1.right;
                }
                else {
                  return b2.height;
                }
              }
              break;
            case 'up':
            case 'down':
              if ( b1.left > b2.left ) {
                temp = b2;
                b2 = b1;
                b1 = temp;
              }
              if ( b1.right > b2.left ) {
                if ( b1.left > b2.right ) {
                  return b2.left - b1.right;
                }
                else {
                  return b2.width;
                }
              }
              break;
            default:
              break;
          }
          return false;
        }

        var intersects_any = false;
        var found = false;

        for ( i = 0, l = arr.length; i < l; i++ ) {
          if ( !this.checkEntryPoint(arr[i].obj, dir) ) {
            continue;
          }

          var b = arr[i].bounds;
          var intersects = isIntersects(objBounds, b, dir);
          dy = Math.abs(b.top - objBounds.top);
          dx = Math.abs(b.left - objBounds.left);
          d = Math.sqrt(dy * dy + dx * dx);
          if ( intersects_any && !intersects ) {
            continue;
          }
          if ( intersects && !intersects_any ) {
            min_dy = dy;
            min_dx = dx;
            max_intersection = intersects;
            found = arr[i].obj;
            intersects_any = true;
            continue;
          }

          switch ( dir ) {
            case 'left':
            case 'right':
              if ( intersects_any ) {
                cond2 = dx < min_dx || (dx == min_dx && dy < min_dy);
              }
              else {
                cond2 = dy < min_dy || (dy == min_dy && dx < min_dx);
              }
              break;
            case 'up':
            case 'down':
              if ( intersects_any ) {
                cond2 = dy < min_dy || (dy == min_dy && dx < min_dx);
              }
              else {
                cond2 = dx < min_dx || (dx == min_dx && dy < min_dy);
              }
              break;
            default:
              break;
          }
          if ( cond2 ) {
            min_dy = dy;
            min_dx = dx;
            min_d = d;
            found = arr[i].obj;
          }
        }

        return found;
      },

      /**
       * Return element defied by user
       * Если юзером ничего не определено или направление равно 0, то возвращает false
       * Если направление определено как none, то переход по этому направлению запрещен
       *
       * @param $el - current element
       * @param dir - direction
       * @returns {*}
       */
      checkUserDefined: function ( $el, dir ) {
          var ep = $el.data('nav_ud'),
              result = false,
              res = $el.data('nav_ud_' + dir);
          if (!ep && !res) {
              return false;
          }

          if ( !res ) {
              var sides = ep.split(','),
                  dirs = ['up', 'right', 'down', 'left'];
              if(sides.length !== 4) {
                  return false;
              }

              $el.data({
                  'nav_ud_up': sides[0],
                  'nav_ud_right': sides[1],
                  'nav_ud_down': sides[2],
                  'nav_ud_left': sides[3]
              });

              res = sides[dirs.indexOf(dir)];
          }

          if ( res == 'none' ) {
              result = 'none';
          } else if( res == '0' ) {
              result = false;
          } else if ( res ) {
              result = $(res).first();
          }
          return result;
      },

      /**
       * Проверяет можно ли войти в элемент с определенной стороны.
       * Работает если у элемента задан атрибут data-nav_ep. Точки входа задаются в атрибуте с помощью 0 и 1 через запятые
       * 0 - входить нельзя
       * 1 - входить можно
       * Стороны указываются в порядке CSS - top, right, bottom, left
       *
       * data-nav_ep="0,0,0,0" - в элемент зайти нельзя, поведение такое же как у элемента не являющегося элементом навигации
       * data-nav_ep="1,1,1,1" - поведение по умолчанию, как без задания этого атрибута
       * data-nav_ep="0,1,0,0" - в элемент можно зайти справа
       * data-nav_ep="1,1,0,1" - в элемент нельзя зайти снизу
       * data-nav_ep="0,1,0,1" - можно зайти слева и справа, но нельзя сверху и снизу
       *
       * @param elem -  проверяемый элемент
       * @param dir - направление
       * @returns {boolean}
       */
      checkEntryPoint: function ( elem, dir ) {
        var $el = $(elem),
          ep = $el.attr('data-nav_ep'),
          res = null;

        if ( !ep ) {
          return true;
        }

        res = $el.attr('data-nav_ep_' + dir);

        if ( res === undefined ) {
          var sides = ep.split(',');
          $el.attr('data-nav_ep_top', sides[0]);
          $el.attr('data-nav_ep_right', sides[1]);
          $el.attr('data-nav_ep_bottom', sides[2]);
          $el.attr('data-nav_ep_left', sides[3]);
          res = $el.attr('data-nav_ep_' + dir);
        }

        return !!parseInt(res);
      }
    };
  }

  nav = window.$$nav = new Navigation();

  $(function () {
    // Navigation events handler
    $(document.body).bind('nav_key:left nav_key:right nav_key:up nav_key:down', function ( e ) {
      var cur = nav.current(),
        $navs,
        n;

      $navs = nav.$container.find(nav.area_selector).filter(':visible');
      n = nav.findNav(cur, e.keyName, $navs);
      n && nav.current(n);
    });
  });

})(this);

(function ($) {
    "use strict";

    var inited = false,
        enabled = false,
        currentVoiceState,
        curOptions,
        $curTarget,
        $buble,
        stack = [],
        $moreDiv = $('<div/>'),

        paused = false;



    var init= function(){
        if (!inited) {

            enabled = $$voice._nativeCheckSupport();
            if (!enabled) {
                return;
            }

            $$voice._init();
            $buble = $('#voice_buble');
            inited = true;
        }
    }


    var defaults = {
        selector: '.voicelink',
        moreText: 'More',
        eventName: 'voice',
        useHidden: false,
        helpText: '',
        // количество показов баббла помощи
        showHelperTimes: 3,
        // количество самсунговских всплывашек с командами
        helpbarMaxItems: 6,
        // включение сортировки по весу
        sortByWeight: true,
        //Вес голосовых ссылок по умолчанию
        helpbarItemWeight: 200,
        candidateWeight: 0
    };


    var helpbarVisibityTimeoutLink;


    window.$$voice = {
        voiceTimeout: 10000,
        _resetVisibilityTimeout: function () {
            $$voice.helpbarVisible = true;

            clearTimeout(helpbarVisibityTimeoutLink);
            helpbarVisibityTimeoutLink = setTimeout(function () {

                //чтобы обновлял подсказки если был вызван голосовой поиск смотри баг #966
                if (typeof voiceServer == 'function') {
                    voiceServer = false;
                    $$voice.restore();
                }


                $buble.hide();
                $$voice.helpbarVisible = false;
            }, this.voiceTimeout);
        },
        _init: function () {

        },
        _nativeCheckSupport: function () {

        },
        helpbarVisible: false,
        enabled: function () {
            init();
            return enabled;
        },
        _setVoiceHelp: function (voicehelp) {

        },
        pause: function () {
            paused = true;
        },
        resume: function () {
            paused = false;
        },
        say: function (text) {
            if (paused)
                return;
            var result = text.toLowerCase();
            var opts = $.extend({}, defaults, curOptions);
            if (elements[result]) {
                elements[result].trigger(opts.eventName);
            }
            if ($curTarget) {
                generateHelpBar.call($curTarget, curOptions);
            }
        },
        _nativeTurnOff: function () {

        },
        hide: function () {
            if(!this.enabled()){
                return;
            }
            this._nativeTurnOff();
            $buble.hide();
            return this;
        },

        setup: function (options) {
            $.extend(defaults, options);
            return this;
        },
        save: function () {
            if (currentVoiceState)
                stack.push(currentVoiceState);
            return this;
        },
        restore: function () {
            var last = stack.pop();
            if (last)
                $.fn.voiceLink.apply(last.self, last.args);
            return this;
        },
        _nativeFromServer: function (title, callback) {

        },
        fromServer: function (title, callback) {
            if (!inited)
                return this;
            this.save();
            this._nativeFromServer(title, callback);
            return this;
        },
        refresh: function () {
            return this.save().restore();
        }
    }


    var generated = false, elements;


    /**
     * Преобразование jQuery коллекции в массив
     * и добавление команд в объект elements
     * @param elems
     * @returns {Array}
     */
    function voiceElementsToArray(elems) {


        var items = [];

        elems.each(function () {
            var $el = $(this);
            var commands = $el.attr('data-voice');
            var group = $el.attr('data-voice-group');
            var hidden = $el.attr('data-voice-hidden') === 'true' ? true : false;
            var weight = $el.attr('data-voice-weight') || 0;
            var main = false;

            if (!commands) {
                console.error('command in ', this, ' is not defined');
                return;
            }


            if ($el.attr('data-voice-disabled')) {
                return;
            }

            if (!weight) {
                if (!group && !hidden) {
                    weight = defaults.helpbarItemWeight
                    main = true;
                }
                else {
                    weight = defaults.candidateWeight
                }
            }

            items.push({
                itemText: commands,
                weight: weight,
                group: group,
                hidden: hidden,
                main: main
            });

            elements[commands.toLowerCase()] = $el;
        });

        return items;
    }

    var groupNames = {},
        gnCount = 0;

    var generateHelpBar = function (options) {

        if (generated) {
            return;
        }
        generated = true;

        $buble.hide().empty();

        var voiceItems,
            helpbarVoiceItems,
            candidateVoiceItems,
            activeItems,
            hiddenItems,
            items = [],
            candidates = [],
            opts = $.extend({}, defaults, options),
            helpbarMaxItems = opts.helpbarMaxItems,
            elems = this.find(opts.selector);


        var voicehelp = {
            helpbarType: "HELPBAR_TYPE_VOICE_CUSTOMIZE",
            bKeepCurrentInfo: "false",
            helpbarItemsList: {}
        };

        elements = {};

        if (!options.useHidden) {
            var force = elems.filter('[data-voice-force-visible]');
            elems = elems.filter(':visible').add(force);
        }


        // сортировка элементов по весу (от большего к меньшему)
        if (opts.sortByWeight) {
            voiceItems = _.sortBy(voiceElementsToArray(elems), function (el) {
                return -el.weight;
            });
        } else {
            voiceItems = voiceElementsToArray(elems);
        }


        // количество скрытых голосовых подсказок
        hiddenItems = $.grep(voiceItems, function (el) {
            return el.hidden === true;
        });


        // количество отображаемых подсказок
        activeItems = _.difference(voiceItems, hiddenItems);


        // добавление кнопки "Еще"
        if (activeItems.length > helpbarMaxItems) {
            activeItems.splice(helpbarMaxItems - 1, 0, {
                itemText: opts.moreText,
                commandList: [
                    {command: opts.moreText}
                ]
            });
            $moreDiv.unbind().bind(opts.eventName, function () {
                $('body').trigger('showVoiceHelpbar');
                $$voice._resetVisibilityTimeout();
                $buble.show();
            });
            elements[opts.moreText.toLowerCase()] = $moreDiv;
        }

        // выбираем элементы для подсказок самсунга
        helpbarVoiceItems = activeItems.splice(0, helpbarMaxItems);

        // остальные голосовые команды
        candidateVoiceItems = _.union(hiddenItems, activeItems);

        // массив для хелпбара самсунга
        _.each(helpbarVoiceItems, function (val) {
            var commands = val.itemText;

            items.push({
                itemText: commands,
                commandList: [
                    {command: commands}
                ]
            });
        });

        // массив команд, не отображаемых в хелпбаре самсунга
        _.each(candidateVoiceItems, function (val) {

            var group = val.group,
                commands = val.itemText,
                hidden = val.hidden,
                main = val.main;

            if (main && !group) {
                group = '';
            }


            if (!hidden) {
                if (!groupNames[group]) {
                    gnCount++;
                    groupNames[group] = gnCount;
                }
                var $groupWrap = $buble.find('#voice_group_body_' + groupNames[group]);
                if ($groupWrap.length) {
                    $groupWrap.append('<div class="voice_help_item">' + commands + '</div>');
                }
                else {
                    $buble.append('<div class="voice_group_head">' + group + '</div>' +
                        '<div class="voice_group_body" id="voice_group_body_' + groupNames[group] + '">' +
                        '<div class="voice_help_item">' + commands + '</div>' +
                        '</div>');
                }
            }

            candidates.push({
                candidate: val.itemText
            });
        });

        voicehelp.helpbarItemsList = items;

        if (candidates.length) {
            voicehelp.candidateList = candidates;
        }


        $$voice._setVoiceHelp(voicehelp);

    };

    $.fn.voiceLink = function (options) {
        // выходим, если нет реализации голоса
        if (inited && !enabled) {
            return;
        }

        init()


        currentVoiceState = {
            self: this,
            args: arguments
        };

        generated = false;
        options || (options = {});
        curOptions = options;
        $curTarget = this;

        if ($$voice.helpbarVisible) {
            generateHelpBar.call(this, curOptions);
        }
    }

})(jQuery);

Date.prototype.stdTimezoneOffset = function () {
  var jan = new Date(this.getFullYear(), 0, 1);
  var jul = new Date(this.getFullYear(), 6, 1);
  return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
}

Date.prototype.isDstObserved = function () {
    return this.getTimezoneOffset() < this.stdTimezoneOffset();
}
