" =============================================================================
" Filename: autoload/calendar/cache.vim
" Author: itchyny
" License: MIT License
" Last Change: 2020/11/20 00:09:42.
" =============================================================================

let s:save_cpo = &cpo
set cpo&vim

" Cache object.
function! calendar#cache#new(...) abort
  let self = copy(s:self)
  let self.subpath = a:0 ? a:1 : ''
  let self.subpath .= len(self.subpath) && self.subpath[len(self.subpath) - 1] !~ '^[/\\]$' ? '/' : ''
  call s:setfperm_dir(self.dir())
  return self
endfunction

function! calendar#cache#clear() abort
  for path in s:clearpath
    call calendar#util#rmdir(path, 'rf')
  endfor
endfunction

let s:clearpath = []

augroup CalendarCache
  autocmd!
  autocmd VimLeavePre * call calendar#cache#clear()
augroup END

let s:self = {}

function! s:self.new(...) dict abort
  return calendar#cache#new(self.subpath . (a:0 ? self.escape(a:1) : ''))
endfunction

function! s:self.escape(key) dict abort
  return substitute(a:key, '[^a-zA-Z0-9_.-]', '\=printf("%%%02X",char2nr(submatch(0)))', 'g')
endfunction

if has('win32')
  function! s:self.dir() dict abort
    return substitute(substitute(s:expand_homedir(calendar#setting#get('cache_directory')), '[/\\]$', '', '') . '/' . self.subpath, '/', '\', 'g')
  endfunction
else
  function! s:self.dir() dict abort
    return substitute(s:expand_homedir(calendar#setting#get('cache_directory')), '[/\\]$', '', '') . '/' . self.subpath
  endfunction
endif

function! s:expand_homedir(path) abort
  if a:path !~# '^[~]/'
    return a:path
  endif
  return expand('~') . a:path[1:]
endfunction

function! s:self.path(key) dict abort
  return self.dir() . self.escape(a:key)
endfunction

function! s:self.rmdir_on_exit() dict abort
  call add(s:clearpath, self.dir())
endfunction

function! s:self.check_dir(...) dict abort
  let dir = self.dir()
  if !get(a:000, 0)
    return !isdirectory(dir)
  endif
  if !isdirectory(dir)
    try
      if exists('*mkdir')
        call mkdir(dir, 'p')
      else
        call calendar#util#system('mkdir -p ' .  shellescape(dir))
      endif
      call s:setfperm(dir)
    catch
    endtry
  endif
  if !isdirectory(dir)
    call calendar#echo#error(calendar#message#get('mkdir_fail') . ': ' . dir)
    return 1
  endif
endfunction

function! s:self.save(key, val) dict abort
  if self.check_dir(1)
    return 1
  endif
  let path = self.path(a:key)
  if filereadable(path) && !filewritable(path)
    call calendar#echo#error(calendar#message#get('cache_file_unwritable') . ': ' . path)
    return 1
  endif
  try
    call writefile(calendar#cache#string(a:val), path)
    call s:setfperm_file(path)
  catch
    call calendar#echo#error(calendar#message#get('cache_write_fail') . ': ' . path)
    return 1
  endtry
endfunction

function! s:self.get(key) dict abort
  if self.check_dir()
    return 1
  endif
  let path = self.path(a:key)
  if filereadable(path)
    call s:setfperm_file(path)
    let result = readfile(path)
    try
      if len(result)
        if exists('*js_decode') && has('patch-8.0.0216')
          return js_decode(len(result) > 1 ? join(result, '') : result[0])
        endif
        sandbox return eval(join(result, ''))
      else
        return 1
      endif
    catch
      return 1
    endtry
  else
    return 1
  endif
endfunction

function! s:self.get_raw(key) dict abort
  if self.check_dir()
    return 1
  endif
  let path = self.path(a:key)
  if filereadable(path)
    call s:setfperm_file(path)
    return readfile(path)
  else
    return 1
  endif
endfunction

function! s:self.delete(key) dict abort
  if self.check_dir()
    return 1
  endif
  let path = self.path(a:key)
  return delete(path)
endfunction

function! s:self.clear() dict abort
  call calendar#util#rmdir(self.dir(), 'rf')
endfunction

if exists('*json_encode')
  function! calendar#cache#string(v) abort
    return [json_encode(a:v)]
  endfunction
else
  " string() with making newlines and indents properly.
  function! calendar#cache#string(v, ...) abort
    let r = []
    let f = 1
    let s = a:0 ? a:1 : ''
    if type(a:v) == type([])
      call add(r, '[ ')
      let s .= '  '
      for i in range(len(a:v))
        call add(r, s . string(a:v[i]) . ',')
      endfor
      if r[-1][len(r[-1]) - 1] ==# ','
        let r[-1] = r[-1][:-2]
      endif
      call add(r, ' ]')
    elseif type(a:v) == type({})
      call add(r, '{ ')
      let s .= '  '
      for k in keys(a:v)
        if type(a:v[k]) == type({}) || type(a:v[k]) == type([]) && len(a:v[k]) > 2
          let result = calendar#cache#string(a:v[k], s . repeat(' ', len(string(k)) + 2))
          let result[-1] .= ','
          call add(r, s . string(k) . ': ' . result[0])
          call remove(result, 0)
          call extend(r, result)
        else
          call add(r, s . string(k) . ': ' . string(a:v[k]) . ',')
        endif
      endfor
      if r[-1][len(r[-1]) - 1] ==# ','
        let r[-1] = r[-1][:-2]
      endif
      call add(r, ' }')
    else
      call add(r, s . string(a:v))
      let f = 0
    endif
    if f
      if len(r[1]) > len(s) + 1
        let r[1] = r[1][len(s):]
      endif
      let r[0] .= r[1]
      call remove(r, 1)
      if len(r) > 1
        let r[-2] .= r[-1]
        call remove(r, -1)
      endif
    endif
    return r
  endfunction
endif

if exists('*getfperm') && exists('*setfperm')
  function! s:setfperm_dir(dir) abort
    let expected = 'rwx------'
    if getfperm(a:dir) !=# expected
      call setfperm(a:dir, expected)
    endif
  endfunction
  function! s:setfperm_file(path) abort
    let expected = 'rw-------'
    if getfperm(a:path) !=# expected
      call setfperm(a:path, expected)
    endif
  endfunction
else
  function! s:setfperm_dir(dir) abort
  endfunction
  function! s:setfperm_file(path) abort
  endfunction
endif

let &cpo = s:save_cpo
unlet s:save_cpo