/*! @license
 * Shaka Player
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */


goog.provide('shakaDemo.Custom');


/** @type {?shakaDemo.Custom} */
let shakaDemoCustom;


/**
 * Shaka Player demo, custom asset page layout.
 */
shakaDemo.Custom = class {
  /**
   * Register the page configuration.
   */
  static init() {
    const elements = shakaDemoMain.addNavButton('custom');
    shakaDemoCustom = new shakaDemo.Custom(elements.container);
  }

  /** @param {!Element} container */
  constructor(container) {
    /** @private {!HTMLDialogElement} */
    this.dialog_ =
      /** @type {!HTMLDialogElement} */(document.createElement('dialog'));

    this.dialog_.classList.add('mdl-dialog');
    container.appendChild(this.dialog_);
    if (!this.dialog_.showModal) {
      dialogPolyfill.registerDialog(this.dialog_);
    }

    /** @private {!Set.<!ShakaDemoAssetInfo>} */
    this.assets_ = this.loadAssetInfos_();

    /** @private {!Array.<!shakaDemo.AssetCard>} */
    this.assetCards_ = [];
    this.savedList_ = document.createElement('div');
    container.appendChild(this.savedList_);

    // Add the "new" button, which shows the dialog.
    const addButtonContainer = document.createElement('div');
    addButtonContainer.classList.add('add-button-container');
    container.appendChild(addButtonContainer);
    // Style it as an MDL Floating Action Button (FAB).
    const addButton = this.makeButton_('add', /* isFAB= */ true, () => {
      this.showAssetDialog_(ShakaDemoAssetInfo.makeBlankAsset());
    });
    addButtonContainer.appendChild(addButton);

    document.addEventListener('shaka-main-selected-asset-changed', () => {
      this.updateSelected_();
    });
    document.addEventListener('shaka-main-offline-progress', () => {
      this.updateOfflineProgress_();
    });
    document.addEventListener('shaka-main-locale-changed', () => {
      this.remakeSavedList_();
    });
    document.addEventListener('shaka-main-page-changed', () => {
      if (!this.savedList_.childNodes.length &&
          !container.classList.contains('hidden')) {
        // Now that the page is showing, create the contents that we deferred
        // until now.
        this.remakeSavedList_();
      }
    });
  }

  /** @return {!Array.<!ShakaDemoAssetInfo>} */
  assets() {
    return Array.from(this.assets_);
  }

  /**
   * Updates progress bars on asset cards.
   * @private
   */
  updateOfflineProgress_() {
    for (const card of this.assetCards_) {
      card.updateProgress();
    }
  }

  /**
   * @param {!ShakaDemoAssetInfo} assetInProgress
   * @private
   */
  showAssetDialog_(assetInProgress) {
    // Remove buttons for any previous assets.
    shaka.util.Dom.removeAllChildren(this.dialog_);

    const inputDiv = document.createElement('div');
    this.dialog_.appendChild(inputDiv);

    const iconDiv = document.createElement('div');
    this.dialog_.appendChild(iconDiv);

    // An array of inputs which have validity checks which we care about.
    const inputsToCheck = [];

    // The license server and drm system fields need to know each others
    // contents, and react to each others changes, to work.
    // To simplify things, this method picks out the process of setting license
    // server URLs; it can be called within both fields.
    let licenseServerUrlInput;
    let customDrmSystemInput;
    const setLicenseServerURLs = () => {
      const licenseServerURL = licenseServerUrlInput.value;
      const customDRMSystem = customDrmSystemInput.value;
      if (licenseServerURL) {
        // Make a license server entry for every common DRM plugin.
        assetInProgress.licenseServers.clear();
        for (const drmSystem of shakaDemo.Main.commonDrmSystems) {
          assetInProgress.licenseServers.set(drmSystem, licenseServerURL);
        }
        if (customDRMSystem) {
          // Make a custom entry too.
          assetInProgress.licenseServers.set(customDRMSystem, licenseServerURL);
        }
      } else {
        assetInProgress.licenseServers.clear();
      }
    };

    const containerStyle = shakaDemo.InputContainer.Style.VERTICAL;
    const container = new shakaDemo.InputContainer(
        inputDiv, /* headerText= */ null, containerStyle,
        /* docLink= */ null);

    /**
     * A utility to simplify the creation of fields on the dialog.
     * @param {string} name
     * @param {function(!Element, !Element)} setup
     * @param {function(!Element)} onChange
     */
    const makeField = (name, setup, onChange) => {
      container.addRow(null, null);
      const input = new shakaDemo.TextInput(container, name, onChange);
      input.extra().textContent = name;
      setup(input.input(), input.container());
    };

    // Make the manifest URL field.
    const manifestSetup = (input, container) => {
      input.value = assetInProgress.manifestUri;
      inputsToCheck.push(input);

      // Make an error that shows up if you did not provide an URL.
      const error = document.createElement('span');
      error.classList.add('mdl-textfield__error');
      error.textContent = shakaDemoMain.getLocalizedString(
          shakaDemo.MessageIds.MANIFEST_URL_ERROR);
      container.appendChild(error);

      // Add a regex that will detect empty strings.
      input.required = true;
      input.pattern = '^(?!([\r\n\t\f\v ]+)$).*$';
    };
    const manifestOnChange = (input) => {
      assetInProgress.manifestUri = input.value;
    };
    const manifestURLName = shakaDemoMain.getLocalizedString(
        shakaDemo.MessageIds.MANIFEST_URL);
    makeField(manifestURLName, manifestSetup, manifestOnChange);

    // Make the license server URL field.
    const licenseSetup = (input, container) => {
      licenseServerUrlInput = input;
      const drmSystems = assetInProgress.licenseServers.keys();
      // Custom assets have only a single license server URL, no matter how
      // many key systems they have. Thus, it's safe to say that the license
      // server URL associated with the first key system is the asset's
      // over-all license server URL.
      const drmSystem = drmSystems.next();
      if (drmSystem && drmSystem.value) {
        input.value = assetInProgress.licenseServers.get(drmSystem.value);
      }
    };
    const licenseOnChange = (input) => {
      setLicenseServerURLs();
    };
    const licenseServerURLName = shakaDemoMain.getLocalizedString(
        shakaDemo.MessageIds.LICENSE_SERVER_URL);
    makeField(licenseServerURLName, licenseSetup, licenseOnChange);

    // Make the license certificate URL field.
    const certSetup = (input, container) => {
      if (assetInProgress.certificateUri) {
        input.value = assetInProgress.certificateUri;
      }
    };
    const certOnChange = (input) => {
      assetInProgress.certificateUri = input.value;
    };
    const licenseCertificateURLName = shakaDemoMain.getLocalizedString(
        shakaDemo.MessageIds.LICENSE_CERTIFICATE_URL);
    makeField(licenseCertificateURLName, certSetup, certOnChange);

    // Make the drm system field.
    const drmSetup = (input, container) => {
      customDrmSystemInput = input;
      const drmSystems = assetInProgress.licenseServers.keys();
      for (const drmSystem of drmSystems) {
        if (!shakaDemo.Main.commonDrmSystems.includes(drmSystem)) {
          input.value = drmSystem;
          break;
        }
      }
    };
    const drmOnChange = (input) => {
      setLicenseServerURLs();
    };
    const DRMSystemName = shakaDemoMain.getLocalizedString(
        shakaDemo.MessageIds.DRM_SYSTEM);
    makeField(DRMSystemName, drmSetup, drmOnChange);

    // Make the ad tag URL field.
    const adTagSetup = (input, container) => {
      if (assetInProgress.adTagUri) {
        input.value = assetInProgress.adTagUri;
      }
    };
    const adTagOnChange = (input) => {
      assetInProgress.adTagUri = input.value;
    };
    const adTagURLName = shakaDemoMain.getLocalizedString(
        shakaDemo.MessageIds.AD_TAG_URL);
    makeField(adTagURLName, adTagSetup, adTagOnChange);

    // Make the name field.
    const nameSetup = (input, container) => {
      input.value = assetInProgress.name;
      inputsToCheck.push(input);

      // Make an error that shows up if you have an empty/duplicate name.
      const error = document.createElement('span');
      error.classList.add('mdl-textfield__error');
      error.textContent = shakaDemoMain.getLocalizedString(
          shakaDemo.MessageIds.NAME_ERROR);
      container.appendChild(error);

      // Make a regex that will detect duplicates.
      input.required = true;
      input.pattern = '^(?!( *';
      for (const asset of this.assets_) {
        if (asset == assetInProgress) {
          // If editing an existing asset, it's okay if the name doesn't change.
          continue;
        }
        const escape = (input) => {
          return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        };
        input.pattern += '|' + escape(asset.name);
      }
      input.pattern += ')$).*$';
    };
    const nameOnChange = (input) => {
      assetInProgress.name = input.value;
    };
    const nameName = shakaDemoMain.getLocalizedString(
        shakaDemo.MessageIds.NAME);
    makeField(nameName, nameSetup, nameOnChange);

    // Make the icon field.
    const iconSetup = (input, container) => {
      if (assetInProgress.iconUri) {
        input.value = assetInProgress.iconUri;

        const img =
          /** @type {!HTMLImageElement} */(document.createElement('img'));
        img.src = input.value;
        img.alt = '';  // Not necessary to understand the page
        iconDiv.appendChild(img);
      }
    };

    const iconOnChange = (input) => {
      shaka.util.Dom.removeAllChildren(iconDiv);
      assetInProgress.iconUri = input.value;

      if (input.value) {
        const img =
          /** @type {!HTMLImageElement} */(document.createElement('img'));
        img.src = input.value;
        img.alt = '';  // Not necessary to understand the page
        iconDiv.appendChild(img);
      }
    };

    const iconURLName = shakaDemoMain.getLocalizedString(
        shakaDemo.MessageIds.ICON_URL);
    makeField(iconURLName, iconSetup, iconOnChange);

    // Create the buttons at the bottom of the dialog.
    const buttonsDiv = document.createElement('tr');
    inputDiv.appendChild(buttonsDiv);
    buttonsDiv.appendChild(this.makeButton_('Save', /* isFAB= */ false, () => {
      for (const input of inputsToCheck) {
        if (!input.validity.valid) {
          return;
        }
      }
      shakaDemoMain.setupOfflineSupport(assetInProgress);
      this.assets_.add(assetInProgress);
      this.saveAssetInfos_(this.assets_);
      this.remakeSavedList_();
      this.dialog_.close();
    }));
    buttonsDiv.appendChild(this.makeButton_(
        'Cancel', /* isFAB= */ false, () => {
          this.dialog_.close();
        }));

    // Update the componentHandler, to account for the new MDL elements.
    componentHandler.upgradeDom();

    // Show the dialog last, so that it knows where to place it.
    this.dialog_.showModal();
  }

  /**
   * @return {!Set.<!ShakaDemoAssetInfo>}
   * @private
   */
  loadAssetInfos_() {
    const savedString = window.localStorage.getItem(shakaDemo.Custom.saveId_);
    if (savedString) {
      const assets =
        /** @type {!Array.<!ShakaDemoAssetInfo>} */(JSON.parse(savedString));
      return new Set(assets.map((json) => {
        const asset = ShakaDemoAssetInfo.fromJSON(json);
        shakaDemoMain.setupOfflineSupport(asset);
        return asset;
      }));
    }
    return new Set();
  }

  /**
   * @param {!Set.<!ShakaDemoAssetInfo>} assetInfos
   * @private
   */
  saveAssetInfos_(assetInfos) {
    const saveId = shakaDemo.Custom.saveId_;
    const assets = Array.from(assetInfos);
    window.localStorage.setItem(saveId, JSON.stringify(assets));
  }

  /**
   * @param {string} name
   * @param {boolean} isFAB Should this button be styled as a Material Design
   *   Floating Action Button (FAB)?
   * @param {function()} callback
   * @return {!Element}
   * @private
   */
  makeButton_(name, isFAB, callback) {
    const button = document.createElement('button');
    if (isFAB) {
      button.classList.add('mdl-button--fab');
      button.classList.add('mdl-button--colored');
      const icon = document.createElement('i');
      icon.classList.add('material-icons-round');
      icon.textContent = name;
      button.appendChild(icon);
    } else {
      button.textContent = name;
      button.classList.add('mdl-button--raised');
    }
    button.addEventListener('click', callback);
    button.classList.add('mdl-button');
    button.classList.add('mdl-js-button');
    button.classList.add('mdl-js-ripple-effect');
    return button;
  }

  /**
   * @param {!ShakaDemoAssetInfo} asset
   * @return {!shakaDemo.AssetCard}
   * @private
   */
  createAssetCardFor_(asset) {
    const savedList = this.savedList_;
    const isFeatured = false;
    return new shakaDemo.AssetCard(savedList, asset, isFeatured, (c) => {
      c.addButton(shakaDemo.MessageIds.PLAY, () => {
        shakaDemoMain.loadAsset(asset);
        this.updateSelected_();
      });
      c.addButton(shakaDemo.MessageIds.EDIT_CUSTOM, async () => {
        if (asset.unstoreCallback) {
          await asset.unstoreCallback();
        }
        this.showAssetDialog_(asset);
      });
      c.addButton(shakaDemo.MessageIds.DELETE_CUSTOM, async () => {
        this.assets_.delete(asset);
        if (asset.unstoreCallback) {
          await asset.unstoreCallback();
        }
        this.saveAssetInfos_(this.assets_);
        this.remakeSavedList_();
      }, shakaDemo.MessageIds.DELETE_CUSTOM);
      c.addStoreButton();
    });
  }

  /**
   * Updates which asset card is selected.
   * @private
   */
  updateSelected_() {
    for (const card of this.assetCards_) {
      card.selectByAsset(shakaDemoMain.selectedAsset);
    }
  }

  /** @private */
  remakeSavedList_() {
    shaka.util.Dom.removeAllChildren(this.savedList_);

    if (this.assets_.size == 0) {
      // Add in a message telling you what to do.
      const makeMessage = (textClass, text) => {
        const textElement = document.createElement('h2');
        textElement.classList.add('mdl-typography--' + textClass);
        textElement.textContent = text;
        this.savedList_.appendChild(textElement);
      };
      makeMessage('title',
          shakaDemoMain.getLocalizedString(
              shakaDemo.MessageIds.CUSTOM_INTRO_ONE));
      makeMessage('body-2',
          shakaDemoMain.getLocalizedString(
              shakaDemo.MessageIds.CUSTOM_INTRO_TWO));
      makeMessage('body-1',
          shakaDemoMain.getLocalizedString(
              shakaDemo.MessageIds.CUSTOM_INTRO_THREE));
    } else {
      // Make asset cards for the assets.
      this.assetCards_ = Array.from(this.assets_).map((asset) => {
        return this.createAssetCardFor_(asset);
      });
      this.updateSelected_();
    }
  }
};


/**
 * The name of the field in window.localStorage that is used to store a user's
 * custom assets.
 * @const {string}
 */
shakaDemo.Custom.saveId_ = 'shakaPlayerDemoSavedAssets';


document.addEventListener('shaka-main-loaded', shakaDemo.Custom.init);
document.addEventListener('shaka-main-cleanup', () => {
  shakaDemoCustom = null;
});
