" Vars:
let s:vim_tab_options = {
      \ '&tabline':     '%!choosewin#tabline()',
      \ '&guitablabel': '%{choosewin#get_tablabel(v:lnum)}',
      \ }

" Util::
let s:_ = choosewin#util#get()

function! s:win_all() "{{{1
  return range(1, winnr('$'))
endfunction

function! s:tab_all() "{{{1
  return range(1, tabpagenr('$'))
endfunction
"}}}

" Wins:
let s:wins = {}

function! s:wins.get(...) "{{{1
  let amt = get(a:000, 0)
  let idx = empty(amt) ? -1 : amt - 1
  return self._data[0 : idx]
endfunction

function! s:wins.set(wins) "{{{1
  " Filter out non-existing window before store.
  let self._data = filter(a:wins, 'index(s:win_all(), v:val) isnot -1')
  return self
endfunction
"}}}

" Main:
let s:cw = {}

function! s:cw.start(wins, ...) "{{{1
  call self.init(a:wins, get(a:000, 0, {}))

  try
    let status = []
    " Some status bar plugin need to know if choosewin active or not.
    let g:choosewin_active = 1

    if empty(self.wins.get()) ||
          \ ( len(self.wins.get()) is 1 && self.conf['return_on_single_win'] )
      throw 'RETURN'
    endif
    if len(self.wins.get()) is 1 && self.conf['auto_choose']
      call self.action.do_win(self.wins.get()[0])
    endif

    call self.setup()
    call self.choose()

  catch /\v^(CHOSE \d+)$/
    if self.conf['noop'] 
      let tab    = tabpagenr()
      let win    = str2nr(matchstr(v:exception, '\v^CHOSE \zs\d+'))
      if tab isnot self.src.tab
        call self.action.do_tab(self.src.tab)
      endif
      let status = [ tab, win ]
    else
      let status = [ tabpagenr(), winnr() ]
    endif
    let self.previous = [ self.src.tab, self.src.win ]
  catch /\v^SWAP$/
    let status = [ tabpagenr(), winnr() ]
    if self.conf['swap_stay']
      let self.previous = status
      call self.action._goto_tabwin(self.src.tab, self.src.win)
    else
      let self.previous = [ self.src.tab, self.src.win ]
    endif
  catch /\v^Vim:Interrupt$/
    call self.label_clear()
    call self.action.do_cancel()
  catch /\v^(RETURN|CANCELED)$/
  catch
    let self.exception = v:exception
  finally
    call self.finish()
    let g:choosewin_active = 0
    return status
  endtry
endfunction

function! s:cw.init(wins, conf) "{{{1
  call choosewin#color#init()
  let self.wins        = s:wins.set(a:wins)
  let self.conf        = extend(choosewin#config#get(), a:conf)
  let self.action      = choosewin#action#init(self)
  let self.exception   = ''
  let self.tab_options = {}
  let self.statusline  = {}
  let self.src         = {'win': winnr(), 'tab': tabpagenr()}
endfunction

function! s:cw.setup() "{{{1
  let self.label2tab  = s:_.dict_create(self.conf['tablabel'], s:tab_all())

  if self.conf['overlay_enable']
    let self.overlay = choosewin#overlay#get()
  endif

  if self.conf['tabline_replace']
    let self.tab_options = s:_.buffer_options_set(bufnr(''), s:vim_tab_options)
  endif
endfunction

function! s:cw.choose() "{{{1
  while 1
    call self.label_show()
    let prompt = (self.conf['swap'] ? '[swap] ' : '') . 'choose > '
    let char = s:_.read_char(prompt)

    call self.label_clear()

    " Tab label is chosen.
    let num = s:_.get_ic(self.label2tab, char)
    if !empty(num)
      call self.action.do_tab(num)
      continue
    endif

    " Win label is chosen.
    let num = s:_.get_ic(self.label2win, char)
    if !empty(num)
      if self.conf['swap']
        call self.action._swap(tabpagenr(), num)
      else
        call self.action.do_win(num)
      endif
    endif

    let action_name = 'do_' . get(self.conf['keymap'], char, 'cancel')
    if !s:_.is_Funcref(get(self.action, action_name))
      throw 'UNKNOWN_ACTION'
    endif
    call self.action[action_name]()
  endwhile
endfunction

function! s:cw.finish() "{{{1
  if !empty(self.tab_options)
    call s:_.buffer_options_set(bufnr(''), self.tab_options)
  endif
  echo ''
  redraw
  if self.conf['blink_on_land']
    call s:_.blink(2, "ChooseWinLand", '\k*\%#\k*')
  endif
  if !empty(self.exception)
    call s:_.message(self.exception)
  endif
endfunction

function! s:cw.call_hook(hook_point, arg) "{{{1
  let HOOK = get(self.conf['hook'], a:hook_point, 0)
  if s:_.is_Funcref(HOOK)
    return call(HOOK, [a:arg])
  else
    return a:arg
  endif
endfunction
"}}}

" Label:
function! s:cw.label_show() "{{{1
  if self.conf['hook_enable'] && index(self.conf['hook_bypass'], 'filter_window' ) is -1
    let wins_new = self.call_hook('filter_window', copy(self.wins.get()))
    call self.wins.set(wins_new)
  endif

  let wins = self.wins.get(len(self.conf['label']))

  let self.label2win = s:_.dict_create(self.conf.label, wins)
  let self.win2label = s:_.dict_create(wins, self.conf.label)

  if self.conf['statusline_replace']
    for n in wins
      let self.statusline[n] = s:_.window_options_set(n,
            \ { '&statusline': self.prepare_label(n) })
    endfor
  endif

  if self.conf['overlay_enable']
    call self.overlay.start(wins, self.conf)
  endif
  redraw
endfunction

function! s:cw.label_clear() "{{{1
  if self.conf['statusline_replace']
    for n in self.wins.get(len(self.conf['label']))
      call s:_.window_options_set(n, self.statusline[n])
    endfor
  endif

  if self.conf['overlay_enable']
    call self.overlay.restore()
  endif
endfunction

function! s:cw.prepare_label(win) "{{{1
  let align = self.conf['label_align']
  let pad   = repeat(' ', self.conf['label_padding'])
  let label = self.win2label[a:win]
  let win_s = pad . label . pad
  let color = "ChooseWinLabel" . (winnr() is a:win ? "Current" : "")

  if align is 'left'
    return printf('%%#%s# %s %%#%s# %%= ', color, win_s, "ChooseWinOther")
  endif

  if align is 'right'
    return printf('%%#%s# %%= %%#%s# %s ', "ChooseWinOther", color, win_s)
  endif

  if align is 'center'
    let padding = repeat(' ', winwidth(a:win)/2-len(win_s))
    return printf('%%#%s# %s %%#%s# %s %%#%s# %%= ',
          \ "ChooseWinOther", padding, color, win_s, "ChooseWinOther")
  endif
endfunction
"}}}

" Tabline:
function! s:cw.tabline() "{{{1
  let R   = ''
  let pad = repeat(' ', self.conf['label_padding'])
  let sepalator = printf('%%#%s# ', "ChooseWinOther")
  let tab_all = s:tab_all()
  for tabnum in tab_all
    let color = "ChooseWinLabel" . (tabpagenr() is tabnum ? "Current" : "")
    let R .= printf('%%#%s# %s ', color,  pad . self.get_tablabel(tabnum) . pad)
    let R .= tabnum isnot tab_all[-1] ? sepalator : ''
  endfor
  let R .= printf('%%#%s#', "ChooseWinOther")
  return R
endfunction

function! s:cw.get_tablabel(num) "{{{1
  return len(self.conf['tablabel']) > a:num
        \ ? self.conf['tablabel'][a:num-1]
        \ : '..'
endfunction
"}}}

" API:
function! choosewin#start(...) "{{{1
  return call(s:cw.start, a:000, s:cw)
endfunction

function! choosewin#tabline() "{{{1
  return s:cw.tabline()
endfunction

function! choosewin#get_tablabel(num) "{{{1
  return s:cw.get_tablabel(a:num)
endfunction

function! choosewin#noop() "{{{1
  return s:cw.conf['noop']
endfunction
"}}}

" vim: foldmethod=marker