#!/usr/bin/env node

var util = require('util');
var cp = require('child_process');

var Q = require('q');
var _ = require('lodash');
var semver = require('semver');

var exec = function (cmd) {
  return function () {
    var args = Array.prototype.slice.call(arguments, 0);
    args.unshift(cmd);
    var fullCmd = util.format.apply(util, args);
    return Q.nfcall(cp.exec, fullCmd).then(function (out) {
      return out[0].split('\n');
    });
  };
};

var andThen = function (fn, after) {
  return function () {
    return fn.apply(this, arguments).then(after);
  };
};

var oneArg = function (fn) {
  return function (arg) {
    return fn(arg);
  };
};

var oneLine = function (lines) {
  return lines[0].trim();
};

var noArgs = function (fn) {
  return function () {
    return fn();
  };
};

var identity = function (i) { return i; };

// like Q.all, but runs the comands in series
// useful for ensuring env state (like which branch is checked out)
var allInSeries = function (fn) {
  return function (args) {
    var results = [];
    var def;
    while (args.length > 0) {
      (function (arg) {
        if (def) {
          def = def.then(function () {
            return fn(arg);
          });
        } else {
          def = fn(arg);
        }
        def = def.then(function (res) {
          results.push(res);
        });
      }(args.pop()));
    }
    return def.then(function () {
      return results;
    });
  };
};

var compareBranches = function (left, right) {
  console.log('# These commits are in ' + left.name + ' but not in ' + right.name + '\n');
  console.log(_(left.log).
    difference(right.log).
    map(function (line) {
      return left.full[left.log.indexOf(line)]; // lol O(n^2)
    }).
    value().
    join('\n'));
};

var checkout = oneArg(exec('git checkout %s'));

var getCurrentBranch = andThen(noArgs(exec('git rev-parse --abbrev-ref HEAD')), oneLine);
var getTags = noArgs(exec('git tag'));
var getShaOfTag = oneArg(exec('git rev-list %s | head -n 1'));
var getTheLog = oneArg(exec('git log --pretty=oneline %s..HEAD | cat'));

// remember this so we can restore state
var currentBranch;

getCurrentBranch().
then(function (branch) {
  currentBranch = branch;
}).
then(getTags).
then(function (tags) {
  return tags.
    filter(semver.valid).
    map(semver.clean).
    sort(semver.rcompare);
}).
then(function (tags) {
  var major = tags[0].split('.')[0] + '.x';
  return tags.
    filter(function (ver) {
      return semver.satisfies(ver, major);
    });
}).
then(function (tags) {
  return _(tags).
    groupBy(function (tag) {
      return tag.split('.')[1];
    }).
    map(function (group) {
      return _.first(group);
    }).
    map(function (tag) {
      return 'v' + tag;
    }).
    value();
}).
then(function (tags) {
  var master = tags.pop();
  var stable = tags.pop();

  return [
    { name: stable.replace(/\d+$/, 'x'), tag: stable },
    { name: 'master', tag: master}
  ];
}).
then(allInSeries(function (branch) {
  return checkout(branch.name).
    then(function () {
      return getTheLog(branch.tag);
    }).
    then(function (log) {
      return log.
        filter(identity);
    }).
    then(function (log) {
      branch.full = log.map(function (line) {
        line = line.split(' ');
        var sha = line.shift();
        var msg = line.join(' ');
        return sha + (msg.toLowerCase().indexOf('fix') === -1 ? '   ' : ' * ') + msg;
      });
      branch.log = log.map(function (line) {
        return line.substr(41)
      });
      return branch;
    });
})).
then(function (pairs) {
  compareBranches(pairs[0], pairs[1]);
  console.log('\n');
  compareBranches(pairs[1], pairs[0]);
  return pairs;
}).
then(function () {
  return checkout(currentBranch);
}).
catch(function (e) {
  console.log(e.stack);
});

