/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
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;

function ReplaceSource(source, name) {
	Source.call(this);
	this._source = source;
	this._name = name;
	this.replacements = [];
}
module.exports = ReplaceSource;

ReplaceSource.prototype = Object.create(Source.prototype);
ReplaceSource.prototype.constructor = ReplaceSource;

ReplaceSource.prototype.replace = function(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]);
};

ReplaceSource.prototype.insert = function(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]);
};

ReplaceSource.prototype.source = function(options) {
	return this._replaceString(this._source.source());
};

ReplaceSource.prototype._sortReplacements = function() {
	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];
	});
};

ReplaceSource.prototype._replaceString = function(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("");
};

require("./SourceAndMapMixin")(ReplaceSource.prototype);

ReplaceSource.prototype.node = function(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);
};

ReplaceSource.prototype.listMap = function(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;
};

ReplaceSource.prototype._replacementToSourceNode = function(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;
	}
};

ReplaceSource.prototype._splitSourceNode = function(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;
	}
};

ReplaceSource.prototype._splitString = function(str, position) {
	return position <= 0 ? ["", str] : [str.substr(0, position), str.substr(position)];
};
