text: /* -*- Mode: js; js-indent-level: 2; -*- */
  /*
   * Copyright 2011 Mozilla Foundation and contributors
   * Licensed under the New BSD license. See LICENSE or:
   * http://opensource.org/licenses/BSD-3-Clause
   */
  if (typeof define !== 'function') {
      var define = require('amdefine')(module, require);
  }
  define(function (require, exports, module) {
  
      var base64VLQ = require('./base64-vlq');
      var util = require('./util');
      var ArraySet = require('./array-set').ArraySet;
  
      /**
       * An instance of the SourceMapGenerator represents a source map which is
       * being built incrementally. You may pass an object with the following
       * properties:
       *
       *   - file: The filename of the generated source.
       *   - sourceRoot: A root for all relative URLs in this source map.
       */
      function SourceMapGenerator(aArgs) {
          if (!aArgs) {
              aArgs = {};
          }
          this._file = util.getArg(aArgs, 'file', null);
          this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
          this._sources = new ArraySet();
          this._names = new ArraySet();
          this._mappings = [];
          this._sourcesContents = null;
      }
  
      SourceMapGenerator.prototype._version = 3;
  
      /**
       * Creates a new SourceMapGenerator based on a SourceMapConsumer
       *
       * @param aSourceMapConsumer The SourceMap.
       */
      SourceMapGenerator.fromSourceMap =
          function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
              var sourceRoot = aSourceMapConsumer.sourceRoot;
              var generator = new SourceMapGenerator({
                  file: aSourceMapConsumer.file,
                  sourceRoot: sourceRoot
              });
              aSourceMapConsumer.eachMapping(function (mapping) {
                  var newMapping = {
                      generated: {
                          line: mapping.generatedLine,
                          column: mapping.generatedColumn
                      }
                  };
  
                  if (mapping.source != null) {
                      newMapping.source = mapping.source;
                      if (sourceRoot != null) {
                          newMapping.source = util.relative(sourceRoot, newMapping.source);
                      }
  
                      newMapping.original = {
                          line: mapping.originalLine,
                          column: mapping.originalColumn
                      };
  
                      if (mapping.name != null) {
                          newMapping.name = mapping.name;
                      }
                  }
  
                  generator.addMapping(newMapping);
              });
              aSourceMapConsumer.sources.forEach(function (sourceFile) {
                  var content = aSourceMapConsumer.sourceContentFor(sourceFile);
                  if (content != null) {
                      generator.setSourceContent(sourceFile, content);
                  }
              });
              return generator;
          };
  
      /**
       * Add a single mapping from original source line and column to the generated
       * source's line and column for this source map being created. The mapping
       * object should have the following properties:
       *
       *   - generated: An object with the generated line and column positions.
       *   - original: An object with the original line and column positions.
       *   - source: The original source file (relative to the sourceRoot).
       *   - name: An optional original token name for this mapping.
       */
      SourceMapGenerator.prototype.addMapping =
          function SourceMapGenerator_addMapping(aArgs) {
              var generated = util.getArg(aArgs, 'generated');
              var original = util.getArg(aArgs, 'original', null);
              var source = util.getArg(aArgs, 'source', null);
              var name = util.getArg(aArgs, 'name', null);
  
              this._validateMapping(generated, original, source, name);
  
              if (source != null && !this._sources.has(source)) {
                  this._sources.add(source);
              }
  
              if (name != null && !this._names.has(name)) {
                  this._names.add(name);
              }
  
              this._mappings.push({
                  generatedLine: generated.line,
                  generatedColumn: generated.column,
                  originalLine: original != null && original.line,
                  originalColumn: original != null && original.column,
                  source: source,
                  name: name
              });
          };
  
      /**
       * Set the source content for a source file.
       */
      SourceMapGenerator.prototype.setSourceContent =
          function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
              var source = aSourceFile;
              if (this._sourceRoot != null) {
                  source = util.relative(this._sourceRoot, source);
              }
  
              if (aSourceContent != null) {
                  // Add the source content to the _sourcesContents map.
                  // Create a new _sourcesContents map if the property is null.
                  if (!this._sourcesContents) {
                      this._sourcesContents = {};
                  }
                  this._sourcesContents[util.toSetString(source)] = aSourceContent;
              } else if (this._sourcesContents) {
                  // Remove the source file from the _sourcesContents map.
                  // If the _sourcesContents map is empty, set the property to null.
                  delete this._sourcesContents[util.toSetString(source)];
                  if (Object.keys(this._sourcesContents).length === 0) {
                      this._sourcesContents = null;
                  }
              }
          };
  
      /**
       * Applies the mappings of a sub-source-map for a specific source file to the
       * source map being generated. Each mapping to the supplied source file is
       * rewritten using the supplied source map. Note: The resolution for the
       * resulting mappings is the minimium of this map and the supplied map.
       *
       * @param aSourceMapConsumer The source map to be applied.
       * @param aSourceFile Optional. The filename of the source file.
       *        If omitted, SourceMapConsumer's file property will be used.
       * @param aSourceMapPath Optional. The dirname of the path to the source map
       *        to be applied. If relative, it is relative to the SourceMapConsumer.
       *        This parameter is needed when the two source maps aren't in the same
       *        directory, and the source map to be applied contains relative source
       *        paths. If so, those relative source paths need to be rewritten
       *        relative to the SourceMapGenerator.
       */
      SourceMapGenerator.prototype.applySourceMap =
          function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
              var sourceFile = aSourceFile;
              // If aSourceFile is omitted, we will use the file property of the SourceMap
              if (aSourceFile == null) {
                  if (aSourceMapConsumer.file == null) {
                      throw new Error(
                          'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
                          'or the source map\'s "file" property. Both were omitted.'
                      );
                  }
                  sourceFile = aSourceMapConsumer.file;
              }
              var sourceRoot = this._sourceRoot;
              // Make "sourceFile" relative if an absolute Url is passed.
              if (sourceRoot != null) {
                  sourceFile = util.relative(sourceRoot, sourceFile);
              }
              // Applying the SourceMap can add and remove items from the sources and
              // the names array.
              var newSources = new ArraySet();
              var newNames = new ArraySet();
  
              // Find mappings for the "sourceFile"
              this._mappings.forEach(function (mapping) {
                  if (mapping.source === sourceFile && mapping.originalLine != null) {
                      // Check if it can be mapped by the source map, then update the mapping.
                      var original = aSourceMapConsumer.originalPositionFor({
                          line: mapping.originalLine,
                          column: mapping.originalColumn
                      });
                      if (original.source != null) {
                          // Copy mapping
                          mapping.source = original.source;
                          if (aSourceMapPath != null) {
                              mapping.source = util.join(aSourceMapPath, mapping.source)
                          }
                          if (sourceRoot != null) {
                              mapping.source = util.relative(sourceRoot, mapping.source);
                          }
                          mapping.originalLine = original.line;
                          mapping.originalColumn = original.column;
                          if (original.name != null) {
                              mapping.name = original.name;
                          }
                      }
                  }
  
                  var source = mapping.source;
                  if (source != null && !newSources.has(source)) {
                      newSources.add(source);
                  }
  
                  var name = mapping.name;
                  if (name != null && !newNames.has(name)) {
                      newNames.add(name);
                  }
  
              }, this);
              this._sources = newSources;
              this._names = newNames;
  
              // Copy sourcesContents of applied map.
              aSourceMapConsumer.sources.forEach(function (sourceFile) {
                  var content = aSourceMapConsumer.sourceContentFor(sourceFile);
                  if (content != null) {
                      if (aSourceMapPath != null) {
                          sourceFile = util.join(aSourceMapPath, sourceFile);
                      }
                      if (sourceRoot != null) {
                          sourceFile = util.relative(sourceRoot, sourceFile);
                      }
                      this.setSourceContent(sourceFile, content);
                  }
              }, this);
          };
  
      /**
       * A mapping can have one of the three levels of data:
       *
       *   1. Just the generated position.
       *   2. The Generated position, original position, and original source.
       *   3. Generated and original position, original source, as well as a name
       *      token.
       *
       * To maintain consistency, we validate that any new mapping being added falls
       * in to one of these categories.
       */
      SourceMapGenerator.prototype._validateMapping =
          function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
                                                      aName) {
              if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
                  && aGenerated.line > 0 && aGenerated.column >= 0
                  && !aOriginal && !aSource && !aName) {
                  // Case 1.
                  return;
              }
              else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
                  && aOriginal && 'line' in aOriginal && 'column' in aOriginal
                  && aGenerated.line > 0 && aGenerated.column >= 0
                  && aOriginal.line > 0 && aOriginal.column >= 0
                  && aSource) {
                  // Cases 2 and 3.
                  return;
              }
              else {
                  throw new Error('Invalid mapping: ' + JSON.stringify({
                          generated: aGenerated,
                          source: aSource,
                          original: aOriginal,
                          name: aName
                      }));
              }
          };
  
      /**
       * Serialize the accumulated mappings in to the stream of base 64 VLQs
       * specified by the source map format.
       */
      SourceMapGenerator.prototype._serializeMappings =
          function SourceMapGenerator_serializeMappings() {
              var previousGeneratedColumn = 0;
              var previousGeneratedLine = 1;
              var previousOriginalColumn = 0;
              var previousOriginalLine = 0;
              var previousName = 0;
              var previousSource = 0;
              var result = '';
              var mapping;
  
              // The mappings must be guaranteed to be in sorted order before we start
              // serializing them or else the generated line numbers (which are defined
              // via the ';' separators) will be all messed up. Note: it might be more
              // performant to maintain the sorting as we insert them, rather than as we
              // serialize them, but the big O is the same either way.
              this._mappings.sort(util.compareByGeneratedPositions);
  
              for (var i = 0, len = this._mappings.length; i < len; i++) {
                  mapping = this._mappings[i];
  
                  if (mapping.generatedLine !== previousGeneratedLine) {
                      previousGeneratedColumn = 0;
                      while (mapping.generatedLine !== previousGeneratedLine) {
                          result += ';';
                          previousGeneratedLine++;
                      }
                  }
                  else {
                      if (i > 0) {
                          if (!util.compareByGeneratedPositions(mapping, this._mappings[i - 1])) {
                              continue;
                          }
                          result += ',';
                      }
                  }
  
                  result += base64VLQ.encode(mapping.generatedColumn
                      - previousGeneratedColumn);
                  previousGeneratedColumn = mapping.generatedColumn;
  
                  if (mapping.source != null) {
                      result += base64VLQ.encode(this._sources.indexOf(mapping.source)
                          - previousSource);
                      previousSource = this._sources.indexOf(mapping.source);
  
                      // lines are stored 0-based in SourceMap spec version 3
                      result += base64VLQ.encode(mapping.originalLine - 1
                          - previousOriginalLine);
                      previousOriginalLine = mapping.originalLine - 1;
  
                      result += base64VLQ.encode(mapping.originalColumn
                          - previousOriginalColumn);
                      previousOriginalColumn = mapping.originalColumn;
  
                      if (mapping.name != null) {
                          result += base64VLQ.encode(this._names.indexOf(mapping.name)
                              - previousName);
                          previousName = this._names.indexOf(mapping.name);
                      }
                  }
              }
  
              return result;
          };
  
      SourceMapGenerator.prototype._generateSourcesContent =
          function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
              return aSources.map(function (source) {
                  if (!this._sourcesContents) {
                      return null;
                  }
                  if (aSourceRoot != null) {
                      source = util.relative(aSourceRoot, source);
                  }
                  var key = util.toSetString(source);
                  return Object.prototype.hasOwnProperty.call(this._sourcesContents,
                      key)
                      ? this._sourcesContents[key]
                      : null;
              }, this);
          };
  
      /**
       * Externalize the source map.
       */
      SourceMapGenerator.prototype.toJSON =
          function SourceMapGenerator_toJSON() {
              var map = {
                  version: this._version,
                  sources: this._sources.toArray(),
                  names: this._names.toArray(),
                  mappings: this._serializeMappings()
              };
              if (this._file != null) {
                  map.file = this._file;
              }
              if (this._sourceRoot != null) {
                  map.sourceRoot = this._sourceRoot;
              }
              if (this._sourcesContents) {
                  map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
              }
  
              return map;
          };
  
      /**
       * Render the source map being generated to a string.
       */
      SourceMapGenerator.prototype.toString =
          function SourceMapGenerator_toString() {
              return JSON.stringify(this);
          };
  
      exports.SourceMapGenerator = SourceMapGenerator;
  
  });
  