const fs = require("fs");
const mkdirp = require("mkdirp");
const lockfile = require("proper-lockfile");

const createMutex = require("./createMutex");
const createTempPath = require("./createTempPath");
const fetchPackage = require("./fetchPackage");

const fetchMutex = createMutex((packageConfig, callback) => {
  const tarballURL = packageConfig.dist.tarball;
  const outputDir = createTempPath(packageConfig.name, packageConfig.version);

  fs.access(outputDir, error => {
    if (error) {
      if (error.code === "ENOENT" || error.code === "ENOTDIR") {
        // ENOENT or ENOTDIR are to be expected when we haven't yet
        // fetched a package for the first time. Carry on!
        mkdirp.sync(outputDir);
        const release = lockfile.lockSync(outputDir);

        fetchPackage(tarballURL, outputDir).then(
          () => {
            release();
            callback(null, outputDir);
          },
          error => {
            release();
            callback(error);
          }
        );
      } else {
        callback(error);
      }
    } else {
      lockfile.check(outputDir).then(locked => {
        if (locked) {
          // Another process on this same machine has locked the
          // directory. We need to wait for it to be unlocked
          // before we callback.
          const timer = setInterval(() => {
            lockfile.check(outputDir).then(
              locked => {
                if (!locked) {
                  clearInterval(timer);
                  callback(null, outputDir);
                }
              },
              error => {
                clearInterval(timer);
                callback(error);
              }
            );
          }, 10);

          timer.unref();
        } else {
          // Best case: we already have this package cached on disk
          // and it's not locked!
          callback(null, outputDir);
        }
      }, callback);
    }
  });
}, packageConfig => packageConfig.dist.tarball);

function getPackage(packageConfig) {
  return new Promise((resolve, reject) => {
    fetchMutex(packageConfig, (error, value) => {
      if (error) {
        reject(error);
      } else {
        resolve(value);
      }
    });
  });
}

module.exports = getPackage;