#!/usr/bin/env bash
# kaleidoscope-builder - Kaleidoscope helper tool
# Copyright (C) 2017-2018  Keyboard.io, Inc.
#
# This program 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, version 3.
#
# This program 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
# this program. If not, see <http://www.gnu.org/licenses/>.

set -e

######
###### Build and output configuration
######

absolute_filename() {
    echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
}



build_version () {
    : "${LIB_PROPERTIES_PATH:="../.."}"
    GIT_VERSION="$(cd "${SKETCH_DIR}"; if [ -d .git ]; then echo -n '-g' && git describe --abbrev=4 --dirty --always; fi)"
    LIB_VERSION="$(cd "${SKETCH_DIR}"; (grep version= "${LIB_PROPERTIES_PATH}/library.properties" 2>/dev/null || echo version=0.0.0) | cut -d= -f2)${GIT_VERSION}"
}

build_paths() {
    # We need that echo because we\re piping to cksum
    # shellcheck disable=SC2005
    SKETCH_IDENTIFIER="$(echo "${SKETCH_FILE_PATH}" | cksum | cut -d ' ' -f 1)-${SKETCH_FILE_NAME}"
    : "${KALEIDOSCOPE_TEMP_PATH:=${TMPDIR:-/tmp}/kaleidoscope-${USER}}"

    : "${KALEIDOSCOPE_BUILD_PATH:=${KALEIDOSCOPE_TEMP_PATH}/sketch}"
    : "${KALEIDOSCOPE_OUTPUT_PATH:=${KALEIDOSCOPE_TEMP_PATH}/sketch}"

    : "${SKETCH_OUTPUT_DIR:=${SKETCH_IDENTIFIER}/output}"
    : "${SKETCH_BUILD_DIR:=${SKETCH_IDENTIFIER}/build}"

    : "${BUILD_PATH:=${KALEIDOSCOPE_BUILD_PATH}/${SKETCH_BUILD_DIR}}"
    : "${OUTPUT_PATH:=${KALEIDOSCOPE_OUTPUT_PATH}/${SKETCH_OUTPUT_DIR}}"

    : "${CCACHE_WRAPPER_PATH:=${KALEIDOSCOPE_TEMP_PATH}/ccache/bin}"
    : "${CORE_CACHE_PATH:=${KALEIDOSCOPE_TEMP_PATH}/arduino-cores}"

    mkdir -p "$CORE_CACHE_PATH"
    mkdir -p "$BUILD_PATH"
}

build_filenames () {
    : "${OUTPUT_FILE_PREFIX:=${SKETCH_BASE_NAME}-${LIB_VERSION}}"
    : "${HEX_FILE_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.hex}"
    : "${HEX_FILE_WITH_BOOTLOADER_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}-with-bootloader.hex}"
    : "${ELF_FILE_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.elf}"
    : "${LIB_FILE_PATH:=${OUTPUT_PATH}/${OUTPUT_FILE_PREFIX}.a}"
}


enable_ccache () {
    if [ -z "${CCACHE_NOT_SUPPORTED}" ] && [ "$(command -v ccache)" ]; then
      if ! [ -d "$CCACHE_WRAPPER_PATH" ]; then
         mkdir -p "$CCACHE_WRAPPER_PATH"

          if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${C_COMPILER_BASENAME}" ]; then
             ln -s "$(command -v ccache)" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${C_COMPILER_BASENAME}"
          fi
    
          if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}" ]; then
             ln -s "$(command -v ccache)" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}"
          fi
    
          if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}nm" ]; then
             ln -s "${AVR_NM}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}nm"
          fi
    
          if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}objcopy" ]; then
             ln -s "${AVR_OBJCOPY}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}objcopy"
          fi
    
          if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}ar" ]; then
             ln -s "${AVR_AR}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}ar"
          fi
    
          if ! [ -h "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}size" ]; then
             ln -s "${AVR_SIZE}" "${CCACHE_WRAPPER_PATH}/${COMPILER_PREFIX}size"
          fi
      fi

      export CCACHE_PATH=${COMPILER_PATH}/
      CCACHE_ENABLE="-prefs compiler.path=${CCACHE_WRAPPER_PATH}/"
    fi
}


firmware_size () {
    if [ "${ARCH}" = "virtual" ]; then
        echo "[Size not computed for virtual build]"
        return
    fi

    : "${MAX_PROG_SIZE:=$(get_arduino_pref 'upload.maximum_size')}"

    ## This is a terrible hack, please don't hurt me. - algernon

    set +e
    raw_output=$("$@" 2> /dev/null)
    rc=$?
    set -e

    if [ $rc -eq 0 ]; then
        output="$(echo "${raw_output}"| grep "\\(Program\\|Data\\):" | sed -e 's,^,  - ,' && echo)"

        PROGSIZE="$(echo "${output}" | grep "Program:" | cut -d: -f2 | awk '{print $1}')"

        PERCENT="$(echo "${PROGSIZE}" "${MAX_PROG_SIZE}" | awk "{ printf \"%02.01f\", \$1 / \$2 * 100 }")"

        # we want the sed there, doing with shell builtins would be worse.
        # shellcheck disable=SC2001 disable=SC1117
        echo "${output}" | sed -e "s/\(Program:.*\)(\([0-9\.]*%\) Full)/\1(${PERCENT}% Full)/"
    else
        echo "Unable to determine image size."
    fi
}


find_sketch () {
    if [ -z "${SKETCH}" ]; then
        echo "SKETCH needs to be set before including this file!" >&2
        exit 1
    fi
  
    SKETCH_DIR="${SKETCH}"
    SKETCH_BASE_NAME=$(basename "$SKETCH")
    SKETCH_FILE_NAME="${SKETCH_BASE_NAME}.ino"

    for path in "${SKETCH_DIR}" \
                    "src" \
		    "."; do
        if [ -f "${path}/${SKETCH_FILE_NAME}" ]; then
      	    SKETCH_DIR="${path}"
      	    SKETCH_FILE_PATH=$(absolute_filename "${SKETCH_DIR}/${SKETCH_FILE_NAME}")
            return
        fi
    done
    echo "I couldn't find your sketch (.ino file)" >&2
    exit 1
}



prompt_before_flashing () {
    flashing_instructions=$(get_arduino_pref 'build.flashing_instructions')

    if [ "x${flashing_instructions}x" = "xx" ]; then
	flashing_instructions="If your keyboard needs you to do something to put it in flashing mode, do that now."
    fi

    printf '%b\n\n' "${flashing_instructions}"
    echo ""
    echo "When you're ready to proceed, press 'Enter'."

    # We do not want to permit line continuations here. We just want a newline.
    # shellcheck disable=SC2162
    read
}

flash () {
    compile "$@"

    # Check to see if we can see a keyboard bootloader port.
    # If we -can-, then we should skip over the "reset to bootloader" thing
    find_bootloader_ports
    if [ -z "${DEVICE_PORT_BOOTLOADER}" ]; then
        prompt_before_flashing

        # This is defined in the (optional) user config.
        # shellcheck disable=SC2154
        ${preFlash_HOOKS}

	# If we're -not- doing a manual reset, then try to do it automatically
        if [ -z "${MANUAL_RESET}" ]; then
            reset_device
            sleep 2
            find_bootloader_ports
	# Otherwise, poll for a bootloader port.
        else
           wait_for_bootloader_port
        fi

    fi

    check_bootloader_port_and_flash

    # This is defined in the (optional) user config.
    # shellcheck disable=SC2154
    ${postFlash_HOOKS}
}

wait_for_bootloader_port() {
    declare -i tries
    tries=15

    while [ "$tries" -gt 0 ] && [ -z "${DEVICE_PORT_BOOTLOADER}" ]; do
        sleep 1
        printf "."
        find_bootloader_ports
        # the variable annotations do appear to be necessary
        # shellcheck disable=SC2004
        tries=$(($tries-1))
    done

    if [ "$tries" -gt 0 ]; then
        echo "Found."
    else
        echo "Timed out."
    fi
}

check_bootloader_port () {
    if [ -z "${DEVICE_PORT_BOOTLOADER}" ]; then
        echo "Unable to detect a keyboard in bootloader mode."
	echo "You may need to hold a key or hit a reset button."
	echo "Please check your keyboard's documentation"
        return 1
    fi

}

check_bootloader_port_and_flash () {

    if ! check_bootloader_port; then
        return 1
    fi

    echo "Flashing your keyboard:"

    # If the flash fails, try a second time
    if ! flash_over_usb; then
        sleep 2
        if  ! flash_over_usb; then
            if [ "${ARDUINO_VERBOSE}" != "-verbose" ]; then
                echo "Something went wrong."
    	        echo "You might want to try flashing again with the VERBOSE environment variable set"
            fi

            return 1
        fi
    fi
    echo "Keyboard flashed successfully!"
    return 0
}

flash_over_usb () {

    FLASH_CMD=$(${AVRDUDE} \
		-C "${AVRDUDE_CONF}" \
		-p"${MCU}" \
 		-cavr109 \
		-D \
		-P "${DEVICE_PORT_BOOTLOADER}" \
		-b57600 \
		"-Uflash:w:${HEX_FILE_PATH}:i")

    if [ "${ARDUINO_VERBOSE}" != "-verbose" ]; then
	${FLASH_CMD} 2>&1 |grep -v ^avrdude | grep -v '^$' |grep -v '^ ' | grep -vi programmer
    	return "${PIPESTATUS[0]}"
    else
        ${FLASH_CMD}
	return $?
    fi
}

flash_from_bootloader() {
    compile "$@"
    prompt_before_flashing
    find_bootloader_ports
    check_bootloader_port_and_flash
}

program() {
    compile "$@"
    prompt_before_flashing
    flash_with_programmer
}

flash_with_programmer() {
    ${AVRDUDE} -v \
	       -C "${AVRDUDE_CONF}" \
	       -p"${MCU}" \
	       -cusbtiny \
	       -D \
	       -B 1 \
	       "-Uflash:w:${HEX_FILE_PATH}:i"
}

find_bootloader_path() {
    BOOTLOADER_FILE=$( get_arduino_pref 'bootloader.file' )
    : "${BOOTLOADER_FILE:=caterina/Caterina.hex}"
    : "${BOOTLOADER_PATH:=${BOARD_HARDWARE_PATH}/keyboardio/avr/bootloaders/${BOOTLOADER_FILE}}"
}


hex_with_bootloader () {
    compile

    find_bootloader_path

    awk '/^:00000001FF/ == 0' "${HEX_FILE_PATH}" > "${HEX_FILE_WITH_BOOTLOADER_PATH}"
    echo "Using ${BOOTLOADER_PATH}"
    ${MD5} "${BOOTLOADER_PATH}"
    cat "${BOOTLOADER_PATH}" >> "${HEX_FILE_WITH_BOOTLOADER_PATH}"
    ln -sf -- "${OUTPUT_FILE_PREFIX}-with-bootloader.hex" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest-with-bootloader.hex"
    cat <<- EOF

		Combined firmware and bootloader are now at ${HEX_FILE_WITH_BOOTLOADER_PATH}
		Make sure you have the bootloader version you expect.

		And TEST THIS ON REAL HARDWARE BEFORE YOU GIVE IT TO ANYONE

		EOF
}

build () {
    compile "$@"
    size "$@"
}


prepare_ccache () {
    build_paths
    enable_ccache
}
compile () {
    find_sketch
    build_version
    build_paths
    build_filenames
    # If the hex file is older than the sketch file, or the hex file does not exist
    # then rebuild. This is not as correct as letting make check our dependencies
    # But it's less broken for most user use cases
    # TODO(anyone): Make this suck less
    if [ "${HEX_FILE_PATH}" -ot "${SKETCH_FILE_PATH}" ]; then
	do_compile "$@"
    fi

}


do_compile () {
    prepare_ccache

    install -d "${OUTPUT_PATH}"

    echo "Building ${SKETCH_FILE_PATH}"

    # This is defined in the (optional) user config.
    # shellcheck disable=SC2154
    ${compile_HOOKS}

    if [ -d "${ARDUINO_LOCAL_LIB_PATH}/libraries" ]; then
        # shellcheck disable=SC2089
        # We want literal backslashes here, not arrays.
        local_LIBS="-libraries \"${ARDUINO_LOCAL_LIB_PATH}/libraries\""
    fi

    ARDUINO_PACKAGES=""
    if [ -d "${ARDUINO_PACKAGE_PATH}" ]; then
        # shellcheck disable=SC2089
        # We want literal backslashes here, not arrays.
	      ARDUINO_PACKAGES="-hardware \"${ARDUINO_PACKAGE_PATH}\""
    fi

    SAVED_BOARD="${BOARD}"
    SAVED_FQBN="${FQBN}"
    if [ -e "${SKETCH_DIR}/.kaleidoscope-builder.conf" ]; then
        # shellcheck disable=SC1090
        BOARD="$(. "${SKETCH_DIR}"/.kaleidoscope-builder.conf && echo "${BOARD}")"
        # shellcheck disable=SC1090
        FQBN="$(. "${SKETCH_DIR}"/.kaleidoscope-builder.conf && echo "${FQBN}")"
        if [ -n "${BOARD}" ]; then
            if [ -z "${ARCH}" ]; then
               FQBN="keyboardio:avr:${BOARD}"
            else
               FQBN="keyboardio:${ARCH}:${BOARD}"
            fi
        fi
    fi

    _CMD_CXX="${CXX:-${COMPILER_PREFIX}${CXX_COMPILER_BASENAME}${COMPILER_SUFFIX}}"
    _CMD_CC="${CC:-${COMPILER_PREFIX}${C_COMPILER_BASENAME}${COMPILER_SUFFIX}}"
    _CMD_AR="${AR:-${COMPILER_PREFIX}${AR_BASENAME}${COMPILER_SUFFIX}}"

    # SC2091: We do not care if quotes or backslashes are not respected.
    # SC2086: We want word splitting.
    # shellcheck disable=SC2086,SC2090
    "${ARDUINO_BUILDER}" \
        -compile \
	      ${ARDUINO_PACKAGES} \
	      -hardware "${ARDUINO_PATH}/hardware" \
	      -hardware "${BOARD_HARDWARE_PATH}" \
	      ${ARDUINO_TOOLS_FLAG:+"${ARDUINO_TOOLS_FLAG}"} ${ARDUINO_TOOLS_PARAM:+"${ARDUINO_TOOLS_PARAM}"} \
	      -tools "${ARDUINO_BUILDER_TOOLS_PATH}" \
	      -fqbn "${FQBN}" \
        -libraries "." \
        -libraries "${KALEIDOSCOPE_DIR}" \
	      -libraries "${BOARD_HARDWARE_PATH}/.." \
        ${local_LIBS} \
	      ${EXTRA_BUILDER_ARGS} \
	      -build-cache "${CORE_CACHE_PATH}" \
	      -build-path "${BUILD_PATH}" \
	      -ide-version "${ARDUINO_IDE_VERSION}" \
	      -built-in-libraries "${ARDUINO_PATH}/libraries" \
	      -prefs "compiler.cpp.extra_flags=${ARDUINO_CFLAGS} ${LOCAL_CFLAGS}" \
	      -prefs "compiler.path=${COMPILER_PATH}" \
	      -prefs "compiler.c.cmd=${_CMD_CC}" \
	      -prefs "compiler.cpp.cmd=${_CMD_CXX}" \
        -prefs "compiler.ar.cmd=${_CMD_AR}" \
        -prefs "compiler.c.elf.cmd=${_CMD_CXX}" \
	      $CCACHE_ENABLE \
	      -warnings all \
        ${ARDUINO_VERBOSE} \
	      ${ARDUINO_AVR_GCC_PREFIX_PARAM} \
	      "${SKETCH_FILE_PATH}"

    if [ -z "${LIBONLY}" ]; then
      cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.hex" "${HEX_FILE_PATH}"
      cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.elf" "${ELF_FILE_PATH}"
      ln -sf "${OUTPUT_FILE_PREFIX}.hex" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.hex"
      ln -sf "${OUTPUT_FILE_PREFIX}.elf" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.elf"
    else
      cp "${BUILD_PATH}/${SKETCH_FILE_NAME}.a" "${LIB_FILE_PATH}"
      ln -sf "${OUTPUT_FILE_PREFIX}.a" "${OUTPUT_PATH}/${SKETCH_BASE_NAME}-latest.a"
    fi

    if [ "${ARDUINO_VERBOSE}" = "-verbose" ]; then
      echo "Build artifacts can be found in ${BUILD_PATH}";
    fi

    BOARD="${SAVED_BOARD}"
    FQBN="${SAVED_FQBN}"
}

find_all_sketches () {
    for plugin in ./*.ino \
  	              $([ -d examples ] && find examples -name '*.ino') \
                  src/*.ino; do
        if [ -d "$(dirname "${plugin}")" ] || [ -f "${plugin}" ]; then
            p="$(basename "${plugin}" .ino)"
            if [ "${p}" != '*' ]; then
                case "${plugin}" in
                    examples/*/${p}/${p}.ino)
                        echo "${plugin}" | sed -e "s,examples/,," | sed -e "s,/${p}\\.ino,,"
                        ;;
                    *)
                        echo "${p}"
                        ;;
                esac
            fi
        fi
    done | sort
}

build_all () {
    plugins="$(find_all_sketches)"

    for plugin in ${plugins}; do
        export SKETCH="${plugin}"
        $0 "${plugin}" build
    done
}


compile_all () {
    plugins="$(find_all_sketches)"

    for plugin in ${plugins}; do
        export SKETCH="${plugin}"
        $0 "${plugin}" compile
    done
}


size () {
    compile

    echo "- Size: ${ELF_FILE_PATH}"
    # shellcheck disable=SC2086
    firmware_size "${AVR_SIZE}" ${AVR_SIZE_FLAGS} "${ELF_FILE_PATH}"
    echo
}

size_map () {
    compile

    "${AVR_NM}" --size-sort -C -r -l -t decimal "${ELF_FILE_PATH}"
}

disassemble () {
    compile

    "${AVR_OBJDUMP}" -C -d "${ELF_FILE_PATH}"
}

decompile () {
    disassemble
}

clean () {
    find_sketch
    build_paths
    rm -rf -- "${OUTPUT_PATH}"
    rm -rf -- "${BUILD_PATH}"
    kaleidoscope_dir="$(dirname "$0")/.."
    if [ -d "${kaleidoscope_dir}/testing/googletest/build" ]; then
         ( cd "${kaleidoscope_dir}/testing/googletest/build" &&
         cmake .. &&
         make clean)
    fi
}

reset_device() {
    find_device_port
    check_device_port
    reset_device_cmd
}

check_device_port () {
    if [ -z "$DEVICE_PORT" ]; then
        cat <<EOF >&2

I couldn't autodetect the keyboard's serial port.

If you see this message and your keyboard is connected to your computer,
it may mean that our serial port detection logic is buggy or incomplete.
In that case, please report this issue at:
	 https://github.com/keyboardio/Kaleidoscope
EOF
        exit 1
    elif echo "$DEVICE_PORT" | grep -q '[[:space:]]'; then
        cat <<EOF >&2
Unexpected whitespace found in detected serial port:

    $DEVICE_PORT

If you see this message, it means that our serial port
detection logic is buggy or incomplete.

Please report this issue at:
	 https://github.com/keyboardio/Kaleidoscope
EOF
        exit 1
    fi

    if ! [ -w "$DEVICE_PORT" ]; then
        cat <<EOF >&2

In order to update your keyboard's firmware you need to have permission
to write to its serial port $DEVICE_PORT.

It appears that you do not have this permission:

  $(ls -l "$DEVICE_PORT")

This may be because you're not in the correct unix group:

  $(stat -c %G "$DEVICE_PORT").

You are currently in the following groups:

  $(id -Gn)

Please ensure you have followed the instructions on setting up your
account to be in the right group:

https://github.com/keyboardio/Kaleidoscope/wiki/Install-Arduino-support-on-Linux

EOF
        exit 1
    fi
}


deprecation_message() {
echo "kaleidoscope-builder is deprecated and will be removed"
echo "by February 15, 2021"
echo ""
echo "To switch to the new build system, replace your sketch's"
echo "Makefile with a copy of:"
echo ""
echo "${KALEIDOSCOPE_DIR}/etc/Makefile.sketch"
echo ""
}

usage () {
    cat <<- EOF
		Usage: $0 SKETCH commands...

		Runs all of the commands in the context of the Sketch.

		Available commands:

		  help
		    This help screen.

		  compile
		    Compiles the sketch.

		  size
		    Reports the size of the compiled sketch.

		  build
		    Runs compile and report-size.

		  clean
		    Cleans up the output directory.

		  size-map
		    Displays the size map for the sketch.

		  disassemble
		    Decompile the sketch.

		  reset-device
		    Reset the device.

		  flash
		    Flashes the firmware using avrdude.

		  build-all
		    Build all Sketches we can find.

		EOF
}


KALEIDOSCOPE_DIR="$(cd "$(dirname "$0")"/..; pwd)"
# shellcheck disable=SC2034
KALEIDOSCOPE_BIN_DIR="${KALEIDOSCOPE_DIR}/bin/"


help () {
    usage
}

if [ $# -lt 1 ]; then
    usage
    exit 1
fi

## Parse the command-line
##  - anything that has a =, is an env var
##  - from the remaining stuff, the first one is the Library/Sketch
##  - everything else are commands
##
##  - if there is only one argument, that's a command


deprecation_message


# shellcheck disable=SC2155
export SOURCEDIR="$(pwd)"


for conf_file in \
    "${HOME}/.kaleidoscope-builder.conf" \
    "${SOURCEDIR}/.kaleidoscope-builder.conf" \
    "${SOURCEDIR}/kaleidoscope-builder.conf" \
    "${KALEIDOSCOPE_DIR}/etc/kaleidoscope-builder.conf"; do
    if [ -e "${conf_file}" ]; then
        # shellcheck disable=SC1090
        . "${conf_file}"
    fi

done

# shellcheck disable=SC1090


if [ -n "${VERBOSE}" ] && [[ "${VERBOSE}" -gt 0 ]]; then
    ARDUINO_VERBOSE="-verbose"
else
    ARDUINO_VERBOSE="-quiet"
fi

cmds=""

## Export vars
for i in $(seq 1 $#); do
    v="$1"
    shift

    case "${v}" in
        *=*)
            # Exporting an expansion is *precisely* what we want here.
            # shellcheck disable=SC2086,SC2163
            export ${v}
            ;;
        *)
            cmds="${cmds} ${v}"
            ;;
    esac
done

# Word splitting is desired here.
# shellcheck disable=SC2086
set -- ${cmds}

if [ $# -eq 1 ]; then
    cmd="$(echo "$1" | tr '-' '_')"
    ${cmd}
    exit $?
fi

SKETCH="$1"
shift

if [ "${SKETCH}" = "default" ]; then
    SKETCH="${DEFAULT_SKETCH}"
fi

cmds=""

# shellcheck disable=2034
for i in $(seq 1 $#); do
    cmds="${cmds} $(echo "$1" | tr '-' '_')"
    shift
done

for cmd in ${cmds}; do
    ${cmd}
done