// Copyright 2018 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('extensions', function() {
  'use strict';

  // A RegExp to roughly match acceptable patterns entered by the user.
  // exec'ing() this RegExp will match the following groups:
  // 0: Full matched string.
  // 1: Scheme + scheme separator (e.g., 'https://').
  // 2: Scheme only (e.g., 'https').
  // 3: Match subdomains ('*.').
  // 4: Hostname (e.g., 'example.com').
  // 5: Port, including ':' separator (e.g., ':80').
  // 6: Path, include '/' separator (e.g., '/*').
  const patternRegExp = new RegExp(
      '^' +
      // Scheme; optional.
      '((http|https|\\*)://)?' +
      // Include subdomains specifier; optional.
      '(\\*\\.)?' +
      // Hostname, required.
      '([a-z0-9\\.-]+\\.[a-z0-9]+)' +
      // Port, optional.
      '(:[0-9]+)?' +
      // Path, optional but if present must be '/' or '/*'.
      '(\\/\\*|\\/)?' +
      '$');

  function getPatternFromSite(site) {
    const res = patternRegExp.exec(site);
    assert(res);
    const scheme = res[1] || '*://';
    const host = (res[3] || '') + res[4];
    const port = res[5] || '';
    const path = '/*';
    return scheme + host + port + path;
  }

  const RuntimeHostsDialog = Polymer({
    is: 'extensions-runtime-hosts-dialog',

    properties: {
      /** @type {!extensions.ItemDelegate} */
      delegate: Object,

      /** @type {string} */
      itemId: String,

      /**
       * The site that this entry is currently managing. Only non-empty if this
       * is for editing an existing entry.
       * @type {?string}
       */
      currentSite: {
        type: String,
        value: null,
      },

      /**
       * Whether the dialog should update the host access to be "on specific
       * sites" before adding a new host permission.
       */
      updateHostAccess: {
        type: Boolean,
        value: false,
      },

      /**
       * The site to add an exception for.
       * @private
       */
      site_: String,

      /**
       * Whether the currently-entered input is valid.
       * @private
       */
      inputInvalid_: {
        type: Boolean,
        value: false,
      },
    },

    /** @override */
    attached: function() {
      if (this.currentSite !== null && this.currentSite !== undefined) {
        this.site_ = this.currentSite;
        this.validate_();
      }
      this.$.dialog.showModal();
    },

    /** @return {boolean} */
    isOpen: function() {
      return this.$.dialog.open;
    },

    /**
     * Validates that the pattern entered is valid.
     * @private
     */
    validate_: function() {
      // If input is empty, disable the action button, but don't show the red
      // invalid message.
      if (this.site_.trim().length == 0) {
        this.inputInvalid_ = false;
        return;
      }

      const valid = patternRegExp.test(this.site_);
      this.inputInvalid_ = !valid;
    },

    /**
     * @return {string}
     * @private
     */
    computeDialogTitle_: function() {
      const stringId = this.currentSite === null ? 'runtimeHostsDialogTitle' :
                                                   'hostPermissionsEdit';
      return loadTimeData.getString(stringId);
    },

    /**
     * @return {boolean}
     * @private
     */
    computeSubmitButtonDisabled_: function() {
      return this.inputInvalid_ || this.site_ === undefined ||
          this.site_.trim().length == 0;
    },

    /**
     * @return {string}
     * @private
     */
    computeSubmitButtonLabel_: function() {
      const stringId = this.currentSite === null ? 'add' : 'save';
      return loadTimeData.getString(stringId);
    },

    /** @private */
    onCancelTap_: function() {
      this.$.dialog.cancel();
    },

    /**
     * The tap handler for the submit button (adds the pattern and closes
     * the dialog).
     * @private
     */
    onSubmitTap_: function() {
      if (this.currentSite !== null) {
        this.handleEdit_();
      } else {
        this.handleAdd_();
      }
    },

    /**
     * Handles adding a new site entry.
     * @private
     */
    handleAdd_: function() {
      assert(!this.currentSite);

      if (this.updateHostAccess) {
        this.delegate.setItemHostAccess(
            this.itemId, chrome.developerPrivate.HostAccess.ON_SPECIFIC_SITES);
      }

      this.addPermission_();
    },

    /**
     * Handles editing an existing site entry.
     * @private
     */
    handleEdit_: function() {
      assert(this.currentSite);
      assert(
          !this.updateHostAccess,
          'Editing host permissions should only be possible if the host ' +
              'access is already set to specific sites.');

      if (this.currentSite == this.site_) {
        // No change in values, so no need to update anything.
        this.$.dialog.close();
        return;
      }

      // Editing an existing entry is done by removing the current site entry,
      // and then adding the new one.
      this.delegate.removeRuntimeHostPermission(this.itemId, this.currentSite)
          .then(() => {
            this.addPermission_();
          });
    },

    /**
     * Adds the runtime host permission through the delegate. If successful,
     * closes the dialog; otherwise displays the invalid input message.
     * @private
     */
    addPermission_: function() {
      const pattern = getPatternFromSite(this.site_);
      this.delegate.addRuntimeHostPermission(this.itemId, pattern)
          .then(
              () => {
                this.$.dialog.close();
              },
              () => {
                this.inputInvalid_ = true;
              });
    },
  });

  return {
    RuntimeHostsDialog: RuntimeHostsDialog,
    getPatternFromSite: getPatternFromSite
  };
});
