text: /**
   * Blockly App: Jigsaw
   *
   * Copyright 2013 Code.org
   *
   */
  var levels = require('./levels');
  
  var patternCache = {
    queued: [],
    created: {},
  
    /**
     * Stick an item in our queue
     */
    addToQueue: function (patternInfo) {
      this.queued.push(patternInfo);
    },
  
    /**
     * Add all the svg patterns we've queued up.
     */
    addQueuedPatterns: function () {
      this.queued.forEach(function (pattern) {
        addPattern(pattern.id, pattern.imagePath, pattern.width, pattern.height,
          pattern.offsetX, pattern.offsetY);
      });
      this.queued = [];
    },
  
    /**
     * Have we already created an svg element for this patternInfo?  Throws if
     * we ask with a patternInfo that has the same id but different attributes.
     */
    wasCreated: function (patternInfo) {
      var equal = true;
      var cached = this.created[patternInfo.id];
      if (!cached) {
        return false;
      }
  
      Object.keys(patternInfo).forEach(function (key) {
        if (patternInfo[key] !== cached[key]) {
          equal = false;
        }
      });
      if (!equal) {
        throw new Error("Can't add attribute of same id with different attributes");
      }
      return true;
    },
  
    /**
     * Mark that we've created an svg pattern
     */
    markCreated: function (patternInfo) {
      if (this.created[patternInfo.id]) {
        throw new Error('Already have cached item with id: ' + patternInfo.id);
      }
      this.created[patternInfo.id] = patternInfo;
    }
  
  };
  
  /**
   * Add an svg pattern for the given image. If document is not yet fully loaded,
   * it will add the pattern to a list for later.
   *
   * @param {string} id Pattern name
   * @param {string} imagePath Url of the image
   * @param {number} width Width of the image
   * @param {number} height Height of the image
   * @param {number|function} offsetX Offset of the image to start pattern
   * @param {number|function} offsetY Offset of the image to start pattern
   */
  var addPattern = function (id, imagePath, width, height, offsetX, offsetY) {
    var x, y, pattern, patternImage;
    var patternInfo = {
      id: id,
      imagePath: imagePath,
      width: width,
      height: height,
      offsetX: offsetX,
      offsetY: offsetY
    };
  
    // If we don't yet have an svgDefs, queue the pattern and wait until we do
    var svgDefs = document.getElementById('blocklySvgDefs');
    if (!svgDefs) {
      patternCache.addToQueue(patternInfo);
    } else if (!patternCache.wasCreated(patternInfo)) {
      // add the pattern
      x = typeof(offsetX) === "function" ? -offsetX() : -offsetX;
      y = typeof(offsetY) === "function" ? -offsetY() : -offsetY;
      pattern = Blockly.createSvgElement('pattern', {
        id: id,
        patternUnits: 'userSpaceOnUse',
        width: "100%",
        height: height,
        x: x,
        y: y
      }, svgDefs);
      patternImage = Blockly.createSvgElement('image', {
        width: width,
        height: height
      }, pattern);
      patternImage.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
        imagePath);
  
      patternCache.markCreated(patternInfo);
    }
    return id;
  };
  
  function addQueuedWhenReady() {
    if (!document.getElementById('blocklySvgDefs')) {
      setTimeout(addQueuedWhenReady, 100);
      return;
    }
    patternCache.addQueuedPatterns();
  }
  
  
  // Install extensions to Blockly's language and JavaScript generator.
  exports.install = function (blockly, blockInstallOptions) {
    var skin = blockInstallOptions.skin;
    // could make this settable on the level if I need
    var HSV = [0, 1.00, 0.98];
  
    var existingBlocks = Object.keys(blockly.Blocks);
  
    Object.keys(levels).forEach(function (key) {
      var level = levels[key];
      generateJigsawBlocksForLevel(blockly, skin, {
        image: skin[level.image.name],
        HSV: level.backgroundHSV || HSV,
        width: level.image.width,
        height: level.image.height,
        numBlocks: level.numBlocks,
        notchedEnds: level.notchedEnds,
        level: key
      });
  
      if (level.numBlocks === 0) {
        // still want the pattern for the ghost
        var patternName = 'pat_' + level.id + 'A';
        addPattern(patternName, skin[level.image.name], level.image.width,
          level.image.height, 0, 0);
      }
    });
  
    generateBlankBlock(blockly, skin, 'jigsaw_green', [140, 1.00, 0.74], 36, "3");
    generateBlankBlock(blockly, skin, 'jigsaw_blue', [184, 1.00, 0.74], 36, "2");
    generateBlankBlock(blockly, skin, 'jigsaw_purple', [312, 0.32, 0.62], 36, "1");
  
    // Go through all added blocks, and add empty generators for those that
    // weren't already given generators
    var generator = blockly.Generator.get('JavaScript');
    blockly.JavaScript = generator;
    Object.keys(blockly.Blocks).forEach(function (block) {
      if (existingBlocks.indexOf(block) === -1 && !generator[block]) {
        generator[block] = function () {
          return '\n';
        };
      }
    });
  
    addQueuedWhenReady();
  
    delete blockly.Blocks.procedures_defreturn;
    delete blockly.Blocks.procedures_ifreturn;
  };
  
  function generateBlankBlock(blockly, skin, name, hsv, width, label) {
    blockly.Blocks[name] = {
      helpUrl: '',
      init: function () {
        this.setHSV.apply(this, hsv);
        this.appendDummyInput()
          .appendTitle(new blockly.FieldImage(skin.blank, width, 54))
          .appendTitle(new blockly.FieldLabel(label,
              {fixedSize: {width: width, height: 64}, fontSize: 32}));
        this.setPreviousStatement(true);
        this.setNextStatement(true);
      }
    };
  }
  
  function generateJigsawBlocksForLevel(blockly, skin, options) {
    var image = options.image;
    var width = options.width;
    var height = options.height;
    var numBlocks = options.numBlocks;
    var level = options.level;
    var HSV = options.HSV;
    // if true, first/last block will still have previous/next notches
    var notchedEnds = options.notchedEnds;
  
    var blockHeight = height / numBlocks;
    var titleWidth = width - 20;
    var titleHeight = blockHeight - 10;
  
    var letters = '-ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  
    function generateBlock(blockNum) {
      var blockName = 'jigsaw_' + level + letters[blockNum];
      var patternName = 'pat_' + level + letters[blockNum];
      blockly.Blocks[blockName] = {
        helpUrl: '',
        init: function () {
          this.setHSV.apply(this, HSV);
          this.appendDummyInput()
            .appendTitle(new blockly.FieldImage(skin.blank, titleWidth, titleHeight));
          this.setPreviousStatement(blockNum !== 1 || notchedEnds);
          this.setNextStatement(blockNum !== numBlocks || notchedEnds);
          this.setFillPattern(
            addPattern(patternName, image, width, height, 0,
              blockHeight * (blockNum - 1)));
        }
      };
    }
  
    for (var i = 1; i <= numBlocks; i++) {
      generateBlock(i);
    }
  }
  