let s:file = ''
let s:using_xolox_shell = -1
let s:exit_code = 0

function! gitgutter#utility#setbufvar(buffer, varname, val)
  let dict = get(getbufvar(a:buffer, ''), 'gitgutter', {})
  let dict[a:varname] = a:val
  call setbufvar(a:buffer, 'gitgutter', dict)
endfunction

function! gitgutter#utility#getbufvar(buffer, varname, ...)
  let dict = get(getbufvar(a:buffer, ''), 'gitgutter', {})
  if has_key(dict, a:varname)
    return dict[a:varname]
  else
    if a:0
      return a:1
    endif
  endif
endfunction

function! gitgutter#utility#warn(message) abort
  echohl WarningMsg
  echo 'vim-gitgutter: ' . a:message
  echohl None
  let v:warningmsg = a:message
endfunction

function! gitgutter#utility#warn_once(message, key) abort
  if empty(gitgutter#utility#getbufvar(s:bufnr, a:key))
    call gitgutter#utility#setbufvar(s:bufnr, a:key, '1')
    echohl WarningMsg
    redraw | echo 'vim-gitgutter: ' . a:message
    echohl None
    let v:warningmsg = a:message
  endif
endfunction

" Returns truthy when the buffer's file should be processed; and falsey when it shouldn't.
" This function does not and should not make any system calls.
function! gitgutter#utility#is_active() abort
  return g:gitgutter_enabled &&
        \ !pumvisible() &&
        \ gitgutter#utility#is_file_buffer() &&
        \ gitgutter#utility#exists_file() &&
        \ gitgutter#utility#not_git_dir()
endfunction

function! gitgutter#utility#not_git_dir() abort
  return gitgutter#utility#full_path_to_directory_of_file() !~ '[/\\]\.git\($\|[/\\]\)'
endfunction

function! gitgutter#utility#is_file_buffer() abort
  return empty(getbufvar(s:bufnr, '&buftype'))
endfunction

" A replacement for the built-in `shellescape(arg)`.
"
" Recent versions of Vim handle shell escaping pretty well.  However older
" versions aren't as good.  This attempts to do the right thing.
"
" See:
" https://github.com/tpope/vim-fugitive/blob/8f0b8edfbd246c0026b7a2388e1d883d579ac7f6/plugin/fugitive.vim#L29-L37
function! gitgutter#utility#shellescape(arg) abort
  if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
    return a:arg
  elseif &shell =~# 'cmd' || gitgutter#utility#using_xolox_shell()
    return '"' . substitute(substitute(a:arg, '"', '""', 'g'), '%', '"%"', 'g') . '"'
  else
    return shellescape(a:arg)
  endif
endfunction

function! gitgutter#utility#set_buffer(bufnr) abort
  let s:bufnr = a:bufnr
  let s:file = resolve(bufname(a:bufnr))
endfunction

function! gitgutter#utility#bufnr()
  return s:bufnr
endfunction

function! gitgutter#utility#file()
  return s:file
endfunction

function! gitgutter#utility#filename() abort
  return fnamemodify(s:file, ':t')
endfunction

function! gitgutter#utility#extension() abort
  return fnamemodify(s:file, ':e')
endfunction

function! gitgutter#utility#full_path_to_directory_of_file() abort
  return fnamemodify(s:file, ':p:h')
endfunction

function! gitgutter#utility#directory_of_file() abort
  return fnamemodify(s:file, ':h')
endfunction

function! gitgutter#utility#exists_file() abort
  return filereadable(s:file)
endfunction

function! gitgutter#utility#has_unsaved_changes() abort
  return getbufvar(s:bufnr, "&mod")
endfunction

function! gitgutter#utility#has_fresh_changes() abort
  return getbufvar(s:bufnr, 'changedtick') != gitgutter#utility#getbufvar(s:bufnr, 'last_tick')
endfunction

function! gitgutter#utility#save_last_seen_change() abort
  call gitgutter#utility#setbufvar(s:bufnr, 'last_tick', getbufvar(s:bufnr, 'changedtick'))
endfunction

function! gitgutter#utility#shell_error() abort
  return gitgutter#utility#using_xolox_shell() ? s:exit_code : v:shell_error
endfunction

function! gitgutter#utility#using_xolox_shell() abort
  if s:using_xolox_shell == -1
    if !g:gitgutter_avoid_cmd_prompt_on_windows
      let s:using_xolox_shell = 0
    " Although xolox/vim-shell works on both windows and unix we only want to use
    " it on windows.
    elseif has('win32') || has('win64') || has('win32unix')
      let s:using_xolox_shell = exists('g:xolox#misc#version') && exists('g:xolox#shell#version')
    else
      let s:using_xolox_shell = 0
    endif
  endif
  return s:using_xolox_shell
endfunction

function! gitgutter#utility#system(cmd, ...) abort
  call gitgutter#debug#log(a:cmd, a:000)

  if gitgutter#utility#using_xolox_shell()
    let options = {'command': a:cmd, 'check': 0}
    if a:0 > 0
      let options['stdin'] = a:1
    endif
    let ret = xolox#misc#os#exec(options)
    let output = join(ret.stdout, "\n")
    let s:exit_code = ret.exit_code
  else
    silent let output = (a:0 == 0) ? system(a:cmd) : system(a:cmd, a:1)
  endif
  return output
endfunction

function! gitgutter#utility#file_relative_to_repo_root() abort
  let file_path_relative_to_repo_root = gitgutter#utility#getbufvar(s:bufnr, 'repo_relative_path')
  if empty(file_path_relative_to_repo_root)
    let dir_path_relative_to_repo_root = gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file(g:gitgutter_git_executable.' rev-parse --show-prefix'))
    let dir_path_relative_to_repo_root = gitgutter#utility#strip_trailing_new_line(dir_path_relative_to_repo_root)
    let file_path_relative_to_repo_root = dir_path_relative_to_repo_root . gitgutter#utility#filename()
    call gitgutter#utility#setbufvar(s:bufnr, 'repo_relative_path', file_path_relative_to_repo_root)
  endif
  return file_path_relative_to_repo_root
endfunction

function! gitgutter#utility#command_in_directory_of_file(cmd) abort
  return 'cd '.gitgutter#utility#shellescape(gitgutter#utility#directory_of_file()).' && '.a:cmd
endfunction

function! gitgutter#utility#highlight_name_for_change(text) abort
  if a:text ==# 'added'
    return 'GitGutterLineAdded'
  elseif a:text ==# 'removed'
    return 'GitGutterLineRemoved'
  elseif a:text ==# 'removed_first_line'
    return 'GitGutterLineRemovedFirstLine'
  elseif a:text ==# 'modified'
    return 'GitGutterLineModified'
  elseif a:text ==# 'modified_removed'
    return 'GitGutterLineModifiedRemoved'
  endif
endfunction

" Dedups list in-place.
" Assumes list has no empty entries.
function! gitgutter#utility#dedup(list)
  return filter(sort(a:list), 'index(a:list, v:val, v:key + 1) == -1')
endfunction

function! gitgutter#utility#strip_trailing_new_line(line) abort
  return substitute(a:line, '\n$', '', '')
endfunction

" True for git v1.7.2+.
function! gitgutter#utility#git_supports_command_line_config_override() abort
  call system(g:gitgutter_git_executable.' -c foo.bar=baz --version')
  return !v:shell_error
endfunction

function! gitgutter#utility#stringify(list) abort
  return join(a:list, "\n")."\n"
endfunction

function! gitgutter#utility#use_known_shell() abort
  if has('unix')
    if &shell !=# 'sh'
      let s:shell = &shell
      let s:shellcmdflag = &shellcmdflag
      let s:shellredir = &shellredir
      let &shell = 'sh'
      set shellcmdflag=-c
      set shellredir=>%s\ 2>&1
    endif
  endif
endfunction

function! gitgutter#utility#restore_shell() abort
  if has('unix')
    if exists('s:shell')
      let &shell = s:shell
      let &shellcmdflag = s:shellcmdflag
      let &shellredir = s:shellredir
    endif
  endif
endfunction