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);
  }
  define(function (require, exports, module) {
  
    var util = require('./util');
    var binarySearch = require('./binary-search');
    var ArraySet = require('./array-set').ArraySet;
    var base64VLQ = require('./base64-vlq');
  
    /**
     * A SourceMapConsumer instance represents a parsed source map which we can
     * query for information about the original file positions by giving it a file
     * position in the generated source.
     *
     * The only parameter is the raw source map (either as a JSON string, or
     * already parsed to an object). According to the spec, source maps have the
     * following attributes:
     *
     *   - version: Which version of the source map spec this map is following.
     *   - sources: An array of URLs to the original source files.
     *   - names: An array of identifiers which can be referrenced by individual mappings.
     *   - sourceRoot: Optional. The URL root from which all sources are relative.
     *   - sourcesContent: Optional. An array of contents of the original source files.
     *   - mappings: A string of base64 VLQs which contain the actual mappings.
     *   - file: The generated file this source map is associated with.
     *
     * Here is an example source map, taken from the source map spec[0]:
     *
     *     {
     *       version : 3,
     *       file: "out.js",
     *       sourceRoot : "",
     *       sources: ["foo.js", "bar.js"],
     *       names: ["src", "maps", "are", "fun"],
     *       mappings: "AA,AB;;ABCDE;"
     *     }
     *
     * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
     */
    function SourceMapConsumer(aSourceMap) {
      var sourceMap = aSourceMap;
      if (typeof aSourceMap === 'string') {
        sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
      }
  
      var version = util.getArg(sourceMap, 'version');
      var sources = util.getArg(sourceMap, 'sources');
      var names = util.getArg(sourceMap, 'names');
      var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
      var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
      var mappings = util.getArg(sourceMap, 'mappings');
      var file = util.getArg(sourceMap, 'file');
  
      if (version !== this._version) {
        throw new Error('Unsupported version: ' + version);
      }
  
      this._names = ArraySet.fromArray(names);
      this._sources = ArraySet.fromArray(sources);
      this.sourceRoot = sourceRoot;
      this.sourcesContent = sourcesContent;
      this.file = file;
  
      // `this._generatedMappings` and `this._originalMappings` hold the parsed
      // mapping coordinates from the source map's "mappings" attribute. Each
      // object in the array is of the form
      //
      //     {
      //       generatedLine: The line number in the generated code,
      //       generatedColumn: The column number in the generated code,
      //       source: The path to the original source file that generated this
      //               chunk of code,
      //       originalLine: The line number in the original source that
      //                     corresponds to this chunk of generated code,
      //       originalColumn: The column number in the original source that
      //                       corresponds to this chunk of generated code,
      //       name: The name of the original symbol which generated this chunk of
      //             code.
      //     }
      //
      // All properties except for `generatedLine` and `generatedColumn` can be
      // `null`.
      //
      // `this._generatedMappings` is ordered by the generated positions.
      //
      // `this._originalMappings` is ordered by the original positions.
      this._generatedMappings = [];
      this._originalMappings = [];
      this._parseMappings(mappings, sourceRoot);
    }
  
    /**
     * The version of the source mapping spec that we are consuming.
     */
    SourceMapConsumer.prototype._version = 3;
  
    /**
     * The list of original sources.
     */
    Object.defineProperty(SourceMapConsumer.prototype, 'sources', {
      get: function () {
        return this._sources.toArray().map(function (s) {
          return this.sourceRoot ? util.join(this.sourceRoot, s) : s;
        }, this);
      }
    });
  
    /**
     * Parse the mappings in a string in to a data structure which we can easily
     * query (an ordered list in this._generatedMappings).
     */
    SourceMapConsumer.prototype._parseMappings =
      function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
        var generatedLine = 1;
        var previousGeneratedColumn = 0;
        var previousOriginalLine = 0;
        var previousOriginalColumn = 0;
        var previousSource = 0;
        var previousName = 0;
        var mappingSeparator = /^[,;]/;
        var str = aStr;
        var mapping;
        var temp;
  
        while (str.length > 0) {
          if (str.charAt(0) === ';') {
            generatedLine++;
            str = str.slice(1);
            previousGeneratedColumn = 0;
          }
          else if (str.charAt(0) === ',') {
            str = str.slice(1);
          }
          else {
            mapping = {};
            mapping.generatedLine = generatedLine;
  
            // Generated column.
            temp = base64VLQ.decode(str);
            mapping.generatedColumn = previousGeneratedColumn + temp.value;
            previousGeneratedColumn = mapping.generatedColumn;
            str = temp.rest;
  
            if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
              // Original source.
              temp = base64VLQ.decode(str);
              mapping.source = this._sources.at(previousSource + temp.value);
              previousSource += temp.value;
              str = temp.rest;
              if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
                throw new Error('Found a source, but no line and column');
              }
  
              // Original line.
              temp = base64VLQ.decode(str);
              mapping.originalLine = previousOriginalLine + temp.value;
              previousOriginalLine = mapping.originalLine;
              // Lines are stored 0-based
              mapping.originalLine += 1;
              str = temp.rest;
              if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
                throw new Error('Found a source and line, but no column');
              }
  
              // Original column.
              temp = base64VLQ.decode(str);
              mapping.originalColumn = previousOriginalColumn + temp.value;
              previousOriginalColumn = mapping.originalColumn;
              str = temp.rest;
  
              if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
                // Original name.
                temp = base64VLQ.decode(str);
                mapping.name = this._names.at(previousName + temp.value);
                previousName += temp.value;
                str = temp.rest;
              }
            }
  
            this._generatedMappings.push(mapping);
            if (typeof mapping.originalLine === 'number') {
              this._originalMappings.push(mapping);
            }
          }
        }
  
        this._originalMappings.sort(this._compareOriginalPositions);
      };
  
    /**
     * Comparator between two mappings where the original positions are compared.
     */
    SourceMapConsumer.prototype._compareOriginalPositions =
      function SourceMapConsumer_compareOriginalPositions(mappingA, mappingB) {
        if (mappingA.source > mappingB.source) {
          return 1;
        }
        else if (mappingA.source < mappingB.source) {
          return -1;
        }
        else {
          var cmp = mappingA.originalLine - mappingB.originalLine;
          return cmp === 0
            ? mappingA.originalColumn - mappingB.originalColumn
            : cmp;
        }
      };
  
    /**
     * Comparator between two mappings where the generated positions are compared.
     */
    SourceMapConsumer.prototype._compareGeneratedPositions =
      function SourceMapConsumer_compareGeneratedPositions(mappingA, mappingB) {
        var cmp = mappingA.generatedLine - mappingB.generatedLine;
        return cmp === 0
          ? mappingA.generatedColumn - mappingB.generatedColumn
          : cmp;
      };
  
    /**
     * Find the mapping that best matches the hypothetical "needle" mapping that
     * we are searching for in the given "haystack" of mappings.
     */
    SourceMapConsumer.prototype._findMapping =
      function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
                                             aColumnName, aComparator) {
        // To return the position we are searching for, we must first find the
        // mapping for the given position and then return the opposite position it
        // points to. Because the mappings are sorted, we can use binary search to
        // find the best mapping.
  
        if (aNeedle[aLineName] <= 0) {
          throw new TypeError('Line must be greater than or equal to 1, got '
                              + aNeedle[aLineName]);
        }
        if (aNeedle[aColumnName] < 0) {
          throw new TypeError('Column must be greater than or equal to 0, got '
                              + aNeedle[aColumnName]);
        }
  
        return binarySearch.search(aNeedle, aMappings, aComparator);
      };
  
    /**
     * Returns the original source, line, and column information for the generated
     * source's line and column positions provided. The only argument is an object
     * with the following properties:
     *
     *   - line: The line number in the generated source.
     *   - column: The column number in the generated source.
     *
     * and an object is returned with the following properties:
     *
     *   - source: The original source file, or null.
     *   - line: The line number in the original source, or null.
     *   - column: The column number in the original source, or null.
     *   - name: The original identifier, or null.
     */
    SourceMapConsumer.prototype.originalPositionFor =
      function SourceMapConsumer_originalPositionFor(aArgs) {
        var needle = {
          generatedLine: util.getArg(aArgs, 'line'),
          generatedColumn: util.getArg(aArgs, 'column')
        };
  
        var mapping = this._findMapping(needle,
                                        this._generatedMappings,
                                        "generatedLine",
                                        "generatedColumn",
                                        this._compareGeneratedPositions);
  
        if (mapping) {
          var source = util.getArg(mapping, 'source', null);
          if (source && this.sourceRoot) {
            source = util.join(this.sourceRoot, source);
          }
          return {
            source: source,
            line: util.getArg(mapping, 'originalLine', null),
            column: util.getArg(mapping, 'originalColumn', null),
            name: util.getArg(mapping, 'name', null)
          };
        }
  
        return {
          source: null,
          line: null,
          column: null,
          name: null
        };
      };
  
    /**
     * Returns the original source content. The only argument is
     * the url of the original source file. Returns null if no
     * original source content is availible.
     */
    SourceMapConsumer.prototype.sourceContentFor =
      function SourceMapConsumer_sourceContentFor(aSource) {
        if (!this.sourcesContent) {
          return null;
        }
  
        if (this.sourceRoot) {
          aSource = util.relative(this.sourceRoot, aSource);
        }
  
        if (this._sources.has(aSource)) {
          return this.sourcesContent[this._sources.indexOf(aSource)];
        }
  
        var url;
        if (this.sourceRoot
            && (url = util.urlParse(this.sourceRoot))
            && (!url.path || url.path == "/")
            && this._sources.has("/" + aSource)) {
          return this.sourcesContent[this._sources.indexOf("/" + aSource)];
        }
  
        throw new Error('"' + aSource + '" is not in the SourceMap.');
      };
  
    /**
     * Returns the generated line and column information for the original source,
     * line, and column positions provided. The only argument is an object with
     * the following properties:
     *
     *   - source: The filename of the original source.
     *   - line: The line number in the original source.
     *   - column: The column number in the original source.
     *
     * and an object is returned with the following properties:
     *
     *   - line: The line number in the generated source, or null.
     *   - column: The column number in the generated source, or null.
     */
    SourceMapConsumer.prototype.generatedPositionFor =
      function SourceMapConsumer_generatedPositionFor(aArgs) {
        var needle = {
          source: util.getArg(aArgs, 'source'),
          originalLine: util.getArg(aArgs, 'line'),
          originalColumn: util.getArg(aArgs, 'column')
        };
  
        if (this.sourceRoot) {
          needle.source = util.relative(this.sourceRoot, needle.source);
        }
  
        var mapping = this._findMapping(needle,
                                        this._originalMappings,
                                        "originalLine",
                                        "originalColumn",
                                        this._compareOriginalPositions);
  
        if (mapping) {
          return {
            line: util.getArg(mapping, 'generatedLine', null),
            column: util.getArg(mapping, 'generatedColumn', null)
          };
        }
  
        return {
          line: null,
          column: null
        };
      };
  
    SourceMapConsumer.GENERATED_ORDER = 1;
    SourceMapConsumer.ORIGINAL_ORDER = 2;
  
    /**
     * Iterate over each mapping between an original source/line/column and a
     * generated line/column in this source map.
     *
     * @param Function aCallback
     *        The function that is called with each mapping.
     * @param Object aContext
     *        Optional. If specified, this object will be the value of `this` every
     *        time that `aCallback` is called.
     * @param aOrder
     *        Either `SourceMapConsumer.GENERATED_ORDER` or
     *        `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
     *        iterate over the mappings sorted by the generated file's line/column
     *        order or the original's source/line/column order, respectively. Defaults to
     *        `SourceMapConsumer.GENERATED_ORDER`.
     */
    SourceMapConsumer.prototype.eachMapping =
      function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
        var context = aContext || null;
        var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
  
        var mappings;
        switch (order) {
        case SourceMapConsumer.GENERATED_ORDER:
          mappings = this._generatedMappings;
          break;
        case SourceMapConsumer.ORIGINAL_ORDER:
          mappings = this._originalMappings;
          break;
        default:
          throw new Error("Unknown order of iteration.");
        }
  
        var sourceRoot = this.sourceRoot;
        mappings.map(function (mapping) {
          var source = mapping.source;
          if (source && sourceRoot) {
            source = util.join(sourceRoot, source);
          }
          return {
            source: source,
            generatedLine: mapping.generatedLine,
            generatedColumn: mapping.generatedColumn,
            originalLine: mapping.originalLine,
            originalColumn: mapping.originalColumn,
            name: mapping.name
          };
        }).forEach(aCallback, context);
      };
  
    exports.SourceMapConsumer = SourceMapConsumer;
  
  });
  