"use strict";

var _classProps = function (child, staticProps, instanceProps) {
  if (staticProps) Object.defineProperties(child, staticProps);
  if (instanceProps) Object.defineProperties(child.prototype, instanceProps);
};

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

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

// All tools to generate source maps
var MapGenerator = (function () {
  var MapGenerator = function MapGenerator(root, opts) {
    this.root = root;
    this.opts = opts;
    this.mapOpts = opts.map || {};
  };

  _classProps(MapGenerator, null, {
    isMap: {
      writable: true,


      // Should map be generated
      value: function () {
        if (typeof (this.opts.map) != "undefined") {
          return !!this.opts.map;
        } else {
          return this.previous().length > 0;
        }
      }
    },
    previous: {
      writable: true,


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

        return this.previousMaps;
      }
    },
    isInline: {
      writable: true,


      // Should we inline source map to annotation comment
      value: function () {
        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;
        }
      }
    },
    isSourcesContent: {
      writable: true,


      // Should we set sourcesContent
      value: function () {
        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;
        }
      }
    },
    clearAnnotation: {
      writable: true,


      // Clear source map annotation comment
      value: function () {
        if (this.mapOpts.annotation === false) return;

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


      // Set origin CSS content
      value: function () {
        var _this2 = this;
        var already = {};
        this.root.eachInside(function (node) {
          if (node.source) {
            var file = node.source.file || node.source.id;
            if (file && !already[file]) {
              already[file] = true;
              var relative = _this2.relative(file);
              _this2.map.setSourceContent(relative, node.source.content);
            }
          }
        });
      }
    },
    applyPrevMaps: {
      writable: true,


      // Apply source map from previous compilation step (like Sass)
      value: function () {
        var prev, previous = this.previous();
        for (var i = 0; i < previous.length; i++) {
          prev = previous[i];

          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));
        }
      }
    },
    isAnnotation: {
      writable: true,


      // Should we add annotation comment
      value: function () {
        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;
        }
      }
    },
    addAnnotation: {
      writable: true,


      // Add source map annotation comment if it is needed
      value: function () {
        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 + " */";
      }
    },
    outputFile: {
      writable: true,


      // Return output CSS file path
      value: function () {
        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";
        }
      }
    },
    generateMap: {
      writable: true,


      // Return Result object with map
      value: function () {
        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];
        }
      }
    },
    relative: {
      writable: true,


      // Return path relative from output CSS file
      value: function (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;
        }
      }
    },
    sourcePath: {
      writable: true,


      // Return path of node source for map
      value: function (node) {
        return this.relative(node.source.file || node.source.id);
      }
    },
    stringify: {
      writable: true,


      // Return CSS string and source map
      value: function () {
        var _this3 = 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) {
          _this3.css += str;

          if (node && node.source && node.source.start && type != "end") {
            _this3.map.addMapping({
              source: _this3.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") {
            _this3.map.addMapping({
              source: _this3.sourcePath(node),
              original: {
                line: node.source.end.line,
                column: node.source.end.column
              },
              generated: {
                line: line,
                column: column
              }
            });
          }
        };

        this.root.stringify(builder);
      }
    },
    generate: {
      writable: true,


      // Return Result object with or without map
      value: function () {
        this.clearAnnotation();

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

  return MapGenerator;
})();

module.exports = MapGenerator;