#!/bin/bash
## ---------------------------------------------------------------------
##
##  Copyright (C) 2015 by the ASPECT authors
##
##  This file is part of ASPECT.
##
##  ASPECT is free software; you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation; either version 2, or (at your option)
##  any later version.
##
##  ASPECT is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with ASPECT; see the file LICENSE.  If not see
##  <http://www.gnu.org/licenses/>.
##
## ---------------------------------------------------------------------


#
# Mac OSX's mktemp doesn't know the --tmpdir option without argument. So,
# let's do all of that by hand:
#
export TMPDIR="${TMPDIR:-/tmp}"


find_astyle() {
  # Split $PATH variable into an array
  IFS=':' PATHS=(${PATH})

  # Loop over available astyle executables in those $PATH directories
  find "${PATHS[@]}" -iname '*astyle*' -print0 2>/dev/null \
  | while IFS=  read -r -d $'\0' astyle; do
    if test "$("${astyle}" --version 2>&1)" == "Artistic Style Version 2.04" ; then
      echo "${astyle}"
      return
    fi
  done
}


if test ! -d source -o ! -d include ; then
  echo "*** This script must be run from the top-level directory of ASPECT."
  exit 1
fi

if test ! -f contrib/utilities/astyle.rc ; then
  echo "*** No style file doc/astyle.rc found."
  exit 1
fi

astyle="$(find_astyle)"
astyle="${astyle:-`which astyle`}" # Fallback to `which astyle`
export astyle

if test -z "${astyle}"; then
  echo "*** No astyle program found."
  exit 1
fi

if test "$("${astyle}" --version 2>&1)" != "Artistic Style Version 2.04" ; then
  echo "*** Found a version of astyle different than the required version 2.04."
  exit 1
fi

echo "--- Found astyle 2.04 at ${astyle}"


# Indent a file.
#
# We use astyle 2.04 for indentation, which is a tool that predates C++11
# and that consequently has trouble with things such as
#   std::vector<Point<dim>>
# where it interprets the closing `>>` as a right shift operator. It then
# gets its template closing angle bracket counter all out of whack and
# produces awkward indentations. To avoid this, we first convert all closing
# `>>` into `> >`, run astyle, and then change it back. This leads to reasonable
# results.
#
# One might think that a regex substitution such as
#   s/>>/> >/g;
# would work (and putting the space on the left hand side for the way back).
# But this is not true because Perl does not go back to look at the substitution
# again, even with the /g modifier. As a consequence, the regex above
# replaces *triple* closing angle brackets as
#   ">>>"  ->  "> >>"
# and it does not look at the ">>" on the right hand side because the 'point'
# within the string where the regex matcher starts looking again with /g
# is now the last ">", having replaced the initial ">>" by "> >". Clearly,
# strings such as ">>>>" with four or more closing brackets encounter
# similar issues.
#
# The way to work around this is to replace "a '>' preceded by another
# '>' by ' >'". This is expressed with positive look-behind, and leads to
# the regex substitution
#   s/(?<=>)>/ >/g
# where "(?<=" starts the positive look-behind expression. Note that the
# look-behind expression is not part of the match itself (which is only ">"
# in the expression above) and is only used to restrict which occurrences
# of ">" are actually matches (and consequently substituted).
#
# For unclear reasons, astyle insists on appending a separate empty line
# at the end of the file. Fix this up using the last two 'sed' commands,
# using the same logic as in the 'ensure_single_trailing_newline()'
# function below.
indent_file() {
  file="${1}"
  tmpfile="$(mktemp "${TMPDIR}/$(basename "$1").tmp.XXXXXXXX")"

  cat ${file} \
      | perl -p -e 's/(?<=>)>/ >/g;' \
      | "${astyle}" --options=contrib/utilities/astyle.rc \
      | perl -p -e 's/(?<=>) >/>/g;' \
      | sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' \
      | sed -e '$a\' \
      > ${tmpfile}

  if ! diff -q "${file}" "${tmpfile}" >/dev/null; then
    mv "${tmpfile}" "${file}"
  else
    rm "${tmpfile}"
  fi
}
export -f indent_file



# Collect all header and source files and process them 
# with up to 10 processes in parallel.
#
echo "--- Indenting all ASPECT header and source files"

find tests include source benchmarks cookbooks unit_tests \
     \( -name '*.cc' -o -name '*.h' -o -name '*.h.in' \) -print \
  | xargs -P 10 -I {} bash -c "indent_file {}"


# remove execute permission on source files:
find tests include source benchmarks cookbooks unit_tests \( -name '*.cc' -o -name '*.h' -o -name '*.prm' \) -print | xargs -n 50 -P 10 chmod -x

# convert dos formatted files to unix file format by stripping out
# carriage returns (15=0x0D):
dos_to_unix()
{
    f=$1
    tr -d '\015' <$f >$f.tmp
    if ! diff -q $f $f.tmp >/dev/null ; then
      mv $f.tmp $f
    else
      rm $f.tmp
    fi
}
export -f dos_to_unix
find tests include source unit_tests cookbooks benchmarks doc \( -name '*.cc' -o -name '*.h' -o -name '*.prm' -o -name '*.h.in' -o -name '*.md' \) -print | xargs -P 10 -I {} bash -c 'dos_to_unix "$@"' _ {}

# convert tabs to two spaces:
tab_to_space()
{
    f=$1
    # awkward tab replacement because of OSX sed, do not change unless you test it on OSX
    TAB=$'\t'
    sed -e "s/$TAB/  /g" $f >$f.tmp
    diff -q $f $f.tmp >/dev/null || mv $f.tmp $f
    rm -f $f.tmp
}
export -f tab_to_space
find tests include source unit_tests cookbooks benchmarks doc \( -name '*.cc' -o -name '*.h' -o -name '*.prm' -o -name '*.h.in' -o -name 'CMakeLists.txt' -o -name '*.cmake' -o -name '*.md' \) -print | xargs -P 10 -I {} bash -c 'tab_to_space "$@"' _ {}

# Remove trailing whitespace from files
remove_trailing_whitespace()
{
    f=$1
    # awkward tab replacement because of OSX sed, do not change unless you test it on OSX
    TAB=$'\t'
    sed -e "s/[ $TAB]*$//"  $f >$f.tmp
    diff -q $f $f.tmp >/dev/null || mv $f.tmp $f
    rm -f $f.tmp
}
export -f remove_trailing_whitespace
find tests include source unit_tests cookbooks benchmarks doc \( -name '*.cc' -o -name '*.h' -o -name '*.prm' -o -name '*.h.in' -o -name 'CMakeLists.txt' -o -name '*.cmake' -o -name '*.md' \) -print | xargs -P 10 -I {} bash -c 'remove_trailing_whitespace "$@"' _ {}


# Ensure only a single newline at end of files
ensure_single_trailing_newline()
{
  f=$1

  # Remove newlines at end of file
  # Check that the current line only contains newlines
  # If it doesn't match, print it
  # If it does match and we're not at the end of the file,
  # append the next line to the current line and repeat the check
  # If it does match and we're at the end of the file,
  # remove the line.
  sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' $f >$f.tmpi

  # Then add a newline to the end of the file
  # '$' denotes the end of file
  # 'a\' appends the following text (which in this case is nothing)
  # on a new line
  sed -e '$a\' $f.tmpi >$f.tmp

  diff -q $f $f.tmp >/dev/null || mv $f.tmp $f
  rm -f $f.tmp $f.tmpi
}
export -f ensure_single_trailing_newline
find include source unit_tests cookbooks benchmarks doc \( -name '*.cc' -o -name '*.h' -o -name '*.prm' -o -name '*.h.in' -o -name 'CMakeLists.txt' -o -name '*.cmake' -o -name '*.md' \) -print | xargs -P 10 -I {} bash -c 'ensure_single_trailing_newline "$@"' _ {}

# Do not recursively search the tests directory for prm files, otherwise the output prms will be changed
find tests \( -name '*.cc' -o -name '*.h' -o -name '*.h.in' -o -name 'CMakeLists.txt' -o -name '*.cmake' \) -print | xargs -P 10 -I {} bash -c 'ensure_single_trailing_newline "$@"' _ {}
find tests -maxdepth 1 \( -name '*.prm' \) -print | xargs -P 10 -I {} bash -c 'ensure_single_trailing_newline "$@"' _ {}
exit 0
