#!/bin/bash

# An hook script to verify what is about to be pushed.
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#   <local ref> <local sha1> <remote ref> <remote sha1>

GITHOOK_CONFIG=.githookrc
GITHOOK_TEMP_FILE="$(mktemp)"

remote="$1"
url="$2"

z40=0000000000000000000000000000000000000000

default_config()
{
  disable_merge_check=
  disable_lint_check=
  use_inc_check=
  cur_dir=
  work_dir=
}

read_config()
{
  if [ -f "$GITHOOK_CONFIG" ]
  then
    . "$GITHOOK_CONFIG"
  fi
  if [ -z "$use_inc_check" ]
  then
    use_inc_check=
  else
    use_inc_check=1
  fi
  if [ -z "$disable_merge_check" ]
  then
    disable_merge_check=
  else
    disable_merge_check=1
  fi
  if [ -z "$disable_lint_check" ]
  then
    disable_lint_check=
  else
    disable_lint_check=1
  fi
}

find_dir()
{
  if [ -z "$work_dir" ]
  then
    cur_dir=$(pwd -P)
    local depth=0
    local node_dir=
    while [ -z "$node_dir" ]
    do
      depth=$(expr $depth + 1)
      node_dir=$(find "$cur_dir" -d $depth -type d -name "node_modules" | head -n 1)
      if [ $depth -gt 10 ]
      then
        echo >&2 "**** Not Found 'node_modules' ****  ⛔️"
        exit 1
      fi
    done
    cd "$node_dir"
    cd ..
  else
    cd "$work_dir"
  fi
  work_dir=$(pwd -P)
}

npm_test()
{
  echo "**** NPM Testing...   ****  🔍"
  npm test &> "$GITHOOK_TEMP_FILE"
  local test_result=$(cat "$GITHOOK_TEMP_FILE" | grep "npm ERR! Test failed.")
  if [ -n "$test_result" ]
  then
    cat "$GITHOOK_TEMP_FILE"
    echo >&2 "**** NPM Test ERROR!  ****  🐞"
    rm "$GITHOOK_TEMP_FILE"
    exit 1
  else
    rm "$GITHOOK_TEMP_FILE"
    echo "**** NPM Test PASSED! ****  🍻"
  fi 
}

check_range()
{
  if [ "$remote_sha" = $z40 ]
  then
    range=$(git merge-base --fork-point HEAD)
    range="$range..."
  else
    range="$remote_sha..$local_sha"
  fi
  echo "Range: $range"
  files=$(git diff --name-status "$range" "$work_dir" |grep "^[AM]" |awk '{ print $2 }' |grep "\.js$")
}

lint_test()
{
  local exit_code=0

  local eslint_bin="$work_dir/node_modules/.bin/eslint"
  local eslint_config="$work_dir/.eslintrc.json"
  local eslint_ignore="$work_dir/.eslintignore"

  if [ ! -e "$eslint_bin" ]
  then
    echo >&2 "Not found 'eslint' bin file, please install it with npm"
    echo "Ignore ESlint check"
    exit 0
  else
    if [ -f "$eslint_config" ]
    then
      eslint_config="--config $eslint_config"
    else
      eslint_config=
    fi
    if [ -f "$eslint_ignore" ]
    then
      eslint_ignore="--ignore-path $eslint_ignore"
    else
      eslint_ignore=
    fi
  fi

  local files
  check_range

  local lint_result
  if [ -z "$files" ]
  then
    # no js files modified or added
    exit 0
  else
    echo "**** ESlint Testing... ****  🔍"

    local tmp_file="$(mktemp)"
    echo "$files" > "$tmp_file"
    cat "$tmp_file"

    echo "eslint version:" "$("$eslint_bin" --version)"
    lint_result=$(cat "$tmp_file" |xargs \
      "$eslint_bin" \
      $eslint_config $eslint_ignore \
      > "$work_dir/$GITHOOK_TEMP_FILE" \
      && echo "**** ESlint PASSED!    ****  🍻")
    cat "$work_dir/$GITHOOK_TEMP_FILE"
    if [ -z "$lint_result" ]
    then
      echo >&2 "**** ESlint NOT Passed ****  🐞"
      exitcode=1
    else
      echo "$lint_result"
    fi

    rm "$work_dir/$GITHOOK_TEMP_FILE"
    rm "$tmp_file"

    # cd "$work_dir"
    echo ""
    exit $exitcode
  fi
}

check_ref_type()
{
  local result="heads"
  echo "$1" |grep -oh 'heads\|tags\|notes'
}

check_ref_merge()
{
  local local_ref1="$1"
  local local_sha1="$2"
  local result_check_merge=1
  if [ "$(check_ref_type "$1")" != "heads" ]
  then
    result_check_merge=0 # tag, note or others
  else
    # TODO: 'master' branch shoudle be config
    git fetch # update local refs
    master_sha1=$(git rev-parse refs/remotes/origin/master)
    if [[ "$(git merge-base --is-ancestor $master_sha1 $local_sha1; echo $?)" = "0" \
      || "$(git merge-base --is-ancestor $local_sha1 $master_sha1; echo $?)" = "0" ]]
    then
      result_check_merge=0
    else
      local master_parents=($(git rev-parse "$master_sha1"^@ | tr "\n" " ")) 
      local parents_count=${#master_parents[@]}
      if [ $parents_count -eq 2 ]
      then
        local master_2nd_parent_sha1=$(git rev-parse "$master_sha1"^2)
        if [[ "$(git merge-base --is-ancestor $master_2nd_parent_sha1 $local_sha1; echo $?)" = "0" ]]
        then
          result_check_merge=0
        fi
      elif [[ $parents_count -gt 2 ]]
      then
        # TODO: need to process case of more than 2 branches merging.
        result_check_merge=0
      fi
    fi
  fi

  if [[ $result_check_merge -eq 1 ]]
  then
    local branch=${local_ref1##*/}
    echo >&2 "*** Branch: $branch is behind refs/remotes/origin/master ***  ❌"
    echo "*** Run \"git checkout $branch && git merge origin/master\" to update $1 to refs/remotes/origin/master ! *** 🔧 "
    exit 1
  fi
}


default_config
read_config
find_dir

while read local_ref local_sha remote_ref remote_sha
do
  if [ "$local_sha" = $z40 ]
  then
    :
  else
    if [ -z "$disable_merge_check" ]
    then
      check_ref_merge "$local_ref" "$local_sha"
    fi

    if [ -z "$disable_lint_check" ] # if not set "disable_lint_check"
    then
      if [ -z "$use_inc_check" ] # if not set "use_inc_check"
      then
        cd "$work_dir"
        npm_test
      else
        cd "$cur_dir"
        lint_test
      fi
    fi
  fi
done
cd "$cur_dir"

exit 0
