" =============================================================================
" Filename: autoload/calendar/time.vim
" Author: itchyny
" License: MIT License
" Last Change: 2017/05/08 07:45:01.
" =============================================================================

let s:save_cpo = &cpo
set cpo&vim

" Time object
"   h: hour
"   m: minute
"   s: second
function! calendar#time#new(h, m, s) abort
  return extend(copy(s:self), { 'h': a:h, 'm': a:m, 's': a:s })
endfunction

if exists('*strftime')
  function! calendar#time#now() abort
    return calendar#time#new(strftime('%H') * 1, strftime('%M') * 1, strftime('%S') * 1)
  endfunction
else
  function! calendar#time#now() abort
    return calendar#time#new(system('date "+%H"') * 1, system('date "+%M"') * 1, system('date "+%S"') * 1)
  endfunction
endif

function! calendar#time#hour12(h) abort
  return a:h == 0 ? 12 : a:h < 13 ? a:h : a:h - 12
endfunction

let s:time_zone_cache = {}
function! calendar#time#time_zone() abort
  let time_zone = calendar#setting#get('time_zone')
  if has_key(s:time_zone_cache, time_zone)
    return s:time_zone_cache[time_zone]
  endif
  if time_zone ==# ''
    return 0
  endif
  let str = time_zone
  let sign_str = str[0] ==# '-' ? '-' : str[0] ==# '+' ? '+' : ''
  let str = str[len(sign_str):]
  let d = matchstr(str, '^\d\+')
  let str = str[len(d):]
  let [ h, m, s ] = [ 0, 0, 0 ]
  let onlyhour = 0
  if len(d) == 1 ||  len(d) == 2
    let h = d + 0
    let onlyhour = 1
  elseif len(d) == 3
    let h = d[0] + 0
    let m = d[1:] + 0
  elseif len(d) == 4
    let h = d[:1] + 0
    let m = d[2:] + 0
  elseif len(d) >= 5
    let h = d[:1] + 0
    let m = d[2:] + 0
    let s = d[4:] + 0
  endif
  let str = substitute(str, '^[^[:digit:]]\+', '', 'g')
  let d = matchstr(str, '^\d\+')
  let str = str[len(d):]
  if len(d) == 1 || len(d) == 2
    if onlyhour
      let m = d + 0
    else
      let s = d + 0
    endif
  elseif len(d) == 3
    if onlyhour
      let m = d[0] + 0
      let s = d[1:] + 0
    else
      let s = d + 0
    endif
  elseif len(d) == 4
    if onlyhour
      let m = d[:1] + 0
      let s = d[2:] + 0
    else
      let s = d + 0
    endif
  endif
  let str = substitute(str, '^[^[:digit:]]\+', '', 'g')
  let d = matchstr(str, '^\d\+')
  if len(d)
    let s = d + 0
  endif
  let s:time_zone_cache[time_zone] = (sign_str ==# '-' ? -1 : 1) * (((h * 60) + m) * 60 + s)
  return s:time_zone_cache[time_zone]
endfunction

let s:time_cache = {}
function! calendar#time#parse(str) abort
  if a:str ==# ''
    return 0
  endif
  if has_key(s:time_cache, a:str)
    return s:time_cache[a:str]
  endif
  let [ h, m, s ] = [ 0, 0, 0 ]
  let timestr = matchstr(a:str, '^\d\+:\d\+\%(:\d\+\)\?')
  let str = a:str[len(timestr):]
  let hms = map(split(timestr, ':'), 'v:val + 0')
  if len(hms) == 3
    let [ h, m, s ] = hms
  elseif len(hms) == 2
    let [ h, m ] = hms
  endif
  let time = ((h * 60) + m) * 60 + s
  if str ==? 'Z'
    let s:time_cache[a:str] = time
    return s:time_cache[a:str]
  endif
  if str ==# ''
    let s:time_cache[a:str] = time - calendar#time#time_zone()
    return s:time_cache[a:str]
  endif
  if has_key(s:time_cache, str)
    let [ dh, dm, ds ] = s:time_cache[str]
  else
    let [ dh, dm, ds ] = [ 0, 0, 0 ]
    let timestr = matchstr(str, '-\?\d\+:\d\+\%(:\d\+\)\?')
    let hms = map(split(timestr, ':'), 'v:val + 0')
    if len(hms) == 3
      let [ dh, dm, ds ] = hms
    elseif len(hms) == 2
      let [ dh, dm ] = hms
    endif
    let s:time_cache[str] = [ dh, dm, ds ]
  endif
  let s:time_cache[a:str] = time - (((dh * 60) + dm) * 60 + ds)
  return s:time_cache[a:str]
endfunction

let s:datetime_cache = {}
function! calendar#time#datetime(str) abort
  let time_zone = calendar#time#time_zone()
  let key = a:str . ',' . time_zone
  if has_key(s:datetime_cache, key)
    return s:datetime_cache[key]
  endif
  let time = a:str =~# 'T' ? calendar#time#parse(matchstr(a:str, 'T\zs.*')) + time_zone : 0
  let ymd = map(split(matchstr(a:str, '\d\+-\d\+-\d\+'), '-'), 'v:val + 0')
  if len(ymd) != 3
    return []
  endif
  let [ y, m, d ] = ymd
  let min = s:div(time, 60)
  let sec = time - 60 * min
  let hour = s:div(min, 60)
  let min -= 60 * hour
  let day = s:div(hour, 24)
  let hour -= 24 * day
  if day != 0
    let [ y, m, d ] = calendar#day#new(y, m, d).add(day).get_ymd()
  endif
  let s:datetime_cache[key] = [ y, m, d, hour, min, sec ]
  return s:datetime_cache[key]
endfunction

let s:self = {}

function! s:div(x, y) abort
  return a:x/a:y-((a:x<0)&&(a:x%a:y))
endfunction

function! s:self.new(h, m, s) dict abort
  return calendar#time#new(a:h, a:m, a:s)
endfunction

function! s:self.get_hms() dict abort
  return [self.h, self.m, self.s]
endfunction

function! s:self.add_hour(diff) dict abort
  let [h, m, s] = self.get_hms()
  let d = 0
  let h += a:diff
  let d += s:div(h, 24)
  let h -= 24 * s:div(h, 24)
  return [d, self.new(h, m, s)]
endfunction

function! s:self.add_minute(diff) dict abort
  let [h, m, s] = self.get_hms()
  let d = 0
  let m += a:diff
  let h += s:div(m, 60)
  let m -= 60 * s:div(m, 60)
  let d += s:div(h, 24)
  let h -= 24 * s:div(h, 24)
  return [d, self.new(h, m, s)]
endfunction

function! s:self.add_second(diff) dict abort
  let [h, m, s] = self.get_hms()
  let d = 0
  let s += a:diff
  let m += s:div(s, 60)
  let s -= 60 * s:div(s, 60)
  let h += s:div(m, 60)
  let m -= 60 * s:div(m, 60)
  let d += s:div(h, 24)
  let h -= 24 * s:div(h, 24)
  return [d, self.new(h, m, s)]
endfunction

function! s:self.second() dict abort
  return self.get_hms()[2]
endfunction

function! s:self.minute() dict abort
  return self.get_hms()[1]
endfunction

function! s:self.hour() dict abort
  return self.get_hms()[0]
endfunction

function! s:self.seconds() dict abort
  return (self.hour() * 60 + self.minute()) * 60 + self.second()
endfunction

function! s:self.add(time) dict abort
  return self.add_second(a:time.seconds())
endfunction

function! s:self.subtract(time) dict abort
  return self.add_second(-a:time.seconds())
endfunction

function! s:self.sub(time) dict abort
  return self.seconds() - a:time.seconds()
endfunction

let &cpo = s:save_cpo
unlet s:save_cpo