// 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.

/**
 * @fileoverview Accesses Chrome's tabs extension API and gives
 * feedback for events that happen in the "Chrome of Chrome".
 */

goog.provide('cvox.TabsApiHandler');

goog.require('Stubs');
goog.require('TabsAutomationHandler');
goog.require('cvox.AbstractEarcons');
goog.require('cvox.AbstractTts');
goog.require('cvox.BrailleInterface');
goog.require('cvox.ChromeVox');
goog.require('cvox.NavBraille');


/**
 * Class that adds listeners and handles events from the tabs API.
 * @constructor
 */
cvox.TabsApiHandler = function() {
  /** @type {function(string, Array<string>=)} @private */
  this.msg_ = Msgs.getMsg.bind(Msgs);
  /**
   * Tracks whether the active tab has finished loading.
   * @type {boolean}
   * @private
   */
  this.lastActiveTabLoaded_ = false;

  chrome.tabs.onCreated.addListener(this.onCreated.bind(this));
  chrome.tabs.onRemoved.addListener(this.onRemoved.bind(this));
  chrome.tabs.onActivated.addListener(this.onActivated.bind(this));
  chrome.tabs.onUpdated.addListener(this.onUpdated.bind(this));
  chrome.windows.onFocusChanged.addListener(this.onFocusChanged.bind(this));

  /**
   * @type {?number} The window.setInterval ID for checking the loading
   *     status of the current tab.
   * @private
   */
  this.pageLoadIntervalID_ = null;

  /**
   * @type {?number} The tab ID of the tab being polled because it's loading.
   * @private
   */
  this.pageLoadTabID_ = null;
};

/**
 * @type {boolean}
 */
cvox.TabsApiHandler.shouldOutputSpeechAndBraille = true;

cvox.TabsApiHandler.prototype = {
  /**
   * Handles chrome.tabs.onCreated.
   * @param {Object} tab
   */
  onCreated: function(tab) {
    if (!cvox.ChromeVox.isActive) {
      return;
    }
    if (cvox.TabsApiHandler.shouldOutputSpeechAndBraille) {
      cvox.ChromeVox.tts.speak(this.msg_('chrome_tab_created'),
                                cvox.QueueMode.FLUSH,
                                cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT);
      cvox.ChromeVox.braille.write(
          cvox.NavBraille.fromText(this.msg_('chrome_tab_created')));
    }
    cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.OBJECT_OPEN);
    if (tab) {
      this.refreshAutomationHandler_(tab.id);
    }
  },

  /**
   * Handles chrome.tabs.onRemoved.
   * @param {Object} tab
   */
  onRemoved: function(tab) {
    if (!cvox.ChromeVox.isActive) {
      return;
    }
    cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.OBJECT_CLOSE);

    chrome.tabs.query({active: true}, function(tabs) {
      if (tabs.length == 0 && this.isPlayingPageLoadingSound_()) {
        cvox.ChromeVox.earcons.cancelEarcon(cvox.Earcon.PAGE_START_LOADING);
        this.cancelPageLoadTimer_();
      }
    }.bind(this));
  },

  /**
   * Handles chrome.tabs.onActivated.
   * @param {Object} activeInfo
   */
  onActivated: function(activeInfo) {
    if (!cvox.ChromeVox.isActive) {
      return;
    }
    this.updateLoadingSoundsWhenTabFocusChanges_(activeInfo.tabId);
    chrome.tabs.get(activeInfo.tabId, function(tab) {
      if (cvox.TabsApiHandler.shouldOutputSpeechAndBraille) {
        var title = tab.title ? tab.title : tab.url;
        cvox.ChromeVox.tts.speak(this.msg_('chrome_tab_selected',
                                           [title]),
                                 cvox.QueueMode.FLUSH,
                                 cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT);
        cvox.ChromeVox.braille.write(
            cvox.NavBraille.fromText(
                this.msg_('chrome_tab_selected', [title])));
      }
      cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.OBJECT_SELECT);
      this.refreshAutomationHandler_(tab.id);
    }.bind(this));
  },

  /**
   * Called when a tab becomes active or focused.
   * @param {number} tabId the id of the tab that's now focused and active.
   * @private
   */
  updateLoadingSoundsWhenTabFocusChanges_: function(tabId) {
    chrome.tabs.get(tabId, function(tab) {
      this.lastActiveTabLoaded_ = tab.status == 'complete';
      if (tab.status == 'loading' && !this.isPlayingPageLoadingSound_()) {
        cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.PAGE_START_LOADING);
        this.startPageLoadTimer_(tabId);
      } else {
        cvox.ChromeVox.earcons.cancelEarcon(cvox.Earcon.PAGE_START_LOADING);
        this.cancelPageLoadTimer_();
      }
    }.bind(this));
  },

  /**
   * Handles chrome.tabs.onUpdated.
   * @param {number} tabId
   * @param {Object} selectInfo
   */
  onUpdated: function(tabId, selectInfo) {
    if (!cvox.ChromeVox.isActive) {
      return;
    }
    chrome.tabs.get(tabId, function(tab) {
      if (!tab.active) {
        return;
      }
      if (tab.status == 'loading') {
        this.lastActiveTabLoaded_ = false;
        if (!this.isPlayingPageLoadingSound_()) {
          cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.PAGE_START_LOADING);
          this.startPageLoadTimer_(tabId);
        }
      } else if (!this.lastActiveTabLoaded_) {
        this.lastActiveTabLoaded_ = true;
        cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.PAGE_FINISH_LOADING);
        this.cancelPageLoadTimer_();
      }
      this.refreshAutomationHandler_(tabId);
    }.bind(this));
  },

  /**
   * Handles chrome.windows.onFocusChanged.
   * @param {number} windowId
   */
  onFocusChanged: function(windowId) {
    if (!cvox.ChromeVox.isActive) {
      return;
    }
    if (windowId == chrome.windows.WINDOW_ID_NONE) {
      return;
    }
    chrome.windows.get(windowId, function(window) {
      chrome.tabs.query({active: true, windowId: windowId}, function(tabs) {
        if (tabs[0])
          this.updateLoadingSoundsWhenTabFocusChanges_(tabs[0].id);

        var tab = tabs[0] || {};
        if (cvox.TabsApiHandler.shouldOutputSpeechAndBraille) {
          var msgId = window.incognito ? 'chrome_incognito_window_selected' :
              'chrome_normal_window_selected';
          var title = tab.title ? tab.title : tab.url;
          cvox.ChromeVox.tts.speak(this.msg_(msgId, [title]),
                                   cvox.QueueMode.FLUSH,
                                   cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT);
          cvox.ChromeVox.braille.write(
              cvox.NavBraille.fromText(this.msg_(msgId, [title])));
        }
        cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.OBJECT_SELECT);
        this.refreshAutomationHandler_(tab.id);
      }.bind(this));
    }.bind(this));
  },

  /**
   * Installs a new automation handler for the given tab.
   * @param {number} tabId
   * @private
   */
  refreshAutomationHandler_: function(tabId) {
    if (!cvox.ChromeVox.isMac ||
        ChromeVoxState.instance.mode == ChromeVoxMode.CLASSIC)
      return;

    chrome.automation.getTree(tabId, function(node) {
      if (this.handler_)
        this.handler_.removeAllListeners();

      this.handler_ = new TabsAutomationHandler(node);
    }.bind(this));
  },

  /**
   * The chrome.tabs API doesn't always fire an onUpdated event when a
   * page finishes loading, so we poll it.
   * @param {number} tabId The id of the tab to monitor.
   * @private
   */
  startPageLoadTimer_: function(tabId) {
    if (this.pageLoadIntervalID_) {
      if (tabId == this.pageLoadTabID_)
        return;
      this.cancelPageLoadTimer_();
    }

    this.pageLoadTabID_ = tabId;
    this.pageLoadIntervalID_ = window.setInterval(function() {
      if (this.pageLoadTabID_)
        this.onUpdated(this.pageLoadTabID_, {});
    }.bind(this), 1000);
  },

  /**
   * Cancel the page loading timer because the active tab is loaded.
   * @private
   */
  cancelPageLoadTimer_: function() {
    if (this.pageLoadIntervalID_) {
      window.clearInterval(this.pageLoadIntervalID_);
      this.pageLoadIntervalID_ = null;
      this.pageLoadTabID_ = null;
    }
  },

  /**
   * @return {boolean} True if the page loading sound is playing and our
   * page loading timer is active.
   */
  isPlayingPageLoadingSound_: function() {
    return this.pageLoadIntervalID_ != null;
  }
};
