" =============================================================================
" Filename: autoload/calendar/google/task.vim
" Author: itchyny
" License: MIT License
" Last Change: 2021/09/18 13:24:16.
" =============================================================================

let s:save_cpo = &cpo
set cpo&vim

let s:cache = calendar#cache#new('google')

let s:task_cache = s:cache.new('task')

function! calendar#google#task#get_url(type) abort
  return 'https://www.googleapis.com/tasks/v1/' . a:type
endfunction

function! calendar#google#task#getTaskList() abort
  let taskList = s:cache.get('taskList')
  if type(taskList) != type({}) || calendar#timestamp#update('google_tasklist', 7 * 24 * 60 * 60)
    call calendar#google#client#get_async(s:newid(['taskList', 0]),
          \ 'calendar#google#task#getTaskList_response',
          \ calendar#google#task#get_url('users/@me/lists'))
    if type(taskList) != type({})
      return {}
    endif
  endif
  return taskList
endfunction

function! calendar#google#task#getTaskList_response(id, response) abort
  let [_tasklist, err; rest] = s:getdata(a:id)
  if a:response.status =~# '^2'
    let cnt = calendar#webapi#decode(a:response.content)
    let content = type(cnt) == type({}) ? cnt : {}
    if has_key(content, 'items') && type(content.items) == type([])
      call s:cache.save('taskList', content)
      silent! let b:calendar.task._updated = 1
      silent! call b:calendar.update()
    endif
  elseif a:response.status == 401
    if err == 0
      call calendar#google#client#refresh_token()
      call calendar#google#client#get_async(s:newid(['taskList', err + 1]),
            \ 'calendar#google#task#getTaskList_response',
            \ calendar#google#task#get_url('users/@me/lists'))
    endif
  endif
endfunction

function! calendar#google#task#getTasks() abort
  if calendar#timestamp#update('google_task', 30 * 60)
    call calendar#async#new('calendar#google#task#downloadTasks(1)')
  endif
  let allTaskList = []
  let taskList = calendar#google#task#getTaskList()
  if has_key(taskList, 'items') && type(taskList.items) == type([])
    for tasklist in taskList.items
      call add(allTaskList, tasklist)
      let allTaskList[-1].items = []
      unlet! cnt
      let cnt = s:task_cache.new(tasklist.id).get('information')
      if type(cnt) == type({}) && cnt != {}
        let i = 0
        let allTaskList[-1].etag = cnt.etag
        let items = []
        while type(cnt) == type({})
          unlet! cnt
          let cnt = s:task_cache.new(tasklist.id).get(i)
          if type(cnt) == type({}) && cnt != {} && has_key(cnt, 'items') && type(cnt.items) == type([])
            call extend(items, cnt.items)
          endif
          let i += 1
        endwhile
        for item in items
          if has_key(item, 'due') && item.due =~# '\v\d+-\d+-\d+T'
            let [y, m, d] = map(split(substitute(substitute(item.due, 'T.*', '', ''), '\s', '', 'g'), '[-/]'), 'substitute(v:val, "^0", "", "") + 0')
            let item.title = calendar#day#join_date([y, m, d]) . ' ' . get(item, 'title', '')
            call remove(item, 'due')
          endif
          if has_key(item, 'notes') && item.notes !=# ''
            let item.title = get(item, 'title', '') . ' note: ' . get(item, 'notes', '')
          endif
        endfor
        call sort(items, function('calendar#google#task#sorter'))
        let i = 0
        while i < len(items)
          if !has_key(items[i], 'parent')
            break
          endif
          let j = i + 1
          let items[i].prefix = ' +- '
          while j < len(items)
            if items[j].id ==# items[i].parent
              while j < len(items) - 1
                if get(items[j + 1], 'parent', '') ==# items[i].parent
                  let items[j + 1].prefix = ' |- '
                  let j += 1
                else
                  break
                endif
              endwhile
              call insert(items, items[i], j + 1)
              call remove(items, i)
              let i -= 1
              break
            endif
            let j += 1
          endwhile
          let i += 1
        endwhile
        let allTaskList[-1].items = items
      else
        call calendar#google#task#downloadTasks()
      endif
    endfor
  endif
  return allTaskList
endfunction

function! calendar#google#task#sorter(x, y) abort
  return has_key(a:x, 'parent') != has_key(a:y, 'parent')
        \ ? (has_key(a:x, 'parent') ? -1 : 1)
        \ : a:x.position ==# a:y.position
        \ ? (a:x.updated > a:y.updated ? 1 : -1)
        \ : a:x.position > a:y.position ? 1 : -1
endfunction

" Optional argument: Force download.
function! calendar#google#task#downloadTasks(...) abort
  let taskList = calendar#google#task#getTaskList()
  if has_key(taskList, 'items') && type(taskList.items) == type([]) && len(taskList.items)
    let j = 0
    while j < len(taskList.items)
      let item = taskList.items[j]
      unlet! cnt
      let cnt = s:task_cache.new(item.id).get('information')
      if type(cnt) != type({}) || cnt == {} || get(a:000, 0) && (a:0 <= 1 || item.id ==# get(a:000, 1, ''))
        let opt = { 'tasklist': item.id, 'maxResults': 100 }
        call calendar#google#client#get_async(s:newid(['download', 0, j, 0, item.id, a:000]),
              \ 'calendar#google#task#response',
              \ calendar#google#task#get_url('lists/' . item.id . '/tasks'), opt)
        break
      endif
      let j += 1
    endwhile
    if j == len(taskList.items)
      silent! let b:calendar.task._updated = 1
      silent! call b:calendar.update()
    endif
  endif
endfunction

function! calendar#google#task#response(id, response) abort
  let taskList = calendar#google#task#getTaskList()
  let [_download, err, j, i, id, force; rest] = s:getdata(a:id)
  let opt = { 'tasklist': id }
  if a:response.status =~# '^2'
    let cnt = calendar#webapi#decode(a:response.content)
    let content = type(cnt) == type({}) ? cnt : {}
    if has_key(content, 'items')
      call s:task_cache.new(id).save(i, content)
      if i == 0
        call remove(content, 'items')
        call s:task_cache.new(id).save('information', content)
      endif
      if has_key(content, 'nextPageToken')
        let opt = extend(opt, { 'pageToken': content.nextPageToken })
        call calendar#google#client#get_async(s:newid(['download', err, j, i + 1, id, force]),
              \ 'calendar#google#task#response',
              \ calendar#google#task#get_url('lists/' . id . '/tasks'), opt)
      else
        let k = i + 1
        while filereadable(s:task_cache.new(id).path(k))
          silent! call s:task_cache.new(id).delete(k)
          let k += 1
        endwhile
        let j += 1
        while j < len(taskList.items)
          let item = taskList.items[j]
          unlet! cnt
          let cnt = s:task_cache.new(item.id).get('information')
          let opt = { 'tasklist': item.id, 'maxResults': 100 }
          if type(cnt) != type({}) || cnt == {} || get(force, 0) && (len(force) <= 1 || item.id ==# get(force, 1, ''))
            call calendar#google#client#get_async(s:newid(['download', 0, j, 0, item.id, force]),
                  \ 'calendar#google#task#response',
                  \ calendar#google#task#get_url('lists/' . item.id . '/tasks'), opt)
            break
          endif
          let j += 1
        endwhile
        if j == len(taskList.items)
          silent! let b:calendar.task._updated = 1
          silent! call b:calendar.update()
        endif
      endif
    elseif i == 0 && has_key(content, 'etag')
      let k = 0
      while filereadable(s:task_cache.new(id).path(k))
        silent! call s:task_cache.new(id).delete(k)
        let k += 1
      endwhile
      if k > 0
        silent! let b:calendar.task._updated = 1
        silent! call b:calendar.update()
      endif
    endif
  elseif a:response.status == 401
    if i == 0 && err == 0
      let opt = { 'tasklist': id }
      call calendar#google#client#refresh_token()
      call calendar#google#client#get_async(s:newid(['download', err + 1, j, i, id, force]),
            \ 'calendar#google#task#response',
            \ calendar#google#task#get_url('lists/' . id . '/tasks'), opt)
    endif
  endif
endfunction

function! calendar#google#task#insert(id, previous, parent, title, ...) abort
  let opt = { 'tasklist': a:id }
  if a:previous !=# ''
    let opt.previous = a:previous
  endif
  if a:parent !=# ''
    let opt.parent = a:parent
  endif
  let due = ''
  if a:0
    let due = get(a:1, 'due', '')
    if due !=# ''
      let due = due . (due =~# 'Z$' ? '' : 'Z')
    endif
  endif
  let note = ''
  if a:title =~# ' note: '
    let note = matchstr(a:title, ' note: .*$')
    let title = a:title[:(len(a:title) - len(note)) - 1]
    let note = substitute(note, ' note:\s*', '', '')
  else
    let note = ''
    let title = a:title
  endif
  call calendar#google#client#post_async(s:newid(['insert', 0, a:id, title, note, due, opt]),
        \ 'calendar#google#task#insert_response',
        \ calendar#google#task#get_url('lists/' . a:id . '/tasks'),
        \ opt, extend({ 'title': title, 'notes': note }, due ==# '' ? {} : { 'due': due ==# '-1Z' ? function('calendar#webapi#null') : due }))
endfunction

function! calendar#google#task#insert_response(id, response) abort
  let [_insert, err, id, title, note, due, opt; rest] = s:getdata(a:id)
  if a:response.status =~# '^2'
    call calendar#google#task#downloadTasks(1, id)
  elseif a:response.status == 401
    if err == 0
      call calendar#google#client#refresh_token()
      call calendar#google#client#post_async(s:newid(['insert', 1, id, title, note, due, opt]),
            \ 'calendar#google#task#insert_response',
            \ calendar#google#task#get_url('lists/' . id . '/tasks'),
            \ opt, extend({ 'title': title, 'notes': note }, due ==# '' ? {} : { 'due': due ==# '-1Z' ? function('calendar#webapi#null') : due }))
    endif
  endif
endfunction

function! calendar#google#task#move(id, taskid, previous, parent) abort
  let opt = { 'tasklist': a:id }
  if a:previous !=# ''
    let opt.previous = a:previous
  endif
  if a:parent !=# ''
    let opt.parent = a:parent
  endif
  call calendar#google#client#post_async(s:newid(['move', 0, a:id, a:taskid, opt]),
        \ 'calendar#google#task#move_response',
        \ calendar#google#task#get_url('lists/' . a:id . '/tasks/' . a:taskid . '/move'),
        \ opt, {})
endfunction

function! calendar#google#task#move_response(id, response) abort
  let [_move, err, id, taskid, opt; rest] = s:getdata(a:id)
  if a:response.status =~# '^2'
    call calendar#google#task#downloadTasks(1, id)
  elseif a:response.status == 401
    if err == 0
      call calendar#google#client#refresh_token()
      call calendar#google#client#post_async(s:newid(['move', 1, id, taskid, opt]),
            \ 'calendar#google#task#move_response',
            \ calendar#google#task#get_url('lists/' . id . '/tasks/' . taskid . '/move'),
            \ opt, {})
    endif
  endif
endfunction

function! calendar#google#task#clear_completed(id) abort
  call calendar#google#client#post_async(s:newid(['clear_completed', 0, a:id]),
        \ 'calendar#google#task#clear_completed_response',
        \ calendar#google#task#get_url('lists/' . a:id . '/clear'),
        \ { 'tasklist': a:id })
endfunction

function! calendar#google#task#clear_completed_response(id, response) abort
  let [_clear_completed, err, id; rest] = s:getdata(a:id)
  if a:response.status =~# '^2'
    call calendar#google#task#downloadTasks(1, id)
  elseif a:response.status == 401
    if err == 0
      call calendar#google#client#refresh_token()
      call calendar#google#client#post_async(s:newid(['clear_completed', 1, id]),
            \ 'calendar#google#task#clear_completed_response',
            \ calendar#google#task#get_url('lists/' . id . '/clear'),
            \ { 'tasklist': id })
    endif
  endif
endfunction

function! calendar#google#task#update(id, taskid, title, ...) abort
  let due = ''
  if a:0
    let due = get(a:1, 'due', '')
    if due !=# ''
      let due = due . (due =~# 'Z$' ? '' : 'Z')
    endif
  endif
  let note = ''
  if a:title =~# ' note: '
    let note = matchstr(a:title, ' note: .*$')
    let title = a:title[:(len(a:title) - len(note)) - 1]
    let note = substitute(note, ' note:\s*', '', '')
  else
    let note = ''
    let title = a:title
  endif
  call calendar#google#client#put_async(s:newid(['update', 0, a:id, a:taskid, title, note, due]),
        \ 'calendar#google#task#update_response',
        \ calendar#google#task#get_url('lists/' . a:id . '/tasks/' . a:taskid),
        \ { 'tasklist': a:id, 'task': a:taskid },
        \ extend({ 'id': a:taskid, 'title': title, 'notes': note }, due ==# '' ? {} : { 'due': due ==# '-1Z' ? function('calendar#webapi#null') : due }))
endfunction

function! calendar#google#task#update_response(id, response) abort
  let [_update, err, id, taskid, title, note, due; rest] = s:getdata(a:id)
  if a:response.status =~# '^2'
    call calendar#google#task#downloadTasks(1, id)
  elseif a:response.status == 401
    if err == 0
      call calendar#google#client#refresh_token()
      call calendar#google#client#put_async(s:newid(['update', 1, id, taskid, title, note, due]),
            \ 'calendar#google#task#update_response',
            \ calendar#google#task#get_url('lists/' . id . '/tasks/' . taskid),
            \ { 'tasklist': id, 'task': taskid },
            \ extend({ 'id': taskid, 'title': title, 'notes': note }, due ==# '' ? {} : { 'due': due ==# '-1Z' ? function('calendar#webapi#null') : due }))
    endif
  endif
endfunction

function! calendar#google#task#complete(id, taskid) abort
  call calendar#google#client#patch_async(s:newid(['complete', 0, a:id, a:taskid]),
        \ 'calendar#google#task#complete_response',
        \ calendar#google#task#get_url('lists/' . a:id . '/tasks/' . a:taskid),
        \ { 'tasklist': a:id, 'task': a:taskid },
        \ { 'id': a:taskid, 'status': 'completed' })
endfunction

function! calendar#google#task#complete_response(id, response) abort
  let [_complete, err, id, taskid; rest] = s:getdata(a:id)
  if a:response.status =~# '^2'
    call calendar#google#task#downloadTasks(1, id)
  elseif a:response.status == 401
    if err == 0
      call calendar#google#client#refresh_token()
      call calendar#google#client#patch_async(s:newid(['complete', 1, id, taskid]),
            \ 'calendar#google#task#complete_response',
            \ calendar#google#task#get_url('lists/' . id . '/tasks/' . taskid),
            \ { 'tasklist': id, 'task': taskid },
            \ { 'id': taskid, 'status': 'completed' })
    endif
  endif
endfunction

function! calendar#google#task#uncomplete(id, taskid) abort
  call calendar#google#client#patch_async(s:newid(['uncomplete', 0, a:id, a:taskid]),
        \ 'calendar#google#task#uncomplete_response',
        \ calendar#google#task#get_url('lists/' . a:id . '/tasks/' . a:taskid),
        \ { 'tasklist': a:id, 'task': a:taskid },
        \ { 'id': a:taskid, 'status': 'needsAction' })
endfunction

function! calendar#google#task#uncomplete_response(id, response) abort
  let [_uncomplete, err, id, taskid; rest] = s:getdata(a:id)
  if a:response.status =~# '^2'
    call calendar#google#task#downloadTasks(1, id)
  elseif a:response.status == 401
    if err == 0
      call calendar#google#client#refresh_token()
      call calendar#google#client#patch_async(s:newid(['uncomplete', 1, id, taskid]),
            \ 'calendar#google#task#uncomplete_response',
            \ calendar#google#task#get_url('lists/' . id . '/tasks/' . taskid),
            \ { 'tasklist': id, 'task': taskid },
            \ { 'id': taskid, 'status': 'needsAction' })
    endif
  endif
endfunction

function! calendar#google#task#delete(id, taskid) abort
  call calendar#google#client#delete_async(s:newid(['delete', 0, a:id, a:taskid]),
        \ 'calendar#google#task#delete_response',
        \ calendar#google#task#get_url('lists/' . a:id . '/tasks/' . a:taskid),
        \ { 'tasklist': a:id, 'task': a:taskid },
        \ { 'id': a:taskid })
endfunction

function! calendar#google#task#delete_response(id, response) abort
  let [_delete, err, id, taskid; rest] = s:getdata(a:id)
  if a:response.status =~# '^2'
    call calendar#google#task#downloadTasks(1, id)
  elseif a:response.status == 401
    if err == 0
      call calendar#google#client#refresh_token()
      call calendar#google#client#delete_async(s:newid(['delete', 1, id, taskid]),
            \ 'calendar#google#task#delete_response',
            \ calendar#google#task#get_url('lists/' . id . '/tasks/' . taskid),
            \ { 'tasklist': id, 'task': taskid },
            \ { 'id': taskid })
    endif
  endif
endfunction

let s:id_data = {}
function! s:newid(data) abort
  let id = join([ 'google', 'task', a:data[0] ], '_') . '_' . calendar#util#id()
  let s:id_data[id] = a:data
  return id
endfunction

function! s:getdata(id) abort
  return s:id_data[a:id]
endfunction

let &cpo = s:save_cpo
unlet s:save_cpo