/* Copyright 2017 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.
 */

ProximityAuth = {
  cryptauthController_: null,
  remoteDevicesController_: null,
  findEligibleDevicesController_: null,

  /**
   * Initializes all UI elements of the ProximityAuth debug page.
   */
  init: function() {
    ProximityAuth.cryptauthController_ = new CryptAuthController();
    ProximityAuth.remoteDevicesController_ = new DeviceListController(
        document.getElementById('remote-devices-control'), true, false);
    ProximityAuth.eligibleDevicesController_ = new EligibleDevicesController();
    WebUI.getLocalState();
  }
};

/**
 * Controller for the CryptAuth controls section.
 */
class CryptAuthController {
  constructor() {
    this.elements_ = {
      localDeviceId: document.getElementById('local-device-id'),
      gcmRegistration: document.getElementById('gcm-registration'),
      currentEid: document.getElementById('current-eid'),
      enrollmentTitle: document.getElementById('enrollment-title'),
      lastEnrollment: document.getElementById('last-enrollment'),
      nextEnrollment: document.getElementById('next-enrollment'),
      enrollmentButton: document.getElementById('force-enrollment'),
      deviceSyncTitle: document.getElementById('device-sync-title'),
      lastDeviceSync: document.getElementById('last-device-sync'),
      nextDeviceSync: document.getElementById('next-device-sync'),
      deviceSyncButton: document.getElementById('force-device-sync'),
      newUserNotifButton: document.getElementById('show-new-user-notif'),
      existingUserNewHostNotifButton:
          document.getElementById('show-existing-user-new-host-notif'),
      existingUserNewChromebookNotifButton:
          document.getElementById('show-existing-user-new-chromebook-notif'),
    };

    this.elements_.enrollmentButton.onclick = this.forceEnrollment_.bind(this);
    this.elements_.deviceSyncButton.onclick = this.forceDeviceSync_.bind(this);
    this.elements_.newUserNotifButton.onclick =
        this.showNewUserNotification_.bind(this);
    this.elements_.existingUserNewHostNotifButton.onclick =
        this.showExistingUserNewHostNotification_.bind(this);
    this.elements_.existingUserNewChromebookNotifButton.onclick =
        this.showExistingUserNewChromebookNotification_.bind(this);

    this.multiDeviceSetup =
        new chromeos.multideviceSetup.mojom.MultiDeviceSetupPtr();
    Mojo.bindInterface(
        chromeos.multideviceSetup.mojom.MultiDeviceSetup.name,
        mojo.makeRequest(this.multiDeviceSetup).handle);
  }

  /**
   * Sets the local device's ID. Note that this value is truncated since the
   * full value is very long and does not cleanly fit on the screen.
   */
  setLocalDeviceId(deviceIdTruncated) {
    this.elements_.localDeviceId.textContent = deviceIdTruncated;
  }

  /**
   * Update the enrollment state in the UI.
   */
  updateEnrollmentState(state) {
    this.elements_.lastEnrollment.textContent =
        this.getLastSyncTimeString_(state, 'Never enrolled');
    this.elements_.nextEnrollment.textContent =
        this.getNextRefreshString_(state);

    if (state['recoveringFromFailure']) {
      this.elements_.enrollmentTitle.setAttribute('state', 'failure');
    } else if (state['operationInProgress']) {
      this.elements_.enrollmentTitle.setAttribute('state', 'in-progress');
    } else {
      this.elements_.enrollmentTitle.setAttribute('state', 'synced');
    }
  }

  /**
   * Updates the device sync state in the UI.
   */
  updateDeviceSyncState(state) {
    this.elements_.lastDeviceSync.textContent =
        this.getLastSyncTimeString_(state, 'Never synced');
    this.elements_.nextDeviceSync.textContent =
        this.getNextRefreshString_(state);

    if (state['recoveringFromFailure']) {
      this.elements_.deviceSyncTitle.setAttribute('state', 'failure');
    } else if (state['operationInProgress']) {
      this.elements_.deviceSyncTitle.setAttribute('state', 'in-progress');
    } else {
      this.elements_.deviceSyncTitle.setAttribute('state', 'synced');
    }
  }

  /**
   * Returns the formatted string of the time of the last sync to be displayed.
   */
  getLastSyncTimeString_(syncState, neverSyncedString) {
    if (syncState.lastSuccessTime == 0)
      return neverSyncedString;
    var date = new Date(syncState.lastSuccessTime);
    return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
  }

  /**
   * Returns the formatted string of the next time to refresh to be displayed.
   */
  getNextRefreshString_(syncState) {
    var deltaMillis = syncState.nextRefreshTime;
    if (deltaMillis == null)
      return 'unknown';
    if (deltaMillis == 0)
      return 'sync in progress...';

    var seconds = deltaMillis / 1000;
    if (seconds < 60)
      return Math.round(seconds) + ' seconds to refresh';

    var minutes = seconds / 60;
    if (minutes < 60)
      return Math.round(minutes) + ' minutes to refresh';

    var hours = minutes / 60;
    if (hours < 24)
      return Math.round(hours) + ' hours to refresh';

    var days = hours / 24;
    return Math.round(days) + ' days to refresh';
  }

  /**
   * Forces a CryptAuth enrollment on button click.
   */
  forceEnrollment_() {
    WebUI.forceEnrollment();
  }

  /**
   * Forces a device sync on button click.
   */
  forceDeviceSync_() {
    WebUI.forceDeviceSync();
  }

  /**
   * Shows the "new user, potential host exists" notification.
   */
  showNewUserNotification_() {
    this.showMultiDeviceSetupPromoNotification_(
        chromeos.multideviceSetup.mojom.EventTypeForDebugging.
            kNewUserPotentialHostExists);
  }

  /**
   * Shows the "existing user, new host" notification.
   */
  showExistingUserNewHostNotification_() {
    this.showMultiDeviceSetupPromoNotification_(
        chromeos.multideviceSetup.mojom.EventTypeForDebugging.
            kExistingUserConnectedHostSwitched);
  }

  /**
   * Shows the "existing user, new Chromebook" notification.
   */
  showExistingUserNewChromebookNotification_() {
    this.showMultiDeviceSetupPromoNotification_(
        chromeos.multideviceSetup.mojom.EventTypeForDebugging.
            kExistingUserNewChromebookAdded);
  }

  /**
   * Shows a "MultiDevice Setup" notification of the given type.
   */
  showMultiDeviceSetupPromoNotification_(type) {
    this.multiDeviceSetup.triggerEventForDebugging(type).then(
        function(responseParams) {
          if (responseParams.success) {
            console.log('Successfully triggered notification for type ' +
                type + '.');
          } else {
            console.error('Failed to trigger notification for type ' + type +
                '; no NotificationPresenter has been registered.');
          }
        }).catch(function(error) {
          console.error('Failed to trigger notification type. ' + error);
        });
  }
};

/**
 * Controller for a list of remote devices. These lists are displayed in a
 * number of locations on the debug page.
 */
class DeviceListController {
  constructor(rootElement, showScanButton, showToggleUnlockKeyButton) {
    this.rootElement_ = rootElement;
    this.showScanButton_ = showScanButton;
    this.showToggleUnlockKeyButton_ = showToggleUnlockKeyButton;
    this.remoteDeviceTemplate_ =
        document.getElementById('remote-device-template');
  }

  /**
   * Updates the UI with the given remote devices.
   */
  updateRemoteDevices(remoteDevices) {
    var existingItems =
        this.rootElement_.querySelectorAll('.remote-device');
    for (var i = 0; i < existingItems.length; ++i) {
      existingItems[i].remove();
    }

    for (var i = 0; i < remoteDevices.length; ++i) {
      this.rootElement_.appendChild(
          this.createRemoteDeviceItem_(remoteDevices[i]));
    }

    this.rootElement_.setAttribute('device-count', remoteDevices.length);
  }

  /**
   * Creates a DOM element for a given remote device.
   */
  createRemoteDeviceItem_(remoteDevice) {
    var isUnlockKey = !!remoteDevice['unlockKey'];
    var hasMobileHotspot = !!remoteDevice['hasMobileHotspot'];
    var isArcPlusPlusEnrollment = !!remoteDevice['isArcPlusPlusEnrollment'];
    var isPixelPhone = !!remoteDevice['isPixelPhone'];

    var t = this.remoteDeviceTemplate_.content;
    t.querySelector('.device-connection-status').setAttribute(
        'state', remoteDevice['connectionStatus']);
    t.querySelector('.device-name').textContent =
        remoteDevice['friendlyDeviceName'];
    t.querySelector('.device-id').textContent =
        remoteDevice['publicKeyTruncated'];
    t.querySelector('.software-features').textContent =
        remoteDevice['featureStates'];
    t.querySelector('.is-unlock-key').textContent = isUnlockKey;
    t.querySelector('.supports-mobile-hotspot').textContent = hasMobileHotspot;
    t.querySelector('.is-arc-plus-plus-enrollment').textContent =
        isArcPlusPlusEnrollment;
    t.querySelector('.is-pixel-phone').textContent = isPixelPhone;
    if (!!remoteDevice['bluetoothAddress']) {
      t.querySelector('.bluetooth-address-row').classList.remove('hidden');
      t.querySelector('.bluetooth-address').textContent =
          remoteDevice['bluetoothAddress'];
    }

    var scanButton = t.querySelector('.device-scan');
    scanButton.classList.toggle(
        'hidden', !this.showScanButton_ || !isUnlockKey);
    scanButton.textContent =
        remoteDevice['connectionStatus'] == 'disconnected'
            ? 'EasyUnlock Scan' : 'EasyUnlock Disconnect';
    t.querySelector('.device-toggle-key').classList.toggle(
        'hidden', !this.showToggleUnlockKeyButton_ || !isUnlockKey);

    var element = document.importNode(this.remoteDeviceTemplate_.content, true);

    // Initialize buttons on new element.
    element.querySelector('.device-scan').onclick =
        this.scanForDevice_.bind(this, remoteDevice['publicKey']);
    element.querySelector('.device-toggle-key').onclick =
        this.toggleUnlockKey_.bind(
            this, remoteDevice['publicKey'], !remoteDevice['unlockKey']);

    return element;
  }

  /**
   * Button handler to start scanning and connecting to a device.
   */
  scanForDevice_(publicKey) {
    WebUI.toggleConnection(publicKey);
  }

  /**
   * Button handler to toggle a device as an unlock key.
   */
  toggleUnlockKey_(publicKey, makeUnlockKey) {
    console.log(publicKey);
    WebUI.toggleUnlockKey(publicKey, makeUnlockKey);
  }
}

/**
 * Controller for the 'Eligible Unlock Keys' controls.
 */
class EligibleDevicesController {
  constructor() {
    this.eligibleDeviceList_ = new DeviceListController(
        document.getElementById('eligible-devices-list'), false, true);
    this.ineligibleDeviceList_ = new DeviceListController(
        document.getElementById('ineligible-devices-list'), false, false);
    this.button_ = document.getElementById('find-eligible-devices');
    this.button_.onclick = this.findEligibleUnlockDevices_.bind(this);
  }

  /**
   * Updates the UI with the fetched eligible and ineligible devices.
   */
  updateEligibleDevices(eligibleDevices, ineligibleDevices) {
    this.eligibleDeviceList_.updateRemoteDevices(eligibleDevices);
    this.ineligibleDeviceList_.updateRemoteDevices(ineligibleDevices);
  }

  /**
   * Button handler to fetch eligible unlock devices.
   */
  findEligibleUnlockDevices_() {
    WebUI.findEligibleUnlockDevices();
  }
}

/**
 * Interface for the native WebUI to call into our JS.
 */
LocalStateInterface = {
  onGotLocalState: function(
      localDeviceId, enrollmentState, deviceSyncState, remoteDevices) {
    LocalStateInterface.setLocalDeviceId(localDeviceId);
    LocalStateInterface.onEnrollmentStateChanged(enrollmentState);
    LocalStateInterface.onDeviceSyncStateChanged(deviceSyncState);
    LocalStateInterface.onRemoteDevicesChanged(remoteDevices);
  },

  setLocalDeviceId: function(localDeviceId) {
    ProximityAuth.cryptauthController_.setLocalDeviceId(localDeviceId);
  },

  onEnrollmentStateChanged: function(enrollmentState) {
    ProximityAuth.cryptauthController_.updateEnrollmentState(enrollmentState);
  },

  onDeviceSyncStateChanged: function(deviceSyncState) {
    ProximityAuth.cryptauthController_.updateDeviceSyncState(deviceSyncState);
  },

  onRemoteDevicesChanged: function(remoteDevices) {
    ProximityAuth.remoteDevicesController_.updateRemoteDevices(remoteDevices);
  }
};

/**
 * Interface for the native WebUI to call into our JS.
 */
CryptAuthInterface = {
  onGotEligibleDevices: function(eligibleDevices, ineligibleDevices) {
    ProximityAuth.eligibleDevicesController_.updateEligibleDevices(
        eligibleDevices, ineligibleDevices);
  }
};

document.addEventListener('DOMContentLoaded', function() {
  WebUI.onWebContentsInitialized();
  Logs.init();
  ProximityAuth.init();
});
