var assert = require(“assert”); var types = require(“./types”); var isString = types.builtInTypes.string; var isNumber = types.builtInTypes.number; var SourceLocation = types.namedTypes.SourceLocation; var Position = types.namedTypes.Position; var linesModule = require(“./lines”); var comparePos = require(“./util”).comparePos;

function Mapping(sourceLines, sourceLoc, targetLoc) {

assert.ok(this instanceof Mapping);
assert.ok(sourceLines instanceof linesModule.Lines);
SourceLocation.assert(sourceLoc);

if (targetLoc) {
    // In certain cases it's possible for targetLoc.{start,end}.column
    // values to be negative, which technically makes them no longer
    // valid SourceLocation nodes, so we need to be more forgiving.
    assert.ok(
        isNumber.check(targetLoc.start.line) &&
        isNumber.check(targetLoc.start.column) &&
        isNumber.check(targetLoc.end.line) &&
        isNumber.check(targetLoc.end.column)
    );
} else {
    // Assume identity mapping if no targetLoc specified.
    targetLoc = sourceLoc;
}

Object.defineProperties(this, {
    sourceLines: { value: sourceLines },
    sourceLoc: { value: sourceLoc },
    targetLoc: { value: targetLoc }
});

}

var Mp = Mapping.prototype; module.exports = Mapping;

Mp.slice = function(lines, start, end) {

assert.ok(lines instanceof linesModule.Lines);
Position.assert(start);

if (end) {
    Position.assert(end);
} else {
    end = lines.lastPos();
}

var sourceLines = this.sourceLines;
var sourceLoc = this.sourceLoc;
var targetLoc = this.targetLoc;

function skip(name) {
    var sourceFromPos = sourceLoc[name];
    var targetFromPos = targetLoc[name];
    var targetToPos = start;

    if (name === "end") {
        targetToPos = end;
    } else {
        assert.strictEqual(name, "start");
    }

    return skipChars(
        sourceLines, sourceFromPos,
        lines, targetFromPos, targetToPos
    );
}

if (comparePos(start, targetLoc.start) <= 0) {
    if (comparePos(targetLoc.end, end) <= 0) {
        targetLoc = {
            start: subtractPos(targetLoc.start, start.line, start.column),
            end: subtractPos(targetLoc.end, start.line, start.column)
        };

        // The sourceLoc can stay the same because the contents of the
        // targetLoc have not changed.

    } else if (comparePos(end, targetLoc.start) <= 0) {
        return null;

    } else {
        sourceLoc = {
            start: sourceLoc.start,
            end: skip("end")
        };

        targetLoc = {
            start: subtractPos(targetLoc.start, start.line, start.column),
            end: subtractPos(end, start.line, start.column)
        };
    }

} else {
    if (comparePos(targetLoc.end, start) <= 0) {
        return null;
    }

    if (comparePos(targetLoc.end, end) <= 0) {
        sourceLoc = {
            start: skip("start"),
            end: sourceLoc.end
        };

        targetLoc = {
            // Same as subtractPos(start, start.line, start.column):
            start: { line: 1, column: 0 },
            end: subtractPos(targetLoc.end, start.line, start.column)
        };

    } else {
        sourceLoc = {
            start: skip("start"),
            end: skip("end")
        };

        targetLoc = {
            // Same as subtractPos(start, start.line, start.column):
            start: { line: 1, column: 0 },
            end: subtractPos(end, start.line, start.column)
        };
    }
}

return new Mapping(this.sourceLines, sourceLoc, targetLoc);

};

Mp.add = function(line, column) {

return new Mapping(this.sourceLines, this.sourceLoc, {
    start: addPos(this.targetLoc.start, line, column),
    end: addPos(this.targetLoc.end, line, column)
});

};

function addPos(toPos, line, column) {

return {
    line: toPos.line + line - 1,
    column: (toPos.line === 1)
        ? toPos.column + column
        : toPos.column
};

}

Mp.subtract = function(line, column) {

return new Mapping(this.sourceLines, this.sourceLoc, {
    start: subtractPos(this.targetLoc.start, line, column),
    end: subtractPos(this.targetLoc.end, line, column)
});

};

function subtractPos(fromPos, line, column) {

return {
    line: fromPos.line - line + 1,
    column: (fromPos.line === line)
        ? fromPos.column - column
        : fromPos.column
};

}

Mp.indent = function(by, skipFirstLine, noNegativeColumns) {

if (by === 0) {
    return this;
}

var targetLoc = this.targetLoc;
var startLine = targetLoc.start.line;
var endLine = targetLoc.end.line;

if (skipFirstLine && startLine === 1 && endLine === 1) {
    return this;
}

targetLoc = {
    start: targetLoc.start,
    end: targetLoc.end
};

if (!skipFirstLine || startLine > 1) {
    var startColumn = targetLoc.start.column + by;
    targetLoc.start = {
        line: startLine,
        column: noNegativeColumns
            ? Math.max(0, startColumn)
            : startColumn
    };
}

if (!skipFirstLine || endLine > 1) {
    var endColumn = targetLoc.end.column + by;
    targetLoc.end = {
        line: endLine,
        column: noNegativeColumns
            ? Math.max(0, endColumn)
            : endColumn
    };
}

return new Mapping(this.sourceLines, this.sourceLoc, targetLoc);

};

function skipChars(

sourceLines, sourceFromPos,
targetLines, targetFromPos, targetToPos

) {

assert.ok(sourceLines instanceof linesModule.Lines);
assert.ok(targetLines instanceof linesModule.Lines);
Position.assert(sourceFromPos);
Position.assert(targetFromPos);
Position.assert(targetToPos);

var targetComparison = comparePos(targetFromPos, targetToPos);
if (targetComparison === 0) {
    // Trivial case: no characters to skip.
    return sourceFromPos;
}

if (targetComparison < 0) {
    // Skipping forward.

    var sourceCursor = sourceLines.skipSpaces(sourceFromPos);
    var targetCursor = targetLines.skipSpaces(targetFromPos);

    var lineDiff = targetToPos.line - targetCursor.line;
    sourceCursor.line += lineDiff;
    targetCursor.line += lineDiff;

    if (lineDiff > 0) {
        // If jumping to later lines, reset columns to the beginnings
        // of those lines.
        sourceCursor.column = 0;
        targetCursor.column = 0;
    } else {
        assert.strictEqual(lineDiff, 0);
    }

    while (comparePos(targetCursor, targetToPos) < 0 &&
           targetLines.nextPos(targetCursor, true)) {
        assert.ok(sourceLines.nextPos(sourceCursor, true));
        assert.strictEqual(
            sourceLines.charAt(sourceCursor),
            targetLines.charAt(targetCursor)
        );
    }

} else {
    // Skipping backward.

    var sourceCursor = sourceLines.skipSpaces(sourceFromPos, true);
    var targetCursor = targetLines.skipSpaces(targetFromPos, true);

    var lineDiff = targetToPos.line - targetCursor.line;
    sourceCursor.line += lineDiff;
    targetCursor.line += lineDiff;

    if (lineDiff < 0) {
        // If jumping to earlier lines, reset columns to the ends of
        // those lines.
        sourceCursor.column = sourceLines.getLineLength(sourceCursor.line);
        targetCursor.column = targetLines.getLineLength(targetCursor.line);
    } else {
        assert.strictEqual(lineDiff, 0);
    }

    while (comparePos(targetToPos, targetCursor) < 0 &&
           targetLines.prevPos(targetCursor, true)) {
        assert.ok(sourceLines.prevPos(sourceCursor, true));
        assert.strictEqual(
            sourceLines.charAt(sourceCursor),
            targetLines.charAt(targetCursor)
        );
    }
}

return sourceCursor;

}