/**
 * @license
 * Copyright 2016 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Javascript for the BlockExporter Tools class, which generates
 * block definitions and generator stubs for given block types.  Also generates
 * toolbox XML for the exporter's workspace.  Depends on the FactoryUtils for
 * its code generation functions.
 *
 * @author quachtina96 (Tina Quach)
 */
'use strict';

/**
 * Block Exporter Tools Class
 * @constructor
 */
function BlockExporterTools() {
  // Create container for hidden workspace.
  this.container = document.createElement('div');
  this.container.id = 'blockExporterTools_hiddenWorkspace';
  this.container.style.display = 'none';  // Hide the hidden workspace.
  document.body.appendChild(this.container);
  /**
   * Hidden workspace for the Block Exporter that holds pieces that make
   * up the block
   * @type {Blockly.Workspace}
   */
  this.hiddenWorkspace = Blockly.inject(this.container.id,
      {collapse: false,
       media: '../../media/'});
};

/**
 * Get Blockly Block object from XML that encodes the blocks used to design
 * the block.
 * @param {!Element} xml XML element that encodes the blocks used to design
 *    the block. For example, the block XMLs saved in block library.
 * @return {!Blockly.Block} Root block (factory_base block) which contains
 *    all information needed to generate block definition or null.
 * @private
 */
BlockExporterTools.prototype.getRootBlockFromXml_ = function(xml) {
  // Render XML in hidden workspace.
  this.hiddenWorkspace.clear();
  Blockly.Xml.domToWorkspace(xml, this.hiddenWorkspace);
  // Get root block.
  var rootBlock = this.hiddenWorkspace.getTopBlocks()[0] || null;
  return rootBlock;
};

/**
 * Return the given language code of each block type in an array.
 * @param {!Object} blockXmlMap Map of block type to XML.
 * @param {string} definitionFormat 'JSON' or 'JavaScript'
 * @return {string} The concatenation of each block's language code in the
 *    desired format.
 */
BlockExporterTools.prototype.getBlockDefinitions =
    function(blockXmlMap, definitionFormat) {
  var blockCode = [];
  for (var blockType in blockXmlMap) {
    var xml = blockXmlMap[blockType];
    if (xml) {
      // Render and get block from hidden workspace.
      var rootBlock = this.getRootBlockFromXml_(xml);
      if (rootBlock) {
        // Generate the block's definition.
        var code = FactoryUtils.getBlockDefinition(blockType, rootBlock,
            definitionFormat, this.hiddenWorkspace);
        // Add block's definition to the definitions to return.
      } else {
        // Append warning comment and write to console.
        var code = '// No block definition generated for ' + blockType +
          '. Could not find root block in XML stored for this block.';
        console.log('No block definition generated for ' + blockType +
          '. Could not find root block in XML stored for this block.');
      }
    } else {
      // Append warning comment and write to console.
      var code = '// No block definition generated for ' + blockType +
        '. Block was not found in Block Library Storage.';
      console.log('No block definition generated for ' + blockType +
        '. Block was not found in Block Library Storage.');
    }
    blockCode.push(code);
  }

  // Surround json with [] and comma separate items.
  if (definitionFormat == "JSON") {
    return "[" + blockCode.join(",\n") + "]";
  }
  return blockCode.join("\n\n");
};

/**
 * Return the generator code of each block type in an array in a given language.
 * @param {!Object} blockXmlMap Map of block type to XML.
 * @param {string} generatorLanguage E.g. 'JavaScript', 'Python', 'PHP', 'Lua',
 *     'Dart'
 * @return {string} The concatenation of each block's generator code in the
 * desired format.
 */
BlockExporterTools.prototype.getGeneratorCode =
    function(blockXmlMap, generatorLanguage) {
  var multiblockCode = [];
  // Define the custom blocks in order to be able to create instances of
  // them in the exporter workspace.
  this.addBlockDefinitions(blockXmlMap);

  for (var blockType in blockXmlMap) {
    var xml = blockXmlMap[blockType];
    if (xml) {
      // Render the preview block in the hidden workspace.
      var tempBlock =
          FactoryUtils.getDefinedBlock(blockType, this.hiddenWorkspace);
      // Get generator stub for the given block and add to  generator code.
      var blockGenCode =
          FactoryUtils.getGeneratorStub(tempBlock, generatorLanguage);
    } else {
      // Append warning comment and write to console.
      var blockGenCode = '// No generator stub generated for ' + blockType +
        '. Block was not found in Block Library Storage.';
      console.log('No block generator stub generated for ' + blockType +
        '. Block was not found in Block Library Storage.');
    }
    multiblockCode.push(blockGenCode);
  }
  return multiblockCode.join("\n\n");
};

/**
 * Evaluates block definition code of each block in given object mapping
 * block type to XML. Called in order to be able to create instances of the
 * blocks in the exporter workspace.
 * @param {!Object} blockXmlMap Map of block type to XML.
 */
BlockExporterTools.prototype.addBlockDefinitions = function(blockXmlMap) {
  var blockDefs = this.getBlockDefinitions(blockXmlMap, 'JavaScript');
  eval(blockDefs);
};

/**
 * Generate XML for the workspace factory's category from imported block
 * definitions.
 * @param {!BlockLibraryStorage} blockLibStorage Block Library Storage object.
 * @return {!Element} XML representation of a category.
 */
BlockExporterTools.prototype.generateCategoryFromBlockLib =
    function(blockLibStorage) {
  var allBlockTypes = blockLibStorage.getBlockTypes();
  // Object mapping block type to XML.
  var blockXmlMap = blockLibStorage.getBlockXmlMap(allBlockTypes);

  // Define the custom blocks in order to be able to create instances of
  // them in the exporter workspace.
  this.addBlockDefinitions(blockXmlMap);

  // Get array of defined blocks.
  var blocks = [];
  for (var blockType in blockXmlMap) {
    var block = FactoryUtils.getDefinedBlock(blockType, this.hiddenWorkspace);
    blocks.push(block);
  }

  return FactoryUtils.generateCategoryXml(blocks,'Block Library');
};

/**
 * Generate selector dom from block library storage. For each block in the
 * library, it has a block option, which consists of a checkbox, a label,
 * and a fixed size preview workspace.
 * @param {!BlockLibraryStorage} blockLibStorage Block Library Storage object.
 * @param {string} blockSelectorId ID of the div element that will contain
 *    the block options.
 * @return {!Object} Map of block type to Block Option object.
 */
BlockExporterTools.prototype.createBlockSelectorFromLib =
    function(blockLibStorage, blockSelectorId) {
  // Object mapping each stored block type to XML.
  var allBlockTypes = blockLibStorage.getBlockTypes();
  var blockXmlMap = blockLibStorage.getBlockXmlMap(allBlockTypes);

  // Define the custom blocks in order to be able to create instances of
  // them in the exporter workspace.
  this.addBlockDefinitions(blockXmlMap);

  var blockSelector = document.getElementById(blockSelectorId);
  // Clear the block selector.
  var child;
  while ((child = blockSelector.firstChild)) {
    blockSelector.removeChild(child);
  }

  // Append each block option's dom to the selector.
  var blockOptions = Object.create(null);
  for (var blockType in blockXmlMap) {
    // Get preview block's XML.
    var block = FactoryUtils.getDefinedBlock(blockType, this.hiddenWorkspace);
    var previewBlockXml = Blockly.Xml.workspaceToDom(this.hiddenWorkspace);

    // Create block option, inject block into preview workspace, and append
    // option to block selector.
    var blockOpt = new BlockOption(blockSelector, blockType, previewBlockXml);
    blockOpt.createDom();
    blockSelector.appendChild(blockOpt.dom);
    blockOpt.showPreviewBlock();
    blockOptions[blockType] = blockOpt;
  }
  return blockOptions;
};
