require("shelljs/global");

var readline = require("readline-sync");
var semver   = require("semver");
var chalk    = require("chalk");
var child    = require("child_process");
var fs       = require("fs");

var PACKAGE_LOC = __dirname + "/../packages";
var VERSION_LOC = __dirname + "/../VERSION";

var CURRENT_VERSION = fs.readFileSync(VERSION_LOC, "utf8").trim();
console.log("Current version:", CURRENT_VERSION);

var FORCE_VERSION = process.env.FORCE_VERSION;
FORCE_VERSION = FORCE_VERSION ? FORCE_VERSION.split(",") : [];

//

function getVersion() {
  var input = readline.question("New version (Leave blank for new patch): ");

  var ver = semver.valid(input);
  if (!ver) {
    ver = semver.inc(CURRENT_VERSION, input || "patch");
  }

  if (ver) {
    return ver;
  } else {
    console.log("Version provided is not valid semver.");
    return getVersion();
  }
}

var NEW_VERSION = getVersion();
fs.writeFileSync(VERSION_LOC, NEW_VERSION, "utf8");

//

function exec(cmd, log) {
  console.log("$", cmd);

  var out = child.execSync(cmd, {
    encoding: "utf8"
  }).trim();

  if (log) {
    if (out) console.log(out);
  } else {
    return out;
  }
}

function getPackageLocation(name) {
  return PACKAGE_LOC + "/" + name;
}

function getPackageConfig(name) {
  return require(getPackageLocation(name) + "/package.json");
}

//

function updateDepsObject(changedPackages, deps) {
  for (var depName in deps) {
    if (changedPackages.indexOf(depName) >= 0) {
      deps[depName] = "^" + NEW_VERSION;
    }
  }
}

function publish() {
  var packageNames = fs.readdirSync(PACKAGE_LOC).filter(function (name) {
    return name[0] !== "." && fs.statSync(PACKAGE_LOC + "/" + name).isDirectory();
  });

  var lastTagCommit = exec("git rev-list --tags --max-count=1");
  var lastTag       = exec("git describe " + lastTagCommit);

  var changedPackages = [];
  var changedFiles = [VERSION_LOC];

  packageNames.forEach(function (name) {
    var config = getPackageConfig(name);
    if (config.private) return;

    // check if package has changed since last release
    var diff = exec("git diff " + lastTag + " -- " + getPackageLocation(name));
    if (diff || FORCE_VERSION.indexOf("*") >= 0 || FORCE_VERSION.indexOf(name) >= 0) {
      console.log(chalk.green("Changes detected to package", name));
      changedPackages.push(name);
    }
  });

  if (!changedPackages.length && !FORCE_VERSION.length) {
    throw new Error(chalk.red("No packages changed."));
  }

  //

  changedPackages.forEach(function (name) {
    var pkgLoc = getPackageLocation(name) + "/package.json";
    var pkg = require(pkgLoc);

    // set new version
    pkg.version = NEW_VERSION;

    // updated dependencies
    updateDepsObject(changedPackages, pkg.dependencies);
    updateDepsObject(changedPackages, pkg.devDependencies);

    // write new package
    fs.writeFileSync(pkgLoc, JSON.stringify(pkg, null, "  "));

    // push to be git committed
    changedFiles.push(pkgLoc);
  });

  changedFiles.forEach(function (loc) {
    exec("git add " + loc, true);
  });

  var NEW_TAG_NAME = "v" + NEW_VERSION;
  exec("git commit -m " + NEW_TAG_NAME, true);
  exec("git tag " + NEW_TAG_NAME, true);

  exec("make build-dist");

  changedPackages.forEach(function (name) {
    // prepublish script
    var prePub = getPackageLocation(name) + "/scripts/prepublish.js";
    if (fs.existsSync(prePub)) require(prePub);
  });

  changedPackages.forEach(function (name) {
    var loc = getPackageLocation(name);
    exec("cd " + loc + " && npm publish", true);

    // postpublish script
    var postPub = loc + "/scripts/postpublish.js";
    if (fs.existsSync(postPub)) require(postPub);
    publishedPackages.push(name);
  });
}

var publishedPackages = [];
var originalCommit = exec("git rev-list --all --max-count=1");;

try {
  publish();
} catch (err) {
  console.log(chalk.red("There was a problem publishing."));
  console.log(err.stack);
  return;

  if (publishedPackages.length) {
    console.log(chalk.warning("Unpublishing published packages..."));

    publishedPackages.forEach(function () {
      var verInfo = name + "@" + NEW_VERSION;
      try {
        console.log(chalk.warning("Unpublishing " + verInfo + "..."));
        //exec("npm unpublish --force " + verInfo);
      } catch (err) {
        console.log(chalk.red("Failed to unpublish " + verInfo));
        console.log(err.stack);
      }
    });
  } else {
    console.log(chalk.warning("Rolling back to commit", originalCommit, "..."));
    //exec("git checkout --hard " + originalCommit, true);
  }

  return;
}

exec("git push", true);
exec("git push --tags", true);
