" ingo/window/locate.vim: Functions to locate a window.
"
" DEPENDENCIES:
"   - ingo/actions.vim autoload script
"
" Copyright: (C) 2016 Ingo Karkat
"   The VIM LICENSE applies to this script; see ':help copyright'.
"
" Maintainer:	Ingo Karkat <ingo@karkat.de>
"
" REVISION	DATE		REMARKS
"   1.028.001	25-Nov-2016	file creation

function! s:Match( winVarName, Predicate, winNr, ... )
    if a:0 >= 2 && a:winNr == a:2
	return 0
    endif
    let l:tabNr = (a:0 ? a:1 : tabpagenr())

    let l:value = gettabwinvar(l:tabNr, a:winNr, a:winVarName)
    return !! ingo#actions#EvaluateWithValOrFunc(a:Predicate, l:value)
endfunction

function! s:CheckTabPageNearest( tabNr, winVarName, Predicate, ... )
    let l:skipWinNr = (a:0 ? a:1 : 0)
    let [l:currentWinNr, l:previousWinNr, l:lastWinNr] = [tabpagewinnr(a:tabNr), tabpagewinnr(a:tabNr, '#'), tabpagewinnr(a:tabNr, '$')]
    if s:Match(a:winVarName, a:Predicate, l:currentWinNr, a:tabNr, l:skipWinNr)
	return [a:tabNr, l:currentWinNr]
    elseif s:Match(a:winVarName, a:Predicate, l:previousWinNr, a:tabNr, l:skipWinNr)
	return [a:tabNr, l:previousWinNr]
    endif

    let l:offset = 1
    while l:currentWinNr - l:offset > 0 || l:currentWinNr + l:offset <= l:lastWinNr
	if s:Match(a:winVarName, a:Predicate, l:currentWinNr - l:offset, a:tabNr, l:skipWinNr)
	    return [a:tabNr, l:currentWinNr - l:offset]
	elseif s:Match(a:winVarName, a:Predicate, l:currentWinNr + l:offset, a:tabNr, l:skipWinNr)
	    return [a:tabNr, l:currentWinNr + l:offset]
	endif
	let l:offset += 1
    endwhile
    return [0, 0]
endfunction

function! ingo#window#locate#NearestByPredicate( isSearchOtherTabPages, winVarName, Predicate )
"******************************************************************************
"* PURPOSE:
"   Locate the window closest to the current one where the window variable a:winVarName makes
"   a:Predicate (passed in as argument or v:val) true.
"* ASSUMPTIONS / PRECONDITIONS:
"   None.
"* EFFECTS / POSTCONDITIONS:
"   None.
"* INPUTS:
"   a:isSearchOtherTabPages Flag whether windows in other tab pages should also
"			    be considered.
"   a:winVarName            Name of the window-local variable, like in
"			    |gettabwinvar()|
"   a:Predicate             Either a Funcref or an expression to be eval()ed.
"			    Gets the value of a:winVarName passed, should return
"			    a boolean value.
"* RETURN VALUES:
"   [tabpagenr, winnr] if a:isSearchOtherTabPages and the found window is on a
"	different tab page
"   [0, winnr] if the window is on the current tab page
"   [0, 0] if a:Predicate did not yield true in any other window
"******************************************************************************
    let l:lastWinNr = winnr('#')
    if l:lastWinNr != 0 && s:Match(a:winVarName, a:Predicate, l:lastWinNr)
	return [tabpagenr(), l:lastWinNr]
    endif

    let l:result = s:CheckTabPageNearest(tabpagenr(), a:winVarName, a:Predicate, winnr())
    if l:result != [0, 0] || ! a:isSearchOtherTabPages
	return l:result
    endif


    let [l:currentTabPageNr, l:lastTabPageNr] = [tabpagenr(), tabpagenr('$')]
    let l:offset = 1
    while l:currentTabPageNr - l:offset > 0 || l:currentTabPageNr + l:offset <= l:lastTabPageNr
	let l:result = s:CheckTabPageNearest(l:currentTabPageNr - l:offset, a:winVarName, a:Predicate)
	if l:result != [0, 0] | return l:result | endif

	let l:result = s:CheckTabPageNearest(l:currentTabPageNr + l:offset, a:winVarName, a:Predicate)
	if l:result != [0, 0] | return l:result | endif

	let l:offset += 1
    endwhile

    return [0, 0]
endfunction

function! ingo#window#locate#FirstByPredicate( isSearchOtherTabPages, winVarName, Predicate )
"******************************************************************************
"* PURPOSE:
"   Locate the first window (in this tab page, or with a:isSearchOtherTabPages
"   in other tabs) where the window variable a:winVarName makes a:Predicate
"   (passed in as argument or v:val) true.
"* ASSUMPTIONS / PRECONDITIONS:
"   None.
"* EFFECTS / POSTCONDITIONS:
"   None.
"* INPUTS:
"   a:isSearchOtherTabPages Flag whether windows in other tab pages should also
"			    be considered.
"   a:winVarName            Name of the window-local variable, like in
"			    |gettabwinvar()|
"   a:Predicate             Either a Funcref or an expression to be eval()ed.
"			    Gets the value of a:winVarName passed, should return
"			    a boolean value.
"* RETURN VALUES:
"   [tabpagenr, winnr] if a:isSearchOtherTabPages and the found window is on a
"	different tab page
"   [0, winnr] if the window is on the current tab page
"   [0, 0] if a:Predicate did not yield true in any other window
"******************************************************************************
    for l:winNr in range(1, winnr('$'))
	if s:Match(a:winVarName, a:Predicate, l:winNr)
	    return [0, l:winNr]
	endif
    endfor
    if ! a:isSearchOtherTabPages
	return [0, 0]
    endif

    for l:tabPageNr in filter(range(1, tabpagenr('$')), 'v:val != ' . tabpagenr())
	let l:lastWinNr = tabpagewinnr(l:tabPageNr, '$')
	for l:winNr in range(1, l:lastWinNr)
	    if s:Match(a:winVarName, a:Predicate, l:winNr, l:tabPageNr)
		return [l:tabPageNr, l:winNr]
	    endif
	endfor
    endfor

    return [0, 0]
endfunction

function! ingo#window#locate#ByPredicate( strategy, isSearchOtherTabPages, winVarName, Predicate )
"******************************************************************************
"* PURPOSE:
"   Locate a window (in this tab page, or with a:isSearchOtherTabPages in other
"   tabs), with a:strategy to determine precedences, where the window variable
"   a:winVarName makes a:Predicate (passed in as argument or v:val) true.
"* ASSUMPTIONS / PRECONDITIONS:
"   None.
"* EFFECTS / POSTCONDITIONS:
"   None.
"* INPUTS:
"   a:strategy              One of "first" or "nearest".
"   a:isSearchOtherTabPages Flag whether windows in other tab pages should also
"			    be considered.
"   a:winVarName            Name of the window-local variable, like in
"			    |gettabwinvar()|
"   a:Predicate             Either a Funcref or an expression to be eval()ed.
"			    Gets the value of a:winVarName passed, should return
"			    a boolean value.
"* RETURN VALUES:
"   [tabpagenr, winnr] if a:isSearchOtherTabPages and the found window is on a
"	different tab page
"   [0, winnr] if the window is on the current tab page
"   [0, 0] if a:Predicate did not yield true in any other window
"******************************************************************************
    if a:strategy ==# 'first'
	return ingo#window#locate#FirstByPredicate(a:isSearchOtherTabPages, a:winVarName, a:Predicate)
    elseif a:strategy ==# 'nearest'
	return ingo#window#locate#NearestByPredicate(a:isSearchOtherTabPages, a:winVarName, a:Predicate)
    else
	throw 'ASSERT: Unknown strategy ' . string(a:strategy)
    endif
endfunction

" vism: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :
