#!/usr/bin/env node

'use strict';

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 /** @this */ 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 commands 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 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 = semver(tags[0]).major;
  return tags.
    filter(function(ver) {
      return semver(ver).major === 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 + ((/fix\([^)]+\):/i.test(msg))  ? ' * ' : '   ') + 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);
});

