" Author:  Eric Van Dewoestine
"
" License: {{{
"
" Copyright (C) 2005 - 2014  Eric Van Dewoestine
"
" This program is free software: you can redistribute it and/or modify
" it under the terms of the GNU General Public License as published by
" the Free Software Foundation, either version 3 of the License, or
" (at your option) any later version.
"
" This program is distributed in the hope that it will be useful,
" but WITHOUT ANY WARRANTY; without even the implied warranty of
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
" GNU General Public License for more details.
"
" You should have received a copy of the GNU General Public License
" along with this program.  If not, see <http://www.gnu.org/licenses/>.
"
" }}}

" Global Variables {{{
  if !exists('g:EclimProjectTreeActions')
    let g:EclimProjectTreeActions = [
        \ {'pattern': '.*', 'name': 'Split', 'action': 'split'},
        \ {'pattern': '.*', 'name': 'VSplit', 'action': 'vsplit'},
        \ {'pattern': '.*', 'name': 'Tab', 'action': 'tablast | tabnew'},
        \ {'pattern': '.*', 'name': 'Edit', 'action': 'edit'},
      \ ]
  endif
" }}}

" Script Variables {{{
  let s:project_tree_ids = 0
  let s:shared_instances_by_buffer = {}
  let s:shared_instances_by_names = {}
" }}}

function! eclim#project#tree#ProjectTree(...) " {{{
  " Open a tree view of the current or specified projects.

  " no project dirs supplied, use current project
  if len(a:000) == 0
    let name = eclim#project#util#GetCurrentProjectName()
    let names = [name]
    if name == ''
      if exists('t:cwd')
        let names = [t:cwd]
      else
        call eclim#project#util#UnableToDetermineProject()
        return
      endif
    endif

  " list of project names supplied
  elseif type(a:000[0]) == g:LIST_TYPE
    let names = a:000[0]
    if len(names) == 1 && (names[0] == '0' || names[0] == '')
      return
    endif

  " list or project names
  else
    let names = a:000
  endif

  let dirs = []
  let index = 0
  let names_copy = copy(names)
  for name in names
    if name == 'CURRENT'
      let name = eclim#project#util#GetCurrentProjectName()
      let names_copy[index] = name
    endif

    let dir = eclim#project#util#GetProjectRoot(name)
    if dir == ''
      let dir = expand(name, ':p')
      if !isdirectory(dir)
        if eclim#EclimAvailable(0)
          call eclim#util#EchoWarning('Project not found: ' . name)
        endif
        call remove(names_copy, index)
        continue
      endif
      let names_copy[index] = fnamemodify(substitute(dir, '/$', '', ''), ':t')
    endif
    call add(dirs, dir)
    let index += 1
  endfor
  let names = names_copy

  if len(dirs) == 0
    return
  endif

  " for session reload
  let g:Eclim_project_tree_names = join(names, '|')

  let display = len(names) == 1 ?
    \ 'Project: ' . names[0] :
    \ 'Projects: ' . join(names, ', ')

  call eclim#project#tree#ProjectTreeClose()
  call eclim#project#tree#ProjectTreeOpen(display, names, dirs)
endfunction " }}}

function! eclim#project#tree#ProjectTreeToggle() " {{{
  let title = s:GetTreeTitle()
  let bufnum = bufnr(title)
  let winnum = bufwinnr(title)
  if bufnum == -1 || winnum == -1
    call eclim#project#tree#ProjectTree()
  else
    exec winnum . 'winc w'
    close
    winc p
  endif
endfunction " }}}

function! eclim#project#tree#ProjectTreeOpen(display, names, dirs) " {{{
  let expand = len(a:dirs) == 1
  let expandDir = ''
  if expand && g:EclimProjectTreeExpandPathOnOpen
    let expandDir = substitute(expand('%:p:h'), '\', '/', 'g')
  endif

  " see if we should just use a shared tree
  let shared = s:GetSharedTreeBuffer(a:names)
  if shared != -1 && bufloaded(shared)
    call eclim#display#window#VerticalToolWindowOpen(bufname(shared), 9)
    "exec 'buffer ' . shared
    if line('$') > 1 || getline(1) !~ '^\s*$'
      setlocal nowrap nonumber
      setlocal foldmethod=manual foldtext=getline(v:foldstart)
      exec 'setlocal statusline=' . escape(a:display, ' ')
      if !exists('t:project_tree_name')
        exec 'let t:project_tree_id = ' .
          \ substitute(bufname(shared), g:EclimProjectTreeTitle . '\(\d\+\)', '\1', '')
      endif

      if expand && expandDir != ''
        call eclim#tree#ExpandPath(s:GetTreeTitle(), expandDir)
      endif

      return
    endif
  endif

  " clear the project tree id if we are replacing a shared tree instance
  if g:EclimProjectTreeSharedInstance && exists('t:project_tree_id')
    unlet t:project_tree_id
  endif

  call eclim#display#window#VerticalToolWindowOpen(s:GetTreeTitle(), 9)

  " command used to navigate to a content window before executing a command.
  if !exists('g:EclimProjectTreeContentWincmd')
    if g:VerticalToolWindowSide == 'right'
      let g:EclimProjectTreeContentWincmd = 'winc h'
    else
      let g:EclimProjectTreeContentWincmd = 'winc l'
    endif
  endif

  if exists('g:TreeSettingsFunction')
    let s:TreeSettingsFunction = g:TreeSettingsFunction
  endif
  let g:TreeSettingsFunction = 'eclim#project#tree#ProjectTreeSettings'

  try
    call eclim#tree#Tree(s:GetTreeTitle(), a:dirs, a:names, expand, [])
  finally
    if exists('s:TreeSettingsFunction')
      let g:TreeSettingsFunction = s:TreeSettingsFunction
    else
      unlet g:TreeSettingsFunction
    endif
  endtry

  setlocal bufhidden=hide
  exec 'setlocal statusline=' . escape(a:display, ' ')

  if expand && expandDir != ''
    call eclim#util#DelayedCommand(
      \ 'call eclim#tree#ExpandPath("' . s:GetTreeTitle() . '", "' . expandDir . '")')
  endif

  normal! zs

  let instance_names = join(a:names, '_')
  let instance_names = substitute(instance_names, '\W', '_', 'g')

  " remove the old associated tree value if one exists
  silent! unlet s:shared_instances_by_names[s:shared_instances_by_buffer[bufnr('%')]]

  let s:shared_instances_by_buffer[bufnr('%')] = instance_names
  let s:shared_instances_by_names[instance_names] = bufnr('%')

  call s:Mappings()
  setlocal modifiable
  call append(line('$'), ['', '" use ? to view help'])
  call s:InfoLine()
  setlocal nomodifiable
endfunction " }}}

function! eclim#project#tree#ProjectTreeClose() " {{{
  if exists('t:project_tree_name') || exists('t:project_tree_id')
    let winnr = bufwinnr(s:GetTreeTitle())
    if winnr != -1
      exec winnr . 'winc w'
      close
    endif
  endif
endfunction " }}}

function! eclim#project#tree#Restore() " {{{
  if exists('t:project_tree_restoring')
    return
  endif
  let t:project_tree_restoring = 1

  " prevent auto open from firing after session is loaded.
  augroup project_tree_autoopen
    autocmd!
  augroup END

  let title = s:GetTreeTitle()
  let winnum = bufwinnr(title)
  if winnum != -1
    if exists('g:Eclim_project_tree_names')
      let projects = split(g:Eclim_project_tree_names, '|')
      call map(projects, 'escape(v:val, " ")')
      let names = join(projects, ' ')
      call eclim#util#DelayedCommand(
        \ 'let bufnum = bufnr("%") | ' .
        \ 'exec "ProjectTree ' . names . '" | ' .
        \ 'exec bufwinnr(bufnum) . "winc w" | ' .
        \ 'unlet t:project_tree_restoring')
    else
      exec 'bd ' . bufnr(title)
    endif
  endif
endfunction " }}}

function! s:GetTreeTitle() " {{{
  " support a custom name from an external plugin
  if exists('t:project_tree_name')
    return t:project_tree_name
  endif

  if !exists('t:project_tree_id')
    let t:project_tree_id = s:project_tree_ids + 1
    let s:project_tree_ids += 1
  endif
  return g:EclimProjectTreeTitle . t:project_tree_id
endfunction " }}}

function! s:GetSharedTreeBuffer(names) " {{{
  let instance_names = join(a:names, '_')
  let instance_names = substitute(instance_names, '\W', '_', 'g')
  if g:EclimProjectTreeSharedInstance &&
   \ has_key(s:shared_instances_by_names, instance_names)
    return s:shared_instances_by_names[instance_names]
  endif
  return -1
endfunction " }}}

function! s:Mappings() " {{{
  nnoremap <buffer> <silent> E :call <SID>OpenFile('edit')<cr>
  nnoremap <buffer> <silent> S :call <SID>OpenFile('split')<cr>
  nnoremap <buffer> <silent> \| :call <SID>OpenFile('vsplit')<cr>
  nnoremap <buffer> <silent> T :call <SID>OpenFile('tablast \| tabnew')<cr>
  nnoremap <buffer> <silent> F :call <SID>OpenFileName()<cr>
  nnoremap <buffer> <silent> Y :call <SID>YankFileName()<cr>

  " assign to buffer var to get around weird vim issue passing list containing
  " a string w/ a '<' in it on execution of mapping.
  let b:project_tree_help = [
      \ '<cr> - open/close dir, open file',
      \ 'o - toggle dir fold, choose file open action',
      \ 'E - open with :edit',
      \ 'S - open in a new split window',
      \ '| (pipe) - open in a new vertical split window',
      \ 'T - open in a new tab',
      \ 'R - refresh directory',
      \ 'i - view file info',
      \ 's - open shell at directory',
      \ 'p - move cursor to parent dir',
      \ 'P - move cursor to last child of dir',
      \ 'C - set root to dir under the cursor',
      \ 'B - set root up one dir',
      \ '~ - set root to home dir',
      \ 'K - set root to top most dir',
      \ 'F - open/create a file by name',
      \ 'D - create a new directory',
      \ 'Y - yank current file/dir path to the clipboard',
      \ 'A - toggle hide/view hidden files',
      \ ':CD <dir> - set the root to <dir>',
    \ ]
  nnoremap <buffer> <silent> ?
    \ :call eclim#help#BufferHelp(b:project_tree_help, 'horizontal', 10)<cr>
endfunction " }}}

function! s:InfoLine() " {{{
  setlocal modifiable
  let pos = getpos('.')
  if len(b:roots) == 1
    let lnum = line('$') - 1
    if getline(lnum) =~ '^"'
      exec lnum . ',' . lnum . 'delete _'
    endif

    let info = ''
    try
      let info = function('vcs#util#GetInfo')(b:roots[0])
    catch /E\(117\|700\)/
      " fall back to fugitive
      try
        " fugitive calls a User autocmd, so stop if that one is triggering
        " this one to prevent a recursive loop
        if exists('b:eclim_fugative_autocmd')
          return
        endif

        " make sure fugitive has the git dir for the current project
        if !exists('b:git_dir') || (b:git_dir !~ '^\M' . b:roots[0])
          let cwd = ''
          if getcwd() . '/' != b:roots[0]
            let cwd = getcwd()
            exec 'lcd ' . escape(b:roots[0], ' ')
          endif

          if exists('b:git_dir')
            unlet b:git_dir
          endif

          " slight hack to prevent recursive autocmd loop with fugitive
          let b:eclim_fugative_autocmd = 1

          silent! doautocmd fugitive BufReadPost %

          if cwd != ''
            exec 'lcd ' . escape(cwd, ' ')
          endif
        endif

        let info = function('fugitive#statusline')()
        if info != ''
          let branch = substitute(info, '^\[\Git(\(.*\))\]$', '\1', 'g')
          if branch != info
            let info = 'git:' . branch
          endif
        endif
      catch /E\(117\|700\)/
        " noop if the neither function was found
      finally
          silent! unlet b:eclim_fugative_autocmd
      endtry
    endtry

    " &modifiable check for silly side effect of fugitive autocmd
    if info != '' && &modifiable
      call append(line('$') - 1, '" ' . info)
    endif
  endif
  call setpos('.', pos)
  setlocal nomodifiable
endfunction " }}}

function! s:PathEcho() " {{{
  if mode() != 'n'
    return
  endif

  let path = eclim#tree#GetPath()
  let path = substitute(path, eclim#tree#GetRoot(), '', '')
  if path !~ '^"'
    call eclim#util#WideMessage('echo', path)
  else
    call eclim#util#WideMessage('echo', '')
  endif
endfunction " }}}

function! s:OpenFile(action) " {{{
  let path = eclim#tree#GetPath()
  if path !~ '/$'
    if !filereadable(path)
      echo "File is not readable or has been deleted."
      return
    endif

    call eclim#tree#ExecuteAction(path,
      \ "call eclim#project#tree#OpenProjectFile('" . a:action . "', '<file>')")
  endif
endfunction " }}}

function! s:OpenFileName() " {{{
  let path = eclim#tree#GetPath()
  if !isdirectory(path)
    let path = fnamemodify(path, ':h') . '/'
  endif

  let response = input('file: ', path, 'file')
  if response != ''
    let actions = eclim#tree#GetFileActions(response)
    call eclim#tree#ExecuteAction(response, actions[0].action)
  endif
endfunction " }}}

function! s:YankFileName() " {{{
  let path = eclim#tree#GetPath()
  let [@*, @+, @"] = [path, path, path]
  call eclim#util#Echo('Copied path to clipboard: ' . path)
endfunction " }}}

function! eclim#project#tree#ProjectTreeSettings() " {{{
  for action in g:EclimProjectTreeActions
    call eclim#tree#RegisterFileAction(action.pattern, action.name,
      \ "call eclim#project#tree#OpenProjectFile('" . action.action . "', '<file>')")
  endfor

  call eclim#tree#RegisterDirAction(function('eclim#project#tree#InjectLinkedResources'))

  if exists('s:TreeSettingsFunction')
    let l:Settings = function(s:TreeSettingsFunction)
    call l:Settings()
  endif

  augroup eclim_tree
    autocmd User <buffer> call <SID>InfoLine()
    if g:EclimProjectTreePathEcho
      autocmd CursorMoved <buffer> call <SID>PathEcho()
    endif
  augroup END
endfunction " }}}

function! eclim#project#tree#OpenProjectFile(cmd, file) " {{{
  " Execute the supplied command in one of the main content windows.
  if eclim#util#GoToBufferWindow(a:file)
    return
  endif

  let file = a:file
  let cmd = a:cmd
  let cwd = getcwd()

  exec g:EclimProjectTreeContentWincmd

  " if the buffer is a no name and action is split, use edit instead.
  if cmd =~ 'split' && expand('%') == '' &&
   \ !&modified && line('$') == 1 && getline(1) == ''
    let cmd = 'edit'
  endif

  " current file doesn't share same cwd as the project tree
  let lcwd = getcwd()
  if lcwd != cwd && !filereadable(file)
    let file = escape(substitute(cwd, '\', '/', 'g'), ' &') . '/' . file
  endif

  try
    exec cmd . ' ' file
  catch /E325/
    " ignore attention error since the user should be prompted to handle it.
  finally
    if lcwd != cwd
      exec 'lcd ' . escape(cwd, ' ')
    endif
  endtry
endfunction " }}}

function! eclim#project#tree#InjectLinkedResources(dir, contents) " {{{
  let project = eclim#project#util#GetProject(a:dir)
  if len(project) == 0
    return
  endif

  " listing the project root, so inject our project links
  if len(get(project, 'links', {})) &&
   \ substitute(a:dir, '/$', '', '') == project.path
    if !exists('b:links')
      let b:links = {}
    endif
    call extend(b:links, project.links)

    let links = keys(project.links)
    call sort(links)

    let index = 0
    for entry in copy(a:contents)
      if !len(links)
        break
      endif

      while len(links) && links[0] < fnamemodify(entry, ':h:t')
        call insert(a:contents, a:dir . remove(links, 0) . '/', index)
      endwhile
      let index += 1
    endfor

    for link in links
      call add(a:contents, a:dir . link . '/')
    endfor
  endif
endfunction " }}}

function! eclim#project#tree#HorizontalContentWindow() " {{{
  " Command for g:EclimProjectTreeContentWincmd used when relative to a
  " horizontal taglist window.
  winc k
  if exists('g:TagList_title') && bufname(bufnr('%')) == g:TagList_title
    winc k
  endif
endfunction " }}}

" vim:ft=vim:fdm=marker
