#!/usr/bin/env zsh

# Configuration variables:
# - ZSHSELECT_BOLD
# - ZSHSELECT_COLOR_PAIR
# - ZSHSELECT_BORDER
# - ZSHSELECT_ACTIVE_TEXT
# - ZSHSELECT_START_IN_SEARCH_MODE

emulate -LR zsh
setopt extendedglob

# Should the list (text, borders) be drawn in bold
# Value 1 is the default
local bold=1
[[ "$ZSHSELECT_BOLD" = <-> ]] && bold="$ZSHSELECT_BOLD"

# Main color pair (foreground/background)
local colorpair="white/black"
[[ "$ZSHSELECT_COLOR_PAIR" = [[:alnum:]]##/[[:alnum:]]## ]] && colorpair="$ZSHSELECT_COLOR_PAIR"

# Should draw the border?
local border=0
[[ "$ZSHSELECT_BORDER" = <-> ]] && border="$ZSHSELECT_BORDER"

# Combinations of colors to try out with Ctrl-T and Ctrl-G
# The last number is the bold option, 0 or 1
local -a themes
themes=( "white/black/0" "white/black/1" "green/black/0" "green/black/1" "white/blue/0" "white/blue/1"
         "magenta/black/0" "magenta/black/1" )

# If 1, pressing enter when in search mode will not only
# leave the mode, but also do selection and leave tool
local NLIST_INSTANT_SELECT=0

# How should be current element of the list drawn. Possible values: reverse, underline.
# On Linux virtual terminal this will be enforced to reverse (because of poor
# underline support on that terminal). The same for screen/tmux. You can fix
# those terminals, see: http://psprint.github.io/Fixing-TMux-Screen-and-Linuxs-VT/
local active_text=reverse
[[ "$ZSHSELECT_ACTIVE_TEXT" = (reverse|underline) ]] && active_text="$ZSHSELECT_ACTIVE_TEXT"

NLIST_START_IN_SEARCH_MODE=1
[[ "$ZSHSELECT_START_IN_SEARCH_MODE" = <-> ]] && NLIST_START_IN_SEARCH_MODE="$ZSHSELECT_START_IN_SEARCH_MODE"

#######################################################################################

_sellist_has_terminfo=0

setopt extendedglob typesetsilent noshortloops
zmodload zsh/curses
zmodload zsh/terminfo 2>/dev/null && _sellist_has_terminfo=1

local IFS="
"

NLIST_REMEMBER_STATE=0

#######################################################################################

_sellist_compute_first_to_show_idx() {
    from_what_idx_list_is_shown=0+((current_idx-1)/page_height)*page_height+1
}

_sellist_update_from_keywords() {
    keywordisfresh="1"
    if [ "$nkeywords" -gt 0 ]; then
        curkeyword=$(( (curkeyword+1) % (nkeywords+1) ))
        if [ "$curkeyword" -eq "0" ]; then
            buffer=""
        else
            buffer="${keywords[curkeyword]}"
        fi
    fi
}

_sellist_iterate_theme() {
    themeisfresh="1"
    if [ "$1" = "1" ]; then
        curtheme=$(( (curtheme+1) % (nthemes+1) ))
    else
        curtheme=curtheme-1
        [ "$curtheme" -lt 0 ] && curtheme=nthemes
    fi

    if [ "$nthemes" -gt 0 ]; then
        local theme=${themes[curtheme]}
        [ "$curtheme" -eq "0" ] && theme="$backuptheme"

        colorpair="${theme%/*}"
        bold="${theme##*/}"
        background="${colorpair#*/}"
        zcurses bg main "$colorpair"
        zcurses bg inner "$colorpair"
    fi
}

_sellist_rotate_buffer() {
    setopt localoptions noglob

    local -a words
    words=( ${(s: :)buffer} )
    words=( ${words[-1]} ${words[1,-2]} )

    local space=""
    [ "${buffer[-1]}" = " " ] && space=" "

    buffer="${(j: :)words}$space"
}

sel-list-input() {
    typeset -ga reply
    reply=( -1 '' )
    integer current_idx="$1"
    integer from_what_idx_list_is_shown="$2"
    integer page_height="$3"
    integer page_width="$4"
    integer last_element="$5"
    integer hscroll="$6"
    local key="$7"
    integer search="$8"
    local buffer="$9"
    integer uniq_mode="$10"
    integer f_mode="$11"

    #
    # Listening for input
    #

    if [ "$search" = "0" ]; then

    case "$key" in
        (UP|k|$'\C-P')
            # Are there any elements before the current one?
            [ "$current_idx" -gt 1 ] && current_idx=current_idx-1;
            _sellist_compute_first_to_show_idx
            ;;
        (DOWN|j|$'\C-N')
            # Are there any elements after the current one?
            [ "$current_idx" -lt "$last_element" ] && current_idx=current_idx+1;
            _sellist_compute_first_to_show_idx
            ;;
        (PPAGE|$'\b'|$'\C-?'|BACKSPACE)
            current_idx=current_idx-page_height
            [ "$current_idx" -lt 1 ] && current_idx=1;
            _sellist_compute_first_to_show_idx
            ;;
        (NPAGE|" ")
            current_idx=current_idx+page_height
            [ "$current_idx" -gt "$last_element" ] && current_idx=last_element;
            _sellist_compute_first_to_show_idx
            ;;
        ($'\C-U')
            current_idx=current_idx-page_height/2
            [ "$current_idx" -lt 1 ] && current_idx=1;
            _sellist_compute_first_to_show_idx
            ;;
        ($'\C-D')
            current_idx=current_idx+page_height/2
            [ "$current_idx" -gt "$last_element" ] && current_idx=last_element;
            _sellist_compute_first_to_show_idx
            ;;
        (HOME|g)
            current_idx=1
            _sellist_compute_first_to_show_idx
            ;;
        (END|G)
            current_idx=last_element
            _sellist_compute_first_to_show_idx
            ;;
        ($'\n'|ENTER)
            # Is that element selectable?
            # Check for this only when there is no search
            if [[ "$NLIST_SEARCH_BUFFER" != "" || "$NLIST_IS_UNIQ_MODE" -eq 1 ||
                ${NLIST_NONSELECTABLE_ELEMENTS[(r)$current_idx]} != $current_idx ]]
            then
                # Save current element in the result variable
                reply=( $current_idx "SELECT" )
            fi
            ;;
        (H|'?')
            # This event needs to be enabled
            if [[ "${NLIST_ENABLED_EVENTS[(r)HELP]}" = "HELP" ]]; then
                reply=( -1 "HELP" )
            fi
            ;;
        (F1)
            # This event needs to be enabled
            if [[ "${NLIST_ENABLED_EVENTS[(r)F1]}" = "F1" ]]; then
                reply=( -1 "$key" )
            fi
            ;;
        (F4|F5|F6|F7|F8|F9|F10|DC)
            # ignore; F2, F3 are used below
            ;;
        (q)
            reply=( -1 "QUIT" )
            ;;
        (/)
            search=1
            _sellist_cursor_visibility 1
            ;;
        ($'\t')
            reply=( $current_idx "LEAVE" )
            ;;
        ($'\C-L')
            reply=( -1 "REDRAW" )
            ;;
        (\])
            [[ "${(t)NLIST_HOP_INDEXES}" = "array" || "${(t)NLIST_HOP_INDEXES}" = "array-local" ]] &&
            [ -z "$NLIST_SEARCH_BUFFER" ] && [ "$NLIST_IS_UNIQ_MODE" -eq 0 ] &&
            for idx in "${(n)NLIST_HOP_INDEXES[@]}"; do
                if [ "$idx" -gt "$current_idx" ]; then
                    current_idx=$idx
                    _sellist_compute_first_to_show_idx
                    break
                fi
            done
            ;;
        (\[)
            [[ "${(t)NLIST_HOP_INDEXES}" = "array" || "${(t)NLIST_HOP_INDEXES}" = "array-local" ]] &&
            [ -z "$NLIST_SEARCH_BUFFER" ] && [ "$NLIST_IS_UNIQ_MODE" -eq 0 ] &&
            for idx in "${(nO)NLIST_HOP_INDEXES[@]}"; do
                if [ "$idx" -lt "$current_idx" ]; then
                    current_idx=$idx
                    _sellist_compute_first_to_show_idx
                    break
                fi
            done
            ;;
        ('<'|'{'|LEFT|'h')
            hscroll=hscroll-7
            [ "$hscroll" -lt 0 ] && hscroll=0
            ;;
        ('>'|'}'|RIGHT|'l')
            hscroll+=7
            ;;
        ($'\E')
            buffer=""
            ;;
        (F3)
            if [ "$search" = "1" ]; then
                search=0
                _sellist_cursor_visibility 0
            else
                search=1
                _sellist_cursor_visibility 1
            fi
            ;;
        (o|$'\C-O')
            uniq_mode=1-uniq_mode
            ;;
        (f|$'\C-F')
            (( f_mode=(f_mode+1) % 3 ))
            ;;
        ($'\x1F'|F2|$'\C-X')
            search=1
            _sellist_cursor_visibility 1
            _sellist_update_from_keywords
            ;;
        ($'\C-T')
            _sellist_iterate_theme 1
            ;;
        ($'\C-G')
            _sellist_iterate_theme 0
            ;;
        ($'\C-E'|e)
            # This event needs to be enabled
            if [[ "${NLIST_ENABLED_EVENTS[(r)EDIT]}" = "EDIT" ]]; then
                reply=( -1 "EDIT" )
            fi
            ;;
        ($'\C-A')
            _sellist_rotate_buffer
            ;;
        (*)
            ;;
    esac

    else

    case "$key" in
        ($'\n'|ENTER)
            if [ "$NLIST_INSTANT_SELECT" = "1" ]; then
                if [[ "$NLIST_SEARCH_BUFFER" != "" || "$NLIST_IS_UNIQ_MODE" -eq 1 ||
                    ${NLIST_NONSELECTABLE_ELEMENTS[(r)$current_idx]} != $current_idx ]]
                then
                    reply=( $current_idx "SELECT" )
                fi
            else
                search=0
                _sellist_cursor_visibility 0
            fi
            ;;
        ($'\C-L')
            reply=( -1 "REDRAW" )
            ;;

        #
        # Slightly limited navigation
        #

        (UP|$'\C-P')
            [ "$current_idx" -gt 1 ] && current_idx=current_idx-1;
            _sellist_compute_first_to_show_idx
            ;;
        (DOWN|$'\C-N')
            [ "$current_idx" -lt "$last_element" ] && current_idx=current_idx+1;
            _sellist_compute_first_to_show_idx
            ;;
        (PPAGE)
            current_idx=current_idx-page_height
            [ "$current_idx" -lt 1 ] && current_idx=1;
            _sellist_compute_first_to_show_idx
            ;;
        (NPAGE)
            current_idx=current_idx+page_height
            [ "$current_idx" -gt "$last_element" ] && current_idx=last_element;
            _sellist_compute_first_to_show_idx
            ;;
        ($'\C-U')
            current_idx=current_idx-page_height/2
            [ "$current_idx" -lt 1 ] && current_idx=1;
            _sellist_compute_first_to_show_idx
            ;;
        ($'\C-D')
            current_idx=current_idx+page_height/2
            [ "$current_idx" -gt "$last_element" ] && current_idx=last_element;
            _sellist_compute_first_to_show_idx
            ;;
        (HOME)
            current_idx=1
            _sellist_compute_first_to_show_idx
            ;;
        (END)
            current_idx=last_element
            _sellist_compute_first_to_show_idx
            ;;
        (LEFT)
            hscroll=hscroll-7
            [ "$hscroll" -lt 0 ] && hscroll=0
            ;;
        (RIGHT)
            hscroll+=7
            ;;
        (F1)
            # This event needs to be enabled
            if [[ "${NLIST_ENABLED_EVENTS[(r)F1]}" = "F1" ]]; then
                reply=( -1 "$key" )
            fi
            ;;
        (F4|F5|F6|F7|F8|F9|F10|DC)
            # ignore; F2, F3 are used below
            ;;

        #
        # The input
        #

        ($'\b'|$'\C-?'|BACKSPACE)
            buffer="${buffer%?}"
            ;;
        ($'\C-W')
            [ "$buffer" = "${buffer% *}" ] && buffer="" || buffer="${buffer% *}"
            ;;
        ($'\C-K')
            buffer=""
            ;;
        ($'\E')
            buffer=""
            search=0
            _sellist_cursor_visibility 0
            ;;
        (F3)
            if [ "$search" = "1" ]; then
                search=0
                _sellist_cursor_visibility 0
            else
                search=1
                _sellist_cursor_visibility 1
            fi
            ;;
        ($'\C-O')
            uniq_mode=1-uniq_mode
            ;;
        ($'\C-F')
            (( f_mode=(f_mode+1) % 3 ))
            ;;
        ($'\x1F'|F2|$'\C-X')
            _sellist_update_from_keywords
            ;;
        ($'\C-T')
            _sellist_iterate_theme 1
            ;;
        ($'\C-G')
            _sellist_iterate_theme 0
            ;;
        ($'\C-E')
            # This event needs to be enabled
            if [[ "${NLIST_ENABLED_EVENTS[(r)EDIT]}" = "EDIT" ]]; then
                reply=( -1 "EDIT" )
            fi
            ;;
        ($'\C-A')
            _sellist_rotate_buffer
            ;;
        (*)
            if [[ $#key == 1 && $((#key)) -lt 31 ]]; then
                # ignore all other control keys
            else
                buffer+="$key"
            fi
            ;;
    esac

    fi

    reply[3]="$current_idx"
    reply[4]="$from_what_idx_list_is_shown"
    reply[5]="$hscroll"
    reply[6]="$search"
    reply[7]="$buffer"
    reply[8]="$uniq_mode"
    reply[9]="$f_mode"

}

#######################################################################################

_sellist_print_with_ansi() {
    local win="$1" text="$2" out col chunk Xout
    integer text_offset="$3" max_text_len="$4" text_len=0 no_match=0 nochunk_text_len to_skip_from_chunk to_chop_off_from_chunk before_len

    # 1 - non-escaped text, 2 - first number in the escaped text, with ;
    # 3 - second number, 4 - text after whole escape text

    typeset -a c
    c=( black red green yellow blue magenta cyan white )

    while [[ -n "$text" && "$no_match" -eq 0 ]]; do
        if [[ "$text" = (#b)([^$'\x1b']#)$'\x1b'\[([0-9](#c0,2))(#B)(\;|)(#b)([0-9](#c0,2))m(*) ]]; then
            # Text for further processing
            text="$match[4]"
            # Text chunk to output now
            out="$match[1]"
            # Save color
            col="$match[2]"
            (( match[3] >= 30 && match[3] <= 37 )) && col="$match[3]"
        else
            out="$text"
            no_match=1
        fi

        if [ -n "$out" ]; then
################ Expand tabs ################
            chunk="$out"
            before_len="$text_len"
            Xout=""

            while [ -n "$chunk" ]; do
                [[ "$chunk" = (#b)([^$'\t']#)$'\t'(*) ]] && {
                    (( all_text_len=((before_len+${#match[1]})/8+1)*8 ))

                    Xout+="${(r:all_text_len-before_len:: :)match[1]}"

                    before_len+=all_text_len-before_len
                    chunk="$match[2]"
                } || {
                    Xout+="$chunk"
                    break
                }
            done
#############################################

            # Input text length without the current chunk
            nochunk_text_len=text_len
            # Input text length up to current chunk
            text_len+="$#Xout"

            # Should start displaying with this chunk?
            # I.e. stop skipping left part of the input text?
            if (( text_len > text_offset )); then
                to_skip_from_chunk=text_offset-nochunk_text_len

                # LEFT - is chunk off the left skip boundary? +1 for 1-based index in string
                (( to_skip_from_chunk > 0 )) && Xout="${Xout[to_skip_from_chunk+1,-1]}"

                # RIGHT - is text off the screen?
                if (( text_len-text_offset > max_text_len )); then
                    to_chop_off_from_chunk=0+(text_len-text_offset)-max_text_len
                    Xout="${Xout[1,-to_chop_off_from_chunk-1]}"
                fi
                
                [ -n "$Xout" ] && zcurses string "$win" "$Xout"
            fi
        fi

        if (( no_match == 0 )); then
            if (( col >= 30 && col <= 37 )); then
                zcurses attr "$win" $c[col-29]/"$background"
            elif [[ "$col" -eq 0 ]]; then
                zcurses attr "$win" "$colorpair"
            fi
        fi
    done
}

sel-list-draw() {
    integer highlight="$1"
    integer page_height="$2"
    integer page_width="$3"
    local y_offset="$4"
    local x_offset="$5"
    local text_offset="$6"
    local win="$7"
    shift 7
    integer max_text_len=page_width-x_offset

    [[ "$bold" = "0" || "$bold" = "-bold" ]] && bold="-bold" || bold="+bold"
    [[ "$active_text" = "underline" || "$active_text" = "reverse" ]] || local active_text="reverse"
    # Linux has ncv 18, screen* has ncv 3 - underline won't work properly
    (( ${terminfo[ncv]:-0} & 2 )) && active_text="reverse"
    # FreeBSD uses TERM=xterm for newcons but doesn't actually support underline
    [[ "$TERM" = "xterm" && -z "$DISPLAY" ]] && active_text="reverse"

    integer max_idx=page_height
    integer end_idx=max_idx
    [ "$end_idx" -gt "$#" ] && end_idx="$#"
    integer y=y_offset

    zcurses attr "$win" "$bold" "$colorpair"

    integer i text_len
    local text
    for (( i=1; i<=end_idx; i++ )); do
        zcurses move "$win" $y "$x_offset"

        [ "$i" = "$highlight" ] && zcurses attr "$win" +"$active_text"
        _sellist_print_with_ansi "$win" "$@[i]" "$text_offset" "$max_text_len"
        zcurses clear "$win" eol
        [ "$i" = "$highlight" ] && zcurses attr "$win" -"$active_text"

        y+=1
    done

    if [ "$end_idx" -lt "$max_idx" ]; then
        zcurses move "$win" $y "$x_offset"
        zcurses clear "$win" eol
    fi

    zcurses attr "$win" white/black
}

#######################################################################################

# Cleanup before any exit
_sellist_exit() {
    setopt localoptions
    setopt extendedglob

    [[ "$REPLY" = -(#c0,1)[0-9]## || "$REPLY" = F<-> || "$REPLY" = "EDIT" || "$REPLY" = "HELP" ]] || REPLY="-1"
    zcurses 2>/dev/null delwin inner
    zcurses 2>/dev/null delwin main
    zcurses 2>/dev/null refresh
    zcurses end
    _sellist_alternate_screen 0
    _sellist_cursor_visibility 1
    unset _sellist_has_terminfo
}

# Outputs a message in the bottom of the screen
_sellist_status_msg() {
    # -1 for border, -1 for 0-based indexing
    zcurses move main $(( term_height - 1 - 1 )) 2
    zcurses clear main eol
    zcurses string main "$1"
    #status_msg_strlen is localized in caller
    status_msg_strlen=$#1
}

# Prefer tput, then module terminfo
_sellist_cursor_visibility() {
    # NOP, because Ctrl-C will cause problems
    return

    if type tput 2>/dev/null 1>&2; then
        [ "$1" = "1" ] && { tput cvvis; tput cnorm }
        [ "$1" = "0" ] && tput civis
    elif [ "$_sellist_has_terminfo" = "1" ]; then
        [ "$1" = "1" ] && { [ -n $terminfo[cvvis] ] && echo -n $terminfo[cvvis];
                           [ -n $terminfo[cnorm] ] && echo -n $terminfo[cnorm] }
        [ "$1" = "0" ] && [ -n $terminfo[civis] ] && echo -n $terminfo[civis]
    fi 
}

# Reason for this function is that on some systems
# smcup and rmcup are not knowing why left empty
_sellist_alternate_screen() {
    [ "$_sellist_has_terminfo" -ne "1" ] && return
    [[ "$1" = "1" && -n "$terminfo[smcup]" ]] && return
    [[ "$1" = "0" && -n "$terminfo[rmcup]" ]] && return

    case "$TERM" in
        *rxvt*)
            [ "$1" = "1" ] && echo -n $'\x1b7\x1b[?47h'
            [ "$1" = "0" ] && echo -n $'\x1b[2J\x1b[?47l\x1b8'
            ;;
        *)
            [ "$1" = "1" ] && echo -n $'\x1b[?1049h'
            [ "$1" = "0" ] && echo -n $'\x1b[?1049l'
            # just to remember two other that work: $'\x1b7\x1b[r\x1b[?47h', $'\x1b[?47l\x1b8'
            ;;
    esac
}

_sellist_compute_user_vars_difference() {
        if [[ "${(t)NLIST_NONSELECTABLE_ELEMENTS}" != "array" &&
                "${(t)NLIST_NONSELECTABLE_ELEMENTS}" != "array-local" ]]
        then
            last_element_difference=0
            current_difference=0
        else
            last_element_difference=$#NLIST_NONSELECTABLE_ELEMENTS
            current_difference=0
            local idx
            for idx in "${(n)NLIST_NONSELECTABLE_ELEMENTS[@]}"; do
                [ "$idx" -le "$NLIST_CURRENT_IDX" ] && current_difference+=1 || break
            done
        fi
}

# List was processed, check if variables aren't off range
_sellist_verify_vars() {
    [ "$NLIST_CURRENT_IDX" -gt "$last_element" ] && NLIST_CURRENT_IDX="$last_element"
    [[ "$NLIST_CURRENT_IDX" -eq 0 && "$last_element" -ne 0 ]] && NLIST_CURRENT_IDX=1
    (( NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=0+((NLIST_CURRENT_IDX-1)/page_height)*page_height+1 ))
}

# Compute the variables which are shown to the user
_sellist_setup_user_vars() {
    if [ "$1" = "1" ]; then
        # Basic values when there are no non-selectables
        NLIST_USER_CURRENT_IDX="$NLIST_CURRENT_IDX"
        NLIST_USER_LAST_ELEMENT="$last_element"
    else
        _sellist_compute_user_vars_difference
        NLIST_USER_CURRENT_IDX=$(( NLIST_CURRENT_IDX - current_difference ))
        NLIST_USER_LAST_ELEMENT=$(( last_element - last_element_difference ))
    fi
}

_sellist_colorify_disp_list() {
    local col=$'\x1b[00;34m' reset=$'\x1b[0m'
    [ -n "$NLIST_COLORING_COLOR" ] && col="$NLIST_COLORING_COLOR"
    [ -n "$NLIST_COLORING_END_COLOR" ] && reset="$NLIST_COLORING_END_COLOR"

    if [ "$NLIST_COLORING_MATCH_MULTIPLE" -eq 1 ]; then
        disp_list=( "${(@)disp_list//(#mi)$~NLIST_COLORING_PATTERN/$col${MATCH}$reset}" )
    else
        disp_list=( "${(@)disp_list/(#mi)$~NLIST_COLORING_PATTERN/$col${MATCH}$reset}" )
    fi
}

#
# Main sel-list code
#
sel-list() {

#trap "REPLY=-2; reply=(); return" TERM INT QUIT
trap "_sellist_exit" EXIT

# Check if there is proper input
if [ "$#" -lt 1 ]; then
    echo "Usage: sel-list element_1 ..."
    return 1
fi

REPLY="-1"
typeset -ga reply
reply=()

integer term_height="$LINES"
integer term_width="$COLUMNS"
if [[ "$term_height" -lt 1 || "$term_width" -lt 1 ]]; then
    local stty_out=$( stty size )
    term_height="${stty_out% *}"
    term_width="${stty_out#* }"
fi
integer inner_height=term_height-3
integer inner_width=term_width-3
integer page_height=inner_height
integer page_width=inner_width

typeset -a list disp_list
integer last_element=$#
local action
local final_key
integer selection
integer last_element_difference=0
integer current_difference=0
local prev_search_buffer=""
integer prev_uniq_mode=0
integer prev_start_idx=-1
local MBEGIN MEND MATCH mbegin mend match

# Iteration over predefined keywords
integer curkeyword nkeywords
local keywordisfresh="0"
if [[ "${(t)keywords}" != *array* ]]; then
    local -a keywords
    keywords=()
fi
curkeyword=0
nkeywords=${#keywords}

# Iteration over themes
integer curtheme nthemes
local themeisfresh="0"
if [[ "${(t)themes}" != *array* ]]; then
    local -a themes
    themes=()
fi
curtheme=0
nthemes=${#themes}

# Ability to remember the list between calls
if [[ -z "$NLIST_REMEMBER_STATE" || "$NLIST_REMEMBER_STATE" -eq 0 || "$NLIST_REMEMBER_STATE" -eq 2 ]]; then
    NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN=1
    NLIST_CURRENT_IDX=1
    NLIST_IS_SEARCH_MODE=0
    NLIST_SEARCH_BUFFER=""
    NLIST_TEXT_OFFSET=0
    NLIST_IS_UNIQ_MODE=0
    NLIST_IS_F_MODE=0

    # Zero - because it isn't known, unless we
    # confirm that first element is selectable
    NLIST_USER_CURRENT_IDX=0
    [[ ${NLIST_NONSELECTABLE_ELEMENTS[(r)1]} != 1 ]] && NLIST_USER_CURRENT_IDX=1
    NLIST_USER_LAST_ELEMENT=$(( last_element - $#NLIST_NONSELECTABLE_ELEMENTS ))

    # 2 is init once, then remember
    [ "$NLIST_REMEMBER_STATE" -eq 2 ] && NLIST_REMEMBER_STATE=1
fi

if [ "$NLIST_START_IN_SEARCH_MODE" -eq 1 ]; then
    NLIST_START_IN_SEARCH_MODE=0
    NLIST_IS_SEARCH_MODE=1
fi

if [ -n "$NLIST_SET_SEARCH_TO" ]; then
    NLIST_SEARCH_BUFFER="$NLIST_SET_SEARCH_TO"
    NLIST_SET_SEARCH_TO=""
fi

if [ "$NLIST_START_IN_UNIQ_MODE" -eq 1 ]; then
    NLIST_START_IN_UNIQ_MODE=0
    NLIST_IS_UNIQ_MODE=1
fi

_sellist_alternate_screen 1
zcurses init
zcurses delwin main 2>/dev/null
zcurses delwin inner 2>/dev/null
zcurses addwin main "$term_height" "$term_width" 0 0
zcurses addwin inner "$inner_height" "$inner_width" 1 2
# From sel-list.conf
[ "$colorpair" = "" ] && colorpair="white/black"
[ "$border" = "0" ] || border="1"
local background="${colorpair#*/}"
local backuptheme="$colorpair/$bold"
zcurses bg main "$colorpair"
zcurses bg inner "$colorpair"
if [ "$NLIST_IS_SEARCH_MODE" -ne 1 ]; then
    _sellist_cursor_visibility 0
fi

zcurses refresh

#
# Listening for input
#

local key keypad

# Clear input buffer
zcurses timeout main 0
zcurses input main key keypad
zcurses timeout main -1
key=""
keypad=""

# This loop makes script faster on some Zsh's (e.g. 5.0.8)
repeat 1; do
    list=( "$@" )
done

last_element="$#list"

while (( 1 )); do
    # Do searching (filtering with string)
    if [ -n "$NLIST_SEARCH_BUFFER" ]; then
        # Compute new list?
        if [[ "$NLIST_SEARCH_BUFFER" != "$prev_search_buffer" || "$NLIST_IS_UNIQ_MODE" -ne "$prev_uniq_mode"
                || "$NLIST_IS_F_MODE" -ne "$prev_f_mode" ]]
        then
            prev_search_buffer="$NLIST_SEARCH_BUFFER"
            prev_uniq_mode="$NLIST_IS_UNIQ_MODE"
            prev_f_mode="$NLIST_IS_F_MODE"
            # regenerating list -> regenerating disp_list
            prev_start_idx=-1

            # Take all elements, including duplicates and non-selectables
            typeset +U list
            repeat 1; do
                list=( "$@" )
            done

            # Remove non-selectable elements
            [ "$#NLIST_NONSELECTABLE_ELEMENTS" -gt 0 ] && for i in "${(nO)NLIST_NONSELECTABLE_ELEMENTS[@]}"; do
                if [[ "$i" = <-> ]]; then
                    list[$i]=()
                fi
            done

            # Remove duplicates
            [ "$NLIST_IS_UNIQ_MODE" -eq 1 ] && typeset -U list

            last_element="$#list"

            # Next do the filtering
            local search_buffer="${NLIST_SEARCH_BUFFER%% ##}"
            search_buffer="${search_buffer## ##}"
            search_buffer="${search_buffer//(#m)[][*?|#~^()><\\]/\\$MATCH}"
            local search_pattern=""
            local colsearch_pattern=""
            if [ -n "$search_buffer" ]; then
                # The repeat will make the matching work on a fresh heap
                repeat 1; do
                    if [ "$NLIST_IS_F_MODE" -eq "1" ]; then
                        search_pattern="${search_buffer// ##/*~^(#a1)*}"
                        colsearch_pattern="${search_buffer// ##/|(#a1)}"
                        list=( "${(@M)list:#(#ia1)*$~search_pattern*}" )
                    elif [ "$NLIST_IS_F_MODE" -eq "2" ]; then
                        search_pattern="${search_buffer// ##/*~^(#a2)*}"
                        colsearch_pattern="${search_buffer// ##/|(#a2)}"
                        list=( "${(@M)list:#(#ia2)*$~search_pattern*}" )
                    else
                        # Patterns will be *foo*~^*bar* and (foo|bar)
                        search_pattern="${search_buffer// ##/*~^*}"
                        colsearch_pattern="${search_buffer// ##/|}"
                        list=( "${(@M)list:#(#i)*$~search_pattern*}" )
                    fi
                done

                last_element="$#list"
            fi

            # Called after processing list
            _sellist_verify_vars
        fi

        _sellist_setup_user_vars 1

        integer end_idx=$(( NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN + page_height - 1 ))
        [ "$end_idx" -gt "$last_element" ] && end_idx=last_element

        if [ "$prev_start_idx" -ne "$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN" ]; then
            prev_start_idx="$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN"
            disp_list=( "${(@)list[NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN, end_idx]}" )

            if [ -n "$colsearch_pattern" ]; then
                local red=$'\x1b[00;31m' reset=$'\x1b[00;00m'
                # The repeat will make the matching work on a fresh heap
                repeat 1; do
                    if [ "$NLIST_IS_F_MODE" -eq "1" ]; then
                        disp_list=( "${(@)disp_list//(#mia1)($~colsearch_pattern)/$red${MATCH}$reset}" )
                    elif [ "$NLIST_IS_F_MODE" -eq "2" ]; then
                        disp_list=( "${(@)disp_list//(#mia2)($~colsearch_pattern)/$red${MATCH}$reset}" )
                    else
                        disp_list=( "${(@)disp_list//(#mi)($~colsearch_pattern)/$red${MATCH}$reset}" )
                    fi
                done
            fi

            # We have display list, lets replace newlines with "\n" when needed (1/2)
            [ "$NLIST_REPLACE_NEWLINES" -eq 1 ] && disp_list=( "${(@)disp_list//$'\n'/\\n}" )
        fi

        # Output colored list
        zcurses clear inner
        sel-list-draw "$(( (NLIST_CURRENT_IDX-1) % page_height + 1 ))" \
            "$page_height" "$page_width" 0 0 "$NLIST_TEXT_OFFSET" inner \
            "$disp_list[@]"
    else
        # There is no search, but there was in previous loop
        # OR
        # Uniq mode was entered or left out
        # -> compute new list
        if [[ -n "$prev_search_buffer" || "$NLIST_IS_UNIQ_MODE" -ne "$prev_uniq_mode" ]]; then
            prev_search_buffer=""
            prev_uniq_mode="$NLIST_IS_UNIQ_MODE"
            # regenerating list -> regenerating disp_list
            prev_start_idx=-1

            # Take all elements, including duplicates and non-selectables
            typeset +U list
            repeat 1; do
                list=( "$@" )
            done

            # Remove non-selectable elements only when in uniq mode
            [ "$NLIST_IS_UNIQ_MODE" -eq 1 ] && [ "$#NLIST_NONSELECTABLE_ELEMENTS" -gt 0 ] &&
            for i in "${(nO)NLIST_NONSELECTABLE_ELEMENTS[@]}"; do
                if [[ "$i" = <-> ]]; then
                    list[$i]=()
                fi
            done

            # Remove duplicates when in uniq mode
            [ "$NLIST_IS_UNIQ_MODE" -eq 1 ] && typeset -U list

            last_element="$#list"
            # Called after processing list
            _sellist_verify_vars
        fi

        # "1" - shouldn't bother with non-selectables
        _sellist_setup_user_vars "$NLIST_IS_UNIQ_MODE"

        integer end_idx=$(( NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN + page_height - 1 ))
        [ "$end_idx" -gt "$last_element" ] && end_idx=last_element

        if [ "$prev_start_idx" -ne "$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN" ]; then
            prev_start_idx="$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN"
            disp_list=( "${(@)list[NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN, end_idx]}" )

            [ -n "$NLIST_COLORING_PATTERN" ] && _sellist_colorify_disp_list

            # We have display list, lets replace newlines with "\n" when needed (2/2)
            [ "$NLIST_REPLACE_NEWLINES" -eq 1 ] && disp_list=( "${(@)disp_list//$'\n'/\\n}" )
        fi

        # Output the list
        zcurses clear inner
        sel-list-draw "$(( (NLIST_CURRENT_IDX-1) % page_height + 1 ))" \
            "$page_height" "$page_width" 0 0 "$NLIST_TEXT_OFFSET" inner \
            "$disp_list[@]"
    fi

    local status_msg_strlen
    local keywordmsg=""
    if [ "$keywordisfresh" = "1" ]; then
        keywordmsg="($curkeyword/$nkeywords) "
        keywordisfresh="0"
    fi

    local thememsg=""
    if [ "$themeisfresh" = "1" ]; then
        local theme="$backuptheme"
        [ "$curtheme" -gt 0 ] && theme="${themes[curtheme]}"
        thememsg="($curtheme/$nthemes $theme) "
        themeisfresh="0"
    fi

    local _txt2="" _txt3=""
    [ "$NLIST_IS_UNIQ_MODE" -eq 1 ] && _txt2="[-UNIQ-] "
    [ "$NLIST_IS_F_MODE" -eq 1 ] && _txt3="[-FIX-] "
    [ "$NLIST_IS_F_MODE" -eq 2 ] && _txt3="[-FIX2-] "

    if [ "$NLIST_IS_SEARCH_MODE" = "1" ]; then
        _sellist_status_msg "${_txt2}${_txt3}${keywordmsg}${thememsg}Filtering with: ${NLIST_SEARCH_BUFFER// /+}"
    elif [[ ${NLIST_NONSELECTABLE_ELEMENTS[(r)$NLIST_CURRENT_IDX]} != $NLIST_CURRENT_IDX ||
            -n "$NLIST_SEARCH_BUFFER" || "$NLIST_IS_UNIQ_MODE" -eq 1 ]]; then
        local _txt=""
        [ -n "$NLIST_GREP_STRING" ] && _txt=" [$NLIST_GREP_STRING]"
        _sellist_status_msg "${_txt2}${_txt3}${keywordmsg}${thememsg}Current #$NLIST_USER_CURRENT_IDX (of #$NLIST_USER_LAST_ELEMENT entries)$_txt"
    else
        _sellist_status_msg "${keywordmsg}${thememsg}"
    fi

    [ "$border" = "1" ] && zcurses border main

    local top_msg=" ${(C)ZSH_NAME} $ZSH_VERSION, $USER "
    zcurses move main 0 $(( term_width / 2 - $#top_msg / 2 ))
    zcurses string main $top_msg

    zcurses refresh main inner
    zcurses move main $(( term_height - 1 - 1 )) $(( status_msg_strlen + 2 ))

    # Wait for input
    zcurses input main key keypad

    # Get the special (i.e. "keypad") key or regular key
    if [ -n "$key" ]; then
        final_key="$key"
    elif [ -n "$keypad" ]; then
        final_key="$keypad"
    else
        _sellist_status_msg "Inproper input detected"
        zcurses refresh main inner
    fi

    REPLY=-2
    sel-list-input "$NLIST_CURRENT_IDX" "$NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN" \
                    "$page_height" "$page_width" "$last_element" "$NLIST_TEXT_OFFSET" \
                    "$final_key" "$NLIST_IS_SEARCH_MODE" "$NLIST_SEARCH_BUFFER" \
                    "$NLIST_IS_UNIQ_MODE" "$NLIST_IS_F_MODE"

    selection="$reply[1]"
    action="$reply[2]"
    NLIST_CURRENT_IDX="$reply[3]"
    NLIST_FROM_WHAT_IDX_LIST_IS_SHOWN="$reply[4]"
    NLIST_TEXT_OFFSET="$reply[5]"
    NLIST_IS_SEARCH_MODE="$reply[6]"
    NLIST_SEARCH_BUFFER="$reply[7]"
    NLIST_IS_UNIQ_MODE="$reply[8]"
    NLIST_IS_F_MODE="$reply[9]"

    if [ -z "$action" ]; then
        continue
    elif [ "$action" = "SELECT" ]; then
        REPLY="$selection"
        reply=( "$list[@]" )
        break
    elif [ "$action" = "QUIT" ]; then
        REPLY=-1
        reply=( "$list[@]" )
        break
    elif [ "$action" = "REDRAW" ]; then
        zcurses clear main redraw
        zcurses clear inner redraw
    elif [[ "$action" = F<-> ]]; then
        REPLY="$action"
        reply=( "$list[@]" )
        break
    elif [[ "$action" = "EDIT" ]]; then
        REPLY="EDIT"
        reply=( "$list[@]" )
        break
    elif [[ "$action" = "HELP" ]]; then
        REPLY="HELP"
        reply=( "$list[@]" )
        break
    fi
done

}

#######################################################################################

local list selected
integer stdout_FD

# Remember where to direct output
exec {stdout_FD}>&1
exec 2>/dev/null

#
# Regain STDOUT
#
if [ ! -t 1 ]; then
    # Check if can reattach to terminal
    if [[ ! -c /dev/tty && ! -t 2 ]]; then
        echo "No terminal available (no /dev/tty)"
        return 1
    fi

    if [[ ! -c /dev/tty ]]; then
        exec >&2
    else
        exec >/dev/tty
    fi
fi

#
# Regain STDIN, reading data
#
if [ -t 0 ]; then
    # Check if there is proper input
    if [ "$#" -lt 1 ]; then
        echo "Usage: zsh-select [a [b [c ...]]]"
        return 1
    fi

    # Form: zsh-select -- data1 data2 ...
    if [ "$1" = "--" ]; then
        shift
        list=( "$@" )
    # Form: zsh-select -c command arg1 arg2 ...
    elif [ "$1" = "-c" ]; then
        shift
        # This loop makes script faster on some Zsh's (e.g. 5.0.8)
        repeat 1; do
            list=( `"$@"` )
        done
    # Form: zsh-select data1 data2 ...
    else
        list=( "$@" )
    fi

    # TODO: $? doesn't reach user
    [ "$?" -eq 127 ] && return $?
else
    # Check if can reattach to terminal
    if [[ ! -c /dev/tty && ! -t 2 ]]; then
        echo "No terminal available (no /dev/tty)"
        return 1
    fi

    # This loop makes script faster on some Zsh's (e.g. 5.0.8)
    repeat 1; do
        list=( "${(@f)"$(<&0)"}" )
    done

    if [[ ! -c /dev/tty ]]; then
        exec <&2
    else
        exec </dev/tty
    fi
fi

sel-list "${list[@]}"

if [ "$REPLY" -gt 0 ]; then
    selected="$reply[REPLY]"
    print -r -- "$selected" >&$stdout_FD
fi

# vim: set filetype=zsh:
