#!/bin/sh

esc() {
    case $1 in
        # vt100 (IL is vt102)
        CUD)        printf '%s[%sB'     "$esc_c" "$2"      ;; # cursor down
        CUP)        printf '%s[%s;%sH'  "$esc_c" "$2" "$3" ;; # cursor home
        CUU)        printf '%s[%sA'     "$esc_c" "$2"      ;; # cursor up
        DECAWM)     printf '%s[?7%s'    "$esc_c" "$2"      ;; # line wrap
        DECRC)      printf '%s8'        "$esc_c"           ;; # cursor restore
        DECSC)      printf '%s7'        "$esc_c"           ;; # cursor save
        DECSTBM)    printf '%s[%s;%sr'  "$esc_c" "$2" "$3" ;; # scroll region
        ED[0-2])    printf '%s[%sJ'     "$esc_c" "${1#ED}" ;; # clear screen
        EL[0-2])    printf '%s[%sK'     "$esc_c" "${1#EL}" ;; # clear line
        IL)         printf '%s[%sL'     "$esc_c" "$2"      ;; # insert line
        SGR)        printf '%s[%s;%sm'  "$esc_c" "$2" "$3" ;; # colors

        # private (and optional)
        DECTCEM)    printf '%s[?25%s'   "$esc_c" "$2" ;; # cursor visible (vt520)
        screen_alt) printf '%s[?1049%s' "$esc_c" "$2" ;; # alternate buf  (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))
}

term_scroll_down() {
    case $((y - $#)) in
        -*)
            y=$((y + 1))
            y2=$((y2 + 1 < bottom ? y2 + 1 : bottom))

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

term_scroll_up() {
    case $y in
        -*|0|1) ;;

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

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

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

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

cmd_run() {
    esc screen_alt h
    esc DECTCEM h
    esc DECSTBM
    "$@"
    esc DECSTBM 1 "$((LINES - 2))"
    esc screen_alt l
    esc DECTCEM l
    esc CUP "$y2"
}

file_escape() {
    tmp=$1 safe=

    # loop over string char by char
    while c=${tmp%"${tmp#?}"}; do
        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
        case $i in
            "$y2") esc SGR 34 7
        esac

        case $((i - end)) in
            -*)
                line_format "$file"
                esc CUD
            ;;
        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"
}

line_print() {
    offset=$1

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

    shift "$offset"

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

    line_format "$1"
}

line_format() {
    file_escape "$1"
    esc EL0
    printf %s "$safe"
    test -d "$1" && printf /
    esc SGR
    printf '\r'
}

main() {
    set -e

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

    esc_c=$(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)
                term_scroll_up "$@"
            ;;

            j?|B2)
                term_scroll_down "$@"
            ;;

            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?)
                case $y in
                    1) continue
                esac

                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_c"*) esc=1 ;;
                 '[1') esc=2 ;;
                    *) esc=0 ;;
        esac
    done
}

main "$@"
