'use strict';

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

const Packager = require('./Packager');
const t = require('babel-types');
const path = require('path');
const fs = require('fs');

const concat = require('../transforms/concat');

const prelude = fs.readFileSync(path.join(__dirname, '../builtins/prelude2.js'), 'utf8').trim();

class JSConcatPackager extends Packager {
  write(string) {
    var _this = this;

    return _asyncToGenerator(function* () {
      _this.buffer += string;
    })();
  }

  start() {
    var _this2 = this;

    return _asyncToGenerator(function* () {
      _this2.addedAssets = new Set();
      _this2.exposedModules = new Set();
      _this2.buffer = '';
      _this2.exports = new Map();
      _this2.wildcards = new Map();
      _this2.moduleMap = new Map();
      _this2.needsPrelude = process.env.NODE_ENV === 'test';

      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

      try {
        for (var _iterator = _this2.bundle.assets[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
          let asset = _step.value;

          // If this module is referenced by another bundle, it needs to be exposed externally.
          let isExposed = !Array.from(asset.parentDeps).every(function (dep) {
            return _this2.bundle.assets.has(_this2.bundler.loadedAssets.get(dep.parent));
          });

          if (isExposed || _this2.bundle.entryAsset === asset && _this2.bundle.parentBundle) {
            _this2.exposedModules.add(asset);
            _this2.needsPrelude = true;
          }

          var _iteratorNormalCompletion2 = true;
          var _didIteratorError2 = false;
          var _iteratorError2 = undefined;

          try {
            for (var _iterator2 = asset.depAssets.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
              let mod = _step2.value;

              if (!_this2.bundle.assets.has(mod)) {
                _this2.needsPrelude = true;
                break;
              }
            }
          } catch (err) {
            _didIteratorError2 = true;
            _iteratorError2 = err;
          } finally {
            try {
              if (!_iteratorNormalCompletion2 && _iterator2.return) {
                _iterator2.return();
              }
            } finally {
              if (_didIteratorError2) {
                throw _iteratorError2;
              }
            }
          }
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator.return) {
            _iterator.return();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }

      if (_this2.needsPrelude) {
        if (_this2.bundle.entryAsset) {
          _this2.exposedModules.add(_this2.bundle.entryAsset);
        }

        yield _this2.write(prelude + '(function (require) {\n');
      } else {
        yield _this2.write('(function () {\n');
      }
    })();
  }

  getExportIdentifier(asset) {
    return '$' + asset.id + '$exports';
  }

  addAsset(asset) {
    var _this3 = this;

    return _asyncToGenerator(function* () {
      if (_this3.addedAssets.has(asset)) {
        return;
      }

      _this3.addedAssets.add(asset);
      let js = asset.generated.js;

      _this3.moduleMap.set(asset.id, asset);
      _this3.wildcards.set(asset.id, asset.cacheData.wildcards);

      for (let key in asset.cacheData.exports) {
        let local = '$' + asset.id + '$export$' + asset.cacheData.exports[key];
        if (key !== local) {
          _this3.exports.set(key, local);
        }
      }

      var _iteratorNormalCompletion3 = true;
      var _didIteratorError3 = false;
      var _iteratorError3 = undefined;

      try {
        for (var _iterator3 = asset.depAssets[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
          let _ref = _step3.value;

          var _ref2 = _slicedToArray(_ref, 2);

          let dep = _ref2[0];
          let mod = _ref2[1];

          let depName = '$' + asset.id + '$require$' + t.toIdentifier(dep.name);
          let moduleName = _this3.getExportIdentifier(mod);

          // If this module is not in the current bundle, generate a `require` call for it.
          if (!_this3.bundle.assets.has(mod)) {
            moduleName = `require(${mod.id})`;
          }

          js = js.split(depName).join(moduleName);

          // If this was an ES6 export all (e.g. export * from 'foo'), resolve to the original exports.
          if (dep.isExportAll) {
            for (let exp in mod.cacheData.exports) {
              let id = mod.cacheData.exports[exp];
              if (id !== 'default') {
                let key = '$' + asset.id + '$export$' + id;
                asset.cacheData.exports[key] = id;
                _this3.exports.set(key, _this3.exports.get('$' + mod.id + '$export$' + id) || exp);
              }
            }
          }

          if (dep.isES6Import) {
            if (mod.cacheData.isES6Module) {
              js = js.split('$' + asset.id + '$import$' + t.toIdentifier(dep.name)).join('$' + mod.id + '$export');
            } else {
              js = js.split('$' + asset.id + '$import$' + t.toIdentifier(dep.name) + '$default').join('$' + mod.id + '$exports');
              js = js.split('$' + asset.id + '$import$' + t.toIdentifier(dep.name) + '$').join('$' + mod.id + '$exports.');
            }
          }

          let depResolve = '$' + asset.id + '$require_resolve$' + t.toIdentifier(dep.name);
          let resolved = '' + mod.id;

          if (dep.dynamic && _this3.bundle.childBundles.has(mod.parentBundle)) {
            let bundles = [_this3.getBundleSpecifier(mod.parentBundle)];
            var _iteratorNormalCompletion4 = true;
            var _didIteratorError4 = false;
            var _iteratorError4 = undefined;

            try {
              for (var _iterator4 = mod.parentBundle.siblingBundles[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
                let child = _step4.value;

                if (!child.isEmpty) {
                  bundles.push(_this3.getBundleSpecifier(child));
                  yield _this3.addBundleLoader(child.type);
                }
              }
            } catch (err) {
              _didIteratorError4 = true;
              _iteratorError4 = err;
            } finally {
              try {
                if (!_iteratorNormalCompletion4 && _iterator4.return) {
                  _iterator4.return();
                }
              } finally {
                if (_didIteratorError4) {
                  throw _iteratorError4;
                }
              }
            }

            bundles.push(mod.id);
            resolved = JSON.stringify(bundles);
            yield _this3.addBundleLoader(mod.type);
          }

          js = js.split(depResolve).join(resolved);
        }

        // Replace all re-exported variables
      } catch (err) {
        _didIteratorError3 = true;
        _iteratorError3 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion3 && _iterator3.return) {
            _iterator3.return();
          }
        } finally {
          if (_didIteratorError3) {
            throw _iteratorError3;
          }
        }
      }

      js = js.trim() + '\n';

      yield _this3.write(`\n/* ASSET: ${asset.id} - ${path.relative(_this3.options.rootDir, asset.name)} */\n${js}`);
    })();
  }

  getBundleSpecifier(bundle) {
    let name = path.basename(bundle.name);
    if (bundle.entryAsset) {
      return [name, bundle.entryAsset.id];
    }

    return name;
  }

  addAssetToBundle(asset) {
    var _this4 = this;

    return _asyncToGenerator(function* () {
      if (_this4.bundle.assets.has(asset)) {
        return;
      }
      _this4.bundle.addAsset(asset);
      if (!asset.parentBundle) {
        asset.parentBundle = _this4.bundle;
      }

      // Add all dependencies as well
      var _iteratorNormalCompletion5 = true;
      var _didIteratorError5 = false;
      var _iteratorError5 = undefined;

      try {
        for (var _iterator5 = asset.depAssets.values()[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
          let child = _step5.value;

          yield _this4.addAssetToBundle(child, _this4.bundle);
        }
      } catch (err) {
        _didIteratorError5 = true;
        _iteratorError5 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion5 && _iterator5.return) {
            _iterator5.return();
          }
        } finally {
          if (_didIteratorError5) {
            throw _iteratorError5;
          }
        }
      }

      yield _this4.addAsset(asset);
    })();
  }

  addBundleLoader(bundleType) {
    var _this5 = this;

    return _asyncToGenerator(function* () {
      let bundleLoader = _this5.bundler.loadedAssets.get(require.resolve('../builtins/bundle-loader'));
      if (!bundleLoader) {
        bundleLoader = yield _this5.bundler.getAsset('_bundle_loader');
      }

      if (bundleLoader) {
        yield _this5.addAssetToBundle(bundleLoader);
      } else {
        return;
      }

      let loader = _this5.options.bundleLoaders[bundleType];
      if (loader) {
        let asset = yield _this5.bundler.getAsset(loader);
        if (!_this5.bundle.assets.has(asset)) {
          yield _this5.addAssetToBundle(asset);
          yield _this5.write(`${_this5.getExportIdentifier(bundleLoader)}.register(${JSON.stringify(bundleType)},${_this5.getExportIdentifier(asset)});\n`);
        }
      }
    })();
  }

  end() {
    var _this6 = this;

    return _asyncToGenerator(function* () {
      if (_this6.needsPrelude) {
        let exposed = [];
        var _iteratorNormalCompletion6 = true;
        var _didIteratorError6 = false;
        var _iteratorError6 = undefined;

        try {
          for (var _iterator6 = _this6.exposedModules[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
            let m = _step6.value;

            exposed.push(`${m.id}: ${_this6.getExportIdentifier(m)}`);
          }
        } catch (err) {
          _didIteratorError6 = true;
          _iteratorError6 = err;
        } finally {
          try {
            if (!_iteratorNormalCompletion6 && _iterator6.return) {
              _iterator6.return();
            }
          } finally {
            if (_didIteratorError6) {
              throw _iteratorError6;
            }
          }
        }

        yield _this6.write(`return {${exposed.join(', ')}};\n})`);
      } else {
        yield _this6.write('})();');
      }

      Packager.prototype.write.call(_this6, concat(_this6.buffer, _this6.exports, _this6.moduleMap, _this6.wildcards));
    })();
  }
}

module.exports = JSConcatPackager;