// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('login', function() {
  /** @const */ var CALLBACK_USER_ACTED = 'userActed';

  var OobeScreenBehavior = {
    properties: {
      /**
       * Internal storage of |this.context|. Short name has been choosen for
       * reason: such name doesn't take much space in HTML data bindings, which
       * are used very often.
       * C binded to the native part of the context, that means that all the
       * changes in the native part appear in C automatically. Reverse is not
       * true, you should use:
       *    this.context.set(...);
       *    this.context.commitContextChanges();
       * to send updates to the native part.
       * TODO(dzhioev): make binding two-way.
       */
      C: Object,

      name: String
    },

    /**
     * The login.Screen which is hosting |this|.
     */
    screen_: null,

    /**
     * Dictionary of context observers that are methods of |this| bound to
     * |this|.
     */
    contextObservers_: null,

    /**
     * login.ScreenContext used for sharing data with native backend.
     */
    context: null,

    /**
     * Called when the screen is being registered.
     */
    initialize: function() {},

    ready: function() {
      if (this.decorate_) {
        this.initialize();
      } else {
        this.ready_ = true;
      }
    },

    userActed: function(e) {
      this.send(CALLBACK_USER_ACTED,
                e.detail.sourceEvent.target.getAttribute('action'));
    },

    i18n: function(args) {
      if (!(args instanceof Array))
        args = [args];
      args[0] = 'login_' + this.name + '_' + args[0];
      return loadTimeData.getStringF.apply(loadTimeData, args);
    },

    /**
     * Called by login.Screen when the screen is beeing registered.
     */
    decorate: function(screen) {
      this.screen_ = screen;
      this.context = screen.screenContext_;
      this.C = this.context.storage_;
      this.contextObservers_ = {};
      var self = this;
      if (this.ready_) {
        this.initialize();
      } else {
        this.decorate_ = true;
      }
    },

    /**
     * Should be called for every context field which is used in Polymer
     * declarative data bindings (e.g. {{C.fieldName}}).
     */
    registerBoundContextField: function(fieldName) {
      this.addContextObserver(fieldName, this.onContextFieldChanged_);
    },

    onContextFieldChanged_: function(_, _, fieldName) {
      this.notifyPath('C.' + fieldName, this.C[fieldName]);
    },

    /**
     * @final
     */
    send: function() {
      return this.sendImpl_.apply(this, arguments);
    },

    /**
     * @final
     */
    addContextObserver: function() {
      return this.addContextObserverImpl_.apply(this, arguments);
    },

    /**
     * @final
     */
    removeContextObserver: function() {
      return this.removeContextObserverImpl_.apply(this, arguments);
    },

    /**
     * @final
     */
    commitContextChanges: function() {
      return this.commitContextChangesImpl_.apply(this, arguments);
    },

    /**
     * @override
     * @final
     */
    querySelector: function() {
      return this.querySelectorImpl_.apply(this, arguments);
    },

    /**
     * @override
     * @final
     */
    querySelectorAll: function() {
      return this.querySelectorAllImpl_.apply(this, arguments);
    },

    /**
     * See login.Screen.send.
     * @private
     */
    sendImpl_: function() {
      return this.screen_.send.apply(this.screen_, arguments);
    },

    /**
     * Starts observation of property with |key| of the context attached to
     * current screen. This method differs from "login.ScreenContext" in that
     * it automatically detects if observer is method of |this| and make
     * all needed actions to make it work correctly. So it's no need for client
     * to bind methods to |this| and keep resulting callback for
     * |removeObserver| call:
     *
     *   this.addContextObserver('key', this.onKeyChanged_);
     *   ...
     *   this.removeContextObserver('key', this.onKeyChanged_);
     * @private
     */
    addContextObserverImpl_: function(key, observer) {
      var realObserver = observer;
      var propertyName = this.getPropertyNameOf_(observer);
      if (propertyName) {
        if (!this.contextObservers_.hasOwnProperty(propertyName))
          this.contextObservers_[propertyName] = observer.bind(this);
        realObserver = this.contextObservers_[propertyName];
      }
      this.context.addObserver(key, realObserver);
    },

    /**
     * Removes |observer| from the list of context observers. Supports not only
     * regular functions but also screen methods (see comment to
     * |addContextObserver|).
     * @private
     */
    removeContextObserverImpl_: function(observer) {
      var realObserver = observer;
      var propertyName = this.getPropertyNameOf_(observer);
      if (propertyName) {
        if (!this.contextObservers_.hasOwnProperty(propertyName))
          return;
        realObserver = this.contextObservers_[propertyName];
        delete this.contextObservers_[propertyName];
      }
      this.context.removeObserver(realObserver);
    },

    /**
     * See login.Screen.commitContextChanges.
     * @private
     */
    commitContextChangesImpl_: function() {
      return this.screen_.commitContextChanges.apply(this.screen_, arguments);
    },

    /**
     * Calls |querySelector| method of the shadow dom and returns the result.
     * @private
     */
    querySelectorImpl_: function(selector) {
      return this.shadowRoot.querySelector(selector);
    },


    /**
     * Calls standart |querySelectorAll| method of the shadow dom and returns
     * the result converted to Array.
     * @private
     */
    querySelectorAllImpl_: function(selector) {
      var list = this.shadowRoot.querySelectorAll(selector);
      return Array.prototype.slice.call(list);
    },

    /**
     * If |value| is the value of some property of |this| returns property's
     * name. Otherwise returns empty string.
     * @private
     */
    getPropertyNameOf_: function(value) {
      for (var key in this)
        if (this[key] === value)
          return key;
      return '';
    }
  };

  return {
    OobeScreenBehavior: OobeScreenBehavior
  };
});

