#!/bin/sh

term_setup() {
    stty=$(stty -g)
    stty -icanon -echo

    printf '\033[?1049h\033[?7l\033[?25l\033[2J\033[1;%sr' \
        "$((LINES - 2))"
}

term_reset() {
    stty "$stty"

    printf '\033[?7h\033[?25h\033[2J\033[;r\033[?1049l'
}

term_resize() {
    set -f
    set +f -- $(stty size)

    LINES=$1 COLUMNS=$2

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

hist_search() {
    hist=0 j=1

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

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

list_print() {
    printf '\033[2J\033[H'

    i=1
    end=$((bottom + 1))

    case $hist in
        1) hist_search "$@"
    esac

    case $((y - bottom)) in
        [0-9]*) shift "$((y - bottom))"
    esac

    for file do
        case $i in
            "$y2") printf '\033[34;7m'
        esac

        case $((i - end)) in
            -*) printf '%s\033[m\033[B\r' "$file"
        esac

        i=$((i + 1))
    done

    printf '\033[%sH' "$((y > bottom ? bottom : y))"
}

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

status_line() {
    printf '\0337\033[%sH\033[31;7m%-*s\033[m\0338' \
        "$LINES" "$COLUMNS" "$1"
}

prompt() {
    printf '\0337\033[%sH%s\033[?25h\033[K' "$LINES" "$1"

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

    printf '\0338\033[?25l'
    status_line "($y/$#) $PWD"
}

print_line() {
    offset=$1

    case $offset in
        "$y") printf '\033[34;7m'
    esac

    shift "$offset"
    printf '\033[K%s\033[m\r' "$1"

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

main() {
    set -e

    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)
                                printf '\033[L'
                            ;;

                            *)
                                printf '\033[A'
                                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
                else
                    printf '\033[?25h'
                    "${SHFM_OPENER:="${EDITOR:=vi}"}" "$cur"
                    printf '\033[?25l'
                fi

                redraw "$@"
            ;;

            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))

                term_reset
                "${SHELL:=/bin/sh}"
                term_setup
                redraw "$@"
            ;;

            q?)
                exit 0
            ;;

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

main "$@"
