#!/usr/bin/env bash
#
# Copyright (c) 2020 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

_usage()
{
  echo ''
  echo 'Usage: '
  echo ' '$(basename $0)' ['"$(echo ${_g_options[@]}|sed 's/ /|/g')"']' '<subcommand>' '[<model>...]'
  echo ''
  echo 'where a <subcommand> is one of:'
  echo ' '$(echo ${_g_commands[@]})
  echo ''
  echo 'and where a <model> is one of:'
  echo ' '"${_all_models[@]}"
  echo ''
  echo "Examples:"
  echo "# -- Initialize a new model specification --"
  echo 'model-builder init-spec -f <framework> <modelname-precision-mode> <use_case>'
  echo ''
  echo "# -- List all frameworks --"
  echo 'model-builder frameworks'
  echo ''
  echo "# -- List all models in the default framework ("${DEFAULT_FRAMEWORK}") --"
  echo 'model-builder models'
  echo ''
  echo "# -- Create two model packages --"
  echo 'model-builder package <modelname> <modelname>'
  echo ''
  echo "# -- Create all model packages for the given framework --"
  echo 'model-builder package -f <framework>'
  echo ''
  echo "# -- Build all model images from the given framework --"
  echo 'model-builder build -f <framework>'
  echo ''
  echo "# -- View the call to docker run when generating a model's Dockerfile --"
  echo "# -- Adding --dry-run will show the docker call but not execute it --"
  echo 'model-builder --dry-run generate-dockerfile <modelname>'
  echo ''
  echo "# -- Generate a model's documentation --"
  echo 'model-builder generate-documentation <modelname>'
  echo ''
  echo "# -- Do {generate-documentation,package,generate-dockerfile,build} for several models --"
  echo 'model-builder make <modelname> <modelname>'
  echo ''
  echo "# -- Generate a k8 model's deployment file --"
  echo 'model-builder generate-deployment <modelname>'
  echo ''
  echo "# -- Run the test-suite for the subcommand generate-documentation with the framework tensorflow and models: resnet50-fp32-inference and resnet50-int8-inference. --"
  echo '  -- {subcommand,framework,[model model]} --'
  echo 'model-builder run-test-suite --command generate-documentation --framework tensorflow resnet50-fp32-inference resnet50-int8-inference'
  exit 0
}

#
# Called at exit to clean up temp files and show source of error if one has occured
# Outputs caller and error
#
_model_builder._onexit()
{
  if [[ -f $_all_specs ]]; then
    echo ${_tmp_files[@]} | xargs rm -rf
  fi
  if (( $# > 0 )) && [[ -n $1 ]] && (( $1 != 0 )); then
    # error handling goes here
    echo "Script exited with error $1"
  fi
}

#
# Checks if the imz-tf-tools docker image exists
# Returns:
#  0 success
#  1 failure
#
_model_builder._check-for-imz-tf-tools()
{
  local _verbose=''
  if (( $# > 0 )); then
    case "$1" in
      --verbose)
         _verbose='true'
         ;;
      *)
         ;;
    esac
  fi
  [[ -n $_verbose ]] && echo '> Checking for imz-tf-tools:latest container' >&2
  docker inspect --format '{{index .RepoTags 0 }}' imz-tf-tools:latest 2>/dev/null 1>/dev/null
}

#
# Builds the imz-tf-tools container
# Returns:
#  0 success
#  1 failure
#
_model_builder._build-imz-tf-tools()
{
  local _dir _quiet='' _proxy_build
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in 
      -q|--quiet)
         _quiet=$1
         shift
         ;;
      *)
         echo 'unknown argument '$1
         exit 1
         ;;
    esac
  done
  _dir=$1
  if ! [[ -d $_dir ]]; then
    echo $_dir' is not a directory'
    exit 1
  fi
  if [[ -n $http_proxy && -n $https_proxy && -n $no_proxy ]]; then
    _proxy_build=' --build-arg HTTPS_PROXY='$https_proxy' --build-arg HTTP_PROXY='$http_proxy' --build-arg NO_PROXY='$no_proxy
  fi
  test -z $_quiet && echo '> Building imz-tf-tools docker image' >&2
  cat <<DOCKERIGNORE >.dockerignore
*
!bashrc
DOCKERIGNORE
  pushd $_dir 2>/dev/null 1>/dev/null
  docker build $_quiet $_proxy_build --tag imz-tf-tools:latest -f tools.Dockerfile . && echo '> Build successful' >&2
  docker_exit_code=$?
  rm -f .dockerignore
  popd 2>/dev/null 1>/dev/null

  if [[ $docker_exit_code != 0 ]]; then
    echo "ERROR: Unable to build the imz-tf-tools container to run the assembler"
    exit $docker_exit_code
  fi
}

#
# Called to fetch proxy vars
# Outputs format that can be used when calling docker run
#
_model_builder._get-proxy-env-vars()
{
  local _proxy_run=''
  if [[ -n $http_proxy && -n $https_proxy && -n $no_proxy ]]; then
    _proxy_run=' -e HTTPS_PROXY='$https_proxy' -e HTTP_PROXY='$http_proxy' -e NO_PROXY='$no_proxy
  fi
  echo "$_proxy_run"
}

#
# Called by _model_builder._init to create functions that hold memoized info from the specifications.
# Called within xargs to extract specification info.
# Xargs allows parallel processing for performance.
#
_model_builder._generator()
{
  local _framework=$1
  eval $'__model_builder_'$_framework$'_models() { echo $(ls '$_spec_folder$'/'$_framework$' | grep -v base_spec.yml | sed \'s/_spec.yml//\'); }'
  eval "_model_builder_"$_framework"_models() { echo $(__model_builder_"$_framework"_models); }"
  eval "type _model_builder_"$_framework"_models" | sed '1d' > $_tmp_dir/'_model_builder_'$_framework'_models'
}

#
# Sets global vars 
#  _current_models set to all models in _current_framework array
#  _selected_models set to empty array
#  _remaining_models set to _current_models
#  _show_framework_options set to true
#
_model_builder._set_current_models()
{
  local _i
  _current_models=()
  _selected_models=()
  _remaining_models=()
  _show_framework_options=true

  _current_models+=(${_all_models["$_current_framework"]});

  if (( ${#_current_models[@]} > 0 )); then
    _model_builder._sort_unique _current_models
    _remaining_models=( ${_current_models[@]} )
  fi
}

#
# Utility function
# Remove an item from an array. 
# Can't use ~= since it matches substrings: 
#   unet-fp32-inference would match 3d-unet-fp32-inference 
#
_model_builder._remove_item()
{
  local _target=$2 
  declare -n _array=$1 
  for i in "${!_array[@]}"; do
    if [[ ${_array[i]} == $_target ]]; then
      unset '_array[i]'
    fi
  done
}

#
# Utility function
# Rebuilds array to be sorted with unique entries
#
_model_builder._sort_unique()
{
  declare -n _array=$1 
  _array=($(printf "%s\n" "${_array[@]}" | sort -u))
}

#
# Called at start to initialize global vars from the specifications
#
_model_builder._init()
{
  local _asm_images _file _generator _i _ii _parallel _release=$1 _spec_folder _xdg_config_home _platform
  _xdg_config_home=${XDG_CONFIG_HOME:-$HOME/.config}
  declare -g _spec_home=${_xdg_config_home}/$(basename $0)/specs

  trap '_model_builder._onexit $? $LINENO' EXIT

  declare -g -r MODEL_DIR=${MODEL_DIR-$PWD}
  # Tag to use for the 'versioned' images
  declare -g -r TENSORFLOW_TAG=${TENSORFLOW_TAG:=latest}
  # Image name to use for the 'versioned' images
  declare -g -r TENSORFLOW_IMAGE=${TENSORFLOW_IMAGE:=intel/intel-optimized-tensorflow}
  # Local container repo used for building images
  declare -g -r LOCAL_REPO=${LOCAL_REPO-model-zoo}
  declare -g -r TAG_PREFIX=${TENSORFLOW_TAG}
  # If no framework folder is specified, use tensorflow
  declare -g -r DEFAULT_FRAMEWORK="tensorflow"
  declare -g _current_framework=${DEFAULT_FRAMEWORK}

  _model_builder._root 1>/dev/null
  declare -g -r _g_root=$(_model_builder._root)
  declare -g -r _g_options=("-h" "--help" "--verbose" "--dry-run")
  declare -g _g_commands=($(echo $(cat $0|grep '^_model_builder\.[^_]\([a-zA-Z0-9_]*\)[ \t]*('|sed 's/_model_builder\.\([a-zA-Z0-9_]*\)[ \t]*()/\1/'|tr '_' '-'|sort)))
  declare -g _g_commands_case_statement="$(_model_builder._join '|' ${_g_commands[@]})"

  declare -g -A _all_models
  declare -g -a _current_models _selected_models _remaining_models
  declare -g -a _all_commands _current_commands _remaining_commands
  declare -g -a _all_frameworks _remaining_frameworks
  declare -g -a _release_groups
  declare -g -a _all_specs _tmp_files
  declare -g _show_framework_options=true

  _model_builder._root 1>/dev/null

  _spec_folder=$(_model_builder._root)/docker/specs
  _spec_dir_mounted='./specs'

  # add platforms other than ubuntu:
  _platform='centos'
  if [[ ${TAG_PREFIX} == *"${_platform}"* ]] || [[ ${TAG_PREFIX} == *"spr"* ]] ; then
    _spec_folder=${_spec_folder}/${_platform}
    _spec_home=${_spec_home}/${_platform}
    _spec_dir_mounted=${_spec_dir_mounted}/${_platform}
  fi

  declare -g _spec_dir_mounted=${_spec_dir_mounted}

  if [[ ! -d $_spec_folder ]]; then
    echo "Error: The spec folder does not exist ($_spec_folder)."
    exit 1
  fi
  if [[ ! -d $_spec_home ]]; then
    mkdir -p $_spec_home && cp -r $_spec_folder/k8s $_spec_home && rm -f $_spec_home/k8s/base_spec.yml
    if (( $? != 0 )); then
      echo "Unable to create ${_spec_home}." >&2
      exit 1
    fi
  fi

  if (( ${#_all_frameworks[@]} == 0 )) && (( ${#_all_models[*]} == 0 )); then
    _all_specs=$(mktemp /tmp/$(basename $0)_specs.XXXXXXXXX)
    _tmp_files+=($_all_specs)

    cat $(find ${_spec_folder} -name '*.yml') > $_all_specs

    _all_commands=(generate-deployment generate-documentation package generate-dockerfile build)
    eval $'__model_builder_frameworks_function() { [[ -d ${_spec_folder} ]] && echo $(ls $_spec_folder); }'

    eval "_model_builder_frameworks_function() { echo $(__model_builder_frameworks_function); }"
    _all_frameworks+=( $(_model_builder_frameworks_function) )

    _generator=$(mktemp /tmp/model-builder._generator.XXXXXXXXX)
    _tmp_files+=($_generator)
    _tmp_dir=$(mktemp -d /tmp/model-builder._generator_files.XXXXXXXXX)
    _tmp_files+=($_tmp_dir)
    echo '#!/usr/bin/env bash' > $_generator
    echo '_tmp_dir='$_tmp_dir >> $_generator
    echo '_spec_folder='$_spec_folder >> $_generator
    type _model_builder._generator | sed '1d' >> $_generator
    echo '_model_builder._generator $1' >> $_generator
    chmod +x $_generator
    _parallel=${#_all_frameworks[@]}
    echo ${_all_frameworks[@]} | xargs -n1 -P${#_all_frameworks[@]} $_generator
    for _i in "${_all_frameworks[@]}"; do
      _file=$_tmp_dir'/_model_builder_'$_i'_models'
      eval "source $_file"
      _ii="$(eval $'\"_model_builder_'$_i$'_models\"')"
      _all_models["$_i"]=$_ii
    done

    _model_builder._set_current_models
  fi
}

#
# Utility function
# Joins an array with the first arg being the delimiter 
#
_model_builder._join() 
{
  local IFS="$1"
  shift
  echo "$*"
}

#
# Outputs the root directory of the model-builder repo
#
_model_builder._root()
{
  local _root=$(dirname $(dirname $0))
  if [[ -d $_root/docker/specs ]]; then
    echo $_root
  elif [[ -d $PWD/tools/docker/specs ]]; then
    echo $PWD/tools
  else
    echo $(basename $0)$' isn\'t in tools/scripts and/or the working directory isn\'t at the root of the model repository.' >&2
    exit 1
  fi
}

#
# Outputs completion logic for a subcommand that only has cli options (-o or --option)
#
_model_builder._subcommand()
{
  local _arg _cur _i=2 _options _prev _subcommand=$1
  case "$_subcommand" in
    *)
      while (( $_i < $COMP_CWORD )); do
        local s="${COMP_WORDS[_i]}"
        case "$s" in
          -*) 
            ;;
        esac
        (( _i++ ))
      done
      _cur="${COMP_WORDS[COMP_CWORD]}"
      _prev="${COMP_WORDS[COMP_CWORD-1]}"
      if (( $_i == $COMP_CWORD )); then
        _options='-h --help'
        COMPREPLY=($(compgen -W "$_options" -- "$_cur"))
        return
      fi
      ;;
  esac
}

#
# Outputs completion logic for subcommands that have
#   (-f|--framework) <framework>
#
_model_builder._subcommand_show_frameworks()
{
  local _arg _cur _i=2 _options _prev _subcommand=$1
  case "$_subcommand" in
    images|models|packages)
      while (( $_i < $COMP_CWORD )); do
        local s="${COMP_WORDS[_i]}"
        case "$s" in
          -f|--framework)
            if [[ $_first_framework == true ]]; then
              _current_framework=''
              _first_framework=false
            fi
            ;;
          -*)
            ;;
          *)
            if [[ "${_remaining_frameworks[@]}" =~ $s ]]; then
              _current_framework=$s
              _model_builder._remove_item _remaining_frameworks $s
              _model_builder._set_current_models
              _show_framework_options=false
            fi
            ;;
        esac
        (( _i++ ))
      done
      _cur="${COMP_WORDS[COMP_CWORD]}"
      _prev="${COMP_WORDS[COMP_CWORD-1]}"
      if [[ $_prev == --framework ]] || [[ $_prev == -f ]]; then
        _options="$(echo ${_remaining_frameworks[@]})"
        COMPREPLY=($(compgen -W "$_options" -- "$_cur"))
        return
      fi
      if (( $_i == $COMP_CWORD )); then
        if [[ $_show_framework_options == true ]]; then
          _options='-h --help -f --framework '
        else
          _options='-h --help '
        fi
        COMPREPLY=($(compgen -W "$_options" -- "$_cur"))
        return
      fi
      ;;
  esac
}

#
# Outputs completion logic for subcommands that have 
#   (-f|--framework) <framework> models
#
_model_builder._subcommand_show_frameworks_and_models()
{
  local _arg _cur _i=2 _options _prev _subcommand=$1
  case "$_subcommand" in
    build|generate-deployment|generate-dockerfile|generate-documentation|make|package)
      while (( $_i < $COMP_CWORD )); do
        local s="${COMP_WORDS[_i]}"
        case "$s" in
          -f|--framework)
            if [[ $_first_framework_group == true ]]; then
              _current_framework=''
              _first_framework=false
            fi
            ;;
          -*) 
            ;;
          *)
            if [[ "${_remaining_frameworks[@]}" =~ $s ]]; then
              _current_framework=$s
              _model_builder._remove_item _remaining_frameworks $s
              _model_builder._set_current_models
              _show_framework_options=false
            fi
            if [[ "${_remaining_models[@]}" =~ $s ]] && ! [[ "${_selected_models[@]}" =~ $s ]]; then
              _show_framework_options=false
              _selected_models+=( $s )
              _model_builder._remove_item _remaining_models $s
            fi
            ;;
        esac
        (( _i++ ))
      done
      _cur="${COMP_WORDS[COMP_CWORD]}"
      _prev="${COMP_WORDS[COMP_CWORD-1]}"
      if [[ $_prev == --framework ]] || [[ $_prev == -f ]]; then
        _options="$(echo ${_remaining_frameworks[@]})"
        COMPREPLY=($(compgen -W "$_options" -- "$_cur"))
        return
      fi
      if (( $_i == $COMP_CWORD )); then
        if [[ $_show_framework_options == true ]]; then
          _options='-h --help -f --framework '"${_remaining_models[@]}"
        else
          _options='-h --help '"${_remaining_models[@]}"
        fi
        COMPREPLY=($(compgen -W "$_options" -- "$_cur"))
        return
      fi
      ;;
  esac
}

#
# Outputs completion logic for subcommands that have 
#   (-c|--command) <command> (-f|--framework) <framework> models
#
_model_builder._subcommand_show_commands_frameworks_and_models()
{
  local _arg _cur _i=2 _options _prev _subcommand=$1
  case "$_subcommand" in
    run-test-suite)
      while (( $_i < $COMP_CWORD )); do
        local s="${COMP_WORDS[_i]}"
        case "$s" in
          -c|--command)
            if [[ $_first_command == true ]]; then
              _current_commands=()
              _first_command=false
            fi
            ;;
          -f|--framework)
            if [[ $_first_framework == true ]]; then
              _current_framework=''
              _first_framework=false
              _show_command_options=false
            fi
            ;;
          -*)
            if [[ "${_remaining_commands[@]}" =~ $s ]]; then
              _current_commands+=( $s )
              _model_builder._remove_item _remaining_commands $s
              if (( ${#_remaining_commands[@]} == 0 )); then
                _show_command_options=false
              fi
            fi
            ;;
          *)
            if [[ "${_remaining_commands[@]}" =~ $s ]]; then
              _current_commands+=( $s )
              _model_builder._remove_item _remaining_commands $s
              if (( ${#_remaining_commands[@]} == 0 )); then
                _show_command_options=false
              fi
            fi
            if [[ "${_remaining_frameworks[@]}" =~ $s ]]; then
              _current_framework=$s
              _model_builder._remove_item _remaining_frameworks $s
              _model_builder._set_current_models
              _show_command_options=false
              _show_framework_options=false
            fi
            if [[ "${_remaining_models[@]}" =~ $s ]] && ! [[ "${_selected_models[@]}" =~ $s ]]; then
              _show_command_options=false
              _show_framework_options=false
              _selected_models+=( $s )
              _model_builder._remove_item _remaining_models $s
            fi
            ;;
        esac
        (( _i++ ))
      done
      _cur="${COMP_WORDS[COMP_CWORD]}"
      _prev="${COMP_WORDS[COMP_CWORD-1]}"
      if [[ $_prev == --command ]] || [[ $_prev == -c ]]; then
        _options="$(echo ${_remaining_commands[@]})"
        COMPREPLY=($(compgen -W "$_options" -- "$_cur"))
        return
      fi
      if [[ $_prev == --framework ]] || [[ $_prev == -f ]]; then
        _options="$(echo ${_remaining_frameworks[@]})"
        COMPREPLY=($(compgen -W "$_options" -- "$_cur"))
        return
      fi
      if (( $_i == $COMP_CWORD )); then
        if [[ $_show_command_options == true ]]; then 
          _options='-h --help -c --command -f --framework '"${_remaining_models[@]}"
        elif [[ $_show_framework_options == true ]]; then
          _options='-h --help -f --framework '"${_remaining_models[@]}"
        else
          _options='-h --help '"${_remaining_models[@]}"
        fi
        COMPREPLY=($(compgen -W "$_options" -- "$_cur"))
        return
      fi
      ;;
  esac
}

#
# Main entry point for completion logic
#
_model_builder._completion()
{
  local _i=1 _subcommand
  _model_builder._completion_init

  _model_builder._set_current_models
  # find the subcommand
  while (( $_i < $COMP_CWORD )); do
    local s="${COMP_WORDS[_i]}"
    case "$s" in
      -f|--framework)
          (( _i++ ))
          ;;
      -*) ;;
      *)
        _subcommand="$s"
        break
        ;;
    esac
    (( _i++ ))
  done

  if (( $_i == $COMP_CWORD )); then
    local cur="${COMP_WORDS[COMP_CWORD]}"
    COMPREPLY=($(compgen -W "$(_model_builder._get_options)" -- "$cur"))
    return
  fi

  # we've completed the 'current' command and now need to call the next completion function
  # subcommands have their own completion functions
  case "$_subcommand" in
    commands|completion|init-spec|install-completion|frameworks)
      _model_builder._subcommand $_subcommand
      return
      ;;
    build|make)
      _current_framework=$DEFAULT_FRAMEWORK
      _remaining_frameworks=(${_all_frameworks[@]})
      _model_builder._set_current_models
      _model_builder._subcommand_show_frameworks_and_models $_subcommand
      return
      ;;
    images)
      _current_framework=$DEFAULT_FRAMEWORK
      _remaining_frameworks=(${_all_frameworks[@]})
      _model_builder._set_current_models
      _model_builder._subcommand_show_frameworks $_subcommand
      return
      ;;
    generate-dockerfile|generate-documentation|package)
      _current_framework=$DEFAULT_FRAMEWORK
      _remaining_frameworks=(${_all_frameworks[@]})
      _model_builder._set_current_models
      _model_builder._subcommand_show_frameworks_and_models $_subcommand
      return
      ;;
    generate-deployment)
      _current_framework=k8s
      _remaining_frameworks=(${_all_frameworks[@]})
      _model_builder._set_current_models
      _model_builder._subcommand_show_frameworks_and_models $_subcommand
      return
      ;;
    models|packages)
      _current_framework=$DEFAULT_FRAMEWORK
      _remaining_frameworks=(${_all_frameworks[@]})
      _model_builder._set_current_models
      _model_builder._subcommand_show_frameworks $_subcommand
      return
      ;;
    run-test-suite)
      _current_commands=(${_all_commands[@]})
      _remaining_commands=(${_current_commands[@]})
      _current_framework=$DEFAULT_FRAMEWORK
      _remaining_frameworks=(${_all_frameworks[@]})
      _model_builder._set_current_models
      _model_builder._subcommand_show_commands_frameworks_and_models $_subcommand
      return
      ;;
    *)
      echo $_subcommand' huh?' >&2
      exit 1
      ;;
  esac
}

#
# Outputs the bash completion script
#
_model_builder.completion()
{
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'Emit the bash completion source\n'
        exit 0
        ;;
      *)
        ;;
    esac
    shift
  done
  eval $'__model_builder_options() { echo ${_g_options[@]}\' \'${_g_commands[@]}; }'
  eval "_model_builder._get_options() { echo $(__model_builder_options); }"
  echo '_model_builder._completion_init()'
  echo '{'
  _model_builder._export '_all_models' _all_models
  echo '  declare -g '$'DEFAULT_FRAMEWORK=('${DEFAULT_FRAMEWORK}$')'
  echo '  declare -g '$'_all_frameworks=('${_all_frameworks[@]}$')'
  echo '  declare -g '$'_current_framework='${DEFAULT_FRAMEWORK}$
  echo '  declare -g _remaining_frameworks=(${_current_framework[@]})'
  echo '  declare -g _first_framework=true'
  echo '  declare -g _show_framework_options=true'
  echo '  declare -g _g_commands=('${_g_commands[@]}')'
  echo '  declare -g _g_commands_case_statement="'$'$(_model_builder._join \'|\' ${_g_commands[@]})'$'"'
  echo '  declare -g _first_command=true'
  echo '  declare -g _show_command_options=true'
  echo '  declare -g -a _all_commands=('${_all_commands[@]}')'
  echo '  declare -g -a _current_commands'
  echo '  declare -g -a _remaining_commands'
  echo '  declare -g -a _current_models'
  echo '  declare -g -a _selected_models'
  echo '  declare -g -a _remaining_models'
  echo '}'
  type _model_builder._get_options | sed '1d'
  type _model_builder._set_current_models | sed '1d'
  type _model_builder._remove_item | sed '1d'
  type _model_builder._sort_unique | sed '1d'
  type _model_builder._join | sed '1d'
  type _model_builder._subcommand | sed '1d'
  type _model_builder._subcommand_show_frameworks | sed '1d'
  type _model_builder._subcommand_show_frameworks_and_models | sed '1d'
  type _model_builder._subcommand_show_commands_frameworks_and_models | sed '1d'
  type _model_builder._completion | sed '1d'
  echo 'complete -o bashdefault -F _model_builder._completion model-builder'
}

#
# Echos all args if --verbose || --dry-run
# Evals all args if ! --dry-run
#
_model_builder._echo_command()
{
  if [[ $@ =~ --verbose ]] || [[ $@ =~ --dry_run ]]; then
    echo $@
  fi
  if ! [[ $@ =~ --dry_run ]]; then
    eval $@
  fi
}

#
# Entry point for 'model-builder init-spec ...'
# Creates model from specification
#
_model_builder.init_spec()
{
  local _args=() _asm_new_spec _new_model _model_dir _model_dir_mount _use_case

  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'Creates a new specification based on the model arguments\n'
        return 0
        ;;
      --verbose)
        _args+=("$1")
        ;;
      -f|--framework)
        shift
        ;;
      --dry-run)
        _args+=("--dry_run")
        ;;
      *)
        ;;
    esac
    shift
  done
  # Used for auto a new spec with the list of package files for the specified model

  # Check for the tf-tool container and build it if it doesn't exist
  _model_builder._check-for-imz-tf-tools $_quiet || _model_builder._build-imz-tf-tools $_g_root/docker

  _new_model=$1
  _use_case=$2

  echo "Auto-generating a new spec file for: $_new_model"

  _model_dir_mount='-v '$MODEL_DIR':/tf/models'
  _model_dir='--model_dir=models'

  _model_download=""
  if ! [[ -z $MODEL_URL ]]; then
    _model_download="--model_download=${MODEL_URL}"
  fi

  if [[ ! -z ${_use_case} ]]; then
    echo "Using use case '${_use_case}' if the model's directories don't already exist"
    _use_case="--use_case=${_use_case}"
  fi

  pushd $_g_root/docker 1>/dev/null 2>/dev/null

  # Note: Run as user so that the spec file isn't owned by root
  _asm_new_spec="docker run --rm -u $(id -u):$(id -g) $(_model_builder._get-proxy-env-vars) $_model_dir_mount -v $(pwd):/tf -it imz-tf-tools python3 assembler.py "

  _model_builder._echo_command ${_asm_new_spec} \
      --arg http_proxy=${http_proxy} \
      --arg https_proxy=${https_proxy} \
      --arg no_proxy=${no_proxy} \
      --spec_dir=${_spec_dir_mounted}/${_current_framework} \
      --generate_new_spec=${_new_model} \
      --framework=${_current_framework} \
      ${_use_case} \
      $_model_download \
      $_model_dir \
      ${_args[@]}
  popd 1>/dev/null 2>/dev/null
}

_model_builder_get_images_from_log()
{
  # Checks the log file for the list of 'IMAGES_BUILT=' which should be
  # formatted as a comma separated list. Returns that comma separated value
  log_path=$1

  if [[ -f ${log_path} ]]; then
    # Find the last match for the list of images built in the output file
    images_built=$(grep '^IMAGES_BUILT=' ${log_path} | tail -1)
    # Strip off the "IMAGES_BUILT=" from that line to return
    echo $(echo $images_built | sed 's/^IMAGES_BUILT=//')
  else
    echo ""
  fi
}

#
# Entry point for 'model-builder build ...'
# Builds one or more images from models
# Calls assembler.py
#
_model_builder.build()
{
  local _abs_model_dir='' _args=() _asm_images _count _dry_run=false _log_image_lists=() _model_dir='' 
  local _model_dir_mount='' _model_package_folder _output_dir='' _release_args=() _release_group 
  local _temp_model_package_dir _verbose='' 
  pushd $_g_root/docker 1>/dev/null 2>/dev/null
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'Builds the images for the specified models (default=*) in the specified framework (default='"${DEFAULT_FRAMEWORK}"').\n'
        exit 0
        ;;
      --dry-run)
        _dry_run=true
        _args+=("--dry_run") 
        ;;
      --model_dir=*)
        _abs_model_dir=${1#*=}
        _model_dir_mount='-v '$_abs_model_dir':/tf/models'
        _model_dir='--model_dir=models'
        _args+=("$_model_dir") 
        ;;
      --output_dir=*)
        _output_dir=${1#*=}
        _output_dir=${_output_dir##*/}
        _args+=("$1") 
        ;;
      -f|--framework)
        ;;
      --verbose)
        _verbose=$1
        _args+=("$1") 
        ;;
      *)
        _args+=("$1") 
        ;;
    esac
    shift
  done

  # Define default values for the variables
  if [ -z "${MODEL_PACKAGE_DIR}" ]; then
    MODEL_PACKAGE_DIR=../output
  fi

  if [ -z "${MODEL_WORKSPACE}" ]; then
    MODEL_WORKSPACE=/workspace
  fi
  
  # Check for the tf-tool container and build it if it doesn't exist
  _model_builder._check-for-imz-tf-tools $_quiet || _model_builder._build-imz-tf-tools $_g_root/docker
  
  # Verify that the model package directory exists
  if [ ! -d ${MODEL_PACKAGE_DIR} ]; then
    echo "The model package directory was not found at: ${MODEL_PACKAGE_DIR}"
    echo "Model packages must be built before building containers"
    exit 1
  fi 
  
  # Copy model package directory to this docker folder so that it's included in
  # the imz-tf-tools volume mount and it'll be in the same build context (directory)
  # that we are running from.
  _model_package_folder="model_packages"
  _temp_model_package_dir="$(pwd)/${_model_package_folder}/"
  rm -rf ${_temp_model_package_dir}
  mkdir -p ${_temp_model_package_dir}
  _count=$(ls ${MODEL_PACKAGE_DIR}/*.tar.gz 2>/dev/null | wc -l)
  if (( $_count > 0 )); then
    cp ${MODEL_PACKAGE_DIR}/*.tar.gz ${_temp_model_package_dir}
  fi

  _asm_images="docker run --rm $(_model_builder._get-proxy-env-vars) -v $(pwd):/tf $_model_dir_mount -v /var/run/docker.sock:/var/run/docker.sock imz-tf-tools python3 assembler.py "

  for _release_group in "${_release_groups[@]}"
  do
    temp_output_file=$(mktemp output.XXXXXXXXX)
    _tmp_files+=($temp_output_file)
    case "$_release_group" in
      versioned)
        _release_args=("--arg" "_TAG_PREFIX=${TAG_PREFIX}" "--arg" "TENSORFLOW_TAG=${TENSORFLOW_TAG}" "--arg" "TENSORFLOW_IMAGE=${TENSORFLOW_IMAGE}")
        ;;
      tf_1.15.2_containers)
        _release_args=("--arg" "_TAG_PREFIX=1.15.2" "--arg" "TENSORFLOW_TAG=1.15.2")
        ;;
      *)
        _release_args=()
        ;;
    esac
    _model_builder._echo_command ${_asm_images} \
      --arg http_proxy=${http_proxy} \
      --arg https_proxy=${https_proxy} \
      --arg no_proxy=${no_proxy} \
      --arg PACKAGE_DIR=${_model_package_folder} \
      --arg MODEL_WORKSPACE=${MODEL_WORKSPACE} \
      --spec_dir=${_spec_dir_mounted}/${_current_framework} \
      --repository ${LOCAL_REPO} \
      --release ${_release_group} \
      ${TEST_DIR:+"--run_tests_path" "$TEST_DIR"} \
      --build_images \
      ${_release_args[@]} \
      ${_args[@]} 2>&1 | tee $temp_output_file

    result=$?
    if (( $result != 0 )); then
      echo "Warning: A non-zero error code ($result) was returned when building the '$_release_group' images"
    fi
    _log_image_lists+=($(_model_builder_get_images_from_log $temp_output_file))
    rm $temp_output_file
  done

  if [[ $_dry_run == false ]]; then
    echo "--------------------------------------"
  fi

  # Print out the images that were just built, and append to the IMAGE_LIST_FILE
  count=0
  for _log_image_list in "${_log_image_lists[@]}"
  do
    for i in $(echo ${_log_image_list} | sed "s/,/ /g")
    do
      echo $i
      count=$(($count+1))
      if [[ ! -z ${IMAGE_LIST_FILE} ]]; then
        echo $i >> ${IMAGE_LIST_FILE}
      fi
    done
  done

  if [[ $_dry_run == false ]] && [[ ${count} == 0 ]]; then
    echo ""
    echo "No containers were built for framework: ${_current_framework} "
    echo "Check the log output above for errors and ensure that you have the proper --framework and build names specified."
  fi

  # Delete the temporary copy of the model package directory
  rm -rf ${_temp_model_package_dir}
  popd 1>/dev/null 2>/dev/null
}

#
# Entry point for 'model-builder package ...'
# Packages one or more models
# Calls assembler.py
#
_model_builder.package()
{
  local _abs_model_dir='' _args=() _asm_dockerfiles _dry_run=false _model_dir='' _model_dir_mount='' _output_dir='' _release_args=() _framework _verbose=''
  pushd $_g_root/docker 1>/dev/null 2>/dev/null
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'Builds the model packages for the specified models (default=*) in the specified framework.\n'
        return 0
        ;;
      --dry-run)
        _dry_run=true
        _args+=("--dry_run")
        ;;
      --model_dir=*)
        _abs_model_dir=${1#*=}
        _model_dir_mount='-v '$_abs_model_dir':/tf/models'
        _model_dir='--model_dir=models'
        _args+=("$_model_dir") 
        ;;
      --output_dir=*)
        _output_dir=${1#*=}
        _output_dir=${_output_dir##*/}
        _args+=("$1") 
        ;;
      -f|--framework)
        ;;
      --verbose)
        _verbose=$1
        _args+=("$1") 
        ;;
      *)
        _args+=("$1") 
        ;;
    esac
    shift
  done

  # Check for the imz-tf-tools container and build it if it doesn't exist
  _model_builder._check-for-imz-tf-tools $_quiet || _model_builder._build-imz-tf-tools $_g_root/docker

  # Check for git branch/commit env vars (normally from Jenkins) for the wrapper package info.txt
  _git_vars=""
  if [[ ! -z "${GIT_BRANCH}" ]]; then
    _git_vars=" --env GIT_BRANCH=${GIT_BRANCH}"
  fi
  if [[ ! -z "${GIT_COMMIT}" ]]; then
    _git_vars="${_git_vars} --env GIT_COMMIT=${GIT_COMMIT}"
  fi
  
  # Note: Run as user so that the dockerfiles have the right permissions
  _asm_dockerfiles="docker run --rm -u $(id -u):$(id -g) $(_model_builder._get-proxy-env-vars) ${_git_vars} -v $(pwd):/tf $_model_dir_mount imz-tf-tools python3 assembler.py "

  for _release_group in "${_release_groups[@]}"
  do
    case "$_release_group" in
      versioned)
        _release_args=("--arg" "_TAG_PREFIX=${TAG_PREFIX}" "--arg" "TENSORFLOW_TAG=${TENSORFLOW_TAG}" "--arg" "TENSORFLOW_IMAGE=${TENSORFLOW_IMAGE}")
        ;;
      tf_1.15.2_containers)
        _release_args=("--arg" "_TAG_PREFIX=1.15.2" "--arg" "TENSORFLOW_TAG=1.15.2")
        ;;
      *)
        _release_args=()
        ;;
    esac
    # Call the assembler to build model packages
    _model_builder._echo_command ${_asm_dockerfiles} \
      --release $_release_group \
      --spec_dir=${_spec_dir_mounted}/${_current_framework} \
      --build_packages \
      ${_release_args[@]} \
      ${_args[@]}
  done

  popd 1>/dev/null 2>/dev/null
}

#
# Entry point for 'model-builder generate-deployment ...'
# Generates one or more yaml files from specified k8 packages
#
_model_builder.generate_deployment()
{
  local _abs_model_dir _args=() _asm_dockerfiles _count _dry_run=false _model_dir='' _model_dir_mount='' _output_dir='' _release_args=() _framework _verbose=''
  pushd $_g_root/docker 1>/dev/null 2>/dev/null
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'Generates k8 yaml files for the specified model in the k8 framework.\n'
        return 0
        ;;
      --model_dir=*)
        _abs_model_dir=${1#*=}
        _model_dir_mount='-v '$_abs_model_dir':/tf/models'
        _model_dir='--model_dir=models'
        _args+=("$_model_dir") 
        ;;
      --verbose)
        _verbose=$1
        _args+=("$1") 
        ;;
      --dry-run)
        _dry_run=true
        _args+=("--dry_run")
        ;;
      -f|--framework)
        ;;
      *)
        _args+=("$1") 
        ;;
    esac
    shift
  done
  
  # Check for the imz-tf-tools container and build it if it doesn't exist
  _model_builder._check-for-imz-tf-tools $_quiet || _model_builder._build-imz-tf-tools $_g_root/docker
  
  # Note: Run as user so that the generated deployment(s) have the right permissions
  _asm_dockerfiles="docker run --rm -u $(id -u):$(id -g) -e MODEL_BUILDER_SPEC_HOME=/specs $(_model_builder._get-proxy-env-vars) -v ${_spec_home}:/specs -v $(pwd):/tf -w /tf $_model_dir_mount imz-tf-tools python3 assembler.py "

  for _release_group in "${_release_groups[@]}"
  do
    case "$_release_group" in
      versioned)
        _release_args=("--arg" "_TAG_PREFIX=${TAG_PREFIX}" "--arg" "TENSORFLOW_TAG=${TENSORFLOW_TAG}" "--arg" "TENSORFLOW_IMAGE=${TENSORFLOW_IMAGE}")
        ;;
      tf_1.15.2_containers)
        _release_args=("--arg" "_TAG_PREFIX=1.15.2" "--arg" "TENSORFLOW_TAG=1.15.2")
        ;;
      *)
        _release_args=()
        ;;
    esac
    # Call the assembler to generate deployments
    _model_builder._echo_command ${_asm_dockerfiles} \
      --release $_release_group \
      --spec_dir=${_spec_dir_mounted}/${_current_framework} \
      --generate_deployments \
      ${_release_args[@]} \
      ${_args[@]}
  done
  popd 1>/dev/null 2>/dev/null
}

#
# Entry point for 'model-builder generate-dockerfile ...'
# Generates one or more dockerfiles from specified models
# Calls assembler.py
#
_model_builder.generate_dockerfile()
{
  local _abs_model_dir _args=() _asm_dockerfiles _count _dry_run=false _model_dir='' _model_dir_mount='' _output_dir='' _release_args=() _framework _verbose=''
  pushd $_g_root/docker 1>/dev/null 2>/dev/null
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'Builds the dockerfiles for the specified models (default=*) in the specified framework.\n'
        return 0
        ;;
      --model_dir=*)
        _abs_model_dir=${1#*=}
        _model_dir_mount='-v '$_abs_model_dir':/tf/models'
        _model_dir='--model_dir=models'
        _args+=("$_model_dir") 
        ;;
      --verbose)
        _verbose=$1
        _args+=("$1") 
        ;;
      --dry-run)
        _dry_run=true
        _args+=("--dry_run")
        ;;
      -f|--framework)
        ;;
      *)
        _args+=("$1") 
        ;;
    esac
    shift
  done
  
  # Check for the imz-tf-tools container and build it if it doesn't exist
  _model_builder._check-for-imz-tf-tools $_quiet || _model_builder._build-imz-tf-tools $_g_root/docker
  
  # Note: Run as user so that the dockerfiles have the right permissions
  _asm_dockerfiles="docker run --rm -u $(id -u):$(id -g) $(_model_builder._get-proxy-env-vars) -v $(pwd):/tf $_model_dir_mount imz-tf-tools python3 assembler.py "

  for _release_group in "${_release_groups[@]}"
  do
    case "$_release_group" in
      versioned)
        _release_args=("--arg" "_TAG_PREFIX=${TAG_PREFIX}" "--arg" "TENSORFLOW_TAG=${TENSORFLOW_TAG}" "--arg" "TENSORFLOW_IMAGE=${TENSORFLOW_IMAGE}")
        ;;
      tf_1.15.2_containers)
        _release_args=("--arg" "_TAG_PREFIX=1.15.2" "--arg" "TENSORFLOW_TAG=1.15.2")
        ;;
      *)
        _release_args=()
        ;;
    esac
    # Call the assembler to construct dockerfiles
    _model_builder._echo_command ${_asm_dockerfiles} \
      --release $_release_group \
      --spec_dir=${_spec_dir_mounted}/${_current_framework} \
      --construct_dockerfiles \
      ${_release_args[@]} \
      ${_args[@]}
  done
  popd 1>/dev/null 2>/dev/null
  
  if [[ $_dry_run == false ]]; then
    if ! [[ -d dockerfiles ]]; then
      echo $(pwd)/dockerfiles does not exist >&2
      exit 1
    fi
  fi
}

#
# Entry point for 'model-builder generate-documentation ...'
# Creates one or more README.md's from specified models
# Calls assembler.py
#
_model_builder.generate_documentation()
{
  local _abs_model_dir _args=() _asm_dockerfiles _count _model_dir='' _model_dir_mount='' _output_dir='' _verbose=''
  pushd $_g_root/docker 1>/dev/null 2>/dev/null
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'Builds a README.md file using the doc partials for the specified models (default=*) in the specified framework.\n'
        return 0
        ;;
      --model_dir=*)
        _abs_model_dir=${1#*=}
        _model_dir_mount='-v '$_abs_model_dir':/tf/models'
        _model_dir='--model_dir=models'
        _args+=("$_model_dir") 
        ;;
      --verbose)
        _verbose=$1
        _args+=("$1") 
        ;;
      --dry-run)
        _args+=("--dry_run")
        ;;
      -f|--framework)
        ;;
      *)
        _args+=("$1") 
        ;;
    esac
    shift
  done
  
  # Check for the imz-tf-tools container and build it if it doesn't exist
  _model_builder._check-for-imz-tf-tools $_quiet || _model_builder._build-imz-tf-tools $_g_root/docker

  # Note: Run as user so that the dockerfiles have the right permissions
  _asm_dockerfiles="docker run --rm -u $(id -u):$(id -g) $(_model_builder._get-proxy-env-vars) -v $(pwd):/tf $_model_dir_mount imz-tf-tools python3 assembler.py "

  for _release_group in "${_release_groups[@]}"
  do
    case "$_release_group" in
      versioned)
        _release_args=("--arg" "_TAG_PREFIX=${TAG_PREFIX}" "--arg" "TENSORFLOW_TAG=${TENSORFLOW_TAG}" "--arg" "TENSORFLOW_IMAGE=${TENSORFLOW_IMAGE}")
        ;;
      tf_1.15.2_containers)
        _release_args=("--arg" "_TAG_PREFIX=1.15.2" "--arg" "TENSORFLOW_TAG=1.15.2")
        ;;
      *)
        _release_args=()
        ;;
    esac
    # Call the assembler to generate documentation
    _model_builder._echo_command ${_asm_dockerfiles} \
      --release $_release_group \
      --spec_dir=${_spec_dir_mounted}/${_current_framework} \
      --generate_documentation \
      ${_release_args[@]} \
      ${_args[@]}
  done
  popd 1>/dev/null 2>/dev/null
}

_model_builder.commands()
{
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'List all subcommands within model-builder\n'
        exit 0
        ;;
      *)
        ;;
    esac
    shift
  done
  echo ${_g_commands[@]}
}

#
# Entry point for 'model-builder images ...'
#
_model_builder.images()
{
  local _abs_model_dir _args=() _asm_dockerfiles _dry_run=false _model_dir='' _model_dir_mount='' _release_args=() _framework _verbose=''
  pushd $_g_root/docker 1>/dev/null 2>/dev/null
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'List the images for the specified framework (default='"${DEFAULT_FRAMEWORK}"').\n'
        exit 0
        ;;
      --dry-run)
        _dry_run=true
        _args+=("--dry_run") 
        ;;
      --model_dir=*)
        _abs_model_dir=${1#*=}
        _model_dir_mount='-v '$_abs_model_dir':/tf/models'
        _model_dir='--model_dir=models'
        _args+=("$_model_dir") 
        ;;
      -f|--framework)
        ;;
      --verbose)
        _verbose=$1
        _args+=("$1")
        ;;
      *)
        _args+=("$1") 
        ;;
    esac
    shift
  done
  
  # Check for the imz-tf-tools container and build it if it doesn't exist
  _model_builder._check-for-imz-tf-tools $_quiet || _model_builder._build-imz-tf-tools $_g_root/docker
  
  # Note: Run as user so that the dockerfiles have the right permissions
  _asm_dockerfiles="docker run --rm -u $(id -u):$(id -g) $(_model_builder._get-proxy-env-vars) -v $(pwd):/tf $_model_dir_mount imz-tf-tools python3 assembler.py "

  # Call the assembler to list the images
  for _release_group in "${_release_groups[@]}"
  do
    case "$_release_group" in
      versioned)
        _release_args=("--arg" "_TAG_PREFIX=${TAG_PREFIX}" "--arg" "TENSORFLOW_TAG=${TENSORFLOW_TAG}" "--arg" "TENSORFLOW_IMAGE=${TENSORFLOW_IMAGE}")
        ;;
      tf_1.15.2_containers)
        _release_args=("--arg" "_TAG_PREFIX=1.15.2" "--arg" "TENSORFLOW_TAG=1.15.2")
        ;;
      *)
        _release_args=("--arg" "_TAG_PREFIX=${TAG_PREFIX}")
        ;;
    esac
    _model_builder._echo_command ${_asm_dockerfiles} \
      --release ${_release_group} \
      --repository ${LOCAL_REPO} \
      --list_images \
      --spec_dir=${_spec_dir_mounted}/${_current_framework} \
      ${_release_args[@]} \
      ${_args[@]} 2>&1 | sort | column -c 2 -t
    if [[ $_dry_run == false ]]; then
      if (( ${#_current_releases[@]} > 1 )); then
        echo ""
      fi
    fi
  done
  popd 1>/dev/null 2>/dev/null
}

#
# Entry point for 'model-builder models'
#
_model_builder.models()
{
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'List all models for the specified framework.\n'
        exit 0
        ;;
      *)
        ;;
    esac
    shift
  done
  echo ${_current_models[@]}
}

#
# Entry point for 'model-builder packages ...'
#
_model_builder.packages()
{
  local _abs_model_dir _args=() _asm_dockerfiles _dry_run=false _model_dir='' _model_dir_mount='' _verbose=''
  pushd $_g_root/docker 1>/dev/null 2>/dev/null
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'List the packages that would be built for the specified framework.\n'
        exit 0
        ;;
      --dry-run)
        _dry_run=true
        _args+=("--dry_run")
        ;;
      --model_dir=*)
        _abs_model_dir=${1#*=}
        _model_dir_mount='-v '$_abs_model_dir':/tf/models'
        _model_dir='--model_dir=models'
        _args+=("$_model_dir") 
        ;;
      -f|--framework)
        ;;
      --verbose)
        _verbose=$1
        _args+=("$1")
        ;;
      *)
        _args+=("$1") 
        ;;
    esac
    shift
  done
  
  # Check for the imz-tf-tools container and build it if it doesn't exist
  _model_builder._check-for-imz-tf-tools $_quiet || _model_builder._build-imz-tf-tools $_g_root/docker
  
  # Note: Run as user so that the dockerfiles have the right permissions
  _asm_dockerfiles="docker run --rm -u $(id -u):$(id -g) $(_model_builder._get-proxy-env-vars) -v $(pwd):/tf $_model_dir_mount imz-tf-tools python3 assembler.py "

  if [[ ${#_current_framework[@]} == 0 ]]; then
    _current_framework=${DEFAULT_FRAMEWORK}
  fi

  # Call the assembler to list packages
  _model_builder._echo_command ${_asm_dockerfiles} \
    --spec_dir=${_spec_dir_mounted}/${_current_framework} \
    --repository ${LOCAL_REPO} \
    --list_packages \
    --arg _TAG_PREFIX=${TAG_PREFIX} \
    --arg TENSORFLOW_TAG=${TENSORFLOW_TAG} \
    ${_args[@]} 2>&1 | sort | column -c 2 -t

  popd 1>/dev/null 2>/dev/null
}

#
# Entry point for 'model-builder frameworks'
#
_model_builder.frameworks()
{
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'List all frameworks within the specifications\n'
        exit 0
        ;;
      *)
        ;;
    esac
    shift
  done
  echo ${_all_frameworks[@]}
}

#
# Called at beginning of test
#
setup()
{
  # TBD sanity check that command, framework, models are non-zero
  if (( 0 != 0 )); then
    echo '# setup' >&3
  fi
}

#
# Called at end of test
#
teardown()
{
  # TBD sanity check that command, framework, models are non-zero
  if (( 0 != 0 )); then
    echo '# teardown' >&3
  fi
}

#
# test function, returns last modified for image
#
last_built()
{
  local _image _created _os=$(uname -s)
  _image=$(docker images | grep $1 | head -1 | awk '{printf("%s:%s\n", $1, $2)}')
  _created="$(docker inspect $_image --format '{{.Created}}' | sed 's/T/ /g' | sed 's/\.[0-9]*Z$//')"
  case "$_os" in
    Darwin)
      echo $(( $(date +%s) - $(date -juf'%Y-%m-%d %H:%M:%S' "$_created" +%s) ))
      ;;
    Linux)
      echo $(( $(date +%s) - $(date --utc --date="$_created" '+%s') ))
      ;;
  esac
}

#
# test function, returns last modified for file
#
last_modified()
{
  local _os=$(uname -s)
  case "$_os" in
    Darwin)
      echo $(( $(date +%s) - $(stat -f%c $1) ))
      ;;
    Linux)
      expr $(date +%s) - $(stat -c %Y $1)
      ;;
  esac
}

#
# Generates tests for model-builder build -f <framework> models...
#
_model_builder._tests.build()
{
  local _image_name _image_names _model _framework=''
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      --arg)
        shift
        ;;
      -h|--help)
        echo -e 'generate-documentation tests\n'
        exit 0
        ;;
      --model_dir=|--output_dir=|--dry_run)
        ;;
      --framework)
        shift
        _framework=$1
        ;;
      *)
        ;;
    esac
    shift
  done
  echo ''  >> $_g_root'/tests/model-builder.bats'
  echo '#' >> $_g_root'/tests/model-builder.bats'
  echo '# *** tests for build ***' >> $_g_root'/tests/model-builder.bats'
  echo '#' >> $_g_root'/tests/model-builder.bats'
  echo ''  >> $_g_root'/tests/model-builder.bats'
  _image_names=$(mktemp /tmp/image_names.XXXXXXXXXX)
  _tmp_files+=($_image_names)
  model-builder images -f $_framework > $_image_names
  for _model in "$@"; do
    _image_name=$(cat $_image_names | grep '^.*'$_model'$' | awk '{print $3}' || exit 0)
    if [[ -n $_image_name ]]; then
      echo $'@test "validate build image for '$_model$' in framework '$_framework$' creates '$_image_name$'" {
  run model-builder build -f '$_framework$' '$_model$'
  (( $status == 0 ))
  [[ "${lines[@]}" =~ '$_model$' ]]
}' >> $_g_root'/tests/model-builder.bats'
    fi
  done
}

#
# parses model name into parts: _prefix, _precision, _mode
# also sets the top-level directory to either quickstart or k8s
#
_model_builder._parse_model_name()
{
  local _model=$1 _framework=$2 _last_suffix _lmode _lprefix _suffix
  declare -n __prefix=$3
  declare -n __precision=$4
  declare -n __mode=$5
  declare -n __dir=$6
  if [[ $_model =~ bfloat16 ]]; then
    __precision=bfloat16
  elif [[ $_model =~ fp32 ]]; then
    __precision=fp32
  elif [[ $_model =~ int8 ]]; then
    __precision=int8
  else 
    echo 'Unknown precision specified in model: '$_model >&2
    return 1
  fi
  _lprefix=${_model%-${__precision}-*}
  _suffix=${_model/${_lprefix}-/}
  _lmode=${_suffix#*-}
  _last_suffix=${_lmode##*-}
  if [[ $_last_suffix =~ k8s ]]; then
    __prefix=/${_lprefix//-/[_-]}/
    __dir=k8s
  else
    __prefix=/${_framework}/${_lprefix//-/[_-]}/
    __dir=quickstart
  fi
  __mode=${_lmode%%-*}

  return 0
}

#
# Generates tests for model-builder generate-documentation -f <framework> models...
#
_model_builder._tests.generate_documentation()
{
  local _dir='' _docdir _framework='' _image_name _image_names _mode _model _precision _prefix _re="[[:space:]]+"
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      --arg)
        shift
        ;;
      -h|--help)
        echo -e 'generate-documentation tests\n'
        exit 0
        ;;
      --model_dir=|--output_dir=|--dry_run)
        ;;
      --framework)
        shift
        _framework=$1
        ;;
      *)
        ;;
    esac
    shift
  done
  echo '#' >> $_g_root'/tests/model-builder.bats'
  echo '# *** tests for generate-documentation ***' >> $_g_root'/tests/model-builder.bats'
  echo '#' >> $_g_root'/tests/model-builder.bats'
  echo ''  >> $_g_root'/tests/model-builder.bats'
  for _model in "$@"; do
    _model_builder._parse_model_name $_model $_framework _prefix _precision _mode _dir
    ret_val=$?
    if (( $ret_val != 0 )); then
      echo "Skipping validation of generate-documentation for $_model"
      continue
    fi
    _docdir=$(find $_dir -type d | grep $_prefix | grep $_mode | grep $_precision'$')
    if [[ -n $_docdir ]]; then
      if [[ $_docdir =~ $_re ]]; then
        echo "More than one path returned from \`find $_dir -type d ' | grep '$_prefix' | grep '$_mode' | grep '$_precision'$'\`: $(echo $_docdir). Skipping." >&2
        continue
      fi
      _readme=$_docdir/README.md
      echo $'@test "validate generate-documentation for '$_model$' in framework '$_framework$' creates '$_readme$'" {
  run model-builder generate-documentation -f '$_framework$' '$_model$'
  (( $status == 0 ))
  [[ -f '$_readme$' ]]
  (( $(last_modified '$_readme$') <= 50 ))
}' >> $_g_root'/tests/model-builder.bats'
    fi
  done
}

#
# Generates tests for model-builder generate-dockerfile -f <framework> models...
#
_model_builder._tests.generate_dockerfile()
{
  local _dockerfile_path _dockerfile _path_length _model _framework _result _results _subdir
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      --arg)
        shift
        ;;
      -h|--help)
        echo -e 'generate-dockerfile tests\n'
        exit 0
        ;;
      --model_dir=|--output_dir=|--dry_run)
        ;;
      --framework)
        shift
        _framework=$1
        ;;
      *)
        ;;
    esac
    shift
  done
  case "$_framework" in
    tensorflow)
      _subdir='{model_containers,dataset_containers}'
      ;;
    *)
      _subdir=$_framework
      ;;
  esac
  echo ''  >> $_g_root'/tests/model-builder.bats'
  echo '#' >> $_g_root'/tests/model-builder.bats'
  echo '# *** tests for generate-dockerfile ***' >> $_g_root'/tests/model-builder.bats'
  echo '#' >> $_g_root'/tests/model-builder.bats'
  echo ''  >> $_g_root'/tests/model-builder.bats'
  for _model in "$@"; do
    _results=( $(find $(eval "echo dockerfiles/$_subdir") -name '*'$_model'.Dockerfile' 2>/dev/null) )
    case ${#_results[@]} in 
      0)
        _dockerfile_path=''
        ;;
      1)
       _dockerfile_path=${_results[0]}
       ;;
      *)
       # disambiguate between intel-tf-image-segmentation-3d-unet-fp32-inference.Dockerfile and intel-tf-image-segmentation-unet-fp32-inference.Dockerfile
       _path_length=${#_results[0]}
       _dockerfile_path=${_results[0]}
       for _result in "${_results[@]}"; do
         if (( ${#_result} < $_path_length )); then
           _dockerfile_path=$_result
         fi
       done
       ;;
    esac
    if [[ -n $_dockerfile_path ]]; then
      _dockerfile=${_dockerfile_path##*/}
      echo $'@test "validate generate-dockerfile for '$_model$' in framework '$_framework$' creates '$_dockerfile$'" {
  run model-builder generate-dockerfile -f '$_framework$' '$_model$'
  (( $status == 0 ))
  [[ "${lines[@]}" =~ '$_dockerfile$' ]]
  (( $(last_modified '$_dockerfile_path$') <= 50 ))
}' >> $_g_root'/tests/model-builder.bats'
   fi
 done
}

#
# Generates tests for model-builder generate-deployment models...
#
_model_builder._tests.generate_deployment()
{
  local _asm_dockerfiles _model _framework
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      --arg)
        shift
        ;;
      -h|--help)
        echo -e 'generate-deployment tests\n'
        exit 0
        ;;
      --model_dir=|--output_dir=|--dry_run)
        ;;
      --framework)
        shift
        _framework=$1
        ;;
      *)
        ;;
    esac
    shift
  done
  pushd $_g_root/docker 1>/dev/null 2>/dev/null
  # Check for the tf-tool container and build it if it doesn't exist
  _model_builder._check-for-imz-tf-tools $_quiet || _model_builder._build-imz-tf-tools $_g_root/docker
  _model_dir=$_g_root/..
  _model_dir_mount='-v $_model_dir:$_model_dir'
  # Note: Run as user so that the generated deployment(s) have the right permissions
  _asm_dockerfiles="docker run --rm -u $(id -u):$(id -g) -e MODEL_BUILDER_SPEC_HOME=/specs $(_model_builder._get-proxy-env-vars) -v ${_spec_home}:/specs -v $(pwd):$_model_dir/.. -w $_model_dir/.. $_model_dir_mount imz-tf-tools python3 assembler.py "
  _model_builder._echo_command ${_asm_dockerfiles} \
    --release $_release_group \
    --spec_dir=./specs/$_framework \
    --generate_deployments_tests \
    ${_release_args[@]} \
    ${_args[@]}
  popd 1>/dev/null 2>/dev/null
}

#
# Generates tests for model-builder package -f <framework> models...
#
_model_builder._tests.package()
{
  local _model _package_names _package_path _package _framework
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      --arg)
        shift
        ;;
      -h|--help)
        echo -e 'package tests\n'
        exit 0
        ;;
      --model_dir=|--output_dir=|--dry_run)
        ;;
      --framework)
        shift
        _framework=$1
        ;;
      *)
        ;;
    esac
    shift
  done
  echo ''  >> $_g_root'/tests/model-builder.bats'
  echo '#' >> $_g_root'/tests/model-builder.bats'
  echo '# *** tests for package ***' >> $_g_root'/tests/model-builder.bats'
  echo '#' >> $_g_root'/tests/model-builder.bats'
  echo ''  >> $_g_root'/tests/model-builder.bats'
  _package_names=$(mktemp /tmp/package_names.XXXXXXXXXX)
  _tmp_files+=($_package_names)
  model-builder packages -f $_framework 2>&1 > $_package_names
  for _model in "$@"; do
    _package_path=$(cat $_package_names | grep '^.*'$_model'.tar.gz$' | awk '{print $2}' || exit 0)
    if [[ -n $_package_path ]]; then
      _package=${_package_path##*/}
      echo $'@test "validate package for '$_model$' in framework '$_framework$' creates '$_package$'" {
  run model-builder package -f '$_framework$' '$_model$'
  (( $status == 0 ))
  [[ "${lines[@]}" =~ '$_package$' ]]
  (( $(last_modified '$_package_path$') <= 50 ))
}' >> $_g_root'/tests/model-builder.bats'
    fi
  done
}

_model_builder.run_test_suite()
{
  local _abs_model_dir='' _args=() _dry_run='' _model_dir='' _model_dir_mount='' _model_package_folder 
  local _output_dir='' _release_args=() _framework _frameworks=() _temp_model_package_dir _verbose='' 
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'Builds and runs tests for the specified models (default=*) in the specified framework (default='"${_current_framework}"').\n'
        exit 0
        ;;
      -c|--command)
        ;;
      --dry-run)
        _dry_run='--dry_run'
        ;;
      --model_dir=*)
        _abs_model_dir=${1#*=}
        _model_dir_mount='-v '$_abs_model_dir':/tf/models'
        _model_dir='--model_dir=models'
        _args+=("$_model_dir") 
        ;;
      --output_dir=*)
        _output_dir=${1#*=}
        _output_dir=${_output_dir##*/}
        _args+=("$1") 
        ;;
      -f|--framework)
        ;;
      --verbose)
        _verbose=$1
        _args+=("$1") 
        ;;
      *)
        _args+=("$1") 
        ;;
    esac
    shift
  done
  rm -f $_g_root'/tests/model-builder.bats'
  type setup | sed '1d' >> $_g_root'/tests/model-builder.bats'
  type teardown | sed '1d' >> $_g_root'/tests/model-builder.bats'
  type last_modified | sed '1d' >> $_g_root'/tests/model-builder.bats'
  echo 'pushd '$(dirname $_g_root) >> $_g_root'/tests/model-builder.bats'

  for _command in "${_current_commands[@]}"
  do
    for _release_group in "${_release_groups[@]}"
    do
      case "$_release_group" in
        versioned)
          _release_args=("--arg" "_TAG_PREFIX=${TAG_PREFIX}" "--arg" "TENSORFLOW_TAG=${TENSORFLOW_TAG}" "--arg" "TENSORFLOW_IMAGE=${TENSORFLOW_IMAGE}")
          ;;
        tf_1.15.2_containers)
          _release_args=("--arg" "_TAG_PREFIX=1.15.2" "--arg" "TENSORFLOW_TAG=1.15.2")
          ;;
        *)
          _release_args=()
          ;;
      esac
    done
    case "$_command" in
      build)
        _model_builder._echo_command \
          _model_builder._tests.build \
          --arg http_proxy=${http_proxy} \
          --arg https_proxy=${https_proxy} \
          --arg no_proxy=${no_proxy} \
          --spec_dir=${_spec_dir_mounted}/${_current_framework} \
          --framework ${_current_framework} \
          ${_release_args[@]} \
          ${_args[@]} \
          $_dry_run \
          ${_selected_models[@]}
        ;;
      generate-documentation)
        _model_builder._echo_command \
          _model_builder._tests.generate_documentation \
          --arg http_proxy=${http_proxy} \
          --arg https_proxy=${https_proxy} \
          --arg no_proxy=${no_proxy} \
          --spec_dir=${_spec_dir_mounted}/${_current_framework} \
          --framework ${_current_framework} \
          ${_release_args[@]} \
          ${_args[@]} \
          $_dry_run \
          ${_selected_models[@]}
        ;;
      generate-dockerfile)
        _model_builder._echo_command \
          _model_builder._tests.generate_dockerfile \
          --arg http_proxy=${http_proxy} \
          --arg https_proxy=${https_proxy} \
          --arg no_proxy=${no_proxy} \
	      --spec_dir=${_spec_dir_mounted}/${_current_framework} \
          --framework ${_current_framework} \
          ${_release_args[@]} \
          ${_args[@]} \
          $_dry_run \
          ${_selected_models[@]}
        ;;
      generate-deployment)
        rm -f $_g_root'/tests/model-builder.bats'
        _current_framework=k8s
        _model_builder._echo_command \
          _model_builder._tests.generate_deployment \
          --arg http_proxy=${http_proxy} \
          --arg https_proxy=${https_proxy} \
          --spec_dir=${_spec_dir_mounted}/${_current_framework} \
          --framework ${_current_framework} \
          ${_release_args[@]} \
          ${_args[@]} \
          $_dry_run \
          ${_selected_models[@]}
        ;;
      package)
        _model_builder._echo_command \
          _model_builder._tests.package \
          --arg http_proxy=${http_proxy} \
          --arg https_proxy=${https_proxy} \
          --arg no_proxy=${no_proxy} \
          --spec_dir=${_spec_dir_mounted}/${_current_framework} \
          --framework ${_current_framework} \
          ${_release_args[@]} \
          ${_args[@]} \
          $_dry_run \
          ${_selected_models[@]}
        ;;
    esac
  done

  _model_builder._echo_command \
    $_g_root/tests/bin/bats \
    $_g_root/tests/model-builder.bats \
    $_dry_run

  result=$?
  if (( $result != 0 )); then
    echo "Warning: A non-zero error code ($result) was returned when building the '$_framework' tests"
  fi
}

#
# Calls model-builder completion and redirects its output to /usr/local/etc/bash_completion.d/'$(basename $0)
#
_model_builder.install_completion()
{
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        echo -e 'Call '$(basename $0)' completion > /usr/local/etc/bash_completion.d/'$(basename $0)
        exit 0
        ;;
      *)
        ;;
    esac
    shift
  done
  if [[ -d /usr/local/etc/bash_completion.d ]]; then
    $0 completion > /usr/local/etc/bash_completion.d/$(basename $0)
  fi
}

#
# Convenience function: Calls generate-documentation, package, generate-dockerfile, build
#
_model_builder.make()
{
  # generate documentation
  _model_builder.generate_documentation $@
  if (( $? != 0 )); then
    echo "There was an error while generating documentation." >&2
    exit 1
  fi

  # build model packages
  _model_builder.package $@
  if (( $? != 0 )); then
    echo "There was an error building model packages." >&2
    exit 1
  fi

  # construct dockerfiles
  _model_builder.generate_dockerfile $@
  if (( $? != 0 )); then
    echo "There was an error while constructing dockerfiles." >&2
    exit 1
  fi

  # build images
  MODEL_PACKAGE_DIR=${MODEL_DIR}/output _model_builder.build $@
  if (( $? != 0 )); then
    echo "There was an error while building docker images." >&2
    exit 1
  fi
}

#
# Parses model args and verifies that the model is 
# is in the set of _current_models[@]
#
_model_builder._validate_models()
{
  local _arg
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        return 0
        ;;
      -c|--command)
        shift
        ;;
      -f|--framework)
        shift
        ;;
      *)
        ;;
    esac
    shift
  done

  for _arg in "$@"; do
    if ! [[ "${_current_models[@]}" =~ $_arg ]]; then
      echo 'unknown model '$_arg >&2
      _usage
      exit 1
    elif ! [[ "${_selected_models[@]}" =~ $_arg ]]; then
      _selected_models+=( $_arg )
      _remaining_models=( ${_remaining_models[@]/$_arg/} )
    fi
  done
  # all models
  if [[ ${#_selected_models[@]} == 0 ]]; then
    _selected_models=( ${_current_models[@]}  )
    _remaining_models=()
  fi
}

#
# Parses args that are frameworks and adds all models within 
# that framework to _current_models[]
#
_model_builder._validate_frameworks()
{
  local _arg _args=() _i _index
  while (( $# > 0 )) && [[ $1 =~ ^- ]]; do
    case "$1" in
      -c|--command)
        shift
        ;;
      -h|--help)
        return 0
        ;;
      -f|--framework)
        _first_framework=true
        shift
        _args+=($1)
        ;;
      *)
        ;;
    esac
    shift
  done
  for _index in "${!_args[@]}"; do
    _arg=${_args[_index]}
    if [[ $_first_framework == true ]]; then
        _current_framework=''
        _current_models=()
        _remaining_models=()
        _first_framework=false
    fi
    if ! [[ "${_all_frameworks[@]}" =~ $_arg ]]; then
      echo '_model_builder._validate_frameworks unknown framework '$_arg >&2
      _usage
      exit 1
    fi
    if [[ "${_all_frameworks[@]}" =~ $_arg ]] && ! [[ "${_current_framework[@]}" =~ $_arg ]]; then
      _current_framework=$_arg
      _remaining_frameworks=( ${_remaining_frameworks[@]/$_arg} )
      _current_models+=( ${_all_models["$_current_framework"]} )
      _remaining_models+=(${_current_models[@]})
    fi
  done

  _release_groups=("versioned")
  if [[ ${_current_framework} == "tensorflow" ]]; then
    if [[ ${TAG_PREFIX} != *"centos"* ]] && [[ ${TAG_PREFIX} != *"spr"* ]]; then
      _release_groups+=("tf_1.15.2_containers")
    fi
  fi
}

#
# Parses args that are commands and verifies 
# the command is in _all_commands[] and adds the command 
# to _current_commands[]
#
_model_builder._validate_commands()
{
  local _arg _args=($@) _commands=() _first_command=true _i _index
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -h|--help)
        return 0
        ;;
      -c|--command)
        shift
        _commands+=($1)
        ;;
      *)
        ;;
    esac
    shift
  done
  for _index in "${!_commands[@]}"; do
    _arg=${_commands[_index]}
    if [[ $_first_command == true ]]; then
      _current_commands=()
      _first_command=false
    fi
    if [[ $_arg == ${_args[@]: -1:1} ]] && (( $_index == ${#_args[@]} - 1 )); then
      echo ${_remaining_commands[@]}
      return 1
    fi
    if ! [[ "${_all_commands[@]}" =~ $_arg ]]; then
      echo 'unknown command '$_arg >&2
      _usage
      exit 1
    fi
    if [[ "${_all_commands[@]}" =~ $_arg ]] && ! [[ "${_current_commands[@]}" =~ $_arg ]]; then
      _current_commands+=( $_arg )
      _remaining_commands=( ${_remaining_commands[@]/$_arg/} )
      _current_framework=${DEFAULT_FRAMEWORK}
      _model_builder._sort_unique _current_framework
    fi
  done
  if [[ ${#_current_commands[@]} == 0 ]]; then
    _current_commands=(${_all_commands[@]})
    _remaining_commands=()
  fi
}

#
# Filters input models as only those in _current_models[@] and returns one or more
#  ' --only_tags_matching=^.*<model>$ --exclude_tags_matching=^.*<model>$'
#
_model_builder._build_only_tags_matching_args()
{
  local _arg _curent_models_count _exclude_model _excluded_models=() _exclude_models=() _key _models=()
  declare -A _models_map
  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      -c|--command)
        shift
        ;;
      -h|--help)
        return 0
        ;;
      -f|--framework)
        shift
        ;;
      *)
        ;;
    esac
    shift
  done
  _current_models_count=${#_current_models[@]}
  for _arg in "$@";do
    if [[ ${_current_models[@]} =~ $_arg ]]; then
      _models+=(' --only_tags_matching=^.*'$_arg'$')
      _key=X${_arg//-/_}
      _models_map[$_key]="true"
      _exclude_models=(${_current_models[@]//*$_arg} $_arg)
      if (( $_current_models_count != ${#_exclude_models[@]} )); then
        _exclude_models=(${_current_models[@]} ${_exclude_models[@]})
        if (( ${#_exclude_models[@]} > 0 )); then
          _excluded_models=($(printf "%s\n" "${_exclude_models[@]}" | sort | uniq -u))
        fi
      fi
    fi
  done
  _exclude_models=()
  for _exclude_model in "${_excluded_models[@]}"; do
    _key=X${_exclude_model//-/_}
    if [[ -z ${_models_map[$_key]} ]]; then
      _exclude_models+=(' --exclude_tags_matching=^.*'$_exclude_model'$')
    fi
  done
  echo ${_exclude_models[@]} ${_models[@]} 
}

#
# Serializes an associative array (map) so can be reified as a global in a different context
# example
#   dict=[["a"]="1 2" ["b"]="3 4" ["c"]="5 6"]
#   exported as 
#     'declare -g -A dict=[["a"]="1 2" ["b"]="3 4" ["c"]="5 6"]
#
_model_builder._export() 
{
  local _name=$1 _exportable=''
  declare -n _assoc=$2
  _exportable+='  declare -g -A '$_name'=('
  for _key in "${!_assoc[@]}"; do
    _exportable+=$'["'$_key$'"]="'${_assoc[$_key]}$'" '
  done
  _exportable+=')'
  echo "  $_exportable"
}

#
# Main entry point for model-builder
#  handles all options and subcommands
#
_model_builder_main()
{
  local _dry_run='' _model_dir='--model_dir='$MODEL_DIR _only_tags_matching='' _output_dir='--output_dir=models/output' _subcommand _verbose=''

  while [[ "$#" -gt "0" && $1 =~ ^- ]]; do
    case "$1" in
      --dry-run)
          _dry_run=$1
          shift
          ;;
      -h|--help)
          _usage
          exit 0
          ;;
      --verbose)
          _verbose=$1
          shift
          ;;
      *)
          echo '_model_builder_main unknown option '$1
          shift
          exit 1
          ;;
    esac
  done

  case 1 in
    $(($#==0))) 
      _usage
      exit 0
      ;;
    $(($#>=1)))
      _subcommand=$1
      shift
      case "$_subcommand" in
        build)
          _current_framework=${DEFAULT_FRAMEWORK}
          _model_builder._set_current_models
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder._validate_models $@ || exit 0
          _only_tags_matching="$(_model_builder._build_only_tags_matching_args $@)"
          MODEL_PACKAGE_DIR=${MODEL_DIR}/output _model_builder.build $_model_dir $_output_dir $_only_tags_matching $_verbose $_dry_run $_nocache $@
          if (( $? != 0 )); then
            echo "There was an error while building docker images." >&2
            exit 1
          fi
          ;;
        commands)
          _model_builder.commands $@
          ;;
        completion)
          _model_builder.completion $@
          ;;
        generate-deployment)
          _current_framework=k8s
          _model_builder._set_current_models
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder._validate_models $@ || exit 0
          _only_tags_matching="$(_model_builder._build_only_tags_matching_args $@)"
          _model_builder.generate_deployment $_model_dir $_output_dir $_only_tags_matching $_verbose $_dry_run $@
          if (( $? != 0 )); then
            echo "There was an error while generating deployments." >&2
            exit 1
          fi
          ;;
        generate-dockerfile)
          _model_builder._set_current_models
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder._validate_models $@ || exit 0
          _only_tags_matching="$(_model_builder._build_only_tags_matching_args $@)"
          _model_builder.generate_dockerfile $_model_dir $_output_dir $_only_tags_matching $_verbose $_dry_run $@
          if (( $? != 0 )); then
            echo "There was an error while constructing dockerfiles." >&2
            exit 1
          fi
          ;;
        generate-documentation)
          _model_builder._set_current_models
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder._validate_models $@ || exit 0
          _only_tags_matching="$(_model_builder._build_only_tags_matching_args $@)"
          _model_builder.generate_documentation $_model_dir $_output_dir $_only_tags_matching $_verbose $_dry_run $@
          if (( $? != 0 )); then
            echo "There was an error while creating documentation." >&2
            exit 1
          fi
          ;;
        images)
          _model_builder._set_current_models
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder.images $_model_dir $_verbose $_dry_run $@
          ;;
        init-spec)
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder.init_spec $_verbose $_dry_run $@
          ;;
        install-completion)
          _model_builder.install_completion $@
          ;;
        make)
          _model_builder._set_current_models
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder._validate_models $@ || exit 0
          _only_tags_matching="$(_model_builder._build_only_tags_matching_args $@)"
          _model_builder.make $_model_dir $_output_dir $_only_tags_matching $_verbose $_dry_run $@
          ;;
        models)
          _model_builder._set_current_models
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder.models $@
          ;;
        package)
          _model_builder._set_current_models
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder._validate_models $@ || exit 0
          _only_tags_matching="$(_model_builder._build_only_tags_matching_args $@)"
          _model_builder.package $_model_dir $_output_dir $_only_tags_matching $_verbose $_dry_run $@
          if (( $? != 0 )); then
            echo "There was an error building model packages."
            exit 1
          fi
          ;;
        packages)
          _model_builder._set_current_models
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder.packages $_model_dir $_verbose $_dry_run $@
          return 0
          ;;
        frameworks)
          _model_builder.frameworks $@
          ;;
        run-test-suite)
          _model_builder._validate_commands $@ || exit 0
          _model_builder._set_current_models
          _model_builder._validate_frameworks $@ || exit 0
          _model_builder._validate_models $@ || exit 0
          _only_tags_matching="$(_model_builder._build_only_tags_matching_args $@)"
          _model_builder.run_test_suite $_only_tags_matching $_verbose $_dry_run $@
          if (( $? != 0 )); then
            echo "There was an error while running tests."
            exit 1
          fi
          ;;
        *)
          echo 'unknown option '$1' '$@ >&2
          exit 1
          ;;
      esac
      ;;
  esac
}

_model_builder._init $@
_model_builder_main $@
