// Copyright (c) 2012 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('options', function() {
  /** @const */ var Page = cr.ui.pageManager.Page;
  /** @const */ var PageManager = cr.ui.pageManager.PageManager;

  /**
   * Enumeration of possible states during pairing.  The value associated with
   * each state maps to a localized string in the global variable
   * |loadTimeData|.
   * @enum {string}
   */
  var PAIRING = {
    STARTUP: 'bluetoothStartConnecting',
    ENTER_PIN_CODE: 'bluetoothEnterPinCode',
    ENTER_PASSKEY: 'bluetoothEnterPasskey',
    REMOTE_PIN_CODE: 'bluetoothRemotePinCode',
    REMOTE_PASSKEY: 'bluetoothRemotePasskey',
    CONFIRM_PASSKEY: 'bluetoothConfirmPasskey',
    CONNECT_FAILED: 'bluetoothConnectFailed',
    CANCELED: 'bluetoothPairingCanceled',
    DISMISSED: 'bluetoothPairingDismissed', // pairing dismissed(succeeded or
                                            // canceled).
  };

  /**
   * List of IDs for conditionally visible elements in the dialog.
   * @type {Array<string>}
   * @const
   */
  var ELEMENTS = ['bluetooth-pairing-passkey-display',
                  'bluetooth-pairing-passkey-entry',
                  'bluetooth-pairing-pincode-entry',
                  'bluetooth-pair-device-connect-button',
                  'bluetooth-pair-device-cancel-button',
                  'bluetooth-pair-device-accept-button',
                  'bluetooth-pair-device-reject-button',
                  'bluetooth-pair-device-dismiss-button'];

  /**
   * Encapsulated handling of the Bluetooth device pairing page.
   * @constructor
   * @extends {cr.ui.pageManager.Page}
   */
  function BluetoothPairing() {
    Page.call(this, 'bluetoothPairing',
              loadTimeData.getString('bluetoothOptionsPageTabTitle'),
              'bluetooth-pairing');
  }

  cr.addSingletonGetter(BluetoothPairing);

  BluetoothPairing.prototype = {
    __proto__: Page.prototype,

    /**
     * Description of the bluetooth device.
     * @type {?BluetoothDevice}
     * @private
     */
    device_: null,

    /**
     * Can the dialog be programmatically dismissed.
     * @type {boolean}
     */
    dismissible_: true,

    /** @override */
    initializePage: function() {
      Page.prototype.initializePage.call(this);
      var self = this;
      $('bluetooth-pair-device-cancel-button').onclick = function() {
        PageManager.closeOverlay();
      };
      $('bluetooth-pair-device-reject-button').onclick = function() {
        chrome.send('updateBluetoothDevice',
                    [self.device_.address, 'reject']);
        self.device_.pairing = PAIRING.DISMISSED;
        PageManager.closeOverlay();
      };
      $('bluetooth-pair-device-connect-button').onclick = function() {
        var args = [self.device_.address, 'connect'];
        var passkey = self.device_.passkey;
        if (passkey)
          args.push(String(passkey));
        else if (!$('bluetooth-pairing-passkey-entry').hidden)
          args.push($('bluetooth-passkey').value);
        else if (!$('bluetooth-pairing-pincode-entry').hidden)
          args.push($('bluetooth-pincode').value);
        chrome.send('updateBluetoothDevice', args);
        // Prevent sending a 'connect' command twice.
        $('bluetooth-pair-device-connect-button').disabled = true;
      };
      $('bluetooth-pair-device-accept-button').onclick = function() {
        chrome.send('updateBluetoothDevice',
                    [self.device_.address, 'accept']);
        // Prevent sending a 'accept' command twice.
        $('bluetooth-pair-device-accept-button').disabled = true;
      };
      $('bluetooth-pair-device-dismiss-button').onclick = function() {
        PageManager.closeOverlay();
      };
      $('bluetooth-passkey').oninput = function() {
        var inputField = $('bluetooth-passkey');
        var value = inputField.value;
        // Note that using <input type="number"> is insufficient to restrict
        // the input as it allows negative numbers and does not limit the
        // number of charactes typed even if a range is set.  Furthermore,
        // it sometimes produces strange repaint artifacts.
        var filtered = value.replace(/[^0-9]/g, '');
        if (filtered != value)
          inputField.value = filtered;
        $('bluetooth-pair-device-connect-button').disabled =
            inputField.value.length == 0;
      };
      $('bluetooth-pincode').oninput = function() {
        $('bluetooth-pair-device-connect-button').disabled =
            $('bluetooth-pincode').value.length == 0;
      };
      $('bluetooth-passkey').addEventListener('keydown',
          this.keyDownEventHandler_.bind(this));
      $('bluetooth-pincode').addEventListener('keydown',
          this.keyDownEventHandler_.bind(this));
    },

    /** @override */
    didClosePage: function() {
      if (this.device_.pairing != PAIRING.DISMISSED &&
          this.device_.pairing != PAIRING.CONNECT_FAILED) {
        this.device_.pairing = PAIRING.CANCELED;
        chrome.send('updateBluetoothDevice',
                    [this.device_.address, 'cancel']);
      }
    },

    /**
     * Override to prevent showing the overlay if the Bluetooth device details
     * have not been specified.  Prevents showing an empty dialog if the user
     * quits and restarts Chrome while in the process of pairing with a device.
     * @return {boolean} True if the overlay can be displayed.
     */
    canShowPage: function() {
      return !!(this.device_ && this.device_.address && this.device_.pairing);
    },

    /**
     * Sets input focus on the passkey or pincode field if appropriate.
     */
    didShowPage: function() {
      if (!$('bluetooth-pincode').hidden)
        $('bluetooth-pincode').focus();
      else if (!$('bluetooth-passkey').hidden)
        $('bluetooth-passkey').focus();
    },

    /**
     * Configures the overlay for pairing a device.
     * @param {Object} device Description of the bluetooth device.
     */
    update: function(device) {
      this.device_ = /** @type {BluetoothDevice} */({});
      for (var key in device)
        this.device_[key] = device[key];
      // Update the pairing instructions.
      var instructionsEl = assert($('bluetooth-pairing-instructions'));
      this.clearElement_(instructionsEl);
      this.dismissible_ = ('dismissible' in device) ?
        device.dismissible : true;

      var message = loadTimeData.getString(device.pairing);
      message = message.replace('%1', this.device_.name);
      instructionsEl.textContent = message;

      // Update visibility of dialog elements.
      if (this.device_.passkey) {
        this.updatePasskey_(String(this.device_.passkey));
        if (this.device_.pairing == PAIRING.CONFIRM_PASSKEY) {
          // Confirming a match between displayed passkeys.
          this.displayElements_(['bluetooth-pairing-passkey-display',
                                 'bluetooth-pair-device-accept-button',
                                 'bluetooth-pair-device-reject-button']);
          $('bluetooth-pair-device-accept-button').disabled = false;
        } else {
          // Remote entering a passkey.
          this.displayElements_(['bluetooth-pairing-passkey-display',
                                 'bluetooth-pair-device-cancel-button']);
        }
      } else if (this.device_.pincode) {
        this.updatePasskey_(String(this.device_.pincode));
        this.displayElements_(['bluetooth-pairing-passkey-display',
                               'bluetooth-pair-device-cancel-button']);
      } else if (this.device_.pairing == PAIRING.ENTER_PIN_CODE) {
        // Prompting the user to enter a PIN code.
        this.displayElements_(['bluetooth-pairing-pincode-entry',
                               'bluetooth-pair-device-connect-button',
                               'bluetooth-pair-device-cancel-button']);
        $('bluetooth-pincode').value = '';
      } else if (this.device_.pairing == PAIRING.ENTER_PASSKEY) {
        // Prompting the user to enter a passkey.
        this.displayElements_(['bluetooth-pairing-passkey-entry',
                               'bluetooth-pair-device-connect-button',
                               'bluetooth-pair-device-cancel-button']);
        $('bluetooth-passkey').value = '';
      } else if (this.device_.pairing == PAIRING.STARTUP) {
        // Starting the pairing process.
        this.displayElements_(['bluetooth-pair-device-cancel-button']);
      } else {
        // Displaying an error message.
        this.displayElements_(['bluetooth-pair-device-dismiss-button']);
      }
      // User is required to enter a passkey or pincode before the connect
      // button can be enabled.  The 'oninput' methods for the input fields
      // determine when the connect button becomes active.
      $('bluetooth-pair-device-connect-button').disabled = true;
    },

    /**
     * Handles the ENTER key for the passkey or pincode entry field.
     * @param {Event} event A keydown event.
     * @private
     */
    keyDownEventHandler_: function(event) {
      /** @const */ var ENTER_KEY_CODE = 13;
      if (event.keyCode == ENTER_KEY_CODE) {
        var button = $('bluetooth-pair-device-connect-button');
        if (!button.hidden)
          button.click();
      }
    },

    /**
     * Updates the visibility of elements in the dialog.
     * @param {Array<string>} list List of conditionally visible elements that
     *     are to be made visible.
     * @private
     */
    displayElements_: function(list) {
      var enabled = {};
      for (var i = 0; i < list.length; i++) {
        var key = list[i];
        enabled[key] = true;
      }
      for (var i = 0; i < ELEMENTS.length; i++) {
        var key = ELEMENTS[i];
        $(key).hidden = !enabled[key];
      }
    },

    /**
     * Removes all children from an element.
     * @param {!Element} element Target element to clear.
     */
    clearElement_: function(element) {
      var child = element.firstChild;
      while (child) {
        element.removeChild(child);
        child = element.firstChild;
      }
    },

    /**
     * Formats an element for displaying the passkey or PIN code.
     * @param {string} key Passkey or PIN to display.
     */
    updatePasskey_: function(key) {
      var passkeyEl = assert($('bluetooth-pairing-passkey-display'));
      var keyClass = (this.device_.pairing == PAIRING.REMOTE_PASSKEY ||
                      this.device_.pairing == PAIRING.REMOTE_PIN_CODE) ?
          'bluetooth-keyboard-button' : 'bluetooth-passkey-char';
      this.clearElement_(passkeyEl);
      // Passkey should always have 6 digits.
      key = '000000'.substring(0, 6 - key.length) + key;
      var progress = this.device_.entered;
      for (var i = 0; i < key.length; i++) {
        var keyEl = document.createElement('span');
        keyEl.textContent = key.charAt(i);
        keyEl.className = keyClass;
        if (progress != undefined) {
          if (i < progress)
            keyEl.classList.add('key-typed');
          else if (i == progress)
            keyEl.classList.add('key-next');
          else
            keyEl.classList.add('key-untyped');
        }
        passkeyEl.appendChild(keyEl);
      }
      if (this.device_.pairing == PAIRING.REMOTE_PASSKEY ||
          this.device_.pairing == PAIRING.REMOTE_PIN_CODE) {
        // Add enter key.
        var label = loadTimeData.getString('bluetoothEnterKey');
        var keyEl = document.createElement('span');
        keyEl.textContent = label;
        keyEl.className = keyClass;
        keyEl.id = 'bluetooth-enter-key';
        if (progress != undefined) {
          if (progress > key.length)
            keyEl.classList.add('key-typed');
          else if (progress == key.length)
            keyEl.classList.add('key-next');
          else
            keyEl.classList.add('key-untyped');
        }
        passkeyEl.appendChild(keyEl);
      }
      passkeyEl.hidden = false;
    },
  };

  /**
   * Configures the device pairing instructions and displays the pairing
   * overlay.
   * @param {Object} device Description of the Bluetooth device.
   */
  BluetoothPairing.showDialog = function(device) {
    BluetoothPairing.getInstance().update(device);
    PageManager.showPageByName('bluetoothPairing', false);
  };

  /**
   * Displays a message from the Bluetooth adapter.
   * @param {{message: string, address: string}} data Data for constructing the
   *     message. |data.message| is the name of message to show. |data.address|
   *     is the device address.
   */
  BluetoothPairing.showMessage = function(data) {
    var name = data.address;
    if (name.length == 0)
      return;
    var dialog = BluetoothPairing.getInstance();
    if (dialog.device_ && name == dialog.device_.address &&
        dialog.device_.pairing == PAIRING.CANCELED) {
      // Do not show any error message after cancelation of the pairing.
      return;
    }

    var list = $('bluetooth-paired-devices-list');
    if (list) {
      var index = list.find(name);
      if (index == undefined) {
        list = $('bluetooth-unpaired-devices-list');
        index = list.find(name);
      }
      if (index != undefined) {
        var entry = list.dataModel.item(index);
        if (entry && entry.name)
          name = entry.name;
      }
    }
    BluetoothPairing.showDialog({name: name,
                                 address: data.address,
                                 pairing: data.message,
                                 dismissible: false});
  };

  /**
   * Closes the Bluetooth pairing dialog.
   */
  BluetoothPairing.dismissDialog = function() {
    var overlay = PageManager.getTopmostVisiblePage();
    var dialog = BluetoothPairing.getInstance();
    if (overlay == dialog && dialog.dismissible_) {
      dialog.device_.pairing = PAIRING.DISMISSED;
      PageManager.closeOverlay();
    }
  };

  // Export
  return {
    BluetoothPairing: BluetoothPairing
  };
});
