"=============================================================================
" FILE: async_cache.vim
" AUTHOR: Shougo Matsushita <Shougo.Matsu@gmail.com>
" License: MIT license  {{{
"     Permission is hereby granted, free of charge, to any person obtaining
"     a copy of this software and associated documentation files (the
"     "Software"), to deal in the Software without restriction, including
"     without limitation the rights to use, copy, modify, merge, publish,
"     distribute, sublicense, and/or sell copies of the Software, and to
"     permit persons to whom the Software is furnished to do so, subject to
"     the following condition
"
"     The above copyright notice and this permission notice shall be included
"     in all copies or substantial portions of the Software.
"
"     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
"     OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
"     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
"     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
"     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
"     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
"     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
" }}}
"=============================================================================

let s:save_cpo = &cpo
set cpo&vim

function! s:main(argv) abort "{{{
  " args: funcname, outputname filename pattern_file_name mark minlen fileencoding
  let [funcname, outputname, filename, pattern_file_name, mark, minlen, fileencoding]
        \ = a:argv

  if funcname ==# 'load_from_file'
    let keyword_list = s:load_from_file(
          \ filename, pattern_file_name, mark, minlen, fileencoding, 1)

    let string = '{' . escape(string(keyword_list)[1 : -2], '\\') . '}'
  else
    let keyword_list = s:load_from_tags(
          \ filename, pattern_file_name, mark, minlen, fileencoding)
    let string = string(keyword_list)
  endif

  if empty(keyword_list)
    return
  endif

  " For neocomplete.
  " Output cache.
  call writefile([string], outputname)
endfunction"}}}

function! s:load_from_file(filename, pattern_file_name, mark, minlen, fileencoding, is_string) abort "{{{
  if !filereadable(a:filename)
    " File not found.
    return []
  endif

  let lines = readfile(a:filename)
  if a:fileencoding !=# &encoding
    let lines = map(lines, 's:iconv(v:val, a:fileencoding, &encoding)')
  endif

  let pattern = get(readfile(a:pattern_file_name), 0, '\h\w*')
  let pattern2 = '^\%('.pattern.'\m\)'
  let keyword_list = []
  let dup_check = {}

  for line in lines "{{{
    let match = match(line, pattern)
    while match >= 0 "{{{
      let match_str = matchstr(line, pattern2, match)

      if !has_key(dup_check, match_str) && len(match_str) >= a:minlen
        " Append list.
        call add(keyword_list, match_str)
        let dup_check[match_str] = 1
      endif

      if match_str == ''
        break
      endif

      let match += len(match_str)

      let match = match(line, pattern, match)
    endwhile"}}}
  endfor"}}}

  if !a:is_string
    call map(keyword_list, "{'word' : match_str}")
  endif

  return keyword_list
endfunction"}}}

function! s:load_from_tags(filename, pattern_file_name, mark, minlen, fileencoding) abort "{{{
  let keyword_lists = []
  let dup_check = {}

  let [tags_file_name, filter_pattern] =
        \ readfile(a:pattern_file_name)[1 : 2]
  if tags_file_name !=# '$dummy$'
    " Check output.
    let tags_list = []

    let i = 0
    while i < 2
      if filereadable(tags_file_name)
        " Use filename.
        let tags_list = map(readfile(tags_file_name),
              \ 's:iconv(v:val, a:fileencoding, &encoding)')
        break
      endif

      sleep 500m
      let i += 1
    endwhile
  else
    if !filereadable(a:filename)
      return []
    endif

    " Use filename.
    let tags_list = map(readfile(a:filename),
          \ 's:iconv(v:val, a:fileencoding, &encoding)')
  endif

  if empty(tags_list)
    return s:load_from_file(a:filename, a:pattern_file_name,
          \ a:mark, a:minlen, a:fileencoding, 0)
  endif

  for line in tags_list
    let tag = split(substitute(line, "\<CR>", '', 'g'), '\t', 1)

    " Add keywords.
    if line =~ '^!' || len(tag) < 3 || len(tag[0]) < a:minlen
          \ || has_key(dup_check, tag[0])
      continue
    endif

    let opt = join(tag[2:], "\<TAB>")
    let cmd = matchstr(opt, '.*/;"')

    let option = {
          \ 'cmd' : substitute(substitute(substitute(cmd,
          \'^\%([/?]\^\?\)\?\s*\|\%(\$\?[/?]\)\?;"$', '', 'g'),
          \ '\\\\', '\\', 'g'), '\\/', '/', 'g'),
          \ 'kind' : ''
          \}
    if option.cmd =~ '\d\+'
      let option.cmd = tag[0]
    endif

    for opt in split(opt[len(cmd):], '\t', 1)
      let key = matchstr(opt, '^\h\w*\ze:')
      if key == ''
        let option['kind'] = opt
      else
        let option[key] = matchstr(opt, '^\h\w*:\zs.*')
      endif
    endfor

    if has_key(option, 'file')
          \ || (has_key(option, 'access') && option.access != 'public')
      continue
    endif

    let abbr = has_key(option, 'signature')? tag[0] . option.signature :
          \ (option['kind'] == 'd' || option['cmd'] == '') ?
          \ tag[0] : option['cmd']
    let abbr = substitute(abbr, '\s\+', ' ', 'g')
    " Substitute "namespace foobar" to "foobar <namespace>".
    let abbr = substitute(abbr,
          \'^\(namespace\|class\|struct\|enum\|union\)\s\+\(.*\)$',
          \'\2 <\1>', '')
    " Substitute typedef.
    let abbr = substitute(abbr,
          \'^typedef\s\+\(.*\)\s\+\(\h\w*\%(::\w*\)*\);\?$',
          \'\2 <typedef \1>', 'g')
    " Substitute extends and implements.
    let abbr = substitute(abbr,
          \'\<\%(extends\|implements\)\s\+\S\+\>', '', '')
    " Substitute marker.
    let abbr = substitute(abbr, '"\s*{{{', '', '')

    let keyword = {
          \ 'word' : tag[0], 'abbr' : abbr, 'menu' : '',
          \ 'kind' : option['kind'],
          \ }
    if has_key(option, 'struct')
      let keyword.menu = option.struct
    elseif has_key(option, 'class')
      let keyword.menu = option.class
    elseif has_key(option, 'enum')
      let keyword.menu = option.enum
    elseif has_key(option, 'union')
      let keyword.menu = option.union
    endif

    call add(keyword_lists, keyword)
    let dup_check[tag[0]] = 1
  endfor"}}}

  if filter_pattern != ''
    call filter(keyword_lists, filter_pattern)
  endif

  return keyword_lists
endfunction"}}}

function! s:truncate(str, width) abort "{{{
  " Original function is from mattn.
  " http://github.com/mattn/googlereader-vim/tree/master

  if a:str =~# '^[\x00-\x7f]*$'
    return len(a:str) < a:width ?
          \ printf('%-'.a:width.'s', a:str) : strpart(a:str, 0, a:width)
  endif

  let ret = a:str
  let width = strdisplaywidth(a:str)
  if width > a:width
    let ret = s:strwidthpart(ret, a:width)
    let width = strdisplaywidth(ret)
  endif

  if width < a:width
    let ret .= repeat(' ', a:width - width)
  endif

  return ret
endfunction"}}}

function! s:strwidthpart(str, width) abort "{{{
  let ret = a:str
  let width = strdisplaywidth(a:str)
  while width > a:width
    let char = matchstr(ret, '.$')
    let ret = ret[: -1 - len(char)]
    let width -= strwidth(char)
  endwhile

  return ret
endfunction"}}}

function! s:iconv(expr, from, to) abort
  if a:from == '' || a:to == '' || a:from ==? a:to
    return a:expr
  endif
  let result = iconv(a:expr, a:from, a:to)
  return result != '' ? result : a:expr
endfunction

if argc() == 7 &&
      \ (argv(0) ==# 'load_from_file' || argv(0) ==# 'load_from_tags')
  try
    call s:main(argv())
  catch
    call writefile([v:throwpoint, v:exception],
          \     fnamemodify(argv(1), ':h:h').'/async_error_log')
  endtry

  qall!
else
  function! neocomplete#async_cache#main(argv) abort "{{{
    call s:main(a:argv)
  endfunction"}}}
endif

let &cpo = s:save_cpo
unlet s:save_cpo

" vim: foldmethod=marker