#!/bin/sh

esc() {
    case $1 in
        # vt100
        CUD)        printf '%s[%sB'     "$esc_char" "$2"      ;;
        CUP)        printf '%s[%s;%sH'  "$esc_char" "$2" "$3" ;;
        CUU)        printf '%s[%sA'     "$esc_char" "$2"      ;;
        DECAWM)     printf '%s[?7%s'    "$esc_char" "$2"      ;;
        DECRC)      printf '%s8'        "$esc_char"           ;;
        DECSC)      printf '%s7'        "$esc_char"           ;;
        DECSTBM)    printf '%s[%s;%sr'  "$esc_char" "$2" "$3" ;;
        ED[0-2])    printf '%s[%sJ'     "$esc_char" "${1#ED}" ;;
        EL[0-2])    printf '%s[%sK'     "$esc_char" "${1#EL}" ;;
        SGR)        printf '%s[%s;%sm'  "$esc_char" "$2" "$3" ;;
        IL)         printf '%s[%sL'     "$esc_char" "$2"      ;; # VT102

        # private (optional)
        DECTCEM)    printf '%s[?25%s'   "$esc_char" "$2"      ;; # VT520
        screen_alt) printf '%s[?1049%s' "$esc_char" "$2"      ;; # xterm
    esac
}

term_setup() {
    stty=$(stty -g)
    stty -icanon -echo
    esc DECAWM l
    esc DECTCEM l
    esc ED2
    esc DECSTBM 1 "$((LINES - 2))"
}

term_reset() {
    esc DECAWM h
    esc DECTCEM h
    esc ED2
    esc DECSTBM
    stty "$stty"
}

term_resize() {
    # false-positive, behavior intentional, globbing is disabled.
    # shellcheck disable=2046
    {
        set -f
        set +f -- $(stty size)
    }

    LINES=$1 COLUMNS=$2

    # space for status_line
    bottom=$((LINES - 2))
}

cmd_run() {
    esc screen_alt h
    esc DECTCEM h
    "$@"
    esc screen_alt l
    esc DECTCEM l
}

file_escape() {
    tmp=$1 safe=

    # loop over string char by char
    while :; do
        c=${tmp%"${tmp#?}"*}

        case $c in
            [[:print:]]) safe=$safe$c ;;
            '')          return ;;
            *)           safe=$safe\? ;;
        esac

        tmp=${tmp#?}
    done
}

hist_search() {
    hist=0 j=1

    for file do
        case ${PWD%%/}/$file in
            "$old_pwd")
                y=$j
                y2=$((j > bottom ? mid : j))
                cur=$file
            ;;
        esac

        j=$((j + 1))
    done
}

list_print() {
    esc ED2
    esc CUP

    i=1
    end=$((bottom + 1))
    mid=$((bottom / 4 < 5 ? 1 : bottom / 4))

    case $hist in
        1)
            hist_search "$@"
            shift "$((y >= bottom ? y - mid : 0))"
        ;;

        *)
            shift "$((y >= bottom ? y - bottom : 0))"
        ;;
    esac

    for file do
        file_escape "$file"

        case $i in
            "$y2") esc SGR 34 7
        esac

        case $((i - end)) in
            -*)
                printf %s "$safe"
                esc SGR
                esc CUD
                printf '\r'
            ;;
        esac

        i=$((i + 1))
    done

    esc CUP "$((y > bottom ? y2 : y))"
}

redraw() {
    list_print "$@"
    status_line "($y/$#) $PWD"
}

status_line() {
    esc DECSC
    esc CUP "$LINES"
    esc SGR 31 7
    printf '%-*s' "$COLUMNS" "$1"
    esc SGR
    esc DECRC
}

prompt() {
    esc DECSC
    esc CUP "$LINES"
    printf %s "$1"
    esc DECTCEM h
    esc EL0

    case $2 in
        r)
            stty icanon echo
            read -r ans ||:
            stty -icanon -echo
        ;;
    esac

    esc DECRC
    esc DECTCEM l
    status_line "($y/$#) $PWD"
}

print_line() {
    offset=$1

    case $offset in
        "$y") esc SGR 34 7
    esac

    shift "$offset"
    file_escape "$1"

    esc EL0
    printf %s "$safe"
    esc SGR
    printf '\r'

    case $offset in
        "$y") cur=$1
    esac
}

main() {
    set -e

    case $1 in
        -v)
            printf '%s 0.1\n' "${0##*/}"
            exit 0
        ;;
    esac

    esc_char=$(printf '\033')
    bs_char=$(printf '\177')

    cd "${1:-"$PWD"}"
    set -- *
    cur=$1

    term_resize
    term_setup

    trap 'term_reset'  EXIT INT
    trap 'term_resize; term_setup; y=1 y2=1; redraw "$@"' WINCH

    y=1 y2=1
    redraw "$@"

    while key=$(dd ibs=1 count=1 2>/dev/null); do
        case $key${esc:=0} in
            k?|A2) # ARROW UP
                case $y in
                    -*|0|1) ;;

                    *)
                        y=$((y - 1))

                        print_line "$((y + 1))" "$@"

                        case $y2 in
                            1) esc IL ;;
                            *) esc CUU; y2=$((y2 > 1 ? y2 - 1 : 1))
                        esac

                        print_line "$y" "$@"
                        status_line "($y/$#) $PWD"
                    ;;
                esac
            ;;

            j?|B2) # ARROW DOWN
                case $((y - $#)) in
                    -*)
                        y=$((y + 1))
                        y2=$((y2 + 1 < bottom ? y2 + 1 : bottom))

                        print_line "$((y - 1))" "$@"
                        printf '\n'
                        print_line "$y" "$@"
                        status_line "($y/$#) $PWD"
                    ;;
                esac
            ;;

            l?|C2|"$esc") # ARROW RIGHT
                if cd "$cur" >/dev/null 2>&1; then
                    set -- *
                    y=1 y2=1 cur=$1 search=0
                    redraw "$@"
                else
                    cmd_run "${SHFM_OPENER:="${EDITOR:=vi}"}" "$cur"
                fi
            ;;

            h?|D2|"$bs_char"?) # ARROW LEFT
                old_pwd=$PWD

                case $search in
                    1) search=0 ;;
                    *) cd .. || continue ;;
                esac

                set -- *
                y=1 y2=1 cur=$1 hist=1
                redraw "$@"
            ;;

            g?)
                y=1 y2=1 cur=$1
                redraw "$@"
            ;;

            G?)
                y=$#
                y2=$(($# < bottom ? $# : bottom))
                redraw "$@"
            ;;

            .?)
                case ${hidden:=1} in
                    1)
                        hidden=0
                        set -- .[!.]*
                    ;;

                    *)
                        hidden=1
                        set -- *
                    ;;
                esac

                y=1 y2=1 cur=$1
                redraw "$@"
            ;;

            :?)
                prompt "cd: " r
                cd "${ans:="$0"}" >/dev/null 2>&1|| continue
                set -- *
                y=1 y2=1 cur=$1
                redraw "$@"
            ;;

            /?)
                prompt / r
                set -- *"$ans"*
                y=1 y2=1 cur=$1 search=1
                redraw "$@"
                status_line "($y/$#) search $ans"
            ;;

            -?)
                cd "$OLDPWD" >/dev/null 2>&1|| continue
                set -- *
                y=1 y2=1 cur=$1
                redraw "$@"
            ;;

            \~?)
                cd || continue
                set -- *
                y=1 y2=1 cur=$1
                redraw "$@"
            ;;

            \!?)
                export SHFM_LEVEL
                SHFM_LEVEL=$((SHFM_LEVEL + 1))
                cmd_run "${SHELL:=/bin/sh}"
            ;;

            q?)
                exit 0
            ;;

            # handle keys which emit escape sequences
            "$esc_char"*) esc=1 ;;
                    '[1') esc=2 ;;
                       *) esc=0 ;;
        esac
    done
}

main "$@"
