text: /* global Service */
  'use strict';
  
  (function(exports) {
    var TransitionEvents =
      ['open', 'close', 'complete', 'timeout',
        'immediate-open', 'immediate-close'];
  
    var TransitionStateTable = {
      'closed': ['opening', null, null, null, 'opened', null],
      'opened': [null, 'closing', null, null, null, 'closed'],
      'opening': [null, 'closing', 'opened', 'opened', 'opened', 'closed'],
      'closing': ['opened', null, 'closed', 'closed', 'opened', 'closed']
    };
  
    /**
     * AppTransitionController controlls the opening and closing animation
     * of the given appWindow.
     *
     * ##### Flow chart #####
     * ![AppTransition Flow chart](http://i.imgur.com/k0hO2AN.png)
     *
     * ##### State machine #####
     * ![AppTransition State machine](http://i.imgur.com/0arU9rl.png)
     *
     * @param {AppWindow} app The app window instance which this controller
     *                        belongs to.
     *
     * @class AppTransitionController
     */
    var AppTransitionController =
      function AppTransitionController(app) {
        if (!app || !app.element) {
          return;
        }
  
        this.app = app;
        this.app.debug('default animation:',
          this.app.openAnimation, this.app.closeAnimation);
        if (this.app.openAnimation) {
          this.openAnimation = this.app.openAnimation;
        }
  
        if (this.app.closeAnimation) {
          this.closeAnimation = this.app.closeAnimation;
        }
  
        if (this.app.CLASS_NAME == 'AppWindow') {
          this.OPENING_TRANSITION_TIMEOUT = 2500;
        }
  
        this.app.element.addEventListener('_opening', this);
        this.app.element.addEventListener('_closing', this);
        this.app.element.addEventListener('_opened', this);
        this.app.element.addEventListener('_closed', this);
        this.app.element.addEventListener('_opentransitionstart', this);
        this.app.element.addEventListener('_closetransitionstart', this);
        this.app.element.addEventListener('_loaded', this);
        this.app.element.addEventListener('_openingtimeout', this);
        this.app.element.addEventListener('_closingtimeout', this);
        this.app.element.addEventListener('animationend', this);
      };
  
    AppTransitionController.prototype.destroy = function() {
      if (!this.app || !this.app.element) {
        return;
      }
  
      this.app.element.removeEventListener('_opening', this);
      this.app.element.removeEventListener('_closing', this);
      this.app.element.removeEventListener('_opened', this);
      this.app.element.removeEventListener('_closed', this);
      this.app.element.removeEventListener('_opentransitionstart', this);
      this.app.element.removeEventListener('_closetransitionstart', this);
      this.app.element.removeEventListener('_loaded', this);
      this.app.element.removeEventListener('_openingtimeout', this);
      this.app.element.removeEventListener('_closingtimeout', this);
      this.app.element.removeEventListener('animationend', this);
      this.app = null;
    };
  
    AppTransitionController.prototype._transitionState = 'closed';
    AppTransitionController.prototype._waitingForLoad = false;
    AppTransitionController.prototype.openAnimation = 'enlarge';
    AppTransitionController.prototype.closeAnimation = 'reduce';
    AppTransitionController.prototype.OPENING_TRANSITION_TIMEOUT = 200;
    AppTransitionController.prototype.CLOSING_TRANSITION_TIMEOUT = 200;
    AppTransitionController.prototype.SLOW_TRANSITION_TIMEOUT = 3500;
    AppTransitionController.prototype._firstTransition = true;
    AppTransitionController.prototype.changeTransitionState =
      function atc_changeTransitionState(evt) {
        var currentState = this._transitionState;
        var evtIndex = TransitionEvents.indexOf(evt);
        var state = TransitionStateTable[currentState][evtIndex];
        if (!state) {
          return;
        }
  
        this.app.debug(currentState, state, '::', evt);
  
        this.switchTransitionState(state);
        this.resetTransition();
        this['_do_' + state]();
        this.app.publish(state);
  
        //backward compatibility
        if (!this.app) {
          return;
        }
        if (state == 'opening') {
          /**
           * Fired when the app is doing opening animation.
           * @event AppWindow#appopening
           */
          this.app.publish('willopen');
        } else if (state == 'closing') {
          /**
           * Fired when the app is doing closing animation.
           * @event AppWindow#appclosing
           */
          this.app.publish('willclose');
        } else if (state == 'opened') {
          /**
           * Fired when the app's opening animation is ended.
           * @event AppWindow#appopen
           */
          this.app.publish('open');
        } else if (state == 'closed') {
          /**
           * Fired when the app's closing animation is ended.
           * @event AppWindow#appclose
           */
          this.app.publish('close');
        }
      };
  
    AppTransitionController.prototype._do_closing =
      function atc_do_closing() {
        this.app.debug('timer to ensure closed does occur.');
        this._closingTimeout = window.setTimeout(() => {
          if (!this.app) {
            return;
          }
          this.app.broadcast('closingtimeout');
        },
        Service.query('slowTransition') ? this.SLOW_TRANSITION_TIMEOUT :
                                this.CLOSING_TRANSITION_TIMEOUT);
  
        if (!this.app || !this.app.element) {
          return;
        }
        this.app.element.classList.add('transition-closing');
        this.app.element.classList.add(this.getAnimationName('close'));
      };
  
    AppTransitionController.prototype._do_closed =
      function atc_do_closed() {
      };
  
    AppTransitionController.prototype.getAnimationName = function(type) {
      return this.currentAnimation || this[type + 'Animation'] || type;
    };
  
    AppTransitionController.prototype._do_opening =
      function atc_do_opening() {
        this.app.debug('timer to ensure opened does occur.');
        this._openingTimeout = window.setTimeout(function() {
          this.app && this.app.broadcast('openingtimeout');
        }.bind(this),
        Service.query('slowTransition') ? this.SLOW_TRANSITION_TIMEOUT :
                                this.OPENING_TRANSITION_TIMEOUT);
        this._waitingForLoad = false;
        this.app.element.classList.add('transition-opening');
        this.app.element.classList.add(this.getAnimationName('open'));
        this.app.debug(this.app.element.classList);
      };
  
    AppTransitionController.prototype._do_opened =
      function atc_do_opened() {
      };
  
    AppTransitionController.prototype.switchTransitionState =
      function atc_switchTransitionState(state) {
        this._transitionState = state;
        if (!this.app) {
          return;
        }
        this.app._changeState('transition', this._transitionState);
      };
  
    // TODO: move general transition handlers into another object.
    AppTransitionController.prototype.handle_closing =
      function atc_handle_closing() {
        if (!this.app || !this.app.element) {
          return;
        }
        /* The AttentionToaster will take care of that for AttentionWindows */
        /* InputWindow & InputWindowManager will take care of visibility of IM */
        if (!this.app.isAttentionWindow && !this.app.isCallscreenWindow &&
            !this.app.isInputMethod) {
          this.app.setVisible(false);
        }
        this.switchTransitionState('closing');
      };
  
    AppTransitionController.prototype.handle_closed =
      function atc_handle_closed() {
        if (!this.app || !this.app.element) {
          return;
        }
  
        this.resetTransition();
        this.app.setNFCFocus(false);
  
        this.app.element.classList.remove('active');
      };
  
    AppTransitionController.prototype.handle_opening =
      function atc_handle_opening() {
        if (!this.app || !this.app.element) {
          return;
        }
        if (this.app.loaded) {
          var self = this;
          this.app.element.addEventListener('_opened', function onopen() {
            // Perf test needs.
            self.app.element.removeEventListener('_opened', onopen);
            self.app.publish('loadtime', {
              time: parseInt(Date.now() - self.app.launchTime),
              type: 'w',
              src: self.app.config.url
            });
          });
        }
        this.app.reviveBrowser();
        this.app.launchTime = Date.now();
        this.app.fadeIn();
        this.app.requestForeground();
  
        // TODO:
        // May have orientation manager to deal with lock orientation request.
        if (this.app.isHomescreen ||
            this.app.isCallscreenWindow) {
          this.app.setOrientation();
        }
      };
  
    AppTransitionController.prototype.handle_opened =
      function atc_handle_opened() {
        if (!this.app || !this.app.element) {
          return;
        }
  
        this.app.reviveBrowser();
        this.resetTransition();
        this.app.element.removeAttribute('aria-hidden');
        this.app.show();
        this.app.element.classList.add('active');
        this.app.requestForeground();
  
        // TODO:
        // May have orientation manager to deal with lock orientation request.
        if (!this.app.isCallscreenWindow) {
          this.app.setOrientation();
        }
        this.focusApp();
      };
  
    AppTransitionController.prototype.focusApp = function() {
      if (!this.app) {
        return;
      }
  
      if (this._shouldFocusApp()) {
        this.app.debug('focusing this app.');
        this.app.focus();
        this.app.setNFCFocus(true);
      }
    };
  
    AppTransitionController.prototype._shouldFocusApp = function() {
      // SearchWindow should not focus itself,
      // because the input is inside system app.
      var bottomWindow = this.app.getBottomMostWindow();
      var topmostui = Service.query('getTopMostUI');
      var stayBackground = this.app.config &&
                           this.app.config.stayBackground;
      return (this.app.CLASS_NAME !== 'SearchWindow' &&
              this._transitionState == 'opened' &&
              Service.query('getTopMostWindow') === this.app &&
              !stayBackground &&
              topmostui &&
              topmostui.name === bottomWindow.HIERARCHY_MANAGER);
    };
  
    AppTransitionController.prototype.requireOpen = function(animation) {
      this.currentAnimation = animation || this.openAnimation;
      this.app.debug('open with ' + this.currentAnimation);
      if (this.currentAnimation == 'immediate') {
        this.changeTransitionState('immediate-open');
      } else {
        this.changeTransitionState('open');
      }
    };
  
    AppTransitionController.prototype.requireClose = function(animation) {
      this.currentAnimation = animation || this.closeAnimation;
      this.app.debug('close with ' + this.currentAnimation);
      if (this.currentAnimation == 'immediate') {
        this.changeTransitionState('immediate-close');
      } else {
        this.changeTransitionState('close');
      }
    };
  
    AppTransitionController.prototype.resetTransition =
      function atc_resetTransition() {
        if (this._openingTimeout) {
          window.clearTimeout(this._openingTimeout);
          this._openingTimeout = null;
        }
  
        if (this._closingTimeout) {
          window.clearTimeout(this._closingTimeout);
          this._closingTimeout = null;
        }
        this.clearTransitionClasses();
      };
  
    AppTransitionController.prototype.clearTransitionClasses =
      function atc_removeTransitionClasses() {
        if (!this.app || !this.app.element) {
          return;
        }
  
        var classes = ['enlarge', 'reduce', 'to-cardview', 'from-cardview',
          'invoking', 'invoked', 'zoom-in', 'zoom-out', 'fade-in', 'fade-out',
          'transition-opening', 'transition-closing', 'immediate', 'fadeout',
          'slideleft', 'slideright', 'in-from-left', 'out-to-right',
          'will-become-active', 'will-become-inactive',
          'slide-to-top', 'slide-from-top',
          'slide-to-bottom', 'slide-from-bottom',
          'home-from-cardview', 'home-to-cardview'];
  
        classes.forEach(function iterator(cls) {
          this.app.element.classList.remove(cls);
        }, this);
      };
  
    AppTransitionController.prototype.handleEvent =
      function atc_handleEvent(evt) {
        switch (evt.type) {
          case '_opening':
            this.handle_opening();
            break;
          case '_opened':
            this.handle_opened();
            break;
          case '_closed':
            this.handle_closed();
            break;
          case '_closing':
            this.handle_closing();
            break;
          case '_closingtimeout':
          case '_openingtimeout':
            this.changeTransitionState('timeout', evt.type);
            break;
          case '_loaded':
            if (this._waitingForLoad) {
              this._waitingForLoad = false;
              this.changeTransitionState('complete');
            }
            break;
          case 'animationend':
            evt.stopPropagation();
            // Hide touch-blocker when launching animation is ended.
            this.app.element.classList.remove('transition-opening');
  
            // We decide to drop this event if system is busy loading
            // the active app or doing some other more important task.
            // In case of the browser, we don't want to wait for the page
            // being fully loaded to trigger the 'opened' event
            if (!this.app.isBrowser() && Service.query('isBusyLoading')) {
              this._waitingForLoad = true;
              if (this.app.isHomescreen && this._transitionState == 'opening') {
                /**
                 * focusing the app will have some side effect,
                 * but we don't care if we are opening the homescreen.
                 */
                this.app.focus();
              }
              return;
            }
            this.app.debug(evt.animationName + ' has been ENDED!');
            this.changeTransitionState('complete', evt.type);
            break;
        }
      };
    exports.AppTransitionController = AppTransitionController;
  }(window));
  