#!/bin/bash

function showHelp {
  cat << EOF
$(basename $0) - Converts a user option tree to the new Gradle structure

Usage:
  $(basename $0) [OPTIONS] inPath outPath

  inPath  - The input path (see below).
  outPath - The output path (see below)

Options:
  -h | -help   | --help     Show this help and exit.
  -m | -multi  | --multi    Use Multi  Mode (see below).
  -s | -single | --single   Use Single Mode (see below).
  -o | -opt    | --opt      Use Opt    Mode (see below).

  -r name | -rootProj name  The name of the root project (used with
                            Multi Mode).
  -v ver  | -version  ver   The version number (normally taken from
                            version.txt).

Modes:
  This has three modes based on the starting/ending option tree structure.
  In most cases it can auto-detect the mode, but the command-line argument
  can be provided to make the choice explicit.

  Multi Mode
    This mode is where the output will be made up of multiple sub-projects
    similar to what is done for the option trees distributed with NeXtMidas
    (i.e. where SYS, DSP, UCL, etc. are all subprojects grouped under a
    top-level project).

    The input can either be an existing structure with multiple option trees
    or a single option tree similar to Single Mode but with an output that
    matches multi-mode. (In the example below, .git is used as a stand-in
    to show where the roof of the version control is.)

    In any case, the inPath will be the path that contains the nxm directory
    that is the root of the Java classpath and all option trees under it
    will be converted.

    +----------------------------------+----------------------------------+
    | inPath                           | outPath                          |
    |   +-- .git                       |   +-- .git                       |
    |   +-- nxm                        |   +-- build.gradle               |
    |         +-- myopt1   (required)  |   +-- myopt1                     |
    |         +-- myopt2   (optional)  |   |     +-- src                  |
    |                                  |   |     +-- build.gradle         |
    |                                  |   +-- myopt2                     |
    |                                  |         +-- src                  |
    |                                  |         +-- build.gradle         |
    +----------------------------------+----------------------------------+
                                     - or -
    +----------------------------------+----------------------------------+
    | inPath                           | outPath                          |
    |   +-- nxm                        |   +-- .git                       |
    |         +-- myopt1   (required)  |   +-- build.gradle               |
    |         |     +-- .git           |   +-- myopt1                     |
    |         +-- myopt2   (prohibited)|         +-- src                  |
    |                                  |         +-- build.gradle         |
    +----------------------------------+----------------------------------+

  Single Mode
    This mode is where the output will be made up of a single Gradle project
    using an input that contains exactly one option tree that includes the
    full Java classpath. There is also some flexibility as to where the
    .git directory is when using this mode.

    +----------------------------------+----------------------------------+
    | inPath                           | outPath                          |
    |   +-- .git               (opt 1) |   +-- .git                       |
    |   +-- nxm                        |   +-- build.gradle               |
    |         +-- myopt                |   +-- src                        |
    |               +-- .git   (opt 2) |                                  |
    +----------------------------------+----------------------------------+

  Opt Mode
    This mode is similar to the second form of Single Mode except that the
    input does not contain the base of the classpath and thus lives under a
    nmopts-like structure.

    Note that once some basic option tree information is obtained, this mode
    will inPath up to where nmopts is and then process similar to Single Mode
    as there is 90% commonality between the two.

    +----------------------------------+----------------------------------+
    | nmopts                           | outPath                          |
    |   +-- nxm                        |   +-- .git                       |
    |        +-- myopt       (inPath)  |   +-- build.gradle               |
    |        |     +-- .git            |   +-- src                        |
    |        +-- otheropt1   (ignored) |                                  |
    |        +-- otheropt2   (ignored) |                                  |
    +----------------------------------+----------------------------------+
EOF
}

# An error that shows the error message and help before exiting
function fatal {
  printf -- "ERROR: $1\n"
  showHelp
  exit 1
}

# An error that shows the error message (but no help) before exiting
function error {
  printf -- "ERROR: $1\n"
  printf -- "Conversion Failed\n"
  exit 1
}

# A warning message that adds to the list for output later on (this is sensitive
# to the value of optName)
function warning {
  printf -- "WARNING: $1\n"
  if [[ "$optName" == "" ]]; then
    warnings="${warnings}\n  --ALL--\t: $1"
  else
    warnings="${warnings}\n  $(echo $optName | tr a-z A-Z)\t: $1"
  fi
}

# An info message
function info {
  printf -- "$1\n"
}

function getVersionNum {
  if [[ "$projVersion" != "" ]]; then
    echo "$projVersion"
  elif [[ -f "$1" ]]; then
    ver="$(grep -m1 -i version: "$1" | tr A-Z a-z | sed 's/.*version:[ ]*//g' | awk '{print $1}')"
    if [[ "$ver" != "" ]]; then
      echo "$ver"
    else
      echo "0.0.0"
    fi
  else
    echo "0.0.0"
  fi
}

# Generates settings.gradle uses: rootProject
function genSettingsGradle {
  echo "rootProject.name='${rootProject}'"
  echo ""
  echo "// Find the directories containing a build.gradle file in the root directory"
  echo "// of the project. That is, every directory containing a build.gradle will"
  echo "// be automatically the subproject of this project."
  echo "def subDirs = rootDir.listFiles(new FileFilter() {"
  echo "  public boolean accept(File file) {"
  echo "    if (!file.isDirectory()) {"
  echo "      return false"
  echo "    }"
  echo "    if (file.name == 'buildSrc' || file.name == 'xbc' || file.name == 'gradle') {"
  echo "      return false"
  echo "    }"
  echo "    return new File(file, 'build.gradle').isFile()"
  echo "  }"
  echo "});"
  echo "subDirs.each { File dir ->"
  echo "  include dir.name"
  echo "}"
}

# Generates gradle.properties uses: optName, optVersion
function genGradleProperties {
  echo "project.name='${optName}'"
  echo "${optName}.version=${optVersion}"
}

# Generates build.gradle
function genBuildGradle {
  if [[ "$mode" != "multi" ]]; then
    echo "apply plugin: 'java'"
    echo "apply plugin: 'base'"
    echo ""
  fi
  echo "dependencies {"
  echo "  // NeXtMidas SYS dependency (required)"
  echo "  implementation project(':nxm.sys')"
  echo ""
  echo "  // Other dependencies (optional)"
  echo "}"
}

# ==== Parse command-line arguments ===========================================
useLinks=false
while [[ "$1" != "" ]]; do
  case $1 in
    # Switches
    -h | -help     | --help     ) showHelp;                exit 0;;
    -l | -link     | --link     ) useLinks=true;           shift;;
    -m | -multi    | --multi    ) mode="multi";            shift;;
    -s | -single   | --single   ) mode="single";           shift;;
    -o | -opt      | --opt      ) mode="opt";              shift;;

    # Arguments with a value
    -r | -rootProj | --rootProj ) rootProject="$1"; shift; shift;;
    -v | -version  | --version  ) projVersion="$1"; shift; shift;;

    # End of arguments
    --)                                             shift; break;;
    *)                                                     break;;
  esac
done

if [[ "$1" == "" ]]; then fatal "No inPath specified";    fi
if [[ "$2" == "" ]]; then fatal "No outPath specified";   fi
if [[ "$3" != "" ]]; then fatal "Unexpected argument $3"; fi
if [[ ! -d $1    ]]; then error "Invalid path: $1";       fi

# ==== Check environment variables ============================================
if [[ -z "${NMROOT}" ]]; then
  NMROOT=$(dirname $0)
  warning "NMROOT is not defined, using $NMROOT"
fi

# ==== Determine mode =========================================================
inPath="$(readlink -m ${1})"       # This handles the case where the user runs the script with '.'
outPath="$(readlink -m ${2})"      # This handles the case where the user runs the script with '.'

case $mode in
  multi)
    optNames=$(ls ${inPath}/nxm/)
    ;;
  single)
    optNames=$(ls ${inPath}/nxm/)
    ;;
  opt)
    inPath="$(cd ${inPath}/../..; pwd)"
    optNames=$(basename $1)
    ;;
  *)
    if [[ -d ${1}/nxm ]]; then
      optNames=$(ls ${inPath}/nxm/)
      if [[ $(echo optNames | wc -w) == 1 ]]; then
        mode="single"
      else
        mode="multi"
      fi
    elif [[ -d ${inPath}/../../nxm ]]; then
      inPath="$(cd ${inPath}/../..; pwd)"
      optNames=$(basename $1)
      mode="opt"
    else
      error "Unable to determine mode to use with $1"
    fi
    ;;
esac


# ==== Do the Conversion ======================================================
nxmVersion=$(getVersionNum "${NMROOT}/sys/src/main/midas/nxm/sys/version.txt")

info "Configuring..."
info "  NeXtMidas Root    : ${NMROOT}"
info "  NeXtMidas Version : ${nxmVersion}"
info "  Mode              : -${mode}"
if [[ $mode == "opt" ]]; then
  info "  In path           : ${inPath} (${inPath}/nxm/${optNames})"
else
  info "  In path           : ${inPath}"
fi
info "  Out path          : ${outPath}"
info "  Option trees      : ${optNames}"

# ---- Top-Level Structure ----------------------------------------------------
info "Creating directories..."
mkdir -p "$outPath"

info "Copying templates..."
for fname in "common.gradle" "build.gradle"; do
  cp "${NMROOT}/gradle/${fname}" "${outPath}/${name}"
done

if [[ "$mode" == "multi" ]]; then
  info "Generating settings.gradle..."
  if [[ "$rootProject" == "" ]]; then
    rootProject=$(echo $optNames | sed 's/ /_/g')
    warning "Using rootProject=${rootProject} (edit settings.gradle to change)"
  fi
  genSettingsGradle  > "${optPathOut}/settings.gradle"
else
  info "Skipping settings.gradle (not needed)..."
fi


# ---- Convert Option Tree(s) ---------------------------------------------------
for optName in $optNames; do
  info "Converting ${optName}..."
  optPathIn=${inPath}/nxm/${optName}
  if [[ $mode == multi ]]; then
    optPathOut=$outPath/$optName
  else
    optPathOut=$outPath
  fi

  # ---- Find Version Number --------------------------------------------------
  versionFile="${inPath}/nxm/${optName}/version.txt"
  optVersion=$(getVersionNum "$versionFile")
  
  info "  Option Tree       : ${optName}"
  info "  Version           : ${optVersion}"
  info "  Source (nxm/opt)  : ${optPathIn}"
  info "  Destination       : ${optPathOut}"
  
  if [[ "$optVersion" == "0.0.0" ]]; then
    warning "Could not find version information in version.txt using ${optVersion}"
  fi
  
  # ---- Config Files ---------------------------------------------------------
  info "Generating gradle.properties..."
  genGradleProperties  > "${optPathOut}/gradle.properties"

  info "Generating build.gradle..."
  genBuildGradle  > "${optPathOut}/build.gradle"

  # ---- Source Code ----------------------------------------------------------
  # We need to use 'ls -a' to pick up directories like .git
  cd ${optPathIn}
  for fname in $(ls -a) ; do
    case "${fname}" in
      . | ..)
        ;;
      jars)
        for fn in $(find ${fname} -type f) ; do
          warning "Skipping file: ${fname}/${fn}"
        done
        ;;
      test)
        mkdir -p "${optPathOut}/src/test/midas/nxm/${optName}"
        if [[ "${useLinks}" == "true" ]]; then
          ln -r -s ${fname} ${optPathOut}/src/test/midas/nxm/${optName}/${fname}
        else
          cp -r --parents "${fname}" "${optPathOut}/src/test/midas/nxm/${optName}"
        fi
        ;;
      cfg | dat | docs | exp | hlp | rmif)
        mkdir -p "${optPathOut}/src/main/resources/nxm/${optName}"
        mkdir -p "${optPathOut}/src/test/resources/nxm/${optName}"
        if [[ "${useLinks}" == "true" ]]; then
          ln -r -s ${fname} ${optPathOut}/src/main/resources/nxm/${optName}/${fname}
          ln -r -s ${fname} ${optPathOut}/src/test/resources/nxm/${optName}/${fname}
        else
          cp -r --parents "${fname}" "${optPathOut}/src/main/resources/nxm/${optName}"
          cp -r --parents "${fname}" "${optPathOut}/src/test/resources/nxm/${optName}"
        fi
        ;;
      version.txt | updates.txt)
        mkdir -p "${optPathOut}/src/main/midas/nxm/${optName}"
        if [[ "${useLinks}" == "true" ]]; then
          ln -r -s ${fname} ${optPathOut}/src/main/midas/nxm/${optName}/${fname}
        else
          cp -r --parents "${fname}" "${optPathOut}/src/main/midas/nxm/${optName}"
        fi
        ;;
      *.*) # Things that clearly aren't Java packages (like .git and README.md)
        if [[ -f "${optPathOut}/${fname}" ]]; then
          warning "Skipping file: ${fname}"
        else
          mkdir -p "${optPathOut}/src/main/midas/nxm/${optName}"
          if [[ "${useLinks}" == "true" ]]; then
            ln -r -s ${fname} ${optPathOut}/src/main/midas/nxm/${optName}/${fname}
          else
            cp -r --parents "${fname}" "${optPathOut}/src/main/midas/nxm/${optName}"
          fi
        fi
        ;;
      *)   # Things that look like Java packages
        mkdir -p "${optPathOut}/src/main/midas/nxm/${optName}"
        if [[ "${useLinks}" == "true" ]]; then
          ln -r -s ${fname} ${optPathOut}/src/main/midas/nxm/${optName}/${fname}
        else
          cp -r --parents "${fname}" "${optPathOut}/src/main/midas/nxm/${optName}"
        fi
        ;;
    esac
  done

  # ---- Delete Build Products ------------------------------------------------
  for fname in $(find ${optPathOut}/src/) ; do
    case "$(file $fname)" in
      "*compiled Java class*") rm "$fname";;
      "*shared object*")       rm "$fname";;
    esac
  done
done
optName=""

# ---- Copy Other Files -------------------------------------------------------
info "Copy other user files..."
# We need to use 'ls -a' to pick up directories like .git, we do this last
# since it is common for option trees to kick stuff up here when in single
# or opt mode (such as .git or README.md).
cd ${inPath}
for fname in $(ls -a) ; do
  case "${fname}" in
    . | .. | nxm)
      ;;
    *)
      if [[ -f ${outPath}/${fname} ]]; then
        warning "Skipping file: ${fname}"
      else
        cp -r --parents "${fname}" "${outPath}/"
      fi
      ;;
  esac
done

info ""
info "Conversion Complete ($(printf "$warnings" | wc -l) warnings)$warnings"
info ""
