#!/usr/bin/env bash
# Author: Lucas Frérot
# Affiliation:
#     Sorbonne Université, CNRS, Institut Jean Le Rond d'Alembert,
#     F-75005 Paris, France
#
# Copyright © 2024  Lucas Frérot
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

# Sane bash options
set -o errexit
set -o nounset
set -o pipefail

# Colors
readonly RED='\033[0;31m'
readonly ORANGE='\033[0;33m'
readonly GREEN='\033[0;32m'
readonly BLUE='\033[0;34m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m'

# Gitea
readonly ENDPOINT="https://git.dalembert.upmc.fr/api/v1"

# Lab info
readonly AFFILIATION="Sorbonne Université, CNRS, Institut Jean Le Rond d'Alembert, F-75005 Paris, France"

# ----------------- Logging commands -----------------------

# Print error and exit
error() {
    printf "${RED}error${NC}: %s\\n" "$@" 1>&2
    exit 1
}

# Print warning
warning() {
    printf "${ORANGE}warning${NC}: %s\\n" "$@" 1>&2
}

# Print info
info() {
    printf "${GREEN}info${NC}: %b\\n" "$@" 1>&2
}

# Enter value
enter() {
    printf "${BLUE}input${NC}: %b" "$@" 1>&2
    local input_var=''
    read input_var
    printf "${input_var}"
}

# Test if a variable name is set
is_set() {
    local var_name="$1"
    eval "! [[ -z \"\${${var_name}+x}\" ]]"
    return $?
}

# Set var to first value if unset
alt_var() {
    local value_if_unset="$1"
    local variable="$2"
    if is_set "${variable}"; then
        printf "%s" "$(eval "printf \"%s\" \"\${${variable}}\"")"
    else
        printf "%s" "${value_if_unset}"
    fi
}

# Enter value if unset
cond_enter() {
    local input="$1"
    local var_name="$2"
    local value="$(alt_var "" "${var_name}")"

    if [[ "${value}" == "" ]]; then
        value="$(enter "${input}")"
    fi

    printf "%s" "${value}"
}

# Check that command exists
has_command() {
    command -v "$1" >/dev/null 2>&1
}

# Check curl and jq to process API calls to gitea
check_api_prerequisites() {
    if ! has_command curl; then
        error "curl not found, please install"
    fi

    if ! has_command jq; then
        error "jq not found, please install"
    fi
}

# ----------------- Gitea API commands -----------------------
get_gitea_token(){
    if [[ -f token ]]; then
        read TOKEN < token
    else
        TOKEN="$(enter "gitea token: ")"
    fi
}

gitea() {
    check_api_prerequisites

    if ! is_set TOKEN; then
        get_gitea_token
    fi

    readonly TOKEN

    local method="$1"
    local request="$2"
    local data=""

    if [[ $# > 2 ]]; then
        data="$3"
    fi

    \curl -s -X "${request}" \
          -H "Content-Type: application/json" \
          -H "Authorization: token ${TOKEN}" \
          -d "${data}" \
          "${ENDPOINT}/${method}"
}

# ----------------- Git commands -----------------------

# Set value of git config parameter
set_git_config() {
    local param="$1"
    local value=''
    while ! [[ -n "${value}" ]]; do
        value="$(enter "new value for ${param}: ")"
    done
    \git config "${param}" "${value}"
    info "setting new value for ${param}: '$(\git config "${param}")'"
    printf "${value}"
}

# Get value of git config parameter, set if unset
get_git_config() {
    local param="$1"
    local value="$(\git config "${param}")"
    if ! [[ -n "${value}" ]]; then
        warning "git ${param} is unset"
        value="$(set_git_config "${param}")"
    fi
    printf "${value}"
}

# Check git configuration and correct if necessary
check_git_config() {
    if ! has_command git; then
        error "git not found, please install"
    fi

    readonly USER="$(alt_var "$(get_git_config user.name)" USER)"
    readonly EMAIL="$(alt_var "$(get_git_config user.email)" EMAIL)"
    info "found git credentials:\\n  - user.name: '${USER}'\\n  - user.email: '${EMAIL}'"
}

# Initialize git repository
init_repo() {
    check_git_config
    info "$(\git init)"
}

# ----------------- Tree commands -----------------------

# Make bash script stub
script_stub() {
    local script_name="$1"
    local start_phrase="$2"

    if ! [[ -f "${script_name}" ]]; then
        mkdir -p "$(dirname "${script_name}")"
        cat << STUB > "${script_name}"
#!/usr/bin/env bash
set -euo pipefail

main() {
    printf "${start_phrase}\\n" 1>&2
    # put code here
}

main "\$@"
STUB
        chmod +x "${script_name}"
    fi

    info "wrote '${script_name}'"
}

# Fetch licence
fetch_licence() {
    local licence="COPYING"

    if ! [[ -f "${licence}" ]]; then
        if has_command curl; then
            info "setting licence to GPL by default, see https://www.gnu.org/licenses for more options"
            \curl -s "https://www.gnu.org/licenses/gpl-3.0.txt" > "${licence}"
            info "wrote '${licence}'"
        else
            warning "please choose a free software licence, see https://www.gnu.org/licenses"
        fi
    fi
}

# Create README
create_readme() {
    local readme="README.md~"

    if ! [[ -f "${readme}" ]]; then
        local project_name="$(cond_enter "project name: " PROJECT_NAME)"
        local project_desc="$(cond_enter "project short description: " PROJECT_DESC)"

        cat << READMEMSG > "${readme}"
# ${project_name}

${project_desc}

## Dependencies

Here are the dependencies to build and run the code:

- <dependencies_list>

## Running the code

Here is how to run the code:

\`\`\`
./make_all_figures
\`\`\`

## Tests

Here is how to run the tests:

\`\`\`
./tests/run_all_tests
\`\`\`
READMEMSG

        info "wrote '${readme}'"
    fi
}

create_authors_file() {
    if ! [[ -f AUTHORS ]]; then
        printf "%s\\n" "${USER} <${EMAIL}> ${AFFILIATION}" > AUTHORS
    fi
    info "wrote 'AUTHORS'"
}

# Create required files
create_tenet_file_tree() {
    create_readme
    create_authors_file
    fetch_licence
    script_stub tests/run_all_tests "Running all tests..."
    script_stub make_all_figures "Generating figures..."
}

# ----------------- Gitea commands -----------------------

get_gitea_owner() {
    gitea /user GET | jq -r '.login'
}

# Check if repository exists
get_gitea_repo() {
    local owner="$1"
    local repo_name="$2"
    gitea "/repos/${owner}/${repo_name}" GET
}

# Create a repository on gitea
create_gitea_repo() {
    local owner="$1"
    local repo_name="$2"
    local get_response="$(get_gitea_repo "${owner}" "${repo_name}")"

    # Check if repo already exits
    if [[ "$(jq '.id' <<< "${get_response}")" == "null" ]]; then
        info "creating repository '${repo_name}' on gitea"
        gitea /user/repos POST "{ \"name\": \"${repo_name}\", \"private\": true }"
    else
        info "found repository '${repo_name}' on gitea"
        printf "%s" "${get_response}"
    fi
}

# API call to setup the software heritage webhook
setup_software_heritage_hook() {
    local owner="$1"
    local repo_name="$2"
    local remote_url="$3"
    #gitea "/repos/${owner}/${repo_name}/hooks" GET
    gitea "/repos/${owner}/${repo_name}/hooks" POST \
          "{ \"type\": \"gitea\",
             \"config\": {
                 \"content_type\": \"json\",
                 \"url\": \"https://archive.softwareheritage.org/api/1/origin/save/git/url/${remote_url}\"
             },
             \"events\": [\"release\"],
             \"active\": true
           }"
    info "created Software Hertiage webhook for releases in gitea"
}

# Setup remote
setup_dalembert_gitea() {
    local repo_name="$(basename <<< "${PWD}")"
    local owner="$(get_gitea_owner)"
    local repo_json="$(create_gitea_repo "${repo_name}")"
    local remote_ssh="$(jq '.ssh_url' <<< "${repo_json}")"
    local remote_http="$(jq '.clone_url' <<< "${repo_json}")"

    if [[ "$(\git remote | \grep -c '^dalembert$' || true)" == "0" ]]; then
        \git remote add dalembert "${remote_ssh}"
        info "created remote dalembert with '${remote_ssh}"
    else
        \git remote set-url dalembert "${remote_ssh}"
        info "set remote dalembert with '${remote_ssh}"
    fi

    setup_software_heritage_hook "${owner}" "${repo_name}" "${remote_http}"
}

# ----------------- Script subcommands commands -----------------------

init-tree() {
    declare desc="usage: rtenets init-tree <directory>"

    local directory="$1"
    (
        cd "${directory}"
        init_repo
        create_tenet_file_tree
    )
}

init-gitea() {
    declare desc="usage: rtenets init-gitea <directory>"
}

init-workflow() {
    declare desc="usage: rtenets init-workflow <directory>"
}

# Create git repository
create() {
    declare desc="usage: rtenets create <directory>"
    local repo_name="$1"
    info "recursively creating directory '${repo_name}'"
    mkdir -p "${repo_name}"
    (
        info "initializing repository '${repo_name}'"
        cd "${repo_name}"
        init_repo
        create_tenet_file_tree
    )
}

# Print usage and exit
usage() {
    cat <<USAGE
usage: $0 [--help,-h] [--version,-v] command [args...]

Available commands:
    - create: create and populate a repository
    - init-tree: populate a repository with README, AUTHORS, COPYING and test/
    - init-gitea: setup a remote on a gitea server with SoftwareHeritage hook
    - init-workflow: setup a Python virtual environment and Snakemake template
    - check: verify tenent compliance for a repository
USAGE
}

version() {
    cat <<VERSION
rtenets 0.0.1

Copyright © 2024 Lucas Frérot
This program comes with ABSOLUTELY NO WARRANTY;
This is free software, and you are welcome to redistribute it
under certain conditions;
VERSION
}

main() {
    local other_args=()
    local help_mode=0
    for i in "$@"; do
        case $i in
            -v|--version)
                version
                return 0
                ;;
            -h|--help)
                help_mode=1
                ;;
            *)
                other_args+=("${i}")
                ;;
        esac
    done

    local command="${other_args[0]}"

    if [[ "${help_mode}" == 1 ]]; then
        type "${command}" | sed -n -e 's/^.*declare desc="\(.*\)";/\1/p'
    fi

    # Subcommands trick
    # https://blogsh.github.io/2020/03/21/subcommands-in-bash-scripts.html
    declare -A COMMANDS=(
        [default]=usage
        [init-tree]=init-tree
        [init-gitea]=init-gitea
        [init-workflow]=init-workflow
        [create]=create
    )

    "${COMMANDS[${command:-default}]:-${COMMANDS[default]}}" "${other_args}"
}

# Avoid executing main if sourced
# https://stackoverflow.com/questions/2683279/how-to-detect-if-a-script-is-being-sourced
(return 0 2&>/dev/null) && main "$@" || true