#!/usr/bin/env bash

# MIT License
#
# Copyright (c) 2018 GantSign Ltd.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


# Molecule Wrapper the wrapper script for Molecule
# https://github.com/gantsign/molecule-wrapper

set -e

WRAPPER_VERSION=1.2.0

VERSION_DIR='.moleculew'
PYTHON_VERSION_FILE="$VERSION_DIR/python_version"
ANSIBLE_VERSION_FILE="$VERSION_DIR/ansible_version"
MOLECULE_VERSION_FILE="$VERSION_DIR/molecule_version"
YAMLLINT_VERSION_FILE="$VERSION_DIR/yamllint_version"
ANSIBLE_LINT_VERSION_FILE="$VERSION_DIR/ansible_lint_version"
FLAKE8_VERSION_FILE="$VERSION_DIR/flake8_version"
TESTINFRA_VERSION_FILE="$VERSION_DIR/testinfra_version"

BUILD_DEPENDENCIES_INSTALLLED=false
PYENV_INSTALLED=false

ANSIBLE_VERSION=''
MOLECULE_VERSION=''
PYTHON_VERSION=''
YAMLLINT_VERSION=''
ANSIBLE_LINT_VERSION=''
FLAKE8_VERSION=''
TESTINFRA_VERSION=''
USE_SYSTEM_DEPENDENCIES=false

PRE_ARGS=()
MOLECULE_CMD=''
POST_ARGS=()

export PATH="$HOME/.pyenv/bin:$HOME/.local/bin:$PATH"

hr() {
    for ((i = 1; i <= 80; i++)); do
        printf '*'
    done
    echo ''
}

banner() {
    hr
    echo "$1"
    hr
}

run_as_root() {
    if [[ $EUID -eq 0 ]]; then
        "$@"
    elif [ -x "$(command -v sudo)" ]; then
        sudo "$@"
    else
        echo "Error: sudo is not installed" >&2
        exit 1
    fi
}

build_dependencies_present() {
    if [[ $BUILD_DEPENDENCIES_INSTALLLED == true ]]; then
        return
    fi
    if [[ $USE_SYSTEM_DEPENDENCIES == true ]]; then
        return
    fi
    # https://github.com/pyenv/pyenv/wiki/common-build-problems
    if [[ -x "$(command -v apt-get)" ]]; then
        banner 'Installing build dependencies'

        run_as_root apt-get update
        run_as_root apt-get install --assume-yes \
            make build-essential libssl-dev zlib1g-dev libbz2-dev \
            libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev \
            libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev \
            git jq
        echo ''
    elif [[ -x "$(command -v dnf)" ]]; then
        banner 'Installing build dependencies'

        run_as_root dnf install \
            zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel \
            openssl-devel xz xz-devel libffi-devel \
            git curl jq
        echo ''
    elif [[ -x "$(command -v yum)" ]]; then
        banner 'Installing build dependencies'

        run_as_root yum install \
            zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel \
            openssl-devel xz xz-devel libffi-devel \
            git curl jq
        echo ''
    elif [[ -x "$(command -v zypper)" ]]; then
        banner 'Installing build dependencies'

        run_as_root zypper install \
            zlib-devel bzip2 libbz2-devel readline-devel sqlite3 sqlite3-devel \
            libopenssl-devel xz xz-devel \
            git curl jq
        echo ''
    fi
    BUILD_DEPENDENCIES_INSTALLLED=true
}

pyenv_present() {
    if [[ $PYENV_INSTALLED == true ]]; then
        return
    fi
    if [[ $USE_SYSTEM_DEPENDENCIES == true ]]; then
        return
    fi
    if [[ -x "$(command -v pyenv)" ]]; then
        PYENV_INSTALLED=true
        return
    fi

    build_dependencies_present

    banner "Installing pyenv for user $USER"
    bash <(curl --location https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer)
    echo ''
    PYENV_INSTALLED=true
}

query_latest_python_version() {
    pyenv_present

    PYTHON_VERSION="$(~/.pyenv/plugins/python-build/bin/python-build --definitions | grep --color=never '^3\.' | grep --invert-match '\-dev$' | tail -1)"
}

query_latest_package_version() {
    if [[ ! -x "$(command -v curl)" ]]; then
        build_dependencies_present
    fi
    if [[ ! -x "$(command -v jq)" ]]; then
        build_dependencies_present
    fi
    if [[ ! -x "$(command -v curl)" ]]; then
        echo 'Error: curl is not installed.' >&2
        exit 1
    fi
    if [[ ! -x "$(command -v jq)" ]]; then
        echo 'Error: jq is not installed.' >&2
        exit 1
    fi

    local version
    # shellcheck disable=SC2034
    version=$(curl --fail --silent --show-error --location "https://pypi.org/pypi/$2/json" | jq --raw-output '.info.version')

    eval "$1=\"\$version\""
}

docker_present() {
    if [[ -x "$(command -v docker)" ]]; then
        return
    fi
    if [[ $USE_SYSTEM_DEPENDENCIES == true ]]; then
        echo 'Error: Docker is not installed.' >&2
        exit 1
    fi

    build_dependencies_present

    banner 'Installing Docker'
    sh <(curl --fail --silent --show-error --location https://get.docker.com)
    run_as_root usermod --append --groups docker "$USER"
    banner "User '$USER' has been added to the 'docker' group. Logout/restart and log back in for changes to take effect."
    exit
}

python_present() {
    if [[ $PYTHON_VERSION == system ]]; then
        if [[ ! -x "$(command -v python3)" ]] &&
                [[ ! -x "$(command -v python)" ]]; then
            echo 'Error: python is not installed.' >&2
            exit 1
        fi
        if [[ ! -x "$(command -v pip3)" ]] &&
                [[ ! -x "$(command -v pip)" ]]; then
            echo 'Error: pip is not installed.' >&2
            exit 1
        fi
        PYTHON_EXE="$(command -v python3 || command -v python)"
    else
        if [[ ! -x "$(command -v git)" ]]; then
            echo 'Error: git is not installed.' >&2
            exit 1
        fi

        pyenv_present

        export PYENV_VERSION="$PYTHON_VERSION"
        if [[ ! -d "$HOME/.pyenv/versions/$PYTHON_VERSION" ]]; then
            build_dependencies_present

            banner "Making Python version $PYTHON_VERSION available using pyenv"
            pyenv install "$PYTHON_VERSION"
            echo ''
        fi
        eval "$(pyenv init -)"
        PYTHON_EXE="$(pyenv which python)"
    fi
}

virtualenv_presant() {
    if [[ ! -x "$(command -v virtualenv)" ]]; then
        banner "Installing virtualenv for user $USER"
        "$PYTHON_EXE" -m pip install --user virtualenv
        echo ''
    fi
}

install_rich() {
    # Workaround breaking changes in rich 11 by installing version 10
    local RICH_VERSION='10.16.2'
    banner "Installing Rich $RICH_VERSION into virtualenv $VIRTUAL_ENV"
    pip install "rich==$RICH_VERSION"
    echo ''
}

install_ansible() {
    banner "Installing Ansible $ANSIBLE_VERSION into virtualenv $VIRTUAL_ENV"
    pip install "ansible==$ANSIBLE_VERSION"
    echo ''
}

install_molecule() {
    banner "Installing Molecule $MOLECULE_VERSION into virtualenv $VIRTUAL_ENV"

    pip install "molecule[docker]==$MOLECULE_VERSION"
    echo ''
}

install_yamllint() {
    banner "Installing YamlLint $YAMLLINT_VERSION into virtualenv $VIRTUAL_ENV"

    pip install "yamllint==$YAMLLINT_VERSION"
    echo ''
}

install_ansible_lint() {
    banner "Installing Anssible Lint $ANSIBLE_LINT_VERSION into virtualenv $VIRTUAL_ENV"

    pip install "ansible-lint==$ANSIBLE_LINT_VERSION"
    echo ''
}

install_flake8() {
    banner "Installing Flake8 $FLAKE8_VERSION into virtualenv $VIRTUAL_ENV"

    pip install "flake8==$FLAKE8_VERSION"
    echo ''
}

install_testinfra() {
    banner "Installing Testinfra $TESTINFRA_VERSION into virtualenv $VIRTUAL_ENV"

    pip install "testinfra==$TESTINFRA_VERSION"
    echo ''
}

wrapper_clean() {
    local MOLECULE_WRAPPER_HOME="$HOME/.moleculew"
    read -r -p "Delete ${MOLECULE_WRAPPER_HOME} (y/n)? " yn
    case $yn in
        [Yy]|YES|yes|Yes)
            rm -rf "$MOLECULE_WRAPPER_HOME";
            exit
        ;;
        *)
            exit
        ;;
    esac
}

wrapper_upgrade() {
    curl --fail --silent --show-error --location --output moleculew.new \
        'https://raw.githubusercontent.com/gantsign/molecule-wrapper/master/moleculew' \
        && chmod 'u+x' moleculew.new \
        && mv --force moleculew.new moleculew

    local NEW_VERSION
    NEW_VERSION="$(./moleculew wrapper-version)"
    if [ "$WRAPPER_VERSION" != "$NEW_VERSION" ]; then
        echo "Upgraded wrapper from version $WRAPPER_VERSION to $NEW_VERSION"
    else
        echo "You are already using the latest version"
    fi
    exit
}

wrapper_version() {
    echo "$WRAPPER_VERSION"
    exit
}

print_versions() {
    echo "Python: $PYTHON_VERSION"
    echo "Ansible: $ANSIBLE_VERSION"
    echo "Molecule: $MOLECULE_VERSION"
    echo "YamlLint: $YAMLLINT_VERSION"
    echo "Ansible Lint: $ANSIBLE_LINT_VERSION"
    echo "Flake8: $FLAKE8_VERSION"
    echo "Testinfra: $TESTINFRA_VERSION"
}

wrapper_versions() {
    detemine_versions

    print_versions
    exit
}

wrapper_freeze() {
    detemine_versions

    banner 'Freezing versions'

    mkdir -p "$VERSION_DIR"

    echo "$PYTHON_VERSION" > "$PYTHON_VERSION_FILE"
    echo "$ANSIBLE_VERSION" > "$ANSIBLE_VERSION_FILE"
    echo "$MOLECULE_VERSION" > "$MOLECULE_VERSION_FILE"
    echo "$YAMLLINT_VERSION" > "$YAMLLINT_VERSION_FILE"
    echo "$ANSIBLE_LINT_VERSION" > "$ANSIBLE_LINT_VERSION_FILE"
    echo "$FLAKE8_VERSION" > "$FLAKE8_VERSION_FILE"
    echo "$TESTINFRA_VERSION" > "$TESTINFRA_VERSION_FILE"

    print_versions

    exit
}

wrapper_unfreeze() {
    banner 'Un-freezing versions'

    if [[ -f "$PYTHON_VERSION_FILE" ]]; then
        rm --verbose "$PYTHON_VERSION_FILE"
    fi
    if [[ -f "$ANSIBLE_VERSION_FILE" ]]; then
        rm --verbose "$ANSIBLE_VERSION_FILE"
    fi
    if [[ -f "$MOLECULE_VERSION_FILE" ]]; then
        rm --verbose "$MOLECULE_VERSION_FILE"
    fi
    if [[ -f "$YAMLLINT_VERSION_FILE" ]]; then
        rm --verbose "$YAMLLINT_VERSION_FILE"
    fi
    if [[ -f "$ANSIBLE_LINT_VERSION_FILE" ]]; then
        rm --verbose "$ANSIBLE_LINT_VERSION_FILE"
    fi
    if [[ -f "$FLAKE8_VERSION_FILE" ]]; then
        rm --verbose "$FLAKE8_VERSION_FILE"
    fi
    if [[ -f "$TESTINFRA_VERSION_FILE" ]]; then
        rm --verbose "$TESTINFRA_VERSION_FILE"
    fi

    exit
}

wrapper_upgrade_versions() {
    detemine_versions

    banner 'Upgrading versions'

    local CURRENT_PYTHON_VERSION="$PYTHON_VERSION"
    local CURRENT_ANSIBLE_VERSION="$ANSIBLE_VERSION"
    local CURRENT_MOLECULE_VERSION="$MOLECULE_VERSION"
    local CURRENT_YAMLLINT_VERSION="$YAMLLINT_VERSION"
    local CURRENT_ANSIBLE_LINT_VERSION="$ANSIBLE_LINT_VERSION"
    local CURRENT_FLAKE8_VERSION="$FLAKE8_VERSION"
    local CURRENT_TESTINFRA_VERSION="$TESTINFRA_VERSION"

    query_latest_python_version
    query_latest_package_version ANSIBLE_VERSION ansible
    query_latest_package_version MOLECULE_VERSION molecule
    query_latest_package_version YAMLLINT_VERSION yamllint
    query_latest_package_version ANSIBLE_LINT_VERSION ansible-lint
    query_latest_package_version FLAKE8_VERSION flake8
    query_latest_package_version TESTINFRA_VERSION testinfra
    echo ''

    echo 'New versions:'
    if [[ "$CURRENT_PYTHON_VERSION" == "$PYTHON_VERSION" ]]; then
        echo "Python: $CURRENT_PYTHON_VERSION (no change)"
    else
        echo "Python: $CURRENT_PYTHON_VERSION -> $PYTHON_VERSION"
    fi

    if [[ "$CURRENT_ANSIBLE_VERSION" == "$ANSIBLE_VERSION" ]]; then
        echo "Ansible: $CURRENT_ANSIBLE_VERSION (no change)"
    else
        echo "Ansible: $CURRENT_ANSIBLE_VERSION -> $ANSIBLE_VERSION"
    fi

    if [[ "$CURRENT_MOLECULE_VERSION" == "$MOLECULE_VERSION" ]]; then
        echo "Molecule: $CURRENT_MOLECULE_VERSION (no change)"
    else
        echo "Molecule: $CURRENT_MOLECULE_VERSION -> $MOLECULE_VERSION"
    fi

    if [[ "$CURRENT_YAMLLINT_VERSION" == "$YAMLLINT_VERSION" ]]; then
        echo "YamlLint: $CURRENT_YAMLLINT_VERSION (no change)"
    else
        echo "YamlLint: $CURRENT_YAMLLINT_VERSION -> $YAMLLINT_VERSION"
    fi

    if [[ "$CURRENT_ANSIBLE_LINT_VERSION" == "$ANSIBLE_LINT_VERSION" ]]; then
        echo "Ansible Lint: $CURRENT_ANSIBLE_LINT_VERSION (no change)"
    else
        echo "Ansible Lint: $CURRENT_ANSIBLE_LINT_VERSION -> $ANSIBLE_LINT_VERSION"
    fi

    if [[ "$CURRENT_FLAKE8_VERSION" == "$FLAKE8_VERSION" ]]; then
        echo "Flake8: $CURRENT_FLAKE8_VERSION (no change)"
    else
        echo "Flake8: $CURRENT_FLAKE8_VERSION -> $FLAKE8_VERSION"
    fi

    if [[ "$CURRENT_TESTINFRA_VERSION" == "$TESTINFRA_VERSION" ]]; then
        echo "Testinfra: $CURRENT_TESTINFRA_VERSION (no change)"
    else
        echo "Testinfra: $CURRENT_TESTINFRA_VERSION -> $TESTINFRA_VERSION"
    fi

    echo ''

    wrapper_freeze
}

wrapper_help() {
    activate_virtualenv

    molecule --help

    echo "
Molecule Wrapper

Additional options:
  --ansible VERSION          Use the specified version of Ansible
  --molecule VERSION         Use the specified version of Molecule
  --python VERSION           Use the specified version of Python
  --yamllint VERSION         Use the specified version of YamlLint
  --ansible-lint VERSION     Use the specified version of Ansible Lint
  --flake8 VERSION           Use the specified version of Flake8
  --testinfra VERSION        Use the specified version of Testinfra
  --use-system-dependencies  Use system dependencies

Additional commands:
  wrapper-clean             Removes all the wrapper virtual environments
  wrapper-freeze            Freezes the dependency versions being used
  wrapper-unfreeze          Un-freezes the dependency versions
  wrapper-upgrade           Upgrades the Molecule Wrapper to the latest version
  wrapper-upgrade-versions  Upgrades any frozen dependency versions
  wrapper-version           Displays the current version of Molecule Wrapper
"
}

query_package_versions() {
    local package_name="$1"
    local min_version="$2"

    if [[ ! -x "$(command -v curl)" ]]; then
        build_dependencies_present > /dev/null
    fi
    if [[ ! -x "$(command -v jq)" ]]; then
        build_dependencies_present > /dev/null
    fi
    if [[ ! -x "$(command -v curl)" ]]; then
        echo 'Error: curl is not installed.' >&2
        exit 1
    fi
    if [[ ! -x "$(command -v jq)" ]]; then
        echo 'Error: jq is not installed.' >&2
        exit 1
    fi
    if [[ ! -x "$(command -v sort)" ]]; then
        echo 'Error: sort is not installed.' >&2
        exit 1
    fi

    for i in $(curl --fail --silent --show-error \
                --location "https://pypi.org/pypi/$package_name/json" \
                | jq --raw-output ".releases | keys | .[], \"$min_version.\"" \
                | grep --invert-match '[a-zA-Z]' \
                | sort --version-sort --reverse) ; do
        if [[ "$i" == "$min_version." ]]; then
            break
        fi
        echo "$i"
    done
}

wrapper_options_ansible() {
    echo 'latest'
    query_package_versions 'ansible' '2.8'
}

wrapper_options_molecule() {
    echo 'latest'
    query_package_versions 'molecule' '3.1.5'
}

wrapper_options_python() {
    if [[ ! -x "$(command -v sort)" ]]; then
        echo 'Error: sort is not installed.' >&2
        exit 1
    fi

    pyenv_present > /dev/null

    local min_version='3.6'

    echo 'latest'

    for i in $( (echo "$min_version." && \
            ~/.pyenv/plugins/python-build/bin/python-build --definitions) \
            | grep --color=never '^[0-9]' \
            | grep --invert-match '\-dev$' \
            | sort --version-sort --reverse) ; do
        if [[ "$i" == "$min_version." ]]; then
            break
        fi
        echo "$i"
    done
}

wrapper_options_yamllint() {
    echo 'latest'
    query_package_versions 'yamllint' '1.26.3'
}

wrapper_options_ansible_lint() {
    echo 'latest'
    query_package_versions 'ansible_lint' '5.4.0'
}

wrapper_options_flake8() {
    echo 'latest'
    query_package_versions 'flake8' '4.0.1'
}

wrapper_options_testinfra() {
    echo 'latest'
    query_package_versions 'testinfra' '5.3.1'
}

wrapper_options_scenario() {
    (
        cd molecule > /dev/null &&
        for d in *; do
            if [ -d "$d" ]; then
               echo "$d"
            fi
        done
    )
}

wrapper_virtualenv() {
    activate_virtualenv > /dev/null
    echo "$VIRTUAL_ENV"
}

parse_args() {
    set +e

    while [[ $# -gt 0 ]]; do
        key="$1"

        case $key in
            --python=*)
                PYTHON_VERSION="${1#*=}"
                shift
            ;;
            --python)
                shift
                PYTHON_VERSION="$1"
                shift
            ;;
            --ansible=*)
                ANSIBLE_VERSION="${1#*=}"
                shift
            ;;
            --ansible)
                shift
                ANSIBLE_VERSION="$1"
                shift
            ;;
            --molecule=*)
                MOLECULE_VERSION="${1#*=}"
                shift
            ;;
            --molecule)
                shift
                MOLECULE_VERSION="$1"
                shift
            ;;
            --yamllint=*)
                YAMLLINT_VERSION="${1#*=}"
                shift
            ;;
            --yamllint)
                shift
                YAMLLINT_VERSION="$1"
                shift
            ;;
            --ansible-lint=*)
                ANSIBLE_LINT_VERSION="${1#*=}"
                shift
            ;;
            --ansible-lint)
                shift
                ANSIBLE_LINT_VERSION="$1"
                shift
            ;;
            --flake8=*)
                FLAKE8_VERSION="${1#*=}"
                shift
            ;;
            --flake8)
                shift
                FLAKE8_VERSION="$1"
                shift
            ;;
            --testinfra)
                shift
                TESTINFRA_VERSION="$1"
                shift
            ;;
            --testinfra=*)
                TESTINFRA_VERSION="${1#*=}"
                shift
            ;;
            --use-system-dependencies)
                USE_SYSTEM_DEPENDENCIES=true
                shift
            ;;
            --help)
                MOLECULE_CMD='wrapper-help'
                break
            ;;
            wrapper-*)
                MOLECULE_CMD="$1"
                shift
            ;;
            check|converge|create|dependency|destroy|idempotence|init|lint|list|login|matrix|prepare|side-effect|syntax|test|verify)
                if [[ "$MOLECULE_CMD" != '' ]]; then
                    shift
                else
                    MOLECULE_CMD="$1"
                    shift
                    for arg in "$@"; do
                        POST_ARGS+=("$arg")
                    done
                    break
                fi
            ;;
            *)
                PRE_ARGS+=("$1")
                shift
            ;;
        esac
    done
    set -e
}

detemine_versions() {
    if [[ $USE_SYSTEM_DEPENDENCIES == false ]]; then
        USE_SYSTEM_DEPENDENCIES="$MOLECULEW_USE_SYSTEM"
    fi
    if [[ $PYTHON_VERSION == '' ]]; then
        PYTHON_VERSION="$MOLECULEW_PYTHON"
    fi
    if [[ $ANSIBLE_VERSION == '' ]]; then
        ANSIBLE_VERSION="$MOLECULEW_ANSIBLE"
    fi
    if [[ $MOLECULE_VERSION == '' ]]; then
        MOLECULE_VERSION="$MOLECULEW_MOLECULE"
    fi
    if [[ $YAMLLINT_VERSION == '' ]]; then
        YAMLLINT_VERSION="$MOLECULEW_YAMLLINT"
    fi
    if [[ $ANSIBLE_LINT_VERSION == '' ]]; then
        ANSIBLE_LINT_VERSION="$MOLECULEW_ANSIBLE_LINT"
    fi
    if [[ $FLAKE8_VERSION == '' ]]; then
        FLAKE8_VERSION="$MOLECULEW_FLAKE8"
    fi
    if [[ $TESTINFRA_VERSION == '' ]]; then
        TESTINFRA_VERSION="$MOLECULEW_TESTINFRA"
    fi

    if [[ $USE_SYSTEM_DEPENDENCIES == true ]]; then
        if [[ $PYTHON_VERSION != '' ]]; then
            echo "Error: --python and --use-system-dependencies cannot be used together" >&2
            exit 1
        fi
        PYTHON_VERSION=system
    elif [[ $PYTHON_VERSION == '' ]] || [[ $PYTHON_VERSION == 'default' ]]; then
        if [[ -f $PYTHON_VERSION_FILE ]]; then
            PYTHON_VERSION=$(<"$PYTHON_VERSION_FILE")
        fi
        if [[ $PYTHON_VERSION == '' ]]; then
            query_latest_python_version
        fi
    elif [[ $PYTHON_VERSION == 'latest' ]]; then
        query_latest_python_version
    fi

    if [[ $ANSIBLE_VERSION == '' ]] || [[ $ANSIBLE_VERSION == 'default' ]]; then
        if [[ -f $ANSIBLE_VERSION_FILE ]]; then
            ANSIBLE_VERSION=$(<"$ANSIBLE_VERSION_FILE")
        fi
        if [[ $ANSIBLE_VERSION == '' ]]; then
            query_latest_package_version ANSIBLE_VERSION ansible
        fi
    elif [[ $ANSIBLE_VERSION == 'latest' ]]; then
        query_latest_package_version ANSIBLE_VERSION ansible
    fi

    if [[ $MOLECULE_VERSION == '' ]] || [[ $MOLECULE_VERSION == 'default' ]]; then
        if [[ -f $MOLECULE_VERSION_FILE ]]; then
            MOLECULE_VERSION=$(<$MOLECULE_VERSION_FILE)
        fi
        if [[ $MOLECULE_VERSION == '' ]]; then
            query_latest_package_version MOLECULE_VERSION molecule
        fi
    elif [[ $MOLECULE_VERSION == 'latest' ]]; then
        query_latest_package_version MOLECULE_VERSION molecule
    fi

    if [[ $YAMLLINT_VERSION == '' ]] || [[ $YAMLLINT_VERSION == 'default' ]]; then
        if [[ -f $YAMLLINT_VERSION_FILE ]]; then
            YAMLLINT_VERSION=$(<$YAMLLINT_VERSION_FILE)
        fi
        if [[ $YAMLLINT_VERSION == '' ]]; then
            query_latest_package_version YAMLLINT_VERSION yamllint
        fi
    elif [[ $YAMLLINT_VERSION == 'latest' ]]; then
        query_latest_package_version YAMLLINT_VERSION yamllint
    fi

    if [[ $ANSIBLE_LINT_VERSION == '' ]] || [[ $ANSIBLE_LINT_VERSION == 'default' ]]; then
        if [[ -f $ANSIBLE_LINT_VERSION_FILE ]]; then
            ANSIBLE_LINT_VERSION=$(<$ANSIBLE_LINT_VERSION_FILE)
        fi
        if [[ $ANSIBLE_LINT_VERSION == '' ]]; then
            query_latest_package_version ANSIBLE_LINT_VERSION ansible-lint
        fi
    elif [[ $ANSIBLE_LINT_VERSION == 'latest' ]]; then
        query_latest_package_version ANSIBLE_LINT_VERSION ansible-lint
    fi

    if [[ $FLAKE8_VERSION == '' ]] || [[ $FLAKE8_VERSION == 'default' ]]; then
        if [[ -f $FLAKE8_VERSION_FILE ]]; then
            FLAKE8_VERSION=$(<$FLAKE8_VERSION_FILE)
        fi
        if [[ $FLAKE8_VERSION == '' ]]; then
            query_latest_package_version FLAKE8_VERSION flake8
        fi
    elif [[ $FLAKE8_VERSION == 'latest' ]]; then
        query_latest_package_version FLAKE8_VERSION flake8
    fi

    if [[ $TESTINFRA_VERSION == '' ]] || [[ $TESTINFRA_VERSION == 'default' ]]; then
        if [[ -f $TESTINFRA_VERSION_FILE ]]; then
            TESTINFRA_VERSION=$(<$TESTINFRA_VERSION_FILE)
        fi
        if [[ $TESTINFRA_VERSION == '' ]]; then
            query_latest_package_version TESTINFRA_VERSION testinfra
        fi
    elif [[ $TESTINFRA_VERSION == 'latest' ]]; then
        query_latest_package_version TESTINFRA_VERSION testinfra
    fi
}

activate_virtualenv() {
    detemine_versions

    MOLECULE_WRAPPER_ENV="$HOME/.moleculew/ml-${MOLECULE_VERSION}_an-${ANSIBLE_VERSION}_py-${PYTHON_VERSION}_yl-${YAMLLINT_VERSION}_al-${ANSIBLE_LINT_VERSION}_f8-${FLAKE8_VERSION}_ti-${TESTINFRA_VERSION}"

    if [ ! -f "$MOLECULE_WRAPPER_ENV/bin/activate" ]; then

        build_dependencies_present

        docker_present

        python_present

        virtualenv_presant

        banner "Initializing virtualenv $MOLECULE_WRAPPER_ENV"
        virtualenv "--python=$PYTHON_EXE" "$MOLECULE_WRAPPER_ENV"
        # shellcheck disable=SC1090
        source "$MOLECULE_WRAPPER_ENV/bin/activate"
        echo ''

        # Workaround breaking changes in rich 11 by installing version 10
        install_rich

        install_ansible

        install_molecule

        install_yamllint

        install_ansible_lint

        install_flake8

        install_testinfra
    else
        # shellcheck disable=SC1090
        source "$MOLECULE_WRAPPER_ENV/bin/activate"
    fi
}

parse_args "$@"

case $MOLECULE_CMD in
    wrapper-clean)
        wrapper_clean
    ;;
    wrapper-freeze)
        wrapper_freeze
    ;;
    wrapper-help)
        wrapper_help
    ;;
    wrapper-install)
        activate_virtualenv
    ;;
    wrapper-options-ansible)
        wrapper_options_ansible
    ;;
    wrapper-options-molecule)
        wrapper_options_molecule
    ;;
    wrapper-options-python)
        wrapper_options_python
    ;;
    wrapper-options-yamllint)
        wrapper_options_yamllint
    ;;
    wrapper-options-ansible-lint)
        wrapper_options_ansible_lint
    ;;
    wrapper-options-flake8)
        wrapper_options_flake8
    ;;
    wrapper-options-testinfra)
        wrapper_options_testinfra
    ;;
    wrapper-options-scenario)
        wrapper_options_scenario
    ;;
    wrapper-unfreeze)
        wrapper_unfreeze
    ;;
    wrapper-upgrade)
        wrapper_upgrade
    ;;
    wrapper-upgrade-versions)
        wrapper_upgrade_versions
    ;;
    wrapper-version)
        wrapper_version
    ;;
    wrapper-versions)
        wrapper_versions
    ;;
    wrapper-virtualenv)
        wrapper_virtualenv
    ;;
    wrapper-*)
        echo "Unsupported command: $1" >&2
        exit 1
    ;;
    *)
        activate_virtualenv

        # shellcheck disable=SC2086
        exec molecule "${PRE_ARGS[@]}" $MOLECULE_CMD "${POST_ARGS[@]}"
    ;;
esac
