"use strict";

var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; };

var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };

var Result = _interopRequire(require("./result"));

var Base64 = require("js-base64").Base64;
var mozilla = _interopRequire(require("source-map"));

var path = _interopRequire(require("path"));

// All tools to generate source maps
var MapGenerator = (function () {
    function MapGenerator(root, opts) {
        _classCallCheck(this, MapGenerator);

        this.root = root;
        this.opts = opts;
        this.mapOpts = opts.map || {};
    }

    // Should map be generated
    MapGenerator.prototype.isMap = function isMap() {
        if (typeof this.opts.map != "undefined") {
            return !!this.opts.map;
        } else {
            return this.previous().length > 0;
        }
    };

    // Return source map arrays from previous compilation step (like Sass)
    MapGenerator.prototype.previous = function previous() {
        var _this = this;
        if (!this.previousMaps) {
            this.previousMaps = [];
            this.root.eachInside(function (node) {
                if (node.source && node.source.input.map) {
                    var map = node.source.input.map;
                    if (_this.previousMaps.indexOf(map) == -1) {
                        _this.previousMaps.push(map);
                    }
                }
            });
        }

        return this.previousMaps;
    };

    // Should we inline source map to annotation comment
    MapGenerator.prototype.isInline = function isInline() {
        if (typeof this.mapOpts.inline != "undefined") {
            return this.mapOpts.inline;
        }

        var annotation = this.mapOpts.annotation;
        if (typeof annotation != "undefined" && annotation !== true) {
            return false;
        }

        if (this.previous().length) {
            return this.previous().some(function (i) {
                return i.inline;
            });
        } else {
            return true;
        }
    };

    // Should we set sourcesContent
    MapGenerator.prototype.isSourcesContent = function isSourcesContent() {
        if (typeof this.mapOpts.sourcesContent != "undefined") {
            return this.mapOpts.sourcesContent;
        }
        if (this.previous().length) {
            return this.previous().some(function (i) {
                return i.withContent();
            });
        } else {
            return true;
        }
    };

    // Clear source map annotation comment
    MapGenerator.prototype.clearAnnotation = function clearAnnotation() {
        if (this.mapOpts.annotation === false) return;

        var node;
        for (var i = this.root.nodes.length - 1; i >= 0; i--) {
            node = this.root.nodes[i];
            if (node.type != "comment") continue;
            if (node.text.match(/^# sourceMappingURL=/)) {
                this.root.remove(i);
                return;
            }
        }
    };

    // Set origin CSS content
    MapGenerator.prototype.setSourcesContent = function setSourcesContent() {
        var _this = this;
        var already = {};
        this.root.eachInside(function (node) {
            if (node.source) {
                var from = node.source.input.from;
                if (from && !already[from]) {
                    already[from] = true;
                    var relative = _this.relative(from);
                    _this.map.setSourceContent(relative, node.source.input.css);
                }
            }
        });
    };

    // Apply source map from previous compilation step (like Sass)
    MapGenerator.prototype.applyPrevMaps = function applyPrevMaps() {
        for (var _iterator = this.previous(), _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
            var _ref;
            if (_isArray) {
                if (_i >= _iterator.length) break;
                _ref = _iterator[_i++];
            } else {
                _i = _iterator.next();
                if (_i.done) break;
                _ref = _i.value;
            }
            var prev = _ref;
            var from = this.relative(prev.file);
            var root = prev.root || path.dirname(prev.file);
            var map;

            if (this.mapOpts.sourcesContent === false) {
                map = new mozilla.SourceMapConsumer(prev.text);
                map.sourcesContent = map.sourcesContent.map(function (i) {
                    return null;
                });
            } else {
                map = prev.consumer();
            }

            this.map.applySourceMap(map, from, this.relative(root));
        }
    };

    // Should we add annotation comment
    MapGenerator.prototype.isAnnotation = function isAnnotation() {
        if (this.isInline()) {
            return true;
        } else if (typeof this.mapOpts.annotation != "undefined") {
            return this.mapOpts.annotation;
        } else if (this.previous().length) {
            return this.previous().some(function (i) {
                return i.annotation;
            });
        } else {
            return true;
        }
    };

    // Add source map annotation comment if it is needed
    MapGenerator.prototype.addAnnotation = function addAnnotation() {
        var content;

        if (this.isInline()) {
            content = "data:application/json;base64," + Base64.encode(this.map.toString());
        } else if (typeof this.mapOpts.annotation == "string") {
            content = this.mapOpts.annotation;
        } else {
            content = this.outputFile() + ".map";
        }

        this.css += "\n/*# sourceMappingURL=" + content + " */";
    };

    // Return output CSS file path
    MapGenerator.prototype.outputFile = function outputFile() {
        if (this.opts.to) {
            return this.relative(this.opts.to);
        } else if (this.opts.from) {
            return this.relative(this.opts.from);
        } else {
            return "to.css";
        }
    };

    // Return Result object with map
    MapGenerator.prototype.generateMap = function generateMap() {
        this.stringify();
        if (this.isSourcesContent()) this.setSourcesContent();
        if (this.previous().length > 0) this.applyPrevMaps();
        if (this.isAnnotation()) this.addAnnotation();

        if (this.isInline()) {
            return [this.css];
        } else {
            return [this.css, this.map];
        }
    };

    // Return path relative from output CSS file
    MapGenerator.prototype.relative = function relative(file) {
        var from = this.opts.to ? path.dirname(this.opts.to) : ".";

        if (typeof this.mapOpts.annotation == "string") {
            from = path.dirname(path.resolve(from, this.mapOpts.annotation));
        }

        file = path.relative(from, file);
        if (path.sep == "\\") {
            return file.replace(/\\/g, "/");
        } else {
            return file;
        }
    };

    // Return path of node source for map
    MapGenerator.prototype.sourcePath = function sourcePath(node) {
        return this.relative(node.source.input.from);
    };

    // Return CSS string and source map
    MapGenerator.prototype.stringify = function stringify() {
        var _this = this;
        this.css = "";
        this.map = new mozilla.SourceMapGenerator({ file: this.outputFile() });

        var line = 1;
        var column = 1;

        var lines, last;
        var builder = function (str, node, type) {
            _this.css += str;

            if (node && node.source && node.source.start && type != "end") {
                _this.map.addMapping({
                    source: _this.sourcePath(node),
                    original: {
                        line: node.source.start.line,
                        column: node.source.start.column - 1
                    },
                    generated: {
                        line: line,
                        column: column - 1
                    }
                });
            }

            lines = str.match(/\n/g);
            if (lines) {
                line += lines.length;
                last = str.lastIndexOf("\n");
                column = str.length - last;
            } else {
                column = column + str.length;
            }

            if (node && node.source && node.source.end && type != "start") {
                _this.map.addMapping({
                    source: _this.sourcePath(node),
                    original: {
                        line: node.source.end.line,
                        column: node.source.end.column
                    },
                    generated: {
                        line: line,
                        column: column
                    }
                });
            }
        };

        this.root.stringify(builder);
    };

    // Return Result object with or without map
    MapGenerator.prototype.generate = function generate() {
        this.clearAnnotation();

        if (this.isMap()) {
            return this.generateMap();
        } else {
            return [this.root.toString()];
        }
    };

    return MapGenerator;
})();

module.exports = MapGenerator;