text: /*
  	MIT License http://www.opensource.org/licenses/mit-license.php
  	Author Tobias Koppers @sokra
  */
  "use strict";
  
  var Source = require("./Source");
  var SourceNode = require("source-map").SourceNode;
  var SourceListMap = require("source-list-map").SourceListMap;
  var fromStringWithSourceMap = require("source-list-map").fromStringWithSourceMap;
  var SourceMapConsumer = require("source-map").SourceMapConsumer;
  
  class ReplaceSource extends Source {
  	constructor(source, name) {
  		super();
  		this._source = source;
  		this._name = name;
  		this.replacements = [];
  	}
  
  	replace(start, end, newValue) {
  		if(typeof newValue !== "string")
  			throw new Error("insertion must be a string, but is a " + typeof newValue);
  		this.replacements.push([start, end, newValue, this.replacements.length]);
  	}
  
  	insert(pos, newValue) {
  		if(typeof newValue !== "string")
  			throw new Error("insertion must be a string, but is a " + typeof newValue + ": " + newValue);
  		this.replacements.push([pos, pos - 1, newValue, this.replacements.length]);
  	}
  
  	source(options) {
  		return this._replaceString(this._source.source());
  	}
  
  	original() {
  		return this._source;
  	}
  
  	_sortReplacements() {
  		this.replacements.sort(function(a, b) {
  			var diff = b[1] - a[1];
  			if(diff !== 0)
  				return diff;
  			diff = b[0] - a[0];
  			if(diff !== 0)
  				return diff;
  			return b[3] - a[3];
  		});
  	}
  
  	_replaceString(str) {
  		if(typeof str !== "string")
  			throw new Error("str must be a string, but is a " + typeof str + ": " + str);
  		this._sortReplacements();
  		var result = [str];
  		this.replacements.forEach(function(repl) {
  			var remSource = result.pop();
  			var splitted1 = this._splitString(remSource, Math.floor(repl[1] + 1));
  			var splitted2 = this._splitString(splitted1[0], Math.floor(repl[0]));
  			result.push(splitted1[1], repl[2], splitted2[0]);
  		}, this);
  		result = result.reverse();
  		return result.join("");
  	}
  
  	node(options) {
  		this._sortReplacements();
  		var result = [this._source.node(options)];
  		this.replacements.forEach(function(repl) {
  			var remSource = result.pop();
  			var splitted1 = this._splitSourceNode(remSource, Math.floor(repl[1] + 1));
  			var splitted2;
  			if(Array.isArray(splitted1)) {
  				splitted2 = this._splitSourceNode(splitted1[0], Math.floor(repl[0]));
  				if(Array.isArray(splitted2)) {
  					result.push(splitted1[1], this._replacementToSourceNode(splitted2[1], repl[2]), splitted2[0]);
  				} else {
  					result.push(splitted1[1], this._replacementToSourceNode(splitted1[1], repl[2]), splitted1[0]);
  				}
  			} else {
  				splitted2 = this._splitSourceNode(remSource, Math.floor(repl[0]));
  				if(Array.isArray(splitted2)) {
  					result.push(this._replacementToSourceNode(splitted2[1], repl[2]), splitted2[0]);
  				} else {
  					result.push(repl[2], remSource);
  				}
  			}
  		}, this);
  		result = result.reverse();
  		return new SourceNode(null, null, null, result);
  	}
  
  	listMap(options) {
  		this._sortReplacements();
  		var map = this._source.listMap(options);
  		var currentIndex = 0;
  		var replacements = this.replacements;
  		var idxReplacement = replacements.length - 1;
  		var removeChars = 0;
  		map = map.mapGeneratedCode(function(str) {
  			var newCurrentIndex = currentIndex + str.length;
  			if(removeChars > str.length) {
  				removeChars -= str.length;
  				str = "";
  			} else {
  				if(removeChars > 0) {
  					str = str.substr(removeChars);
  					currentIndex += removeChars;
  					removeChars = 0;
  				}
  				var finalStr = "";
  				while(idxReplacement >= 0 && replacements[idxReplacement][0] < newCurrentIndex) {
  					var repl = replacements[idxReplacement];
  					var start = Math.floor(repl[0]);
  					var end = Math.floor(repl[1] + 1);
  					var before = str.substr(0, Math.max(0, start - currentIndex));
  					if(end <= newCurrentIndex) {
  						var after = str.substr(Math.max(0, end - currentIndex));
  						finalStr += before + repl[2];
  						str = after;
  						currentIndex = Math.max(currentIndex, end);
  					} else {
  						finalStr += before + repl[2];
  						str = "";
  						removeChars = end - newCurrentIndex;
  					}
  					idxReplacement--;
  				}
  				str = finalStr + str;
  			}
  			currentIndex = newCurrentIndex;
  			return str;
  		});
  		var extraCode = "";
  		while(idxReplacement >= 0) {
  			extraCode += replacements[idxReplacement][2];
  			idxReplacement--;
  		}
  		if(extraCode) {
  			map.add(extraCode);
  		}
  		return map;
  	}
  
  	_replacementToSourceNode(oldNode, newString) {
  		var map = oldNode.toStringWithSourceMap({
  			file: "?"
  		}).map;
  		var original = new SourceMapConsumer(map.toJSON()).originalPositionFor({
  			line: 1,
  			column: 0
  		});
  		if(original) {
  			return new SourceNode(original.line, original.column, original.source, newString);
  		} else {
  			return newString;
  		}
  	}
  
  	_splitSourceNode(node, position) {
  		if(typeof node === "string") {
  			if(node.length <= position) return position - node.length;
  			return position <= 0 ? ["", node] : [node.substr(0, position), node.substr(position)];
  		} else {
  			for(var i = 0; i < node.children.length; i++) {
  				position = this._splitSourceNode(node.children[i], position);
  				if(Array.isArray(position)) {
  					var leftNode = new SourceNode(
  						node.line,
  						node.column,
  						node.source,
  						node.children.slice(0, i).concat([position[0]]),
  						node.name
  					);
  					var rightNode = new SourceNode(
  						node.line,
  						node.column,
  						node.source, [position[1]].concat(node.children.slice(i + 1)),
  						node.name
  					);
  					leftNode.sourceContents = node.sourceContents;
  					return [leftNode, rightNode];
  				}
  			}
  			return position;
  		}
  	}
  
  	_splitString(str, position) {
  		return position <= 0 ? ["", str] : [str.substr(0, position), str.substr(position)];
  	}
  }
  
  require("./SourceAndMapMixin")(ReplaceSource.prototype);
  
  module.exports = ReplaceSource;
  