'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.safePhysicallyRemove = exports.repairDep = exports.disablePlugin = exports.enablePlugin = exports.readPlugin = exports.removePackage = exports.installPackage = exports.findInstalledTarball = undefined;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

let installPackage = exports.installPackage = (() => {
  var _ref2 = _asyncToGenerator(function* (packageName, version, npmConfig) {
    if (!packageName) {
      return;
    }
    if (version) {
      packageName = `${packageName}@${version}`;
    }
    let args = ['install', '--registry', npmConfig.registry];
    if (npmConfig.http_proxy) {
      args = [...args, '--proxy', npmConfig.http_proxy];
    }
    args = [...args, '--no-progress', '--global-style', '--no-package-lock', packageName];
    yield runScriptAsync(NPM_EXEC_PATH, args, {
      cwd: npmConfig.prefix
    });
  });

  return function installPackage(_x3, _x4, _x5) {
    return _ref2.apply(this, arguments);
  };
})();

let removePackage = exports.removePackage = (() => {
  var _ref3 = _asyncToGenerator(function* (target, npmConfig) {
    const args = ['uninstall', '--no-progress', '--no-save', target];
    yield runScriptAsync(NPM_EXEC_PATH, args, {
      cwd: npmConfig.prefix
    });
    yield repairDep([], npmConfig);
  });

  return function removePackage(_x6, _x7) {
    return _ref3.apply(this, arguments);
  };
})();

let readPlugin = exports.readPlugin = (() => {
  var _ref4 = _asyncToGenerator(function* (pluginPath) {
    let pluginData, packageData, plugin;
    try {
      pluginData = yield (0, _fsExtra.readJson)((0, _pathExtra.join)(ROOT, 'assets', 'data', 'plugin.json'));
    } catch (error) {
      pluginData = {};
      utils.error(error);
    }
    try {
      packageData = yield (0, _fsExtra.readJson)((0, _pathExtra.join)(pluginPath, 'package.json'));
    } catch (error) {
      packageData = {};
      utils.error(error);
    }
    plugin = packageData.poiPlugin || {};
    // omit poiPlugin to avoid circular object
    plugin.packageData = (0, _lodash.omit)(packageData, 'poiPlugin');
    plugin.packageName = plugin.packageData.name || (0, _pathExtra.basename)(pluginPath);
    if (plugin.name == null) {
      plugin.name = plugin.title || plugin.packageName;
    }
    if (plugin.id == null) {
      plugin.id = plugin.packageName;
    }
    plugin.author = (0, _lodash.get)(plugin, 'packageData.author.name') || 'unknown';
    if (typeof (0, _lodash.get)(plugin, 'packageData.author') === 'string') {
      plugin.author = plugin.packageData.author;
    }
    plugin.link = (0, _lodash.get)(plugin, 'packageData.author.links') || (0, _lodash.get)(plugin, 'packageData.author.url') || (pluginData[plugin.packageName] || {}).link || "https://github.com/poooi";
    if (plugin.description == null) {
      plugin.description = (plugin.packageData || {}).description || (pluginData[plugin.packageName] || {})[`des${language}`] || "unknown";
    }
    // Resolve symlink.
    plugin.pluginPath = (0, _fsExtra.realpathSync)(pluginPath);
    // check if it is symbolic linked plugin
    // since function will be called when checking update, the second call
    // will check with real path, make sure to attribute it only when symbolic link
    const pluginStat = (0, _fsExtra.lstatSync)(pluginPath);
    if (pluginStat.isSymbolicLink()) {
      plugin.linkedPlugin = true;
    }
    if (plugin.icon == null) {
      plugin.icon = 'fa/th-large';
    }
    plugin.version = (plugin.packageData || {}).version || '0.0.0';
    plugin.latestVersion = plugin.version;
    if (!plugin.earliestCompatibleMain) {
      plugin.earliestCompatibleMain = '0.0.0';
    }
    if (!plugin.lastApiVer) {
      plugin.lastApiVer = plugin.version;
    }
    if (!plugin.priority) {
      plugin.priority = 10000;
    }
    plugin.enabled = config.get(`plugin.${plugin.id}.enable`, true);
    plugin.isInstalled = true;
    plugin.isUpdating = false;
    plugin.needRollback = false;
    if (plugin.apiVer) {
      let nearestCompVer = 'v214.748.3647';
      for (const mainVersion in plugin.apiVer) {
        if (_semver2.default.lte(window.POI_VERSION, mainVersion) && _semver2.default.lt(mainVersion, nearestCompVer) && _semver2.default.gt(plugin.version, plugin.apiVer[mainVersion])) {
          plugin.needRollback = true;
          nearestCompVer = mainVersion;
          plugin.latestVersion = plugin.apiVer[mainVersion];
        }
      }
    }
    plugin.isOutdated = plugin.needRollback;
    plugin = updateI18n(plugin);
    const icon = plugin.icon.split('/')[1] || plugin.icon || 'th-large';
    plugin.displayName = _react2.default.createElement(
      'span',
      null,
      _react2.default.createElement(_reactFontawesome2.default, { key: 0, name: icon }),
      ' ' + plugin.name
    );
    plugin.timestamp = Date.now();
    return plugin;
  });

  return function readPlugin(_x8) {
    return _ref4.apply(this, arguments);
  };
})();

let enablePlugin = exports.enablePlugin = (() => {
  var _ref5 = _asyncToGenerator(function* (plugin, reread = true) {
    if (!pathAdded.get(plugin.packageName) && !plugin.windowURL) {
      allowedPath.push(plugin.pluginPath);
      (0, _modulePath.setAllowedPath)(allowedPath);
      pathAdded.set(plugin.packageName, true);
    }
    if (plugin.needRollback) return plugin;
    let pluginMain;
    try {
      pluginMain = _extends({}, (yield Promise.resolve().then(() => require(`${plugin.pluginPath}`))), reread ? yield readPlugin(plugin.pluginPath) : {});
      pluginMain.enabled = true;
      pluginMain.isRead = true;
      if (!plugin.id && pluginMain.name) {
        pluginMain.id = pluginMain.name;
      }
    } catch (error) {
      console.error(error.stack);
      pluginMain = {
        enabled: false,
        isBroken: true
      };
    }
    plugin = _extends({}, plugin, pluginMain);
    if (plugin.windowURL) {
      plugin.realClose = !config.get(`poi.backgroundProcess.${plugin.id}`, !plugin.realClose);
    }
    plugin = postEnableProcess(plugin);
    return plugin;
  });

  return function enablePlugin(_x9) {
    return _ref5.apply(this, arguments);
  };
})();

let disablePlugin = exports.disablePlugin = (() => {
  var _ref6 = _asyncToGenerator(function* (plugin) {
    plugin.enabled = false;
    try {
      plugin = unloadPlugin(plugin);
    } catch (error) {
      console.error(error.stack);
    }
    return plugin;
  });

  return function disablePlugin(_x10) {
    return _ref6.apply(this, arguments);
  };
})();

let repairDep = exports.repairDep = (() => {
  var _ref7 = _asyncToGenerator(function* (brokenList, npmConfig) {
    const depList = (yield new Promise(function (res) {
      (0, _glob2.default)(_path2.default.join(npmConfig.prefix, 'node_modules', '*'), function (err, matches) {
        return res(matches);
      });
    })).filter(function (p) {
      return !p.includes('poi-plugin');
    });
    depList.forEach(function (p) {
      try {
        require(p);
      } catch (e) {
        safePhysicallyRemove(p, npmConfig);
      }
    });
    brokenList.forEach(function (pluginName) {
      installPackage(pluginName, null, npmConfig);
    });
  });

  return function repairDep(_x11, _x12) {
    return _ref7.apply(this, arguments);
  };
})();

// Unlink a path if it's a symlink.
// Do nothing (but logging error) if it's a git repo.
// Remove the directory otherwise.


exports.updateI18n = updateI18n;
exports.unloadPlugin = unloadPlugin;
exports.notifyFailed = notifyFailed;

var _lodash = require('lodash');

var _electron = require('electron');

var _pathExtra = require('path-extra');

var _fsExtra = require('fs-extra');

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _reactFontawesome = require('react-fontawesome');

var _reactFontawesome2 = _interopRequireDefault(_reactFontawesome);

var _semver = require('semver');

var _semver2 = _interopRequireDefault(_semver);

var _module2 = require('module');

var _module3 = _interopRequireDefault(_module2);

var _bluebird = require('bluebird');

var _glob = require('glob');

var _glob2 = _interopRequireDefault(_glob);

var _crypto = require('crypto');

var _crypto2 = _interopRequireDefault(_crypto);

var _modulePath = require('lib/module-path');

var _child_process = require('child_process');

var _child_process2 = _interopRequireDefault(_child_process);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _i18n = require('i18n-2');

var _i18n2 = _interopRequireDefault(_i18n);

var _createStore = require('views/create-store');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

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 { ROOT, config, language, toast, MODULE_PATH, APPDATA_PATH } = window;
const windowManager = _electron.remote.require('./lib/window');
const utils = _electron.remote.require('./lib/utils');
const __ = window.i18n.setting.__.bind(window.i18n.setting);

const allowedPath = [ROOT, APPDATA_PATH];
const pathAdded = new Map();
const NPM_EXEC_PATH = _path2.default.join(ROOT, 'node_modules', 'npm', 'bin', 'npm-cli.js');

require('module').globalPaths.push(MODULE_PATH);

// overwrites i18n-2's translation file cache, to be removed if we change to other lib
_i18n2.default.localeCache = new Proxy({}, {
  get: () => null
});

// This reducer clears the substore no matter what is given.
const clearReducer = undefined;

function calculateShasum(path) {
  return new Promise((resolve, reject) => {
    try {
      const hash = _crypto2.default.createHash('sha1');
      const stream = (0, _fsExtra.createReadStream)(path);

      stream.on('data', data => {
        hash.update(data, 'utf8');
      });

      stream.on('end', function () {
        resolve(hash.digest('hex'));
      });

      stream.on('error', function (e) {
        reject(e);
      });
    } catch (e) {
      reject(e);
    }
  });
}

const findInstalledTarball = exports.findInstalledTarball = (() => {
  var _ref = _asyncToGenerator(function* (pluginRoot, tarballPath) {
    const filename = (0, _pathExtra.basename)(tarballPath);
    const pluginPaths = yield (0, _bluebird.promisify)(_glob2.default)((0, _pathExtra.join)(pluginRoot, 'poi-plugin-*'));
    const packageDatas = yield Promise.all(pluginPaths.map(function (pluginPath) {
      return (0, _bluebird.promisify)(_fsExtra.readJson)((0, _pathExtra.join)(pluginPath, 'package.json'));
    }));
    // packageJson._required.raw should contain full path upon installation
    const nameMatchDatas = packageDatas.filter(function (packageData) {
      return (0, _lodash.get)(packageData, '_requested.raw', '').endsWith(filename);
    });
    if (nameMatchDatas.length === 1) {
      return nameMatchDatas[0].name;
    }
    if (nameMatchDatas.length === 0) {
      throw new Error(`Error: Can' find a package matching ${tarballPath}.`);
    }
    // In EXTREMELY tricky cases 2 differently named packages might have been
    // installed from the same path. Unbelievable huh? We can still match checksum.
    // packageJson._shasum should contain shasum.
    const shasum = yield calculateShasum(tarballPath);
    const shasumMatchDatas = nameMatchDatas.filter(function (data) {
      return data._shasum === shasum;
    });
    if (!shasumMatchDatas[0]) throw new Error(`Error: Can' find a package installed from ${tarballPath} matching shasum ${shasum}.`);
    // I believe it won't collide.
    return shasumMatchDatas[0].name;
  });

  return function findInstalledTarball(_x, _x2) {
    return _ref.apply(this, arguments);
  };
})();

const runScriptAsync = (scriptPath, args, options) => new Promise(resolve => {
  const proc = _child_process2.default.fork(scriptPath, args, options);
  proc.on('exit', () => resolve());
});

function updateI18n(plugin) {
  let i18nFile = null;
  if (plugin.i18nDir != null) {
    i18nFile = (0, _pathExtra.join)(plugin.pluginPath, plugin.i18nDir);
  } else {
    try {
      (0, _fsExtra.accessSync)((0, _pathExtra.join)(plugin.pluginPath, 'i18n'));
      i18nFile = (0, _pathExtra.join)(plugin.pluginPath, 'i18n');
    } catch (error) {
      try {
        (0, _fsExtra.accessSync)((0, _pathExtra.join)(plugin.pluginPath, 'assets', 'i18n'));
        i18nFile = (0, _pathExtra.join)(plugin.pluginPath, 'assets', 'i18n');
      } catch (error) {
        console.warn(`${plugin.packageName}: No translate file found.`);
      }
    }
  }
  if (i18nFile != null) {
    const namespace = plugin.id;
    window.i18n[namespace] = new _i18n2.default({
      locales: ['ko-KR', 'en-US', 'ja-JP', 'zh-CN', 'zh-TW'],
      defaultLocale: 'en-US',
      directory: i18nFile,
      updateFiles: false,
      indent: "\t",
      extension: '.json',
      devMode: false
    });
    window.i18n[namespace].setLocale(window.language);
    plugin.name = window.i18n[namespace].__(plugin.name);
    plugin.description = window.i18n[namespace].__(plugin.description);
  }
  return plugin;
}

const postEnableProcess = plugin => {
  if (plugin.reducer) {
    try {
      (0, _createStore.extendReducer)(plugin.packageName, plugin.reducer);
    } catch (e) {
      console.error(e.stack);
    }
  }
  let windowOptions;
  if (plugin.windowURL) {
    if (plugin.windowOptions) {
      windowOptions = plugin.windowOptions;
      if (!(0, _lodash.get)(windowOptions, 'webPreferences.preload')) {
        (0, _lodash.set)(windowOptions, 'webPreferences.preload', (0, _pathExtra.join)(ROOT, 'assets', 'js', 'plugin-preload.js'));
      }
    } else {
      windowOptions = {
        x: config.get('poi.window.x', 0),
        y: config.get('poi.window.y', 0),
        width: 800,
        height: 600,
        webPreferences: {
          preload: (0, _pathExtra.join)(ROOT, 'assets', 'js', 'plugin-preload.js'),
          webSecurity: false,
          plugins: true,
          experimentalFeatures: true,
          backgroundThrottling: false,
          offscreen: true
        }
      };
    }
    Object.assign(windowOptions, {
      realClose: plugin.realClose,
      backgroundColor: process.platform === 'darwin' ? '#00000000' : '#222222'
    });
    if (['darwin'].includes(process.platform) && config.get('poi.vibrant', 0) === 1) {
      Object.assign(windowOptions, {
        vibrancy: 'ultra-dark'
      });
    }
    if (plugin.multiWindow) {
      plugin.handleClick = function () {
        const pluginWindow = windowManager.createWindow(windowOptions);
        pluginWindow.setMenu(require('views/components/etc/menu').appMenu);
        pluginWindow.setAutoHideMenuBar(true);
        pluginWindow.setMenuBarVisibility(false);
        pluginWindow.loadURL(plugin.windowURL);
        pluginWindow.show();
      };
    } else if (plugin.realClose) {
      plugin.pluginWindow = null;
      plugin.handleClick = function () {
        if (plugin.pluginWindow == null) {
          plugin.pluginWindow = windowManager.createWindow(windowOptions);
          plugin.pluginWindow.setMenu(require('views/components/etc/menu').appMenu);
          plugin.pluginWindow.setAutoHideMenuBar(true);
          plugin.pluginWindow.setMenuBarVisibility(false);
          plugin.pluginWindow.on('close', function () {
            plugin.pluginWindow = null;
          });
          plugin.pluginWindow.loadURL(plugin.windowURL);
          plugin.pluginWindow.show();
        } else {
          plugin.pluginWindow.show();
        }
      };
    } else {
      plugin.pluginWindow = windowManager.createWindow(windowOptions);
      plugin.pluginWindow.setMenu(require('views/components/etc/menu').appMenu);
      plugin.pluginWindow.setAutoHideMenuBar(true);
      plugin.pluginWindow.setMenuBarVisibility(false);
      plugin.pluginWindow.loadURL(plugin.windowURL);
      plugin.handleClick = function () {
        return plugin.pluginWindow.show();
      };
    }
  }
  try {
    if (typeof plugin.pluginDidLoad === 'function') {
      plugin.pluginDidLoad();
    }
  } catch (error) {
    console.error(error.stack);
  }
  return plugin;
};

function clearPluginCache(packagePath) {
  for (const path in _module3.default._cache) {
    if (path.includes((0, _pathExtra.basename)(packagePath))) {
      delete _module3.default._cache[path];
    }
  }
  for (const path in _module3.default._pathCache) {
    if (path.includes((0, _pathExtra.basename)(packagePath))) {
      delete _module3.default._pathCache[path];
    }
  }
}

function unloadPlugin(plugin) {
  try {
    if (typeof plugin.pluginWillUnload === 'function') {
      plugin.pluginWillUnload();
    }
  } catch (error) {
    console.error(error.stack);
  }
  if (plugin.pluginWindow) {
    windowManager.closeWindow(plugin.pluginWindow);
  }
  // Here we clear caches of files under pluginPath with symlinks resolved.
  // Problems still exist where deeper symlinks are not resolved.
  // But this solved the major problem of using `npm link` .
  clearPluginCache(plugin.pluginPath);
  (0, _createStore.extendReducer)(plugin.packageName, clearReducer);
  return plugin;
}

function notifyFailed(state, npmConfig) {
  const plugins = state.filter(plugin => plugin.isBroken);
  const unreadList = [];
  const reinstallList = [];
  for (let i = 0; i < plugins.length; i++) {
    const plugin = plugins[i];
    unreadList.push(plugin.name);
    reinstallList.push(plugin.packageName);
  }
  if (unreadList.length > 0) {
    const content = `${unreadList.join(' / ')} ${__('failed to load. Maybe there are some compatibility problems.')}`;
    toast(content, {
      type: 'error',
      title: __('Plugin error')
    });
  }
  repairDep(reinstallList, npmConfig);
}

const safePhysicallyRemove = exports.safePhysicallyRemove = (() => {
  var _ref8 = _asyncToGenerator(function* (packagePath) {
    let packageStat;
    try {
      packageStat = yield (0, _bluebird.promisify)(_fsExtra.lstat)(packagePath);
    } catch (e) {
      // No longer exists
      return;
    }
    // If it's a symlink, unlink it
    if (packageStat.isSymbolicLink()) {
      return yield (0, _bluebird.promisify)(_fsExtra.unlink)(packagePath);
    }
    // If it's a git repo, log error and do nothing
    try {
      const gitStat = yield (0, _bluebird.promisify)(_fsExtra.lstat)((0, _pathExtra.join)(packagePath, '.git'));
      if (gitStat.isDirectory()) {
        console.error(`${packagePath} appears to be a git repository. For the safety of your files in development, please use 'npm link' to install plugins from github.`);
        return;
      }
    } catch (e) {
      return yield (0, _fsExtra.remove)(packagePath);
    }
  });

  return function safePhysicallyRemove(_x13) {
    return _ref8.apply(this, arguments);
  };
})();