" @Author:      Tom Link (micathom AT gmail com?subject=[vim])
" @Website:     http://www.vim.org/account/profile.php?user_id=4037
" @License:     GPL (see http://www.gnu.org/licenses/gpl.txt)
" @Revision:    328


" :filedoc:
" Various agents for use as key handlers in tlib#input#List()

" Number of items to move when pressing <c-up/down> in the input list window.
TLet g:tlib_scroll_lines = 10


" General {{{1

function! tlib#agent#Exit(world, selected) "{{{3
    if a:world.key_mode == 'default'
        call a:world.CloseScratch()
        let a:world.state = 'exit empty escape'
        let a:world.list = []
        " let a:world.base = []
        call a:world.ResetSelected()
    else
        let a:world.key_mode = 'default'
        let a:world.state = 'redisplay'
    endif
    return a:world
endf


function! tlib#agent#CopyItems(world, selected) "{{{3
    let @* = join(a:selected, "\n")
    let a:world.state = 'redisplay'
    return a:world
endf



" InputList related {{{1

function! tlib#agent#PageUp(world, selected) "{{{3
    let a:world.offset -= (winheight(0) / 2)
    let a:world.state = 'scroll'
    return a:world
endf


function! tlib#agent#PageDown(world, selected) "{{{3
    let a:world.offset += (winheight(0) / 2)
    let a:world.state = 'scroll'
    return a:world
endf


function! tlib#agent#Home(world, selected) "{{{3
    let a:world.prefidx = 1
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#End(world, selected) "{{{3
    let a:world.prefidx = len(a:world.list)
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#Up(world, selected, ...) "{{{3
    TVarArg ['lines', 1]
    let a:world.idx = ''
    if a:world.prefidx > lines
        let a:world.prefidx -= lines
    else
        let a:world.prefidx = len(a:world.list)
    endif
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#Down(world, selected, ...) "{{{3
    TVarArg ['lines', 1]
    let a:world.idx = ''
    if a:world.prefidx <= (len(a:world.list) - lines)
        let a:world.prefidx += lines
    else
        let a:world.prefidx = 1
    endif
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#UpN(world, selected) "{{{3
    return tlib#agent#Up(a:world, a:selected, g:tlib_scroll_lines)
endf


function! tlib#agent#DownN(world, selected) "{{{3
    return tlib#agent#Down(a:world, a:selected, g:tlib_scroll_lines)
endf


function! tlib#agent#ShiftLeft(world, selected) "{{{3
    let a:world.offset_horizontal -= (winwidth(0) / 2)
    if a:world.offset_horizontal < 0
        let a:world.offset_horizontal = 0
    endif
    let a:world.state = 'display shift'
    return a:world
endf


function! tlib#agent#ShiftRight(world, selected) "{{{3
    let a:world.offset_horizontal += (winwidth(0) / 2)
    let a:world.state = 'display shift'
    return a:world
endf


function! tlib#agent#Reset(world, selected) "{{{3
    let a:world.state = 'reset'
    return a:world
endf


function! tlib#agent#ToggleRestrictView(world, selected) "{{{3
    if empty(a:world.filtered_items)
        return tlib#agent#RestrictView(a:world, a:selected)
    else
        return tlib#agent#UnrestrictView(a:world, a:selected)
    endif
endf


function! tlib#agent#RestrictView(world, selected) "{{{3
    " TLogVAR a:selected
    let filtered_items = map(copy(a:selected), 'index(a:world.base, v:val) + 1')
    " TLogVAR 1, filtered_items
    let filtered_items = filter(filtered_items, 'v:val > 0')
    " TLogVAR 2, filtered_items
    if !empty(filtered_items)
        let a:world.filtered_items = filtered_items
    endif
    let a:world.state = 'display'
    return a:world
endf


function! tlib#agent#UnrestrictView(world, selected) "{{{3
    let a:world.filtered_items = []
    let a:world.state = 'display'
    return a:world
endf


function! tlib#agent#Input(world, selected) "{{{3
    let flt0 = a:world.CleanFilter(a:world.filter[0][0])
    let flt1 = input('Filter: ', flt0)
    echo
    if flt1 != flt0
        if empty(flt1)
            call getchar(0)
        else
            call a:world.SetFrontFilter(flt1)
        endif
    endif
    let a:world.state = 'display'
    return a:world
endf


" Suspend (see |tlib#agent#Suspend|) the input loop and jump back to the 
" original position in the parent window.
function! tlib#agent#SuspendToParentWindow(world, selected) "{{{3
    let world = a:world
    let winnr = world.win_wnr
    " TLogVAR winnr
    if winnr != -1
        let world = tlib#agent#Suspend(world, a:selected)
        if world.state =~ '\<suspend\>'
            call world.SwitchWindow('win')
            " let pos = world.cursor
            " " TLogVAR pos
            " if !empty(pos)
            "     call setpos('.', pos)
            " endif
            return world
        endif
    endif
    let world.state = 'redisplay'
    return world
endf


" Suspend lets you temporarily leave the input loop of 
" |tlib#input#List|. You can resume editing the list by pressing <c-z>, 
" <m-z>. <space>, <c-LeftMouse> or <MiddleMouse> in the suspended window.
" <cr> and <LeftMouse> will immediatly select the item under the cursor.
" < will select the item but the window will remain opened.
function! tlib#agent#Suspend(world, selected) "{{{3
    if a:world.allow_suspend
        " TAssert IsNotEmpty(a:world.scratch)
        " TLogDBG bufnr('%')
        let br = tlib#buffer#Set(a:world.scratch)
        " TLogVAR br, a:world.bufnr, a:world.scratch
        if bufnr('%') != a:world.scratch
            echohl WarningMsg
            echom "tlib#agent#Suspend: Internal error: Not a scratch buffer:" bufname('%')
            echohl NONE
        endif
        " TLogVAR bufnr('%'), bufname('%'), a:world.scratch
        call tlib#autocmdgroup#Init()
        exec 'autocmd TLib BufEnter <buffer='. a:world.scratch .'> call tlib#input#Resume("world", 0, '. a:world.scratch .')'
        let b:tlib_world = a:world
        exec br
        let a:world.state = 'exit suspend'
    else
        echom 'Suspend disabled'
        let a:world.state = 'redisplay'
    endif
    return a:world
endf


function! tlib#agent#Help(world, selected) "{{{3
    let a:world.state = 'help'
    return a:world
endf


function! tlib#agent#OR(world, selected) "{{{3
    if !empty(a:world.filter[0])
        call insert(a:world.filter[0], '')
    endif
    let a:world.state = 'display'
    return a:world
endf


function! tlib#agent#AND(world, selected) "{{{3
    if !empty(a:world.filter[0])
        call insert(a:world.filter, [''])
    endif
    let a:world.state = 'display'
    return a:world
endf


function! tlib#agent#ReduceFilter(world, selected) "{{{3
    call a:world.ReduceFilter()
    let a:world.offset = 1
    let a:world.state = 'display'
    return a:world
endf


function! tlib#agent#PopFilter(world, selected) "{{{3
    call a:world.PopFilter()
    let a:world.offset = 1
    let a:world.state = 'display'
    return a:world
endf


function! tlib#agent#Debug(world, selected) "{{{3
    " echo string(world.state)
    echo string(a:world.filter)
    echo string(a:world.idx)
    echo string(a:world.prefidx)
    echo string(a:world.sel_idx)
    call getchar()
    let a:world.state = 'display'
    return a:world
endf


function! tlib#agent#Select(world, selected) "{{{3
    call a:world.SelectItem('toggle', a:world.prefidx)
    " let a:world.state = 'display keepcursor'
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#SelectUp(world, selected) "{{{3
    call a:world.SelectItem('toggle', a:world.prefidx)
    if a:world.prefidx > 1
        let a:world.prefidx -= 1
    endif
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#SelectDown(world, selected) "{{{3
    call a:world.SelectItem('toggle', a:world.prefidx)
    if a:world.prefidx < len(a:world.list)
        let a:world.prefidx += 1
    endif
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#SelectAll(world, selected) "{{{3
    let listrange = range(1, len(a:world.list))
    let mode = empty(filter(copy(listrange), 'index(a:world.sel_idx, a:world.GetBaseIdx(v:val)) == -1'))
                \ ? 'toggle' : 'set'
    for i in listrange
        call a:world.SelectItem(mode, i)
    endfor
    let a:world.state = 'display keepcursor'
    return a:world
endf


function! tlib#agent#ToggleStickyList(world, selected) "{{{3
    let a:world.sticky = !a:world.sticky
    let a:world.state = 'display keepcursor'
    return a:world
endf



" EditList related {{{1

function! tlib#agent#EditItem(world, selected) "{{{3
    let lidx = a:world.prefidx
    " TLogVAR lidx
    " TLogVAR a:world.table
    let bidx = a:world.GetBaseIdx(lidx)
    " TLogVAR bidx
    let item = a:world.GetBaseItem(bidx)
    let item = input(lidx .'@'. bidx .': ', item)
    if item != ''
        call a:world.SetBaseItem(bidx, item)
    endif
    let a:world.state = 'display'
    return a:world
endf


" Insert a new item below the current one.
function! tlib#agent#NewItem(world, selected) "{{{3
    let basepi = a:world.GetBaseIdx(a:world.prefidx)
    let item = input('New item: ')
    call insert(a:world.base, item, basepi)
    let a:world.state = 'reset'
    return a:world
endf


function! tlib#agent#DeleteItems(world, selected) "{{{3
    let remove = copy(a:world.sel_idx)
    let basepi = a:world.GetBaseIdx(a:world.prefidx)
    if index(remove, basepi) == -1
        call add(remove, basepi)
    endif
    " call map(remove, 'a:world.GetBaseIdx(v:val)')
    for idx in reverse(sort(remove))
        call remove(a:world.base, idx - 1)
    endfor
    let a:world.state = 'display'
    call a:world.ResetSelected()
    " let a:world.state = 'reset'
    return a:world
endf


function! tlib#agent#Cut(world, selected) "{{{3
    let world = tlib#agent#Copy(a:world, a:selected)
    return tlib#agent#DeleteItems(world, a:selected)
endf


function! tlib#agent#Copy(world, selected) "{{{3
    let a:world.clipboard = []
    let bidxs = copy(a:world.sel_idx)
    call add(bidxs, a:world.GetBaseIdx(a:world.prefidx))
    for bidx in sort(bidxs)
        call add(a:world.clipboard, a:world.GetBaseItem(bidx))
    endfor
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#Paste(world, selected) "{{{3
    if has_key(a:world, 'clipboard')
        for e in reverse(copy(a:world.clipboard))
            call insert(a:world.base, e, a:world.prefidx)
        endfor
    endif
    let a:world.state = 'display'
    call a:world.ResetSelected()
    return a:world
endf


function! tlib#agent#EditReturnValue(world, rv) "{{{3
    return [a:world.state !~ '\<exit\>', a:world.base]
endf



" Files related {{{1

function! tlib#agent#ViewFile(world, selected) "{{{3
    if !empty(a:selected)
        let back = a:world.SwitchWindow('win')
        " TLogVAR back
        for filename in a:selected
            call tlib#file#Edit(filename)
        endfor
        " if !&hidden && &l:modified
        "     let cmd0 = 'split'
        "     let cmd1 = 'sbuffer'
        " else
        "     let cmd0 = 'edit'
        "     let cmd1 = 'buffer'
        " endif
        " call tlib#file#With(cmd0, cmd1, a:selected, a:world)
        " TLogVAR &filetype
        exec back
        let a:world.state = 'display'
    endif
    return a:world
endf


function! tlib#agent#EditFile(world, selected) "{{{3
    return tlib#agent#Exit(tlib#agent#ViewFile(a:world, a:selected), a:selected)
endf


function! tlib#agent#EditFileInSplit(world, selected) "{{{3
    call a:world.CloseScratch()
    " call tlib#file#With('edit', 'buffer', a:selected[0:0], a:world)
    " call tlib#file#With('split', 'sbuffer', a:selected[1:-1], a:world)
    call tlib#file#With('split', 'sbuffer', a:selected, a:world)
    return tlib#agent#Exit(a:world, a:selected)
endf


function! tlib#agent#EditFileInVSplit(world, selected) "{{{3
    call a:world.CloseScratch()
    " call tlib#file#With('edit', 'buffer', a:selected[0:0], a:world)
    " call tlib#file#With('vertical split', 'vertical sbuffer', a:selected[1:-1], a:world)
    let winpos = tlib#fixes#Winpos()
    call tlib#file#With('vertical split', 'vertical sbuffer', a:selected, a:world)
    if !empty(winpos)
        exec winpos
    endif
    return tlib#agent#Exit(a:world, a:selected)
endf


function! tlib#agent#EditFileInTab(world, selected) "{{{3
    " TLogVAR a:selected
    call a:world.CloseScratch()
    call tlib#file#With('tabedit', 'tab sbuffer', a:selected, a:world)
    return tlib#agent#Exit(a:world, a:selected)
endf


function! tlib#agent#ToggleScrollbind(world, selected) "{{{3
    let a:world.scrollbind = get(a:world, 'scrollbind') ? 0 : 1
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#ShowInfo(world, selected)
    let lines = []
    for f in a:selected
        if filereadable(f)
            let desc = [getfperm(f), strftime('%c', getftime(f)),  getfsize(f) .' bytes', getftype(f)]
            call add(lines, fnamemodify(f, ':p'))
            call add(lines, '  '. join(desc, '; '))
        endif
    endfor
    let a:world.temp_lines = lines
    let a:world.state = 'printlines'
    return a:world
endf



" Buffer related {{{1

function! tlib#agent#PreviewLine(world, selected) "{{{3
    let l = a:selected[0]
    let ww = winnr()
    exec a:world.win_wnr .'wincmd w'
    call tlib#buffer#ViewLine(l, 1)
    exec ww .'wincmd w'
    let a:world.state = 'redisplay'
    return a:world
endf


" If not called from the scratch, we assume/guess that we don't have to 
" suspend the input-evaluation loop.
function! tlib#agent#GotoLine(world, selected) "{{{3
    if !empty(a:selected)

        " let l = a:selected[0]
        " " TLogVAR l
        " let back = a:world.SwitchWindow('win')
        " " TLogVAR back
        " " if a:world.win_wnr != winnr()
        " "     let world = tlib#agent#Suspend(a:world, a:selected)
        " "     exec a:world.win_wnr .'wincmd w'
        " " endif
        " call tlib#buffer#ViewLine(l)
        " exec back
        " let a:world.state = 'display'

        let l = a:selected[0]
        if a:world.win_wnr != winnr()
            let world = tlib#agent#Suspend(a:world, a:selected)
            exec a:world.win_wnr .'wincmd w'
        endif
        call tlib#buffer#ViewLine(l, 1)
        
    endif
    return a:world
endf


function! tlib#agent#DoAtLine(world, selected) "{{{3
    if !empty(a:selected)
        let cmd = input('Command: ', '', 'command')
        if !empty(cmd)
            call a:world.SwitchWindow('win')
            " let pos = getpos('.')
            let view = winsaveview()
            for l in a:selected
                call tlib#buffer#ViewLine(l, '')
                exec cmd
            endfor
            " call setpos('.', pos)
            call winrestview(view)
        endif
    endif
    call a:world.ResetSelected()
    let a:world.state = 'exit'
    return a:world
endf


function! tlib#agent#Wildcard(world, selected) "{{{3
    if !empty(a:world.filter[0])
        let rx_type = a:world.matcher.FilterRxPrefix()
        let flt0 = a:world.CleanFilter(a:world.filter[0][0])
        if rx_type == '\V'
            let flt0 .= '\.\{-}'
        else
            let flt0 .= '.\{-}'
        endif
        call a:world.SetFrontFilter(flt0)
    endif
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#Null(world, selected) "{{{3
    let a:world.state = 'redisplay'
    return a:world
endf


function! tlib#agent#ExecAgentByName(world, selected) "{{{3
    let s:agent_names_world = a:world
    let agent_names = {'Help': 'tlib#agent#Help'}
    for def in values(a:world.key_map[a:world.key_mode])
        if has_key(def, 'help') && !empty(def.help) && has_key(def, 'agent') && !empty(def.agent)
            let agent_names[def.help] = def.agent
        endif
    endfor
    let s:agent_names = sort(keys(agent_names))
    let command = input('Command: ', '', 'customlist,tlib#agent#CompleteAgentNames')
    " TLogVAR command
    if !has_key(agent_names, command)
        " TLogVAR command
        silent! let matches = filter(keys(agent_names), 'v:val =~ command')
        " TLogVAR matches
        if len(matches) == 1
            let command = matches[0]
        endif
    endif
    if has_key(agent_names, command)
        let agent = agent_names[command]
        return call(agent, [a:world, a:selected])
    else
        if !empty(command)
            echohl WarningMsg
            echom "Unknown command:" command
            echohl NONE
            sleep 1
        endif
        let a:world.state = 'display'
        return a:world
    endif
endf


function! tlib#agent#CompleteAgentNames(ArgLead, CmdLine, CursorPos)
    return filter(copy(s:agent_names), 'stridx(v:val, a:ArgLead) != -1')
endf


function! tlib#agent#Complete(world, selected) abort "{{{3
    let rxprefix = a:world.matcher.FilterRxPrefix()
    let flt = a:world.filter[0][0]
    " TLogVAR flt
    let fltrx = rxprefix . flt . '\m[^[:space:][:cntrl:][:punct:]<>*+?&~{}()\[\]\\/]\+'
    let fltrx0 = '\m^' . fltrx
    " TLogVAR fltrx, fltrx0
    let words = {}
    for item in a:world.list
        let parts = split(item, '\ze'. fltrx)
        " TLogVAR item, parts
        for part in parts
            let word = matchstr(part, fltrx0)
            " TLogVAR part, word
            if !empty(word)
                let words[word] = 1
            endif
        endfor
    endfor
    " TLogVAR keys(words)
    let completions = keys(words)
    " let completions = filter(keys(words), 'matchstr(v:val, fltrx0)')
    let completions = sort(completions, 's:SortCompletions')
    let completions = tlib#list#Uniq(completions)
    " TLogVAR 0, completions
    while len(completions) > 1
        let nchar = strwidth(completions[0]) - 1
        let completions = map(completions, 'strpart(v:val, 0, nchar)')
        " TLogVAR 'reduce', completions
        let completions = tlib#list#Uniq(completions)
        " TLogVAR 'unique', len(completions), completions
    endwh
    " TLogVAR 9, completions
    if empty(completions)
        let a:world.state = 'redisplay update'
    else
        let a:world.filter[0][0] = completions[0]
        let a:world.state = 'display update'
    endif
    return a:world
endf


function! s:SortCompletions(a, b) abort "{{{3
    let i1 = strwidth(a:a)
    let i2 = strwidth(a:b)
    return i2 - i1
endf

