#!/bin/bash

# config file
gmpy_cdirs_config="$(gettop)/scripts/cdirs/cdirsrc.tina"

cdir_options_list="hn:l:p:k:t:f:F:"
lsdir_options_list="hp:"
cldir_options_list="gha"
setdir_options_list="hg"

cdir_options_list_full="reload,reset,num:,label:,path:,help,key:tag:,find:,Find:,default-key:,find-maxdepth:"
lsdir_options_list_full="path:,help"
cldir_options_list_full="all,reset,help,reload,global"
setdir_options_list_full="global,help"
init_options_list_full="replace-cd,help"

cdir() {
    local flag word opts key f_dirname F_dirname find_maxdepth

    opts="$(getopt -l "${cdir_options_list_full}" -o "${cdir_options_list}" -- $@)" || return 1
    eval set -- "${opts}"
    while true
    do
        case "$1" in
            -h|--help)
                gmpy_cdirs_print_help "cdir"
                return 0
                ;;
            -l|--label)
                flag="label"
                shift
                word="$1"
                shift
                break
                ;;
            -n|--num)
                flag="num"
                shift
                word="$1"
                shift
                break
                ;;
            -p|--path)
                flag="path"
                shift
                word="$1"
                shift
                break
                ;;
            --reload)
                gmpy_cdirs_load_config
                gmpy_cdirs_load_global_labels
                return 0
                ;;
            --reset)
                gmpy_cdirs_reset
                return 0
                ;;
            -k|--key|-t|--tag)
                shift
                gmpy_cdirs_check_key "${1}" || {
                    echo "$1: No this key"
                    return 1
                }
                key="$1"
                shift
                ;;
            -f|--find)
                shift
                f_dirname="$1"
                shift
                ;;
            -F|--Find)
                shift
                F_dirname="$1"
                shift
                ;;
            --default-key)
                shift
                gmpy_cdirs_set_default_key "$1" || {
                    echo "$1: No This Key"
                    return 1
                }
                return 0
                ;;
            --find-maxdepth)
                shift
                gmpy_cdirs_check_whether_num "$1" \
                    && find_maxdepth="$1" \
                    || {
                        echo "$1: Invaild Input - Not Num"
                        return 1
                    }
                shift
                ;;
            --)
                shift
                break
        esac
    done

    [ -n "${find_maxdepth}" ] && {
        [ "$#" -eq 0 -a -z "${f_dirname}" -a -z "${F_dirname}" ] && {
            gmpy_cdirs_set_find_maxdepth "${find_maxdepth}"
            return 0
        }
    }

    # -f|--find|-F|--Find
    if [ -n "${f_dirname}" ]; then
        key=${key:-${gmpy_cdirs_default_key}}
        [ -z "${key}" ] && return 0
        gmpy_cdirs_cd_find_process "${f_dirname}" "${key}" "${find_maxdepth}"
        return 0
    elif [ -n "${F_dirname}" ]; then
        gmpy_cdirs_cd_find_process "${F_dirname}" "${find_maxdepth}"
        return 0
    fi

    if [ -n "${flag}" ]; then
        gmpy_cdirs_replace_cd "$(_cdir "${word}" "${flag}")"
    elif [ "$#" -gt "1" ]; then #for path with space
        gmpy_cdirs_replace_cd "$*"
    elif [ "$#" -eq "0" ]; then
        gmpy_cdirs_replace_cd
    elif [ "$#" -eq "1" ] && ([ "$1" = "," ] || [ "$1" = '0' ]); then
        local path="$(gmpy_cdirs_get_path_from_num "0")"
        [ -z "${path}" ] && return 0
        gmpy_cdirs_replace_cd "${path}"
    else
        gmpy_cdirs_replace_cd "$(_cdir "$1")"
    fi
}

setdir() {
    local flag opts

    flag="no_global"
    opts="$(getopt -l "${setdir_options_list_full}" -o "${setdir_options_list}" -- $@)" || return 1
    eval set -- "${opts}"
    while true
    do
        case "$1" in
            -h|--help)
                gmpy_cdirs_print_help "setdir"
                return 0
                ;;
            -g|--global)
                flag="global"
                shift
                ;;
            --)
                shift
                break
        esac
    done

    if [ "$#" -lt "2" ]; then
        if [ "$1" = ',' ]; then
            gmpy_cdirs_set_mark
        else
            gmpy_cdirs_print_help "setdir"
        fi
    elif [ "$#" -eq "2" ]; then
        _setdir $@ "${flag}"
    elif [ "$#" -gt "2" ]; then
        _setdir "$1" "$(shift;echo "$*")" "${flag}"
    fi
}

lsdir() {
    local opts word
    local flag="not_only_path"

    opts=`getopt -o "${lsdir_options_list}" -l "${lsdir_options_list_full}" -- $@` || return 1
    eval set -- "${opts}"
    while true
    do
        case "$1" in
            -h|--help)
                gmpy_cdirs_print_help "lsdir"
                return 0
                ;;
            -p|--path)
                flag="only_path"
                shift
                word="$1"
                shift
                ;;
            --)
                shift
                break
                ;;
        esac
    done

    [ -n "${word}" -a "${flag}" = "only_path" ] \
        && _lsdir "${flag}" "${word}" \
        || _lsdir "${flag}" $@
}

cldir() {
    local opts all_flag
    local global_flag="no_global"

    opts="$(getopt -l "${cldir_options_list_full}" -o "${cldir_options_list}" -- $@)" || return 1
    eval set -- "${opts}"
    while true
    do
        case "$1" in
            -h|--help)
                gmpy_cdirs_print_help "cldir"
                return 0
                ;;
            --reset)
                gmpy_cdirs_reset
                return 0
                ;;
            --reload)
                gmpy_cdirs_load_global_labels
                return 0
                ;;
            -a|--all)
                shift
                all_flag="all"
                ;;
            -g|--global)
                shift
                global_flag="global"
                ;;
            --)
                shift
                break
                ;;
        esac
    done

    if [ "${all_flag}" = "all" ]; then
        local res="n"
        while true
        do
            read -n 1 -p "Are you sure to clear all labels$([ "${global_flag}" = "global" ] \
                && echo " but also global labels")? [y/n] " res
            echo
            case "${res}" in
                y|Y)
                    break
                    ;;
                n|N)
                    return 0
                    ;;
                *)
                    continue
                    ;;
            esac
        done
        gmpy_cdirs_clear_all "${global_flag}"
        return 0
    fi

    if [ $# -lt 1 ]; then
        gmpy_cdirs_print_help "cldir"
        return 1
    fi
    _cldir "${global_flag}" $@
}

# _cdir <label|num|path> [num|label|path](point out the type)
_cdir() {
    [ -f "$1" -a ! "$2" = "num" -a ! "$2" = "label" ] && {
        echo "$1"
        return 0
    }
    echo "$(gmpy_cdirs_get_path "$1" "$2")"
}

# _lsdir <only_path|not_only_path> [num1|label1|path1] [num2|label2|path2] ...
_lsdir() {
    local path_flag="$1" && shift
    [ "${path_flag}" = "not_only_path" ] && {
        printf '\033[32m%s\t%-16s\t%s\033[0m\n' "num" "label" "path"
        printf '\033[32m%s\t%-16s\t%s\033[0m\n' "---" "-----" "----"
    }
    if [ "$#" -gt 0 ]; then
        for para in $@
        do
            if [ "${para}" = "," ]; then
                if [ "${path_flag}" = "not_only_path" ]; then
                    gmpy_cdirs_list_mark
                else
                    gmpy_cdirs_get_path "0" "num"
                fi
            else
                if [ "${path_flag}" = "not_only_path" ]; then
                    gmpy_cdirs_ls_one_dir "${para}"
                else
                    gmpy_cdirs_get_path "${para}"
                fi
            fi
        done
    else
        gmpy_cdirs_ls_all_dirs
    fi
}

# _setdir <label> <path> <global|no_global>
_setdir() {
    local path="$(gmpy_cdirs_get_absolute_path "$2")"
    local num

    if [ -z "${path}" -o ! -d ${path} ]; then
        echo -e "\033[31m${path} isn't existed or directory\033[0m"
        return 2
    fi

    gmpy_cdirs_check_label "$1" || {
        echo -en "\033[31mERROR: label fromat\n\033[0m"
        echo -n "label starts with "
        [ -n "${gmpy_cdirs_first_symbol}" ] \
            && echo -n "'${gmpy_cdirs_first_symbol}'" \
            || echo -n "letter"
        echo -n " and combinates with letter, number and symbol "
        echo "'${gmpy_cdirs_label_symbol}'"
        return 1
    }

    if gmpy_cdirs_check_label_existed "$1"; then
        echo -en "\033[31mmodify:\033[0m\t"
        num="$(gmpy_cdirs_get_num_from_label "$1")"
        gmpy_cdirs_clear_dir "$3" "$1" &>/dev/null
    else
        echo -en "\033[31mcreate:\033[0m\t"
        num="$(gmpy_cdirs_get_num_cnt)"
        gmpy_cdirs_add_num_cnt
    fi

    gmpy_cdirs_set_env "$3" "${num}" "$1" "${path}"
    gmpy_cdirs_ls_format "${num}" "$1" "${path}"
}

# _cldir <no_global|global> <num1|label1|path1> <num2|label2|path2> ...
_cldir() {
    local global_flag="$1"
    shift

    for para in $@
    do
        if [ "${para}" = "," ]; then
            gmpy_cdirs_clear_mark
        else
            gmpy_cdirs_clear_dir "${global_flag}" "${para}"
        fi
    done
}

##########################################################################################
#                                                                                        #
#   cdirs library                                                                        #
#   version: 2.000                                                                       #
#   author: gmpy                                                                         #
#   date: 2017-1-10                                                                      #
#                                                                                        #
##########################################################################################

# /tmp/cdirs/cdirs.env.$$
# <num> <label> <path>
# 3     work    /home/user/cdirs

#================ count num ================#
gmpy_cdirs_get_num_cnt() {
    [ -z "${gmpy_cdirs_cnt}" ] && export gmpy_cdirs_cnt=1
    echo "${gmpy_cdirs_cnt}"
}

gmpy_cdirs_add_num_cnt() {
    export gmpy_cdirs_cnt="$(( ${gmpy_cdirs_cnt} + 1 ))"
}

#================ mark ================#
# gmpy_cdirs_set_mark
gmpy_cdirs_set_mark() {
    [ -r "${gmpy_cdirs_env}" -a -w "${gmpy_cdirs_env}" ] || return 1
    if grep "^0 " ${gmpy_cdirs_env} &>/dev/null; then
        gmpy_cdirs_clear_dir "no_global" "0" &>/dev/null
        echo -en "\033[31mmodify:\033[0m\t"
    else
        echo -en "\033[31mcreate:\033[0m\t"
    fi
    gmpy_cdirs_set_env "no_global" "0" "," "${PWD}"
    gmpy_cdirs_list_mark
}

# gmpy_cdirs_list_mark
gmpy_cdirs_list_mark() {
    gmpy_cdirs_ls_one_dir "0"
}

# gmpy_cdirs_clear_mark
gmpy_cdirs_clear_mark() {
    gmpy_cdirs_check_label_existed "0" && {
        gmpy_cdirs_clear_dir "no_global" "0"
    }
}

#================ GET path\label\num\env ================#

# gmpy_cdirs_get_path_from_num <num>
gmpy_cdirs_get_path_from_num() {
    [ -r "${gmpy_cdirs_env}" ] \
        && eval "awk '\$1~/^$1$/{print \$3}' ${gmpy_cdirs_env}"
}

# gmpy_cdirs_get_path_from_label <label>
gmpy_cdirs_get_path_from_label() {
    [ -r "${gmpy_cdirs_env}" ] \
        && eval "awk '\$2~/^$1$/{print \$3}' ${gmpy_cdirs_env}"
}

# gmpy_cdirs_get_label_from_num <num>
gmpy_cdirs_get_label_from_num() {
    [ -r "${gmpy_cdirs_env}" ] \
        && eval "awk '\$1~/^$1$/{print \$3}' ${gmpy_cdirs_env}"
}

# gmpy_cdirs_get_num_from_label <label>
gmpy_cdirs_get_num_from_label() {
    [ -r "${gmpy_cdirs_env}" ] \
        && eval "awk '\$2~/^$1$/{print \$1}' ${gmpy_cdirs_env}"
}

# gmpy_cdirs_get_env_from_num <num>
gmpy_cdirs_get_env_from_num() {
    [ -r "${gmpy_cdirs_env}" ] \
        && eval "awk '\$1~/^$1$/{print \$1 \" \" \$2 \" \" \$3}' ${gmpy_cdirs_env}"
}

# gmpy_cdirs_get_env_from_path <path>
# allow more than one envs
gmpy_cdirs_get_env_from_path() {
    [ -r "${gmpy_cdirs_env}" ] \
        && egrep "$1/?$" ${gmpy_cdirs_env} | sort -k 1 -n | awk '{print $1 " " $2 " " $3}'
}

# gmpy_cdirs_get_env_from_label <label>
# enable echo more than one env if input regular expression
gmpy_cdirs_get_env_from_label() {
    [ -r "${gmpy_cdirs_env}" ] \
        && eval "awk '\$2~/^$1$/{print \$1 \" \" \$2 \" \" \$3}' ${gmpy_cdirs_env}" \
            | sort -k 1 -n
}

# gmpy_cdirs_get_all_num
gmpy_cdirs_get_all_num() {
    [ -r "${gmpy_cdirs_env}" ] \
        && awk '{print $1}' ${gmpy_cdirs_env}
}

# gmpy_cdirs_get_all_label
gmpy_cdirs_get_all_label() {
    [ -r "${gmpy_cdirs_env}" ] \
        && awk '($2!~/^,$/ && $1!~/^$/){print $2}' ${gmpy_cdirs_env}
}

# gmpy_cdirs_get_path <label|num|path> [num|label|path](point out the type)
gmpy_cdirs_get_path() {
    local _type
    [ -n "$2" ] && _type="$2" || _type="$(gmpy_cdirs_check_type "$1")"
    case "${_type}" in
        "num")
            echo "$(gmpy_cdirs_get_path_from_num "$1")"
            ;;
        "label")
            gmpy_cdirs_check_label_existed "$1" \
                && echo "$(gmpy_cdirs_get_path_from_label "$1")" \
                || echo "$1"
            ;;
        *)
            echo "$1"
            ;;
    esac
}

# gmpy_cdirs_get_absolute_path <path>
gmpy_cdirs_get_absolute_path() {
    local pwd_path="${PWD}"
    local para_path="$1"

    # deal with word like - ~ . ..
    if [ "${para_path}" = "-" ]; then
        echo "${OLDPWD}"
        return 0
    elif [ "${para_path}" = "." ]; then
        echo "${PWD}"
        return 0
    elif [ "${para_path}" = ".." ]; then
        echo "${PWD%/*}"
        return 0
    elif [ "${para_path}" = "~" ]; then
        echo "${HOME}"
        return 0
    elif [ "${para_path:0:1}" = "~" ]; then
        para_path="${HOME}${para_path:1}"
    fi

    # delete last letter /
    para_path=$(echo "${para_path}" | sed 's/\(.*\)\/$/\1/g')

    # deal with word like ./ ../
    while [ -n "$(echo "${para_path}" | egrep "\./|\.\./")" ]
    do
        if [ "${para_path%%/*}" = ".." ]; then
            pwd_path="${pwd_path%/*}"
        elif [ ! "${para_path%%/*}" = "." ]; then
            pwd_path="${pwd_path}/${para_path%%/*}"
        fi
        para_path="${para_path#*/}"
    done

    if [ ! "${pwd_path}" = "${PWD}" ]; then
        echo "${pwd_path}/${para_path}"
    elif [ -d "${para_path}" ] && [ ! "${para_path:0:1}" = "/" ]; then
        echo "${PWD}/${para_path}"
    else
        echo "${para_path}"
    fi
}

#================ CHECK path\label\num ================#

# gmpy_cdirs_check_type <label|num|path>
# only check the string format but not whether exist the dir
# num: only number
# path: string wiht ./ or ../ or /, sometime it can be ~ or begin with ~
# label: others else
gmpy_cdirs_check_type() {
    if [ "$#" -ne "1" ]; then
        return 1
    fi

    if $(echo "$1" | egrep "^[0-9]+$" &>/dev/null); then
        echo "num"
        return 0
    fi

    if [ -n "$(echo "$1" | egrep "\./|\.\./|/")" ] \
        || [ "${1:0:1}" = "~" ] \
        || [ "$1" = "-" ] \
        || [ "$1" = "." ] \
        || [ "$1" = ".." ]; then
        echo "path"
        return 0
    fi

    gmpy_cdirs_check_label "$1" && echo "label" || echo "null"
}

# gmpy_cdirs_check_label <label>
gmpy_cdirs_check_label() {
    [ -n "$(echo "$1" \
        | egrep "^${gmpy_cdirs_first_symbol}[[:alpha:]]([[:alnum:]]*${gmpy_cdirs_label_symbol}*[[:alnum:]]*)*$")" ] \
        && return 0 || return 1
}

#gmpy_cdirs_check_label_existed <num|label>
gmpy_cdirs_check_label_existed() {
    [ -r "${gmpy_cdirs_env}" ] || return 1
    case "$(gmpy_cdirs_check_type "$1")" in
        "label")
            grep " $1 " ${gmpy_cdirs_env} &>/dev/null && return 0 || return 1
            ;;
        "num")
            grep "^$1 " ${gmpy_cdirs_env} &>/dev/null && return 0 || return 1
            ;;
        *)
            return 1
            ;;
    esac
}

#================ LIST path\label\num ================#

# gmpy_cdirs_ls_all_dirs
gmpy_cdirs_ls_all_dirs() {
    local oIFS line

    [ -r "${gmpy_cdirs_env}" ] || return 1
    oIFS="${IFS}"
    IFS=$'\n'
    for line in $(grep -v "^$" ${gmpy_cdirs_env} | sort -k 1 -n)
    do
        IFS="${oIFS}"
        gmpy_cdirs_ls_format ${line}
        IFS=$'\n'
    done
    IFS="${oIFS}"
}

# gmpy_cdirs_ls_one_dir <num|label|path>
gmpy_cdirs_ls_one_dir() {
    case "`gmpy_cdirs_check_type "$1"`" in
        num)
            gmpy_cdirs_ls_format $(gmpy_cdirs_get_env_from_num "$1")
            ;;
        path)
            gmpy_cdirs_ls_format $(gmpy_cdirs_get_env_from_path "$1")
            ;;
        *)  #support regular expression
            gmpy_cdirs_ls_format $(gmpy_cdirs_get_env_from_label "$1")
            ;;
    esac
}

# gmpy_cdirs_ls_format <num> <label> <path>
gmpy_cdirs_ls_format() {
    [ $# -ne 3 ] && return 1
    echo "$@" \
        | awk '{printf ("\033[32m%d)\t%-16s\t%s\033[0m\n",$1,$2,$3)}'
}


#================ CLEAR path\label\num ================#

# gmpy_cdirs_clear_global_label_from_label <label>
gmpy_cdirs_clear_global_label_from_label() {
    [ $# -lt 1 ] && return 1
    [ ! -f ${gmpy_cdirs_default} ] && return 2
    sed -i "/^$1 *=/d" ${gmpy_cdirs_default} && return 0 || return 1
}

# gmpy_cdirs_clear_dir <global|no_global> <num|label|path>
gmpy_cdirs_clear_dir() {
    local env num label
    case "$(gmpy_cdirs_check_type "$2")" in
        "num")
            env="$(gmpy_cdirs_get_env_from_num "$2" | head -n 1)"
            num="$2"
            label="$(echo "${env}" | awk '{print $2}')"
            ;;
        "label")
            env="$(gmpy_cdirs_get_env_from_label "$2" | head -n 1)"
            num="$(echo "${env}" | awk '{print $1}')"
            label="${2}"
            ;;
        "path")
            env="$(gmpy_cdirs_get_env_from_path "$2" | head -n 1)"
            num="$(echo "${env}" | awk '{print $1}')"
            label="$(echo "${env}" | awk '{print $2}')"
            ;;
        *)
            return 1
            ;;
    esac
    [ -z "${env}" -o -z "${num}" -o -z "${label}" ] && return 1

    echo -ne "\033[31mdelete:\t\033[0m"
    [ "$1" = "global" ] && {
        gmpy_cdirs_clear_global_label_from_label "${label}" \
            && echo -ne "\033[33m[global]\033[0m"
    }
    [ -r "${gmpy_cdirs_env}" -a -w "${gmpy_cdirs_env}" ] || return 1
    eval "sed -i '/^${num} /d' ${gmpy_cdirs_env}"
    gmpy_cdirs_ls_format ${env}
}

# gmpy_cdirs_clear_all [no_global|global]
gmpy_cdirs_clear_all() {
    [ "$1" = "global" ] && echo > ${gmpy_cdirs_default}
    [ -w "${gmpy_cdirs_env}" ] \
        && echo > ${gmpy_cdirs_env}
}

#================ SET path\label\num ================#

# gmpy_cdirs_set_global_dir <label> <path>
gmpy_cdirs_set_global_dir() {
    echo "$1 = ${path}" >> ${gmpy_cdirs_default}
}

# gmpy_cdirs_set_env <global|no_global> <num> <label> <path>
gmpy_cdirs_set_env() {
    [ "$1" = "global" ] \
        && gmpy_cdirs_set_global_dir "$3" "$4" && echo -ne "\033[33m[global]\033[0m"
    [ -w "${gmpy_cdirs_env}" ] \
        && echo "$2 $3 $4" >> ${gmpy_cdirs_env}
}

# gmpy_cdirs_reset
# Turn back to initial status
gmpy_cdirs_reset() {
    gmpy_cdirs_clear_all
    gmpy_cdirs_cnt=1
    gmpy_cdirs_load_config
    gmpy_cdirs_load_global_labels
}

#================ global dirs ================#

# gmpy_cdirs_load_global_labels
# load default label by ~/.cdir_default
gmpy_cdirs_load_global_labels() {
    eval "[ ! -f ${gmpy_cdirs_default} ] && return 2"

    local line
    local oIFS="${IFS}"
    IFS=$'\n'
    for line in $(eval "cat ${gmpy_cdirs_default}" | egrep -v "^#.*$|^$" | grep "=")
    do
        IFS="${oIFS}"
        # Enable path with variable
        _setdir $(echo ${line%%=*}) "$(eval echo ${line##*=})" "no_global"
        IFS=$'\n'
    done
    IFS="${oIFS}"
}

#================ help ================#

# gmpy_cdirs_print_help <cdir|lsdir|setdir|cldir>
gmpy_cdirs_print_help() {
    case "$1" in
        cdir)
            echo -e "\033[33mcdir [-h|--help] [--reload] [--reset] [num|label|path]\033[0m"
            echo -e "\033[33mcdir [<-n|--num> <num>] [<-l|--label|> <label>] [<-p|--path> <path>]\033[0m"
            echo -e "\033[33mcdir [-f|--find <dir> [-k|--key <key>]]\033[0m"
            echo -e "\033[33mcdir <,>\033[0m"
            echo "--------------"
            echo -e "\033[32mcdir <num|label|path> :\033[0m"
            echo -e "    cd to path that pointed out by num|label|path\n"
            echo -e "\033[32mcdir <,> :\033[0m"
            echo -e "    cd to special label-path, which is set by \"setdir ,\", see also \"setdir --help\"\n"
            echo -e "\033[32mcdir [-h|--help] :\033[0m"
            echo -e "    show this introduction\n"
            echo -e "\033[32mcdir [-n|--num] <parameter> :\033[0m"
            echo -e "    describe parameter as num mandatorily\n"
            echo -e "\033[32mcdir [-l|--label] <parameter> :\033[0m"
            echo -e "    describe parameter as label mandatorily\n"
            echo -e "\033[32mcdir [-p|--path] <parameter> :\033[0m"
            echo -e "    describe parameter as path mandatorily\n"
            echo -e "\033[32mcdir [--reload] :\033[0m"
            echo -e "    reload ~/.cdir_default, which record the static label-path\n"
            echo -e "\033[32mcdir [--reset] :\033[0m"
            echo -e "    clear all label-path and reload ~/.cdir_default, which record the static label-path\n"
            echo -e "\033[32mcdir [-f|--find <dir> [-k|--key <key>]]\033[0m"
            echo -e "    search dir in key-path. use -k|--key to appoint key or use default key\n"
            echo -e "    you can set your key and key-path in ~/.cdirsrc with variable gmpy_cdirs_default_key and gmpy_cdirs_key\n"
            echo -e "    you can also set search max depth by variable gmpy_cdirs_search_max_depth\n"
            echo -e "\033[31mNote: cdir is a superset of cd, so you can use it as cd too (In fact, my support for this scripts is to replace cd)\033[0m"
            ;;
        setdir)
            echo -e "\033[33msetdir [-h|--help] [-g|--global] <label> <path>\033[0m"
            echo -e "\033[33msetdir <,>\033[0m"
            echo "--------------"
            echo -e "\033[32msetdir <label> <path> :\033[0m"
            echo -e "    set label to path, after that, you can use \"cdir label\" or \"cdir num\" to go to path (the num is setted by system and you can see by command \"lsdir\""
            echo -e "    moreover, path strings is support characters like . or .. or ~ or -"
            echo -e "    eg. \"setdir work .\" or \"setdir cdirs ~/cdirs\" or \"setdir last_dir -\" or others\n"
            echo -e "\033[32msetdir <,> :\033[0m"
            echo -e "    set current path as a special label ',' , which is usefull and quick for recording working path , you can go back fastly by \"cdir ,\"\n"
            echo -e "\033[32msetdir [-h|--help] :\033[0m"
            echo -e "    show this introduction\n"
            echo -e "\033[32msetdir [-g|--global] <label> <path> :\033[0m"
            echo -e "    set label to path, moreover, record it in ~/.cdir_default. In this way, you can set this label-path automatically everytimes you run a terminal\n"
            echo -en "\033[31mNote: label starts with "
            [ -n "${gmpy_cdirs_first_symbol}" ] \
                && echo -n "'${gmpy_cdirs_first_symbol}'" \
                || echo -n "letter"
            echo -n " and combinates with letter, number and symbol "
            echo -e "'${gmpy_cdirs_label_symbol}'\033[0m"
            ;;
        cldir)
            echo -e "\033[33mcldir [-h|--help] [-g|--global] [-a|--all] [--reset] [--reload] <num1|label1|path1> <num2|label2|path2> ...\033[0m"
            echo -e "\033[33mcldir <,>\033[0m"
            echo "--------------"
            echo -e "\033[32mcldir <num1|label1|path1> <num2|label2|path2> ... :\033[0m"
            echo -e "    clear the label-path. if path, clear all label-path matching this path\n"
            echo -e "\033[32mcldir <,>:\033[0m"
            echo -e "    clear the special label-path. see also \"setdir --help\"\n"
            echo -e "\033[32mcldir [-h|--help] :\033[0m"
            echo -e "    show this introduction\n"
            echo -e "\033[32mcldir [-g|--global] <num|label|path> :\033[0m"
            echo -e "    unset label to path, moreover, delete it in ~/.cdir_default. see also \"setdir -h|--hlep\"\n"
            echo -e "\033[32mcldir [-a|--all] :\033[0m"
            echo -e "    clear all label-path\n"
            echo -e "\033[32mcldir [--reset] :\033[0m"
            echo -e "    clear all label-path and reload ~/.cdir_default, which record the static label-path\n"
            echo -e "\033[32mcldir [--reload] :\033[0m"
            echo -e "    reload ~/.cdir_default, which record the static label-path"
            ;;
        lsdir)
            echo -e "\033[33mlsdir [-h|--help] [-p|--path <num|label|path>] <num1|label1|path1> <num2|label2|path2> ...\033[0m"
            echo -e "\033[33mlsdir <,>\033[0m"
            echo "--------------"
            echo -e "\033[32mlsdir <num1|label1|path1> <num2|label2|path2> ... :\033[0m"
            echo -e "    list the label-path. if path, list all label-path matching this path\n"
            echo -e "\033[32mlsdir <,> :\033[0m"
            echo -e "    list the special label-path. see also \"setdir --help\"\n"
            echo -e "\033[32mlsdir [-h|--help] :\033[0m"
            echo -e "    show this introduction\n"
            echo -e "\033[32mlsdir [-p|--path <num|label|path>] :\033[0m"
            echo -e "    only print path of label-path, which is usefull to embedded in other commands"
            echo -e "    eg. cat \`lsdir -p cdirs\`/readme.txt => cat /home/user/cdirs/readme.txt"
            ;;
    esac
}

#================ basical ================#

#gmpy_cdirs_load_config
gmpy_cdirs_load_config() {
    eval "[ -f "${gmpy_cdirs_config}" ] && source ${gmpy_cdirs_config}"
}

# gmpy_cdirs_replace_cd <path>
gmpy_cdirs_replace_cd() {
    if [ -n "$*" ]; then
        builtin cd "$*"
    else
        builtin cd
    fi
}

gmpy_cdirs_init() {
    local opts="$(getopt -l "${init_options_list_full}" -o "h" -- $@)" || return 1
    eval set -- "${opts}"
    while true
    do
        case "$1" in
            -h|--help)
                shift
                ;;
            --replace-cd)
                alias cd="cdir"
                shift
                ;;
            --)
                shift
                break
                ;;
            *)
                shift
                ;;
        esac
    done

    gmpy_cdirs_load_config
    gmpy_cdirs_env="/tmp/cdirs/cdirs.env.$$" \
        && mkdir -p /tmp/cdirs -m 777\
        && touch ${gmpy_cdirs_env}
    [ -z "${gmpy_cdirs_default}" ] \
        && gmpy_cdirs_default="${HOME}/.cdirs_default"
    gmpy_cdirs_load_global_labels &>/dev/null

    complete -F gmpy_cdirs_complete_func -o dirnames "setdir" "lsdir" "cldir"
    complete -F gmpy_cdirs_complete_func -o dirnames -A directory "cdir" \
        "$([ "$(type -t cd)" = "alias" ] && echo "cd")"

}

#================ completation ================#

# gmpy_cdirs_complete_func <input>
gmpy_cdirs_complete_func() {
    local cmd="$1"
    local cur="${COMP_WORDS[COMP_CWORD]}"
    local pre="${COMP_WORDS[COMP_CWORD-1]}"
    local line="${COMP_LINE}"
    local complete_list

    case "${cmd}" in
        cldir|lsdir)
            if [ "${cur:0:2}" = "--" ]; then
                complete_list="--$(eval "echo \"\${${cmd}_options_list_full}\" | sed 's/,/ --/g' | sed 's/://g'")"
            elif [ "${cur:0:1}" = "-" ]; then
                complete_list="$(eval "echo \"\${${cmd}_options_list}\" | sed 's/://g' | sed 's/[[:alpha:]]/-& /g'")"
            else
                complete_list="$(gmpy_cdirs_get_all_label)"
            fi
            ;;
        setdir)
            if [ "${cur:0:2}" = "--" ]; then
                complete_list="--$(echo "${setdir_options_list_full}" | sed 's/,/ --/g' | sed 's/://g')"
            elif [ "${cur:0:1}" = "-" ]; then
                complete_list="$(echo "${setdir_options_list}" | sed 's/://g' | sed 's/[[:alpha:]]/-& /g')"
            else
                local opts_cnt="$(( $(echo ${line} | wc -w) - $(echo "${line}" | sed -r 's/ -[[:alpha:]]+ / /g' | wc -w) ))"
                [ $(( ${COMP_CWORD} - ${opts_cnt} )) -eq 1 ] && complete_list="$(gmpy_cdirs_get_all_label)"
            fi
            ;;
        cd|cdir)
            case "${pre}" in
                "-l"|"--label")
                    complete_list="$(gmpy_cdirs_get_all_label)"
                    ;;
                "-n"|"--num")
                    complete_list="$(gmpy_cdirs_get_all_num)"
                    ;;
                "-p"|"--path")
                    complete_list=
                    ;;
                "-k"|"--key"|"-t"|"--tag"|"--default-key")
                    complete_list="$(gmpy_cdirs_get_all_key)"
                    ;;
                "--find-maxdepth")
                    complete_list=
                    ;;
                "-f"|"--find")
                    local key opt cnt find_maxdepth

                    # get key
                    cnt=0
                    for opt in ${COMP_WORDS[@]}
                    do
                        cnt=$(( ${cnt} + 1 ))
                        [ "${opt}" = "-k" \
                            -o "${opt}" = "-t" \
                            -o "${opt}" = "--key" \
                            -o "${opt}" = "--tag" ] && break
                    done
                    key="${COMP_WORDS[cnt]:-${gmpy_cdirs_default_key}}"

                    # get find_maxdepth
                    cnt=0
                    for opt in ${COMP_WORDS[@]}
                    do
                        cnt=$(( ${cnt} + 1 ))
                        [ "${opt}" = "--find-maxdepth" ] && break
                    done
                    find_maxdepth="${COMP_WORDS[cnt]}"

                    [ -n "${key}" ] && {
                        [ "${key}" = "${gmpy_cdirs_complete_key}" \
                            -a -n "${gmpy_cdirs_complete_dirs}" ] \
                            && complete_list="${gmpy_cdirs_complete_dirs}" \
                            || {
                                complete_list="$(gmpy_cdirs_get_all_dirs_from_key "${key}" "${find_maxdepth}")"
                                gmpy_cdirs_complete_dirs="${complete_list}"
                                gmpy_cdirs_complete_key="${key}"
                            }
                    }
                    ;;
                "-F"|"--Find")
                    local opt cnt find_maxdepth
                    # get find_maxdepth
                    cnt=0
                    for opt in ${COMP_WORDS[@]}
                    do
                        cnt=$(( ${cnt} + 1 ))
                        [ "${opt}" = "--find-maxdepth" ] && break
                    done
                    find_maxdepth="${COMP_WORDS[cnt]}"

                    [ -n "${gmpy_cdirs_complete_dirs}" -a -z "${gmpy_cdirs_complete_key}" ] \
                        && complete_list="${gmpy_cdirs_complete_dirs}" \
                        || {
                            complete_list="$(gmpy_cdirs_get_all_dirs_from_pwd "${find_maxdepth}")"
                            gmpy_cdirs_complete_dirs="${complete_list}"
                            unset gmpy_cdirs_complete_key
                        }
                    ;;
                *)
                    if [ "${cur:0:2}" = "--" ]; then
                        complete_list="--$(echo "${cdir_options_list_full}" | sed 's/,/ --/g' | sed 's/://g')"
                    elif [ "${cur:0:1}" = "-" ]; then
                        complete_list="$(echo "${cdir_options_list}" | sed 's/://g' | sed 's/[[:alpha:]]/-& /g')"
                    elif [ -n "${gmpy_cdirs_first_symbol}" -a "${cur:0:1}" = "${gmpy_cdirs_first_symbol}" ]; then
                        complete_list="$(gmpy_cdirs_get_all_label)"
                    elif [ -z "${gmpy_cdirs_first_symbol}" ]; then
                        complete_list="$(gmpy_cdirs_get_all_label)"
                    fi
                    ;;
            esac
        ;;
    esac
    COMPREPLY=($(compgen -W "${complete_list}" -- "${cur}"))
}

#================ key path ================#

#gmpy_cdirs_set_find_maxdepth <num>
gmpy_cdirs_set_find_maxdepth() {
    gmpy_cdirs_check_whether_num "$1" \
        && gmpy_cdirs_search_max_depth="$1" || return 1
}

#gmpy_cdirs_set_default_key <key>
gmpy_cdirs_set_default_key() {
    gmpy_cdirs_check_key "$1" \
        && gmpy_cdirs_default_key="$1" \
        || return 1
}

#gmpy_cdirs_get_all_key
gmpy_cdirs_get_all_key() {
    [ -n "${gmpy_cdirs_key}" ] \
        && echo "${gmpy_cdirs_key[@]}" \
            | sed 's/\;/\n/g' \
            | awk '{print $1}'
}

#gmpy_cdirs_get_path_from_key() <key>
gmpy_cdirs_get_path_from_key() {
    [ -n "${gmpy_cdirs_key}" ] \
        && echo "${gmpy_cdirs_key[@]}" \
            | sed 's/\;/\n/g' \
            | eval "awk '\$1~/^$1\$/{print \$3}'"
}

#gmpy_cdirs_find_dir_from_key <key> <dir> [find_maxdepth]
gmpy_cdirs_find_dir_from_key() {
    local cmd kpath find_maxdepth
    kpath="$(gmpy_cdirs_get_path_from_key "$1")"
    [ -z "${kpath}" ] && return 0

    find_maxdepth="${3:-${gmpy_cdirs_search_max_depth}}"
    gmpy_cdirs_check_whether_num "${find_maxdepth}" || unset find_maxdepth

    cmd="$([ -n "${find_maxdepth}" ] \
            && echo "-maxdepth ${find_maxdepth}")"
    cmd="find ${kpath} ${cmd} -name \"${2}\" -type d 2>/dev/null"
    echo $(eval "${cmd}")
}

#gmpy_cdirs_get_all_dirs_from_key <key> [find_maxdepth]
gmpy_cdirs_get_all_dirs_from_key() {
    local cmd kpath find_maxdepth
    kpath="$(gmpy_cdirs_get_path_from_key $1)"
    [ -z "${kpath}" ] && return 0

    find_maxdepth="${2:-${gmpy_cdirs_search_max_depth}}"
    gmpy_cdirs_check_whether_num "${find_maxdepth}" || unset find_maxdepth

    cmd="$([ -n "${find_maxdepth}" ] \
            && echo "-maxdepth ${find_maxdepth}")"
    cmd="find ${kpath} ${cmd} -type d 2>/dev/null | xargs -I {} basename {}"
    echo $(eval "${cmd}")
}

#gmpy_cdirs_check_key <key>
gmpy_cdirs_check_key() {
    [ -n "${gmpy_cdirs_key}" ] \
        && echo "${gmpy_cdirs_key[@]}" \
            | sed 's/\;/\n/g' \
            | awk '{print $1}' \
            | grep -w "${1}" &>/dev/null
}

#gmpy_cdirs_cd_find_process <dirname> <key> <find_maxdepth>
#gmpy_cdirs_cd_find_process <dirname> <find_maxdepth>
gmpy_cdirs_cd_find_process() {
    local f_result dirname
    if [ "$#" -gt "2" ]; then
        f_result=($(gmpy_cdirs_find_dir_from_key "$2" "$1" "$3"))
    else
        f_result=($(gmpy_cdirs_find_dir_from_pwd "$1" "$2"))
    fi
    local f_cnt=${#f_result[@]}

    if [ "${f_cnt}" -gt "1" ]; then
        local cnt=0
        local result
        echo "Which one do you want:"
        for result in ${f_result[@]}
        do
            echo "${cnt}: ${result}"
            cnt="$(( ${cnt} + 1 ))"
        done
        while true
        do
            read -p "Your choice: " cnt
            gmpy_cdirs_check_whether_num "${cnt}" || {
                echo -e "\033[31m${cnt}: Invaild Input - Not Num\033[0m"
                continue
            }
            [ "${cnt}" -ge "${f_cnt}" ] && {
                echo -e "\033[31m${cnt}: Invaild Input - Error Num\033[0m"
                continue
            }
            echo -e "\033[32m${f_result[cnt]}\033[0m"
            gmpy_cdirs_replace_cd "${f_result[cnt]}"
            break
        done
    elif [ "${f_cnt}" -eq "1" ]; then
        echo -e "\033[32m${f_result}\033[0m"
        gmpy_cdirs_replace_cd "${f_result}"
    else
        echo -e "\033[31mCan't find $1\033[0m"
    fi
    unset gmpy_cdirs_complete_dirs
    unset gmpy_cdirs_complete_key
}

#gmpy_cdirs_find_dir_from_pwd <dir> [find_maxdepth]
gmpy_cdirs_find_dir_from_pwd() {
    local cmd find_maxdepth

    find_maxdepth="${2:-${gmpy_cdirs_search_max_depth}}"
    gmpy_cdirs_check_whether_num "${find_maxdepth}" || unset find_maxdepth

    cmd="$([ -n "${find_maxdepth}" ] \
            && echo "-maxdepth ${find_maxdepth}")"
    cmd="find ${PWD} ${cmd} -name \"${1}\" -type d 2>/dev/null"
    echo $(eval "${cmd}")
}

#gmpy_cdirs_get_all_dirs_from_pwd [find_maxdepth]
gmpy_cdirs_get_all_dirs_from_pwd() {
    local cmd find_maxdepth

    find_maxdepth="${1:-${gmpy_cdirs_search_max_depth}}"
    gmpy_cdirs_check_whether_num "${find_maxdepth}" || unset find_maxdepth

    cmd="$([ -n "${find_maxdepth}" ] \
            && echo "-maxdepth ${find_maxdepth}")"
    cmd="find ${PWD} ${cmd} -type d 2>/dev/null | xargs -I {} basename {}"
    echo $(eval "${cmd}")
}

#gmpy_cdirs_check_whether_num <num>
gmpy_cdirs_check_whether_num() {
    echo "$1" | egrep "^[[:digit:]]+$" &>/dev/null
}

gmpy_cdirs_init $@