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

/**
 * @fileoverview Javascript for the Block Exporter Controller class. Allows
 * users to export block definitions and generator stubs of their saved blocks
 * easily using a visual interface. Depends on Block Exporter View and Block
 * Exporter Tools classes. Interacts with Export Settings in the index.html.
 *
 * @author quachtina96 (Tina Quach)
 */

'use strict';

/**
 * BlockExporter Controller Class
 * @param {!BlockLibrary.Storage} blockLibStorage Block Library Storage.
 * @constructor
 */
function BlockExporterController(blockLibStorage) {
  // BlockLibrary.Storage object containing user's saved blocks.
  this.blockLibStorage = blockLibStorage;
  // Utils for generating code to export.
  this.tools = new BlockExporterTools();
  // The ID of the block selector, a div element that will be populated with the
  // block options.
  this.selectorID = 'blockSelector';
  // Map of block types stored in block library to their corresponding Block
  // Option objects.
  this.blockOptions = this.tools.createBlockSelectorFromLib(
      this.blockLibStorage, this.selectorID);
  // View provides the block selector and export settings UI.
  this.view = new BlockExporterView(this.blockOptions);
};

/**
 * Set the block library storage object from which exporter exports.
 * @param {!BlockLibraryStorage} blockLibStorage Block Library Storage object
 *    that stores the blocks.
 */
BlockExporterController.prototype.setBlockLibraryStorage =
    function(blockLibStorage) {
  this.blockLibStorage = blockLibStorage;
};

/**
 * Get the block library storage object from which exporter exports.
 * @return {!BlockLibraryStorage} blockLibStorage Block Library Storage object
 *    that stores the blocks.
 */
BlockExporterController.prototype.getBlockLibraryStorage =
    function(blockLibStorage) {
  return this.blockLibStorage;
};

/**
 * Get selected blocks from block selector, pulls info from the Export
 * Settings form in Block Exporter, and downloads code accordingly.
 */
BlockExporterController.prototype.export = function() {
  // Get selected blocks' information.
  var blockTypes = this.view.getSelectedBlockTypes();
  var blockXmlMap = this.blockLibStorage.getBlockXmlMap(blockTypes);

  // Pull block definition(s) settings from the Export Settings form.
  var wantBlockDef = document.getElementById('blockDefCheck').checked;
  var definitionFormat = document.getElementById('exportFormat').value;
  var blockDef_filename = document.getElementById('blockDef_filename').value;

  // Pull block generator stub(s) settings from the Export Settings form.
  var wantGenStub = document.getElementById('genStubCheck').checked;
  var language = document.getElementById('exportLanguage').value;
  var generatorStub_filename = document.getElementById(
      'generatorStub_filename').value;

  if (wantBlockDef) {
    // User wants to export selected blocks' definitions.
    if (!blockDef_filename) {
      // User needs to enter filename.
      var msg = 'Please enter a filename for your block definition(s) download.';
      BlocklyDevTools.Analytics.onWarning(msg);
      alert(msg);
    } else {
      // Get block definition code in the selected format for the blocks.
      var blockDefs = this.tools.getBlockDefinitions(blockXmlMap,
          definitionFormat);
      // Download the file, using .js file ending for JSON or Javascript.
      FactoryUtils.createAndDownloadFile(
          blockDefs, blockDef_filename, 'javascript');
      BlocklyDevTools.Analytics.onExport(
          BlocklyDevTools.Analytics.BLOCK_DEFINITIONS,
          {
            format: (definitionFormat == 'JSON' ?
                BlocklyDevTools.Analytics.FORMAT_JSON :
                BlocklyDevTools.Analytics.FORMAT_JS)
          });
    }
  }

  if (wantGenStub) {
    // User wants to export selected blocks' generator stubs.
    if (!generatorStub_filename) {
      // User needs to enter filename.
      var msg = 'Please enter a filename for your generator stub(s) download.';
      BlocklyDevTools.Analytics.onWarning(msg);
      alert(msg);
    } else {

      // Get generator stub code in the selected language for the blocks.
      var genStubs = this.tools.getGeneratorCode(blockXmlMap,
          language);

      // Download the file.
      FactoryUtils.createAndDownloadFile(
          genStubs, generatorStub_filename + '.js', 'javascript');
      BlocklyDevTools.Analytics.onExport(
          BlocklyDevTools.Analytics.GENERATOR, { format: BlocklyDevTools.Analytics.FORMAT_JS });
    }
  }

};

/**
 * Update the Exporter's block selector with block options generated from blocks
 * stored in block library.
 */
BlockExporterController.prototype.updateSelector = function() {
  // Get previously selected block types.
  var oldSelectedTypes = this.view.getSelectedBlockTypes();

  // Generate options from block library and assign to view.
  this.blockOptions = this.tools.createBlockSelectorFromLib(
      this.blockLibStorage, this.selectorID);
  this.addBlockOptionSelectHandlers();
  this.view.setBlockOptions(this.blockOptions);

  // Select all previously selected blocks.
  for (var i = 0, blockType; blockType = oldSelectedTypes[i]; i++) {
    if (this.blockOptions[blockType]) {
      this.view.select(blockType);
    }
  }

  this.view.listSelectedBlocks();
};

/**
 * Tied to the 'Clear Selected Blocks' button in the Block Exporter.
 * Deselects all blocks in the selector and updates text accordingly.
 */
BlockExporterController.prototype.clearSelectedBlocks = function() {
  this.view.deselectAllBlocks();
  this.view.listSelectedBlocks();
};

/**
 * Tied to the 'All Stored' button in the Block Exporter 'Select' dropdown.
 * Selects all blocks stored in block library for export.
 */
BlockExporterController.prototype.selectAllBlocks = function() {
  var allBlockTypes = this.blockLibStorage.getBlockTypes();
  for (var i = 0, blockType; blockType = allBlockTypes[i]; i++) {
    this.view.select(blockType);
  }
  this.view.listSelectedBlocks();
};

/**
 * Returns the category XML containing all blocks in the block library.
 * @return {Element} XML for a category to be used in toolbox.
 */
BlockExporterController.prototype.getBlockLibraryCategory = function() {
  return this.tools.generateCategoryFromBlockLib(this.blockLibStorage);
};

/**
 * Add select handlers to each block option to update the view and the selected
 * blocks accordingly.
 */
BlockExporterController.prototype.addBlockOptionSelectHandlers = function() {
  var self = this;

  // Click handler for a block option. Toggles whether or not it's selected and
  // updates helper text accordingly.
  var updateSelectedBlockTypes_ = function(blockOption) {
    // Toggle selected.
    blockOption.setSelected(!blockOption.isSelected());

    // Show currently selected blocks in helper text.
    self.view.listSelectedBlocks();
  };

  // Returns a block option select handler.
  var makeBlockOptionSelectHandler_ = function(blockOption) {
    return function() {
      updateSelectedBlockTypes_(blockOption);
      self.updatePreview();
    };
  };

  // Assign a click handler to each block option.
  for (var blockType in this.blockOptions) {
    var blockOption = this.blockOptions[blockType];
    // Use an additional closure to correctly assign the tab callback.
    blockOption.dom.addEventListener(
        'click', makeBlockOptionSelectHandler_(blockOption));
  }
};

/**
 * Tied to the 'All Used' button in the Block Exporter's 'Select' button.
 * Selects all blocks stored in block library and used in workspace factory.
 */
BlockExporterController.prototype.selectUsedBlocks = function() {
  // Deselect all blocks.
  this.view.deselectAllBlocks();

  // Get list of block types that are in block library and used in workspace
  // factory.
  var storedBlockTypes = this.blockLibStorage.getBlockTypes();
  var sharedBlockTypes = [];
  // Keep list of custom block types used but not in library.
  var unstoredCustomBlockTypes = [];

  for (var i = 0, blockType; blockType = this.usedBlockTypes[i]; i++) {
    if (storedBlockTypes.indexOf(blockType) != -1) {
      sharedBlockTypes.push(blockType);
    } else if (StandardCategories.coreBlockTypes.indexOf(blockType) == -1) {
      unstoredCustomBlockTypes.push(blockType);
    }
  }

  // Select each shared block type.
  for (var i = 0, blockType; blockType = sharedBlockTypes[i]; i++) {
    this.view.select(blockType);
  }
  this.view.listSelectedBlocks();

  if (unstoredCustomBlockTypes.length > 0) {
    // Warn user to import block definitions and generator code for blocks
    // not in their Block Library nor Blockly's standard library.
    var blockTypesText = unstoredCustomBlockTypes.join(', ');
    var customWarning = 'Custom blocks used in workspace factory but not ' +
        'stored in block library:\n ' + blockTypesText +
        '\n\nDon\'t forget to include block definitions and generator code ' +
        'for these blocks.';
    alert(customWarning);
  }
};

/**
 * Set the array that holds the block types used in workspace factory.
 * @param {!Array<string>} usedBlockTypes Block types used in
 */
BlockExporterController.prototype.setUsedBlockTypes =
    function(usedBlockTypes) {
  this.usedBlockTypes = usedBlockTypes;
};

/**
 * Updates preview code (block definitions and generator stubs) in the exporter
 * preview to reflect selected blocks.
 */
BlockExporterController.prototype.updatePreview = function() {
  // Generate preview code for selected blocks.
  var blockDefs = this.getBlockDefinitionsOfSelected();
  var genStubs = this.getGeneratorStubsOfSelected();

  // Update the text areas containing the code.
  FactoryUtils.injectCode(blockDefs, 'blockDefs_textArea');
  FactoryUtils.injectCode(genStubs, 'genStubs_textArea');
};

/**
 * Returns a map of each selected block's type to its corresponding XML.
 * @return {!Object} A map of each selected block's type (a string) to its
 * corresponding XML element.
 */
BlockExporterController.prototype.getSelectedBlockXmlMap = function() {
  var blockTypes = this.view.getSelectedBlockTypes();
  return this.blockLibStorage.getBlockXmlMap(blockTypes);
};

/**
 * Get block definition code in the selected format for selected blocks.
 * @return {string} The concatenation of each selected block's language code
 * in the format specified in export settings.
 */
BlockExporterController.prototype.getBlockDefinitionsOfSelected = function() {
  // Get selected blocks' information.
  var blockXmlMap = this.getSelectedBlockXmlMap();

  // Get block definition code in the selected format for the blocks.
  var definitionFormat = document.getElementById('exportFormat').value;
  return this.tools.getBlockDefinitions(blockXmlMap, definitionFormat);
};

/**
 * Get generator stubs in the selected language for selected blocks.
 * @return {string} The concatenation of each selected block's generator stub
 * in the language specified in export settings.
 */
BlockExporterController.prototype.getGeneratorStubsOfSelected = function() {
  // Get selected blocks' information.
  var blockXmlMap = this.getSelectedBlockXmlMap();

  // Get generator stub code in the selected language for the blocks.
  var language = document.getElementById('exportLanguage').value;
  return this.tools.getGeneratorCode(blockXmlMap, language);
};
