/**
 * @license
 * Copyright 2015 Yahoo Inc. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Implements the looking glass that allows decrypted PGP messages
 * to be securely displayed inside the original web applications.
 */

goog.provide('e2e.ext.ui.Glass');

goog.require('e2e.ext.constants.Actions');
goog.require('e2e.ext.constants.ElementId');
/** @suppress {extraRequire} manually import typedefs due to b/15739810 */
goog.require('e2e.ext.messages.ApiRequest');
goog.require('e2e.ext.ui.templates.glass');
goog.require('e2e.ext.utils');
goog.require('e2e.openpgp.asciiArmor');
goog.require('e2e.random');
goog.require('goog.dom');
goog.require('goog.style');
goog.require('goog.ui.Component');
goog.require('soy');

goog.scope(function() {
var constants = e2e.ext.constants;
var messages = e2e.ext.messages;
var templates = e2e.ext.ui.templates.glass;
var ui = e2e.ext.ui;
var utils = e2e.ext.utils;



/**
 * Constructor for the looking glass.
 * @param {string} pgpMessage The encrypted PGP message that needs to be
 *     decrypted and displayed to the user.
 * @constructor
 * @extends {goog.ui.Component}
 */
ui.Glass = function(pgpMessage) {
  goog.base(this);

  /**
   * The encrypted PGP message that needs to be decrypted and displayed to
   * the user.
   * @type {string}
   * @private
   */
  this.pgpMessage_ = pgpMessage;

  /**
   * The communication channel with the extension.
   * @type {Port}
   * @private
   */
  this.port_ = null;

  /**
   * Whether the glass contents have been rendered.
   * @type {boolean}
   * @private
   */
  this.rendered_ = false;
};
goog.inherits(ui.Glass, goog.ui.Component);


/**
 * The offset to use when scrolling up/down.
 * @type {number}
 * @private
 * @const
 */
ui.Glass.prototype.SCROLL_OFFSET_ = 10;


/** @override */
ui.Glass.prototype.disposeInternal = function() {
  if (this.port_) {
    this.port_.disconnect();
    this.port_ = null;
  }

  goog.base(this, 'disposeInternal');
};


/** @override */
ui.Glass.prototype.decorateInternal = function(elem) {
  goog.base(this, 'decorateInternal', elem);

  if (!this.port_) {
    this.port_ = chrome.runtime.connect();
  }

  window.setTimeout(goog.bind(/** @this ui.Glass */ function() {
    if (this.port_) {
      this.port_.postMessage({
        content: this.pgpMessage_,
        action: constants.Actions.DECRYPT_VERIFY
      });
    } else {
      this.renderContents_({
        completedAction: constants.Actions.DECRYPT_VERIFY,
        error: chrome.i18n.getMessage('glassCannotDecrypt')
      });
    }
  }, this), e2e.random.getRandomBytes(1, [0])[0]);

  this.port_.onDisconnect.addListener(goog.bind(function() {
    this.port_ = null;
  }, this));

  this.port_.onMessage.addListener(goog.bind(function(response) {
    window.setTimeout(
        goog.bind(this.renderContents_, this, response),
        e2e.random.getRandomBytes(1, [0])[0]);
  }, this));
};


/**
 * Renders the contents of the looking glass.
 * @param {messages.ApiResponse} response The response from the extension to
 *     render.
 * @private
 */
ui.Glass.prototype.renderContents_ = function(response) {
  var elem = this.getElement();
  var decrypted = true; // was the message encrypted and then decrypted?
  var verified = true; // was the message signed and then verified?

  if (!response.content && !response.error) {
    // If the response has no content, show an undecryptable error if
    // the error message is missing for some reason.
    response.error = chrome.i18n.getMessage('glassCannotDecrypt');
  }

  if (response.error && response.error.indexOf(
      chrome.i18n.getMessage('promptVerificationFailureMsg')) === 0) {
    // Message must have been decrypted but it wasn't verified
    verified = false;
  } else if (response.error) {
    // Otherwise probably we neither decrypted nor verified
    decrypted = false;
    verified = false;
  }

  // If the pgp message wasn't encrypted in the first place, it wasn't decrypted
  // if (!e2e.openpgp.asciiArmor.isEncrypted(this.pgpMessage_)) {
  if (this.pgpMessage_.indexOf('-----BEGIN PGP MESSAGE-----') === -1) {
    decrypted = false;
    // If it wasn't encrypted or clear signed, it can't have been verified
    if (!e2e.openpgp.asciiArmor.isClearSign(this.pgpMessage_)) {
      verified = false;
    }
  }
  var styles;

  if (!this.rendered_) {
    soy.renderElement(elem, templates.contentFrame, {
      label: chrome.i18n.getMessage('extName'),
      content: response.content || this.pgpMessage_,
      error: response.error
    });
    styles = elem.querySelector('link');
    styles.href = chrome.runtime.getURL('glass_styles.css');

    this.resizeGlass_();
    this.rendered_ = true;
  } else if (response.content) {
    var previousError = goog.dom.getElement(
        constants.ElementId.ERROR_DIV).textContent || undefined;
    if (previousError === chrome.i18n.getMessage('glassCannotDecrypt')) {
      // Since the response content is now the decrypted message, we can
      // get rid of the undecryptable error message. This happens because
      // the decryptVerify API *might* send two messages if a verification
      // error is encountered (one with the error, and one with the decrypted
      // content).
      previousError = undefined;
    }
    soy.renderElement(elem, templates.contentFrame, {
      label: chrome.i18n.getMessage('extName'),
      content: response.content,
      error: previousError
    });
    styles = elem.querySelector('link');
    styles.href = chrome.runtime.getURL('glass_styles.css');

    this.resizeGlass_();
  } else if (response.error) {
    goog.dom.getElement(constants.ElementId.ERROR_DIV).textContent =
        response.error;
  }

  if (!decrypted) {
    goog.dom.getElement(constants.ElementId.LOCK_ICON).style.display = 'none';
  }
  if (!verified) {
    goog.dom.getElement(constants.ElementId.CHECK_ICON).style.display = 'none';
  }
};


/**
 * Waits for stylesheet to be applied, then resizes the glass.
 * @private
 */
ui.Glass.prototype.resizeGlass_ = function() {
  window.setTimeout(goog.bind(function() {
    utils.sendProxyRequest(/** @type {messages.proxyMessage} */ ({
      action: constants.Actions.SET_GLASS_SIZE,
      content: {
        height: window.document.querySelector('fieldset').offsetHeight +
            Math.floor(Math.random() * 18)
      }
    }));
  }, this), 50);
};


/** @override */
ui.Glass.prototype.enterDocument = function() {
  goog.base(this, 'enterDocument');

  /*
  var mouseWheelHandler = new goog.events.MouseWheelHandler(
      this.getElement(), true);
  this.registerDisposable(mouseWheelHandler);

  this.getHandler().listen(
      mouseWheelHandler,
      goog.events.MouseWheelHandler.EventType.MOUSEWHEEL,
      this.scroll_);
  */
};


/**
 * Scrolls the looking glass up/down.
 * @param {goog.events.MouseWheelEvent} evt The mouse wheel event to
 *     scroll up/down.
 * @private
 */
ui.Glass.prototype.scroll_ = function(evt) {
  var fieldset = this.getElement().querySelector('fieldset');
  var position = goog.style.getPosition(fieldset);

  var newY = position.y - evt.deltaY * 5;
  // Set upper boundary.
  newY = Math.min(0, newY);
  // Set lower boundary.
  newY = Math.max(window.innerHeight * -1, newY);

  goog.style.setPosition(fieldset, 0, newY);
};

});  // goog.scope
