#!/bin/bash

#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#

#
# A collection of utilities relating to 'repo'
#


#
# Echo to stderr
#    echo_stderr [any text you want]
#

echo_stderr ()
{
    echo "$@" >&2
}

#
# Get the root dir of a repo managed repository
#    repo_root [<dir_path>]
#

repo_root () {
    local query_dir="${1:-${PWD}}"
    local work_dir

    if [ ! -d "${query_dir}" ]; then
        echo_stderr "not a valid directory: ${query_dir}"
        return 1
    fi

    if [ "${query_dir:0:1}" != "/" ]; then
        query_dir=$(readlink -f ${query_dir})
        if [ $? -ne 0 ]; then
            return 1
        fi
    fi

    work_dir="${query_dir}"
    while true; do
        if [ -d "$work_dir/.repo/manifests" ]; then
            echo $work_dir
            return 0
        fi

        if [ "${work_dir}" == "/" ]; then
            break
        fi

        work_dir="$(dirname "${work_dir}")"
    done

    echo_stderr "directory is not controlled by repo: ${query_dir}"
    return 1
}

#
# Get the active manifest file of a repo managed repository
#    repo_manifest [<dir_path>]
#

REPO_MANIFEST_FILE=
repo_set_manifest_file() {
    REPO_MANIFEST_FILE="$1"
}

repo_manifest () {
    local query_dir="${1:-${PWD}}"
    local root_dir=""
    local repo_manifest=""

    root_dir="$(repo_root "${query_dir}")"
    if [ $? -ne 0 ]; then
        return 1
    fi

    if [[ -n "$REPO_MANIFEST_FILE" ]] ; then
        if [[ "$REPO_MANIFEST_FILE" =~ ^/ ]] ; then
            echo "$REPO_MANIFEST_FILE"
        else
            echo "${root_dir}/.repo/manifests/$REPO_MANIFEST_FILE"
        fi
        return 0
    fi

    repo_manifest="${root_dir}/.repo/manifest.xml"

    # Depending on repo version, ${repo_manifest} is either a symlink to
    # the real manifest, or a wrapper manifest that includes the real manifest
    if [ -L "${repo_manifest}" ]; then
        readlink -f "${repo_manifest}"
    else
        grep "<include " ${repo_manifest} | head -n 1 | sed "s#^.*name=\"\([^\"]*\)\".*#${root_dir}/.repo/manifests/\1#"
    fi
}

#
# Get a list of repo remotes.
#    repo_manifest
#
# Current directory must be withing the repo.

repo_remote_list () {
    repo forall -c 'echo $REPO_REMOTE' | sort --unique
}

repo_is_remote () {
    local query_remote=$1
    local remote
    for remote in $(repo_remote_list); do
        if [ "$query_remote" == "$remote" ]; then
            return 0
        fi
    done
    return 1
}

#
# Get a list of repo projects.
# Optionally restrict the list to projects from listed remotes.
#    repo_manifest [remote_1 remote_2 ...]
#
# Current directory must be withing the repo.

repo_project_list () {
    local remote_list=( $@ )

    if [ ${#remote_list[@]} -eq 0 ]; then
        repo forall -c 'echo $REPO_PROJECT' | sort --unique
    else
        for remote in ${remote_list[@]}; do
            repo forall -c \
                'if [ "$REPO_REMOTE" = "'${remote}'" ]; then echo $REPO_PROJECT; fi' \
                | sort --unique
        done
    fi
}

repo_is_project () {
    local query_project=$1
    local project
    for project in $(repo_project_list); do
        if [ "$query_project" == "$project" ]; then
            return 0
        fi
    done
    return 1
}


#
# manifest_get_revision_of_project <manifest> <project-name>
#
# Extract the revision of a project within the manifest.
# The default revision is supplied in the absence
# of an explicit project revision.
#
#    manifest = Path to manifest.
#    project-name = name of project.
#
manifest_get_revision_of_project () {
    local manifest="${1}"
    local project="${2}"

    local default_revision=""
    local revision=""

    default_revision=$(manifest_get_default_revision "${manifest}")
    revision=$(grep '<project' "${manifest}" | \
                grep -e "name=${project}" -e "name=\"${project}\"" | \
                grep 'revision=' | \
                sed -e 's#.*revision=\([^ ]*\).*#\1#' -e 's#"##g' -e "s#'##g")
    if [ "${revision}" != "" ]; then
        echo "${revision}"
    elif [ "${default_revision}" != "" ]; then
        echo "${default_revision}"
    else
        return 1
    fi
}

#
# manifest_get_default_revision <manifest>
#
# Extract the default revision of the manifest, if any.
#
#    manifest = Path to manifest.
#
manifest_get_default_revision () {
    local manifest="${1}"

    grep '<default' $manifest |sed -e 's#.*revision=\([^ /]*\).*#\1#' -e 's#"##g' -e "s#'##g"
}

#
# manifest_set_revision <old_manifest> <new_manifest> <revision> <lock_down> <project-list> <excluded_project_list>
#
#    old_manifest = Path to original manifest.
#    new_manifest = Path to modified manifest. It will not overwrite an
#                   existing file.
#    revision     = A branch, tag ,or sha.  Branch and SHA can be used
#                   directly, but repo requires that a tag be in the form
#                   "refs/tags/<tag-name>".
#    lock_down    = 0,1 or 2.  If 2, set a revision on all other non-listed
#                   projects to equal the SHA of the current git head.
#                   If 1, similar to 2, but only if the project doesn't have
#                   some other form of revision specified.
#    project-list = A comma seperated list of projects.  Listed projects
#                   will have their revision set to the provided revision
#                   value.
#    excluded_project-list = A comma seperated list of projects.  Listed
#                   projects will not be subject to lock-down.
#
manifest_set_revision () {
    local old_manifest="${1}"
    local new_manifest="${2}"
    local revision="${3}"
    local lock_down="${4}"
    local set_default="${5}"
    local projects="${6//,/ }"
    local ld_exclude_projects="${7//,/ }"

    local old_default_revision=""
    local repo_root_dir=""
    local line=""
    local FOUND=0
    local path=""
    local project=""
    local rev=""

    repo_root_dir=$(repo_root)
    if [ $? -ne 0 ]; then
        echo_stderr "Current directory is not managed by repo."
        return 1
    fi

    if [ ! -f "${old_manifest}" ]; then
        echo_stderr "Old manifest file is missing '${old_manifest}'."
        return 1
    fi

    if [ -f "${new_manifest}" ]; then
        echo_stderr "New manifest file already present '${new_manifest}'."
        return 1
    fi

    mkdir -p "$(dirname "${new_manifest}")"
    if [ $? -ne 0 ]; then
        echo_stderr "Failed to create directory '$(dirname "${new_manifest}")'"
        return 1
    fi

    old_default_revision=$(manifest_get_default_revision "${old_manifest}")
    if [ ${set_default} -eq 1 ] && [ "${old_default_revision}" == "" ]; then
        # We only know how to alter an existing default revision, not set a
        # new one, so continue without setting a default.
        set_default=0
    fi

    while IFS= read -r line; do
        echo "${line}" | grep -q '<project'
        if [ $? -ne 0 ]; then
            # Line does not define a project
            if [ ${set_default} -eq 0 ] || [ "${old_default_revision}" == "" ]; then
                # No further processing, do not modify
                echo "${line}"
                continue
            fi

            # ok setting the default
            echo "${line}" | grep -q '<default'
            if [ $? -ne 0 ]; then
                # Line does not set defaults, do not modify
                echo "${line}"
                continue
            fi

            echo "${line}" | sed "s#${old_default_revision}#${revision}#"
            continue
        fi

        # check if project name is selected
        FOUND=0
        for project in ${projects}; do
            echo "${line}" | grep -q 'name="'${project}'"'
            if [ $? -eq 0 ]; then
                FOUND=1
                break
            fi
        done

        LD_EXCLUDE_FOUND=0
        for project in ${ld_exclude_projects}; do
            echo "${line}" | grep -q 'name="'${project}'"'
            if [ $? -eq 0 ]; then
                LD_EXCLUDE_FOUND=1
                break
            fi
        done

        rev=${revision}
        old_rev=$(echo "${line}" | grep 'revision=' | sed -e 's#.*revision=\([^ ]*\).*#\1#' -e 's#"##g' -e "s#'##g")
        if [ $FOUND -eq 0 ]; then
            # A non-selected project
            if [ ${lock_down} -eq 2 ] && [ $LD_EXCLUDE_FOUND -eq 0 ]; then
                # Hard lock-down
                # Set the revision to current HEAD SHA.
                path="${repo_root_dir}/$(echo "${line}" | sed 's#.*path="\([^"]*\)".*#\1#')"
                rev=$(cd "${path}"; git rev-parse HEAD)
            elif [ ${lock_down} -eq 1 ] && [ $LD_EXCLUDE_FOUND -eq 0 ] && [ "${old_rev}" == "" ]; then
                # Soft lock-down but no revision is currently set on the project.
                # Set the revision to current HEAD SHA.
                path="${repo_root_dir}/$(echo "${line}" | sed 's#.*path="\([^"]*\)".*#\1#')"
                rev=$(cd "${path}"; git rev-parse HEAD)
            elif [ ${lock_down} -eq 1 ] && [ $LD_EXCLUDE_FOUND -eq 0 ] && [ "${old_rev}" == "master" ]; then
                # Soft lock-down and project has revision set to 'master' which is definitly unstable.
                # Set the revision to current HEAD SHA.
                path="${repo_root_dir}/$(echo "${line}" | sed 's#.*path="\([^"]*\)".*#\1#')"
                rev=$(cd "${path}"; git rev-parse HEAD)
            else
                if [ ${set_default} -eq 0 ] || [ "${old_default_revision}" == "${revision}" ]; then
                    # default revision unchanged, leave it be
                    echo "${line}"
                    continue
                fi

                if [ "${old_rev}" != "" ]; then
                    # Non-selected project has an explicit revision, leave it be
                    echo "${line}"
                    continue
                fi

                # The default revision will change, but this project, which
                # relied on the old default, is not supposed to change,
                # so it's revision must now be explicitly set to point to
                # the old default revision.
                rev="${old_default_revision}"
            fi
        else
            # A selected project
            if [ ${set_default} -eq 1 ]; then
                # Selected project does not need to set a revision.
                # The revision will come from the default
                if [ "${old_rev}" == "" ]; then
                    # project has no revision to delete
                    echo "${line}"
                    continue
                fi

                # delete any revision present
                echo "${line}" | sed 's#\(.*\)revision=[^ ]*\(.*\)#\1\2#'
                continue
            fi
        fi

        # Need to set revision on selected project
        if echo "${line}" | grep -q 'revision='; then
            echo "${line}" | sed "s#revision=\"[^\"]*\"#revision=\"${rev}\"#"
        else
            # No prior revision
            # Set revision prior to name
            echo "${line}" | sed "s#name=#revision=\"${rev}\" name=#"
        fi
    done < "${old_manifest}" > "${new_manifest}"

    return 0
}