# Author:       Li Junhao       l@x-cmd.com         # xrc
# shellcheck    shell=dash

# https://stackoverflow.com/questions/1527049/how-can-i-join-elements-of-an-array-in-bash
# join_ws also works

xrc:mod:lib      str     ml regex replace # todo rot

___x_cmd_str(){
    if [ -z "$1" ]; then
        ___x_cmd_str help
        return 1
    fi

    local op="$1"; shift
    case "$op" in
        ml)                     ___x_cmd_str_ml                  "$@" ;;

        join)                   ___x_cmd_str_join                "$@" ;;
        len)                    ___x_cmd_str_len                 "$@" ;;

        trim_eol_space)         ___x_cmd_str_remove_eol_space    "$@" ;;

        trim_|triml_|trimr_|trimm_|\
            trim|triml|trimr|trimm)
                                ___x_cmd_str_"$op"               "$@" ;;

        split)                  ___x_cmd_str_split               "$@" ;;
        split_to_array)         ___x_cmd_str_split               "$@" ;;
        split_to_lines)         ___x_cmd_str_split_to_lines      "$@" ;;
        work)                   ___x_cmd_str_work                "$@" ;;
        upper)                  ___x_cmd_str_upper               "$@" ;;
        lower)                  ___x_cmd_str_lower               "$@" ;;
        startswith)             ___x_cmd_str_startswith          "$@" ;;
        endswith)               ___x_cmd_str_endswith            "$@" ;;

        slice)                  ___x_cmd_str_slice               "$@" ;;
        indexof)                ___x_cmd_str_indexof             "$@" ;;

        dos2unix)               ___x_cmd_str_dos2unix            "$@" ;;
        unix2dos)               ___x_cmd_str_unix2dos            "$@" ;;

        repeat)                 ___x_cmd_str_repeat              "$@" ;;

        base64)                 ___x_cmd_str_base64              "$@" ;;
        unbase64)               ___x_cmd_str_unbase64            "$@" ;;
        md5)                    ___x_cmd_str_md5                 "$@" ;;
        sha)                    ___x_cmd_str_sha                 "$@" ;;
        sha1)                   ___x_cmd_str_sha1                "$@" ;;
        sha256)                 ___x_cmd_str_sha256              "$@" ;;
        sha512)                 ___x_cmd_str_sha512              "$@" ;;

        repr)                   ___x_cmd_str_repr                "$@" ;;

        sub|replace)            ___x_cmd_str_replace             "$@" ;;
        gsub|replace_all)       ___x_cmd_str_replace_all         "$@" ;;
        nsub|replace_nth)       ___x_cmd_str_replace_n           "$@" ;;

        regex|rematch)          ___x_cmd_str_regex               "$@" ;;

        help)                   ___x_cmd_cmds_cat "$___X_CMD_ROOT_MOD/str/lib/help" >&2 ;;
        *)                      return 1 ;;
    esac
}

# Section: join len

# Only works for single char delimeter
# a=(1 2 3)
# IFS=$'\n'; echo "${a[*]}"

___x_cmd_str_join(){
    local sep="$1"
    shift 1
    local bar
    bar=$(printf "${sep}%s" "$@")
    printf "%s\n" "${bar#"${sep}"}"
}

___x_cmd_str_join_with_IFS(){
    local IFS="${1:?Provide character as seperator}"
    printf "%s\n" "$*"
}

___x_cmd_str_len(){
    if [ $# -ne 0 ]; then
        while [ $# -ne 0 ]; do
            printf "%s" "${#1}"
            shift
        done
    else
        local l
        l="$(___x_cmd_cmds_cat)"
        printf "%s" "${#l}"
    fi
}
# EndSection

# Section: trim
___x_cmd_str_trim_(){
    local IFS=" "
    x_="$*"
    # remove leading whitespace characters
    x_="${x_#"${x_%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    x_="${x_%"${x_##*[![:space:]]}"}"
}

___x_cmd_str_triml_(){
    local IFS=" "
    x_="$*"
    # remove leading whitespace characters
    x_="${x_#"${x_%%[![:space:]]*}"}"
}

___x_cmd_str_trimr_(){
    local IFS=" "
    x_="$*"
    # remove leading whitespace characters
    x_="${x_%"${x_##*[![:space:]]}"}"
}

___x_cmd_str_trimm_(){
    x_=""
    [ -n "$*" ] || return 0
    x_="$( ___x_cmd_cmds awk '{ a=$0; gsub("[ \t\b\v\n]+$", "", a); print a; }' <<A
$*
A
    )"
}

___x_cmd_str_trim(){
    local x_=; ___x_cmd_str_trim_ "$@" || return
    printf "%s\n" "$x_"
}

___x_cmd_str_triml(){
    local x_=; ___x_cmd_str_triml_ "$@" || return
    printf "%s\n" "$x_"
}

___x_cmd_str_trimr(){
    local x_=; ___x_cmd_str_trimr_ "$@" || return
    printf "%s\n" "$x_"
}

___x_cmd_str_trimm(){
    local x_=; ___x_cmd_str_trimm_ "$@" || return
    printf "%s\n" "$x_"
}
# EndSection

# Section: split

# https://stackoverflow.com/questions/40686922/howto-split-a-string-on-a-multi-character-delimiter-in-bash#comment99425158_47633817

# Split into list.
___x_cmd_str_split(){
    # ___x_cmd_cmds tr "${1:?split char}" '\n'
    local s
    local IFS
    local delimiter="${1:?Delemiter}"
    local array_name
    local k
    shift 1
    case "$#" in
        0)  echo "Please provide array name.
___x_cmd_str_split <delimiter> [string] <arary_name>
" >&2;
            return 1; ;;        # TODO: Add details information
        1)  array_name="$1";
            s=$(___x_cmd_cmds_cat)  ;;
        *)  s="$1";
            array_name="$2"     ;;
    esac

    eval "$array_name=()"

    s="$s$delimiter"
    while [ "${#s}" -gt 0 ]; do
        k="${s%%"$delimiter"*}"
        eval "$array_name+=( \"\$k\" )"
        s="${s#*"$delimiter"}"

        [ "$s" = "$delimiter" ] && break
    done
}

# According to ___x_cmd_str_split
____x_cmd_str_split_to_lines(){
    # ___x_cmd_cmds tr "${1:?split char}" '\n'
    local s
    local IFS
    case "$#" in
        1) s=$(___x_cmd_cmds_cat)   ;;
        *) s="$2"     ;;
    esac
    echo -e "${s//$1/\n}"
}
# EndSection

# Section: uppper lower
case "$BASH_VERSION" in
    4*|5*)
___x_cmd_str_upper(){
    if [ "$#" -eq 0 ]; then
        ___x_cmd_cmds tr "[:lower:]" "[:upper:]"
    else
        printf "%s\n" "${1^^}"
    fi
}

___x_cmd_str_lower(){
    if [ "$#" -eq 0 ]; then
        ___x_cmd_cmds tr "[:upper:]" "[:lower:]"
    else
        printf "%s\n" "${1,,}"
    fi
}
;;
    *)
___x_cmd_str_upper(){
    if [ "$#" -eq 0 ]; then
        ___x_cmd_cmds tr "[:lower:]" "[:upper:]"
    else
        printf "%s\n" "$1" | ___x_cmd_cmds tr "[:lower:]" "[:upper:]"
    fi
}

___x_cmd_str_lower(){
    if [ "$#" -eq 0 ]; then
        ___x_cmd_cmds tr "[:upper:]" "[:lower:]"
    else
        printf "%s\n" "$1" | ___x_cmd_cmds tr "[:upper:]" "[:lower:]"
    fi
}
;;
esac

# EndSection

# Section: startswith endswith indexof slice
# other format using library

___x_cmd_str_startswith(){
    [ "${1:?Provide string}" != "${1#${2:?Provide prefix}}" ]
}

# TODO: better performance
# ___x_cmd_docker_snap___starswith(){
#     local data="$1"
#     local prefix="$2"

#     case "$data" in
#         "$prefix"*)    ;;
#         *)          return 1
#     esac
# }

___x_cmd_str_endswith(){
    [ "${1:?Provide string}" != "${1%${2:?Provide suffix}}" ]
}

___x_cmd_str_slice(){
    local srcStr="${1:?Please input a string to slice}"
    local start end

    case $# in
        1)  echo "$srcStr"; return 0;;
        2)
            if [[ "$2" == *:* ]]; then
                start=${2%:*}
                end=${2#*:}
            else
                start=$2
                (( end=$2+1 ))
            fi ;;
        *)  echo "Too many parameters" 1>&2;;
    esac

    start=${start:-0}
    end=${end:-${#srcStr}}
    [ "$end" -lt 0 ] && (( end = ${#srcStr} + end ))

    # echo $start $end (( end - start )) >&2
    printf "%s" "${srcStr:start:(( end - start ))}"
}

___x_cmd_str_indexof(){
    local src=${1:?Provide src string}
    local tgt=${2:?Provide target str}
    local after_tgt_in_src=${src#*${tgt}}
    if [ "${#after_tgt_in_src}" -eq "${#src}" ]; then
        return 1
    fi
    printf "%s\n" $(( ${#src} - ${#after_tgt_in_src} - ${#tgt} ))
}
# EndSection

# Section: dos2unix unix2dos
___x_cmd_str_dos2unix(){
    if [ $# -eq 0 ]; then
        sed -e 's/\r//'
    else
        sed -e 's/\r//' -i "${BAK:-""}" "$@"
    fi
}

# refer https://en.wikipedia.org/wiki/Unix2dos
# refer https://www.cyberciti.biz/faq/howto-unix-linux-convert-dos-newlines-cr-lf-unix-text-format/
___x_cmd_str_unix2dos(){
    if [ $# -eq 0 ]; then
        # test cat abc.txt | sed -e 's/$/\r/' | cat -vet -
        sed -e $'s/$/\r/'
    else
        # test cat abc.txt | sed -e 's/$/\r/' | cat -vet -
        # cat abc.txt | sed -e 's/$/\r/' | cat -vet -
        sed -e 's/$/\r/' -e "$ s/..$//g" -i "${BAK:-""}" "$@"
        # sed -e 's/\r*$/\r/' -i ${BAK:-""} "$@"
        # sed -e "s/$/^M/" -i ${BAK:-""} "$@"
    fi
}
# EndSection

# Section: repeat remove_eol_space
# Reference: https://stackoverflow.com/questions/5349718/how-can-i-repeat-a-character-in-bash
___x_cmd_str_repeat(){
    local character="${1:?Charactor}"
    local number="${2:?Number of characters}"
    case "${#character}" in
        0) printf "%s" "Error" >&2;;
        1) seq -f "$character" -s '' "$number" ;;
        *) ___x_cmd_cmds_awk -v num="$number" '
    NR==1   { ch=$0 }
    NR>=2   { ch=ch "\n" $0 }
    END     { s=""; for (i=0; i<num; ++i) { s=s ch }; print s  }' <<A
${character}
A
;;
        # *) local t; t="$(seq -f 1 -s '' $number)"; echo "${t//1/$character}" ;;
    esac
}

# USAGE 1, remove in file: remove_eol_space filepath
# USAGE 2, remove in file and backup with BAK as extensions: BAK='.bak' remove_eol_space filepath
# USAGE 3, remove and output to stdout: cat filepath | remove_eol_space
___x_cmd_str_remove_eol_space(){
    if [ $# -eq 0 ]; then
        sed -e 's/[[:blank:]]*$//g'
    else
        sed -e 's/[[:blank:]]*$//g' -i "${BAK:-""}" "$@"
    fi
}
# EndSection

# Section: base64 unbase64
___x_cmd_str_base64(){
    if [ $# -eq 0 ]; then
        base64
    else
        ___x_cmd_str_trim "$(printf "%s" "${1:?Provide string}" | base64)"
    fi
}

___x_cmd_str_unbase64(){
    if [ $# -eq 0 ]; then
        base64 --decode
    else
        ___x_cmd_str_trim "$(printf "%s" "${1:?Provide string}" | base64 --decode)"
    fi
}
# EndSection

# Section: md5 sha1
___x_cmd_str___md5_inner(){
    local cmd
    if command -v md5 >/dev/null 2>&1; then
        cmd=md5
    elif command -v md5sum >/dev/null 2>&1; then
        cmd=md5sum
    fi
    if [ -z "$1" ]; then
        $cmd | cut -d ' ' -f 1
    else
        printf "%s" "$1" | $cmd | cut -d ' ' -f 1
    fi
}

# Wrap the function with output variable

# shellcheck disable=SC2086
___x_cmd_str_md5(){
    case $# in
        0)  ___x_cmd_str___md5_inner; return 0;;
        1)  ___x_cmd_str___md5_inner "$1"; return 0;;
        2)  if [ "$1" = -- ]; then
                eval $2="$(___x_cmd_str___md5_inner)"
                return 0
            fi ;;
        *)
            eval $3="$(___x_cmd_str___md5_inner "$1")"
            return 0 ;;
    esac
}

___x_cmd_str_sha(){
    local ALGOR="${ALGOR:-1}"
    if command -v shasum >/dev/null 2>&1; then
        printf "%s" "${1:?str to encrypt}" | shasum -a "$ALGOR" | cut -d ' ' -f 1
        return 0
    fi

    local cmd="sha${ALGOR}sum"
    if command -v "$cmd" >/dev/null 2>&1; then
        printf "%s" "${1:?str to encrypt}" | $cmd | cut -d ' ' -f 1
        return 0
    fi

    # TODO: Install x and provide encryption
}

___x_cmd_str_sha1(){     ALGOR=1 ___x_cmd_str_sha "$@"; }
___x_cmd_str_sha256(){   ALGOR=256 ___x_cmd_str_sha "$@"; }
___x_cmd_str_sha512(){   ALGOR=512 ___x_cmd_str_sha "$@"; }

# EndSection


xrc setmain ___x_cmd_str
