#! /usr/bin/env bash

#
# Prelude - make bash behave sanely
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
#
set -euo pipefail
IFS=$'\n\t'

#
# Globals
#
remember_git_start_and_end() {
  HEAD="$(git rev-parse HEAD)"
  ROOT="$(git log --pretty=format:%H | tail -n 1)"
}

#
# Utilities
#
die() {
  local msg="$@"
  [[ -z "${msg}" ]] || {
    tput setaf 1  # red
    tput bold
    echo "${msg}"
    tput sgr0     # reset
  }
  exit 1
}

error() {
  local msg="$@"
  echo -n '| '
  tput setaf 1  # red
  echo -n ' ✖'
  tput sgr0     # reset
  echo " ${msg}"
}

success() {
  local msg="$@"
  echo -n '| '
  tput setaf 2  # green
  echo -n ' ✓'
  tput sgr0     # reset
  echo " ${msg}"
}

log_commit() {
  echo "○ $@"
}

# Check a command is present
ensure_command() {
  local cmd="$1"

  command -v "${cmd}" > /dev/null 2>&1 || {
    die "Couldn't find required command: ${cmd}"
  }
}

#
# Signal handling
#
cleanup() {
  git reset --hard "${HEAD}" > /dev/null 2>&1
  rm -f $$_commit_message
}

trap cleanup SIGHUP SIGINT SIGTERM

#
# Git helpers
#

# Go back one commit in history (first parent for merges)
step_back_one_commit() {
  git reset --hard HEAD^ > /dev/null
  log_commit "$(git rev-parse HEAD)"
}

current_commit_message() {
  GIT_PAGER= git log --format=%B -n 1
}

current_commit_sha() {
  git rev-parse HEAD
}

exit_if_not_git_repo() {
  local gitroot="$(git rev-parse --show-toplevel 2> /dev/null)"

  [[ "${gitroot}" == "" ]] && die 'Current directory is not in a repository'
  return 0
}

#
# Checks
#
check_commit_message() {
  local lineno=0
  local length=0
  local succeded=1

  while read -r line ; do
    let succeded=1
    let lineno+=1
    length=${#line}

    [[ "${lineno}" -eq "1" ]] && {
      [[ "${length}" -gt 50 ]] && {
        error "Commit message: Subject line longer than 50 characters";
        succeded=0
      };

      [[ ! "${line}" =~ ^[A-Z].*$ ]] && {
        error "Commit message: Subject line not capitalised";
        succeded=0
      };

      [[ "${line}" == *. ]] && {
        error "Commit message: Subject line ended with a full stop";
        succeded=0
      };
    }

    [[ "${lineno}" -eq "2" ]] && [[ -n "${line}" ]] && {
      error "Commit message: Subject line not separated by a blank line";
      succeded=0;
    };

    [[ "${lineno}" -gt "1" ]] && [[ "${length}" -gt "72" ]] && {
      error "Commit message: Body not wrapped at 72 characters";
      succeded=0
    };
  done < $$_commit_message

  [[ "${succeded}" -eq "1" ]] && success "Commit message"
  return 0
}

run_checks() {
  current_commit_message > $$_commit_message
  check_commit_message
  rm -f $$_commit_message
  set +e
  npm run lint > /dev/null 2>&1
  if [[ "$?" -eq 0 ]]; then
    success 'Lint'
  else
    error 'Lint: script did not exit successfully'
  fi
  npm test > /dev/null 2>&1
  if [[ "$?" -eq 0 ]]; then
    success 'Test'
  else
    error 'Test: script did not exit successfully'
  fi
  set -e
}

check_project() {
  exit_if_not_git_repo

  [[ -f './package.json' ]] || {
    die 'This does not appear to be a node project'
  }

  [[ -z "$(json -f package.json 'scripts.lint')" ]] && {
    die 'There is no lint script in the package.json'
  }

  [[ -z "$(json -f package.json 'scripts.test')" ]] && {
    die 'There is no test script in the package.json'
  }

  return 0
}

traverse_history() {
  while [[ "${ROOT}" != "$(current_commit_sha)" ]]  ; do
    run_checks
    step_back_one_commit
  done
}

#
# Main
#
ensure_command git
ensure_command tail
ensure_command npm
ensure_command json
check_project
remember_git_start_and_end
log_commit "HEAD: $(current_commit_sha)"
traverse_history
run_checks
log_commit "ROOT: $(current_commit_sha)"
cleanup

# vim: syntax=sh et ts=2 sts=2 sw=2
