" =============================================================================
" Filename: autoload/calendar/constructor/view_days.vim
" Author: itchyny
" License: MIT License
" Last Change: 2017/05/07 23:06:06.
" =============================================================================

let s:save_cpo = &cpo
set cpo&vim

function! calendar#constructor#view_days#new(instance) abort
  return extend({ 'instance': a:instance }, s:constructor)
endfunction

let s:constructor = {}

function! s:constructor.new(source) dict abort
  return extend(extend(s:super_constructor.new(a:source), s:instance), self.instance)
endfunction

let s:instance = {}

function! s:instance.height() dict abort
  return max([self.maxheight(), 6])
endfunction

function! s:instance.on_resize() dict abort
  let self.frame = copy(calendar#setting#frame())
  let width = calendar#string#strdisplaywidth(self.frame.vertical)
  let self.view = {}
  let self.view.width = (self.sizex() - width) / self.daynum / width * width
  let h = max([(self.sizey() - 3) / 6, 1])
  let self.view.week_count = 5
  let self.view.dayheight = h < 3 ? h : max([(self.sizey() - 3) / max([self.view.week_count, 5]), 1])
  let self.view.hourheight = max([(self.sizey() - self.view.dayheight) / 24, 3])
  let self.view.blockmin = 60 / (self.view.hourheight - 1)
  if !(self.view.width > 3 && self.view.dayheight > 1)
    let self.frame = calendar#setting#get('frame_space')
    let width = 1
    let self.view.width = (self.sizex() - width) / self.daynum
  endif
  let self.frame.width = calendar#string#strdisplaywidth(self.frame.vertical)
  let self.frame.strlen = len(self.frame.vertical)
  let self.element = {}
  let self.element.splitter = repeat(self.frame.horizontal, (self.view.width - width) / width)
  let self.element.white = repeat(' ', (self.view.width - width) / width * width)
  let self.element.vwhite = self.frame.vertical . self.element.white
  let self.element.format = '%2d' . repeat(' ', (self.view.width - width) / width * width - 2)
  let self.view.realwidth = self.view.width - width + self.frame.strlen
  let self.view.inner_width = self.view.width - width
  let self.view.offset = self.view.dayheight > 1 ? 3 : 1
  let self.view.locale = calendar#setting#get('locale')
  call self.set_day_name()
  let self._cache_key = []
endfunction

function! s:instance.width() dict abort
  let frame = calendar#setting#frame()
  let width = calendar#string#strdisplaywidth(frame.vertical)
  let w = max([(self.maxwidth() - calendar#setting#get('clock_12hour') * 7) / 8, 3])
  let hh = self.height()
  let h = max([(hh - 3) / 6, 1])
  let h = h < 3 ? h : max([(hh - 3) / calendar#week#week_count(b:calendar.month()), 1])
  if !(w > 3 && h > 1)
    let width = 1
  endif
  let daynum = self.daynum
  if daynum == 1
    let views = calendar#setting#get('views')
    let i = index(views, 'day_1')
    if i >= 0
      let days = filter([i > 0 ? views[i - 1] : '', i + 1 < len(views) ? views[i + 1] : ''], 'v:val =~# ''^day_\d$''')
      if len(days)
        let num = substitute(days[0], 'day_', '', '') + 0
        if 1 < num && num <= 7
          let daynum = num
        endif
      endif
    endif
  endif
  return w * 7 / daynum / width * width * daynum + width
endfunction

function! s:instance.get_min_day() dict abort
  let day = b:calendar.day()
  if has_key(self, 'min_day')
    if day.sub(self.min_day) < 0
      let self.min_day = day
    elseif day.sub(self.max_day) > 0
      let self.min_day = day.add(-self.daynum + 1)
    endif
  else
    let self.min_day = day
  endif
  let self.max_day = self.min_day.add(self.daynum - 1)
  return self.min_day
endfunction

function! s:instance.cache_key() dict abort
  if has_key(self, 'min_hour') && (self.min_hour < 0 || self.max_hour > 23)
    return []
  else
    return copy(self.min_max_hour()) + self.get_min_day().get_ymd() + [b:calendar.day().get_month()]
          \ + calendar#day#today().get_ymd() + [calendar#setting#get('frame'), calendar#setting#get('first_day')]
          \ + b:calendar.event.updated() + [get(g:, 'calendar_google_event_download')]
  endif
endfunction

function! s:instance.min_max_hour() dict abort
  let height = self.height() - self.view.offset - self.view.dayheight
  let heighthour = height / self.view.hourheight
  let time = b:calendar.time()
  let hour = time.hour()
  if has_key(self, 'min_hour')
    if hour < self.min_hour
      let min = hour
    elseif hour > self.max_hour
      let min = self.min_hour + hour - self.max_hour
    else
      let min = self.min_hour
    endif
  else
    let min = time.hour() - heighthour / 2 + 2
  endif
  let min = max([min, 0])
  let max = min([min + heighthour - 1, 23])
  let min = max([max - heighthour + 1, 0])
  let max = min([min + heighthour - 1, 23])
  return [min, max]
endfunction

function! s:instance.set_min_max_hour(hours) dict abort
  let [self.min_hour, self.max_hour] = a:hours
endfunction

function! s:instance.get_min_max_hour() dict abort
  return [self.min_hour, self.max_hour]
endfunction

function! s:instance.set_day_name() dict abort
  let [h, w, ww] = [self.view.dayheight, self.view.width, self.view.realwidth]
  let key = h . ',' . w . ',' . ww . ',' . calendar#setting#get('frame') . ','
        \ . calendar#setting#get('locale') . ',' . calendar#week#week_index(self.get_min_day()) . ',' . calendar#setting#get('first_day')
  if !has_key(self, 'day_name_cache')
    let self.day_name_cache = {}
  endif
  if has_key(self.day_name_cache, key)
    let [self.day_name_text, self.day_name_syntax] = self.day_name_cache[key]
    return
  endif
  let day_name = copy(calendar#message#get('day_name_long'))
  let maxlen = max(map(copy(day_name), 'calendar#string#strdisplaywidth(v:val)'))
  if maxlen >= self.view.inner_width
    let day_name = copy(calendar#message#get('day_name'))
  endif
  let s = repeat([''], 3)
  let syntax = []
  let x = self.frame.strlen
  let idx = self.get_min_day().week()
  for i in range(idx, idx + self.daynum - 1)
    if h > 1
      let s[0] .= (i > idx ? self.frame.top : self.frame.topleft) . self.element.splitter
      let s[2] .= (i > idx ? self.frame.junction : self.frame.left) . self.element.splitter
    endif
    let name = day_name[i % 7]
    let wid = calendar#string#strdisplaywidth(name)
    if wid >= self.view.inner_width
      let name = calendar#string#truncate(name, max([2, self.view.inner_width]))
      let wid = calendar#string#strdisplaywidth(name)
    endif
    let len = calendar#string#strdisplaywidth(self.element.splitter)
    let whiteleft = repeat(' ', (len - wid) / 2)
    let whiteright = repeat(' ', len - len(whiteleft) - wid)
    let weekstr = whiteleft . name . whiteright
    let s[h > 1] .= self.frame.vertical . weekstr
    let syn = i % 7 == 0 ? 'SundayTitle' : i % 7 == 6 ? 'SaturdayTitle' : 'DayTitle'
    if len(syntax)
      call add(syntax[-1].syn, [syn, h > 1, x, x + len(weekstr), 0])
    else
      call add(syntax, calendar#text#new(len(weekstr), x, h > 1, syn))
    endif
    let x += len(weekstr) + self.frame.strlen
  endfor
  if h > 1
    let s[0] .= self.frame.topright
    let s[1] .= self.frame.vertical
    let s[2] .= self.frame.right
  endif
  let self.day_name_text = s
  let self.day_name_syntax = syntax
  let self.day_name_cache[key] = [s, syntax]
endfunction

function! s:get_timeevts(events, blockmin) abort
  let time_events = {}
  let r = range(len(a:events))
  for i in r
    if get(a:events[i], 'isTimeEvent') && has_key(a:events[i], 'hms') && has_key(a:events[i], 'endhms')
      if a:events[i].ymd == a:events[i].endymd ||
            \  (a:events[i].endhms == [0, 0, 0] &&
            \   calendar#day#new(a:events[i].endymd[0], a:events[i].endymd[1], a:events[i].endymd[2]).sub(
            \     calendar#day#new(a:events[i].ymd[0], a:events[i].ymd[1], a:events[i].ymd[2])
            \   ) == 1
            \  )
        let hour = a:events[i].hms[0]
        let min = a:events[i].hms[1] / a:blockmin * a:blockmin
        let endhour = a:events[i].ymd == a:events[i].endymd ? a:events[i].endhms[0] : 24
        let endmin = a:events[i].endhms[1]
        let flg = 0
        let prev = []
        while (hour < endhour || hour == endhour && min < endmin) && hour <= 24
          let timestr = hour . ':' . min
          if !has_key(time_events, timestr)
            let time_events[timestr] = []
          endif
          call add(time_events[timestr], [i, flg, get(a:events[i], 'syntax', '')])
          let prev = time_events[timestr][-1]
          let flg = 1
          let min += a:blockmin
          if min >= 60
            let min = 0
            let hour += 1
          endif
        endwhile
        if len(prev) > 1 && prev[1]
          let prev[1] = 2
        endif
        if len(prev) && prev[1] == 0
          let prev[1] = 4
        endif
      endif
    endif
  endfor
  let maxnum = 0
  let prevdict = {}
  let hour = 0
  let min = 0
  let prevret = {}
  let ret = {}
  while hour <= 24
    let timestr = hour . ':' . min
    if has_key(time_events, timestr)
      let maxnum = max([maxnum, len(time_events[timestr])])
      let new = {}
      let minind = 0
      let rr = range(len(time_events[timestr]))
      for i in rr
        let tvs = time_events[timestr][i]
        if has_key(prevdict, tvs[0])
          let new[prevdict[tvs[0]]] = deepcopy(tvs)
        endif
      endfor
      for i in rr
        let tvs = time_events[timestr][i]
        if !has_key(prevdict, tvs[0])
          while has_key(new, minind)
            let minind += 1
          endwhile
          let new[minind] = deepcopy(tvs)
          let prevdict[tvs[0]] = minind
        endif
      endfor
      let minind = 0
      let prevret[timestr] = []
      for i in range(maxnum)
        if has_key(new, i)
          call add(prevret[timestr], new[i])
        else
          call add(prevret[timestr], [-1, 3, ''])
        endif
      endfor
    else
      let prevdict = {}
      for [key, events] in items(prevret)
        for event in events
          call add(event, maxnum)
        endfor
        let ret[key] = events
      endfor
      let prevret = {}
      let maxnum = 0
    endif
    let min += a:blockmin
    if min >= 60
      let min = 0
      let hour += 1
    endif
  endwhile
  return ret
endfunction

function! s:instance.set_contents() dict abort
  if self.frame.type !=# calendar#setting#get('frame') | call self.on_resize() | endif
  call self.set_day_name()
  let [f, v, e] = [self.frame, self.view, self.element]
  let [h, w, ww] = [v.dayheight, v.width, v.realwidth]
  let height = self.height() - v.offset - h
  let bottompad = (self.height() - v.offset - h) % v.hourheight + 1
  let self.has_today = 0
  let hh = 1 <= h - 2 ? range(1, h - 2) : []
  let s = repeat([''], self.sizey())
  let today = calendar#day#today()
  let month = b:calendar.month()
  let day = b:calendar.day()
  let syntax = deepcopy(self.day_name_syntax)
  for i in range(len(self.day_name_text))
    let s[i] = self.day_name_text[i]
  endfor
  let [so, st, su, sa, tsu, tsa] = ['OtherMonth', 'Today', 'Sunday', 'Saturday', 'TodaySunday', 'TodaySaturday']
  let [i, j] = [0, 0]
  let days = month.get_days()
  let prev_days = month.add(-1).get_days()
  let next_days = month.add(1).get_days()
  let wn = calendar#week#week_index(days[0])
  let sub = day.sub(wn ? prev_days[-wn] : days[0])
  let wnum = sub / 7
  let ld = wn + len(days)
  let events = b:calendar.event.get_events(day.get_year(), day.get_month())
  let head = self.get_min_day().sub(wn ? prev_days[-wn] : days[0])
  let range = range(head, head + self.daynum - 1)
  let [self.min_hour, self.max_hour] = self.min_max_hour()
  let longevt = []
  let longtimeevt = []
  let self.timeevent_syntax = []
  let event_start_time = calendar#setting#get('event_start_time')
  let event_start_time_minwidth = calendar#setting#get('event_start_time_minwidth')
  let clock_12hour = calendar#setting#get('clock_12hour')
  for p in range
    let d = p < wn ? prev_days[-wn + p] : p < ld ? days[p - wn] : next_days[p - ld]
    let evts = get(events, join(d.get_ymd(), '-'), { 'events': [] } )
    let y = v.offset + h * j
    let s[y] .= f.vertical
    let s[y] .= self.oneday(d.get_day(), evts)
    let is_today = today.eq(d)
    if is_today
      let self.has_today = 1
    endif
    let syn = is_today ? st : d.is_sunday() || get(evts, 'hasHoliday') ? su : d.is_saturday() ? sa : ''
    if syn !=# ''
      let l = is_today ? len(calendar#string#truncate_reverse(s[y], v.inner_width)) : 2
      let syn2 = !is_today ? '' : d.is_sunday() || get(evts, 'hasHoliday') ? tsu : d.is_saturday() ? tsa : ''
      let x = len(calendar#string#truncate(s[y], w * i + f.width))
      if syn2 !=# ''
        let x += 2
        let l -= 2
      endif
      call add(syntax, calendar#text#new(l, x, y, syn))
      if syn2 !=# ''
        let l = 2
        let x = len(calendar#string#truncate(s[y], w * i + f.width))
        if len(syntax) && syntax[-1].y == y
          call add(syntax[-1].syn, [syn2, y, x, x + l, 0])
        else
          call add(syntax, calendar#text#new(l, x, y, syn2))
        endif
      endif
    endif
    let z = 0
    let longevtIndex = 0
    for x in hh
      if longevtIndex < len(longevt) && longevt[longevtIndex].viewoffset == x
        let lastday = d.get_day() == longevt[longevtIndex].endymd[2] ||
              \ (get(longevt[longevtIndex], 'isTimeEvent') &&
              \  longevt[longevtIndex].endhms == [0, 0, 0] &&
              \  calendar#day#new(longevt[longevtIndex].endymd[0], longevt[longevtIndex].endymd[1], longevt[longevtIndex].endymd[2]).sub(d) == 1
              \ )
        let eventtext = repeat('=', v.inner_width - lastday) . (lastday ? ']' : '')
        let splitter = i ? repeat('=', f.width) : f.vertical
        let s[y + x] .= splitter . eventtext
        if has_key(longevt[longevtIndex], 'syntax')
          let l = len(eventtext)
          let syn = longevt[longevtIndex].syntax
          let yy = y + x
          let xx = len(s[y + x]) - l - (i ? f.width : 0)
          let l = l + (i ? f.width : 0)
          call add(syntax, calendar#text#new(l, xx, yy, syn))
        endif
        let longevtIndex += 1
      else
        while z < len(evts.events) && (!has_key(evts.events[z], 'summary') || evts.events[z].isHoliday || evts.events[z].isMoon || evts.events[z].isDayNum || evts.events[z].isWeekNum)
          let z += 1
        endwhile
        if z < len(evts.events)
          let is_long_event = evts.events[z].ymd != evts.events[z].endymd &&
                \ (!get(evts.events[z], 'isTimeEvent') ||
                \   evts.events[z].endhms != [0, 0, 0] ||
                \   calendar#day#new(evts.events[z].endymd[0], evts.events[z].endymd[1], evts.events[z].endymd[2]).sub(
                \     calendar#day#new(evts.events[z].ymd[0], evts.events[z].ymd[1], evts.events[z].ymd[2])
                \   ) > 1
                \ )
          if is_long_event
            let trailing = ' ' . repeat('=', v.inner_width)
            call add(longevt, extend(evts.events[z], { 'viewoffset': x }))
          else
            let trailing = ''
          endif
          let starttime = event_start_time && self.view.width >= event_start_time_minwidth
                \ && get(evts.events[z], 'isTimeEvent') ? evts.events[z].starttime . ' ' : ''
          let eventtext = calendar#string#truncate(starttime . evts.events[z].summary . trailing, v.inner_width)
          if has_key(evts.events[z], 'syntax')
            let l = len(eventtext)
            let xx = len(s[y + x]) + len(f.vertical)
            let yy = y + x
            call add(syntax, calendar#text#new(l, xx, yy, evts.events[z].syntax))
          endif
          let s[y + x] .= f.vertical . eventtext
          let z += 1
        else
          let s[y + x] .= e.vwhite
        endif
      endif
    endfor
    call sort(filter(longevt, 'calendar#view#month#longevt_filter(v:val, d)'), 'calendar#view#month#sorter')
    let time_events = s:get_timeevts(evts.events, v.blockmin)
    for k in range(height)
      if k < height - bottompad
        if (k + 1) % v.hourheight
          let hour = self.min_hour + k / v.hourheight
          let min = (k % v.hourheight) * v.blockmin
          let timestr = hour . ':' . min
          if has_key(time_events, timestr) && len(time_events[timestr])
            let maxnum = get(time_events[timestr][0], 3, 1)
            let tevts = map(deepcopy(time_events[timestr]), 'v:val[0] >= 0 ? evts.events[v:val[0]] : {}')
            let onelen = max([(v.inner_width + 2) / maxnum - f.width, f.width * 3])
            let hourcontents = ''
            let texts = []
            let syns = []
            let totallen = 0
            let yy = v.offset + h + k
            let ya = yy - 1
            let framelen = v.inner_width / f.width * f.strlen
            let xx = len(s[ya]) - framelen
            for ii in range(len(tevts))
              let l = maxnum > 1 ? (max([0, min([onelen, v.inner_width - totallen])]) - 1) / f.width * f.width : v.inner_width
              if l >= f.width * 2
                let flg = time_events[timestr][ii][1]
                let border = flg == 3 ? [repeat(' ', f.width), repeat(' ', f.width)] : flg == 1 ? repeat([f.vertical], 2) : [f.bottomleft, f.bottomright]
                let rep = flg == 3 || flg == 1 ? repeat(' ', f.width) : f.horizontal
                if flg && flg < 4
                  call add(texts, border[0] . repeat(rep, l / f.width - 2) . border[1])
                  if flg < 3
                    if (k % v.hourheight) == 0 && k
                      let cutlen = len(s[ya][(xx):])
                      let leftpart = s[ya][:len(s[ya]) - cutlen - 1]
                      let rightpart = s[ya][len(s[ya]) - cutlen + l / f.width * f.strlen :]
                      let s[ya] = leftpart . f.vertical . repeat(' ', l - f.width * 2) . f.vertical . rightpart
                      call add(syntax, calendar#text#new(l - f.width * 2 + f.strlen * 2, xx, ya, get(tevts[ii], 'syntax', '')))
                      let xx += l - 2 * f.width + 2 * f.strlen + f.strlen
                    endif
                  else
                    let xx += l / f.width * f.strlen + f.strlen
                  endif
                else
                  let eventsummary = get(tevts[ii], 'summary', '')
                  let smallspace = repeat(' ', f.width - 1 - (calendar#string#strdisplaywidth(eventsummary) + f.width - 1) % f.width)
                  let newtext = calendar#string#truncate(eventsummary . smallspace . repeat(f.horizontal, l), l - f.width) . (flg ? f.horizontal : f.topright)
                  call add(texts, newtext)
                  let xx += l / f.width * f.strlen + f.strlen
                endif
                call add(syns, get(tevts[ii], 'syntax', ''))
                let totallen += onelen
              else
                let xx += l / f.width * f.strlen + f.strlen
              endif
            endfor
            let xx = len(s[yy]) + f.strlen
            let hourcontents = calendar#string#truncate(join(texts, repeat(' ', f.width)), v.inner_width)
            for ii in range(len(texts))
              let l = len(texts[ii])
              if len(syns[ii])
                call extend(time_events[timestr][ii], [l, xx, yy, syns[ii]])
                call add(syntax, calendar#text#new(l, xx, yy, syns[ii]))
              endif
              let xx += l + f.width
            endfor
          else
            let hourcontents = e.white
          endif
          let s[v.offset + h + k] .= repeat(f.vertical . hourcontents, 1)
        else
          let s[v.offset + h + k] .= repeat(f.vertical . e.splitter, 1)
        endif
      endif
    endfor
    call add(self.timeevent_syntax, deepcopy(time_events))
    if h > 1
      let frame = i ? (j + 1 == 7 ? f.bottom : f.junction) : j + 1 == 7 ? f.bottomleft : f.left
      let s[y + h - 1] .= frame . e.splitter
    endif
    if i == 6
      let [i, j] = [0, j + 1]
    else
      let i = i + 1
    endif
  endfor
  for i in range(1)
    for j in range(h - 1)
      let s[v.offset + h * i + j] .= f.vertical
    endfor
    let s[v.offset + h * i + h - 1] .= (i + 1 == 7 ? f.bottomright : f.right)
  endfor
  let s[self.height() - bottompad] .= f.bottomleft . repeat(e.splitter . f.bottom, self.daynum - 1) . e.splitter . f.bottomright
  for j in range(height)
    if j < height - bottompad
      let s[v.offset + h + j] .= f.vertical
    endif
    if !((j + 1) % v.hourheight)
      let hour = self.min_hour + j / v.hourheight + 1
      if clock_12hour
        let postfix = hour < 12 || hour == 24 ? ' a.m.' : ' p.m.'
        let hour = calendar#time#hour12(hour)
      else
        let postfix = ''
      endif
      let s[v.offset + h + j] .= printf('%2d:%02d', hour, 0) . postfix
    endif
    if !j
      let hour = self.min_hour
      if clock_12hour
        let postfix = ' a.m.'
        let hour = calendar#time#hour12(hour)
      else
        let postfix = ''
      endif
      let s[v.offset + h - 1] .= printf('%2d:%02d', hour, 0) . postfix
    endif
  endfor
  let self._cache_key = self.cache_key()
  let self.days = map(range(len(s)), 'calendar#text#new(s[v:val], 0, v:val, "")')
  let self.syntax = syntax
  let self.min_day = self.get_min_day()
endfunction

function! s:instance.contents() dict abort
  if self._cache_key != self.cache_key() | call self.set_contents() | endif
  let [f, v, e] = [self.frame, self.view, self.element]
  let select = []
  let select_over = []
  let cursor = []
  let now = calendar#time#now()
  if self.is_selected()
    for [i, hour] in self.select_index()
      for h in range(v.hourheight - 1)
        let y = v.offset + v.dayheight + v.hourheight * (hour - self.min_hour) + h
        let x = len(calendar#string#truncate(self.days[y].s, v.width * i + f.width))
        let z = len(calendar#string#truncate(self.days[y].s, v.width * (i + 1)))
        let timestr = hour . ':' . (v.blockmin * h)
        if has_key(self.timeevent_syntax[i], timestr)
          for time_event in self.timeevent_syntax[i][timestr]
            if len(time_event) == 8
              call add(select_over, calendar#text#new(time_event[4], time_event[5], time_event[6], time_event[7] . 'Select'))
            endif
          endfor
        endif
        call add(select, calendar#text#new(z - x, x, y, 'Select'))
      endfor
    endfor
    let y = v.offset + v.dayheight + v.hourheight * (hour - self.min_hour)
    let x = len(calendar#string#truncate(self.days[y].s, v.width * i + f.width))
    let cursor = [calendar#text#new(0, x, y, 'Cursor')]
  endif
  if self.has_today
    let i = calendar#day#today().sub(self.get_min_day())
    let h = now.minute()/ (60 / (self.view.hourheight - 1))
    if self.min_hour <= now.hour() && now.hour() <= self.max_hour
      let y = v.offset + v.dayheight + v.hourheight * (now.hour() - self.min_hour) + h
      let x = len(calendar#string#truncate(self.days[y].s, v.width * i + f.width))
      let z = len(calendar#string#truncate(self.days[y].s, v.width * (i + 1)))
      let nowsyn = [calendar#text#new(z - x, x, y, 'Today')]
    else
      let nowsyn = []
    endif
  else
    let nowsyn = []
  endif
  return deepcopy(self.days) + select + deepcopy(self.syntax) + select_over + cursor + nowsyn
endfunction

function! s:instance.select_index() dict abort
  let lasti = b:calendar.day().sub(self.get_min_day())
  let lasthour = b:calendar.time().hour()
  if !b:calendar.visual_mode()
    return [[lasti, lasthour]]
  endif
  let last = lasti * 24 + lasthour
  let starti = b:calendar.visual_start_day().sub(self.get_min_day())
  let starthour = b:calendar.visual_start_time().hour()
  let start = starti * 24 + starthour
  let ret = []
  if b:calendar.is_visual()
    for i in range(min([start, last]), max([start, last]))
      let j = s:div(i, 24)
      let k = i - j * 24
      if [j, k] != [lasti, lasthour] && 0 <= j && j < self.daynum && self.min_hour <= k && k <= self.max_hour
        call add(ret, [j, k])
      endif
    endfor
  elseif b:calendar.is_line_visual()
    for j in range(min([starti, lasti]), max([starti, lasti]))
      for k in range(24)
        if [j, k] != [lasti, lasthour] && 0 <= j && j < self.daynum && self.min_hour <= k && k <= self.max_hour
          call add(ret, [j, k])
        endif
      endfor
    endfor
  elseif b:calendar.is_block_visual()
    for j in range(min([starti, lasti]), max([starti, lasti]))
      for k in range(min([starthour, lasthour]), max([starthour, lasthour]))
        if [j, k] != [lasti, lasthour] && 0 <= j && j < self.daynum && self.min_hour <= k && k <= self.max_hour
          call add(ret, [j, k])
        endif
      endfor
    endfor
  else
  endif
  call add(ret, [lasti, lasthour])
  return ret
endfunction

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

function! s:instance.timerange() dict abort
  let hour = b:calendar.time().hour()
  if !b:calendar.visual_mode()
    return printf('%d:00-%d:00 ', hour, hour + 1)
  endif
  let x = b:calendar.day()
  let xh = b:calendar.time().hour()
  let y = b:calendar.visual_start_day()
  let yh = b:calendar.visual_start_time().hour()
  let recurrence = ''
  if b:calendar.is_line_visual()
    if x.sub(y) >= 0
      if x.get_year() == y.get_year()
        return printf('%d/%d-%d/%d ', y.get_month(), y.get_day(), x.get_month(), x.get_day()) . recurrence
      else
        return printf('%d/%d/%d-%d/%d/%d ', y.get_year(), y.get_month(), y.get_day(), x.get_year(), x.get_month(), x.get_day()) . recurrence
      endif
    else
      if x.get_year() == y.get_year()
        return printf('%d/%d-%d/%d ', x.get_month(), x.get_day(), y.get_month(), y.get_day()) . recurrence
      else
        return printf('%d/%d/%d-%d/%d/%d ', x.get_year(), x.get_month(), x.get_day(), y.get_year(), y.get_month(), y.get_day()) . recurrence
      endif
    endif
  elseif b:calendar.is_block_visual()
    if x.sub(y) >= 0
      let rec = x.sub(y) + 1
      let [yh, xh] = [min([xh, yh]), max([xh, yh])]
      let x = y
    else
      let rec = y.sub(x) + 1
      let [xh, yh] = [min([xh, yh]), max([xh, yh])]
      let y = x
    endif
    let recurrence = rec > 1 ? rec . 'days ' : ''
  endif
  if x.sub(y) == 0 && !len(recurrence)
    return printf('%d:00 - %d:00 ', min([xh, yh]), max([xh, yh]) + 1)
  elseif x.sub(y) >= 0
    if x.get_year() == y.get_year()
      return printf('%d/%d %d:00 - %d/%d %d:00 ', y.get_month(), y.get_day(), yh, x.get_month(), x.get_day(), xh + 1) . recurrence
    else
      return printf('%d/%d/%d %d:00 - %d/%d/%d %d:00 ', y.get_year(), y.get_month(), y.get_day(), yh, x.get_year(), x.get_month(), x.get_day(), xh + 1) . recurrence
    endif
  else
    if x.get_year() == y.get_year()
      return printf('%d/%d %d:00 - %d/%d %d:00 ', x.get_month(), x.get_day(), xh, y.get_month(), y.get_day(), yh + 1) . recurrence
    else
      return printf('%d/%d/%d %d:00 - %d/%d/%d %d:00 ', x.get_year(), x.get_month(), x.get_day(), xh, y.get_year(), y.get_month(), y.get_day(), yh + 1) . recurrence
    endif
  endif
endfunction

function! s:instance.action(action) dict abort
  let d = b:calendar.day()
  let hday = b:calendar.month().head_day()
  let lday = b:calendar.month().last_day()
  let hour = b:calendar.time().hour()
  let wnum = d.sub(self.get_min_day())
  if a:action ==# 'left'
    call b:calendar.move_day(get(self, 'stopend') ? max([-v:count1, -wnum]) : -v:count1)
  elseif a:action ==# 'right'
    call b:calendar.move_day(get(self, 'stopend') ? min([v:count1, -wnum + self.daynum - 1]) : v:count1)
  elseif index(['prev', 'next', 'space', 'add', 'subtract'], a:action) >= 0
    call b:calendar.move_day(v:count1 * (index(['prev', 'subtract'], a:action) >= 0 ? -1 : 1))
  elseif index(['down', 'up'], a:action) >= 0
    call b:calendar.move_hour(v:count1 * (a:action ==# 'down' ? 1 : -1))
  elseif index(['plus', 'minus'], a:action) >= 0
    let h = v:count1 * (a:action ==# 'plus' ? 1 : -1)
    while h > 23 - hour
      let h = h - 24
      let wnum -= self.daynum
    endwhile
    while h < - hour
      let h = h + 24
      let wnum += self.daynum
    endwhile
    call b:calendar.move_hour(h)
    if has_key(self, 'min_day') && has_key(self, 'max_day')
      if self.min_day.sub(d.add(-wnum)) > 0 || self.max_day.sub(d.add(-wnum)) < 0
        let self.min_day = self.min_day.add(s:div(-wnum + self.daynum - 1, self.daynum) * self.daynum)
        let self.max_day = self.max_day.add(s:div(-wnum + self.daynum - 1, self.daynum) * self.daynum)
      endif
    endif
    call b:calendar.move_day(-wnum)
  elseif index(['down_big', 'up_big'], a:action) >= 0
    let diff = self.max_hour - self.min_hour
    let dir = a:action ==# 'down_big' ? 1 : -1
    let di = max([min([v:count1 * dir * diff * 2 / 3, 23 - hour]), - hour])
    if dir > 0
      let self.min_hour = self.min_hour + v:count1 * dir * diff
      let self.max_hour = self.min_hour + diff
    else
      let self.max_hour = self.max_hour + v:count1 * dir * diff
      let self.min_hour = self.max_hour - diff
    endif
    call b:calendar.move_hour(di)
  elseif index(['down_large', 'up_large'], a:action) >= 0
    let diff = self.max_hour - self.min_hour
    let dir = a:action ==# 'down_large' ? 1 : -1
    let di = max([min([(dir > 0 ? self.max_hour : self.min_hour) - hour + (v:count1 - 1) * dir * diff, 23 - hour]), - hour])
    if dir > 0
      let self.min_hour = self.min_hour + v:count1 * dir * diff
      let self.max_hour = self.min_hour + diff
    else
      let self.max_hour = self.max_hour + v:count1 * dir * diff
      let self.min_hour = self.max_hour - diff
    endif
    call b:calendar.move_hour(di)
  elseif a:action ==# 'line_head'
    call b:calendar.move_day(-wnum)
  elseif a:action ==# 'line_middle'
    call b:calendar.move_day(-wnum + (self.daynum - 1) / 2)
  elseif a:action ==# 'line_last'
    call b:calendar.move_day(self.daynum - 1 - wnum)
  elseif a:action ==# 'bar'
    call b:calendar.move_day(-wnum + min([v:count1 - 1, self.daynum - 1]))
  elseif a:action ==# 'first_line' || a:action ==# 'first_line_head' || (a:action ==# 'last_line' && v:count)
    call b:calendar.move_hour((v:count ? min([v:count1, 23]) : 0) - hour)
  elseif a:action ==# 'last_line'
    call b:calendar.move_hour((v:count ? max([v:count1, 23]) : 23) - hour)
  elseif a:action ==# 'last_line_last'
    call b:calendar.move_hour((v:count ? max([v:count1, 23]) : 23) - hour)
  elseif index(['scroll_down', 'scroll_up'], a:action) >= 0
    let diff = v:count1 * (a:action =~# 'down' ? 1 : -1)
    let old_hours = [self.min_hour, self.max_hour]
    let self.min_hour += diff
    let self.max_hour += diff
    let new_hours = self.min_max_hour()
    if old_hours == new_hours
      call b:calendar.move_hour(diff)
    endif
  elseif index(['scroll_top_head', 'scroll_top', 'scroll_bottom_head', 'scroll_bottom'], a:action) >= 0
    let self.min_hour += 23 * (a:action =~# 'top' ? 1 : -1)
    let self.max_hour += 23 * (a:action =~# 'top' ? 1 : -1)
    call b:calendar.move_day(a:action =~# 'head' ? -wnum : 0)
  elseif index(['scroll_center_head', 'scroll_center'], a:action) >= 0
    let diff = self.max_hour - self.min_hour
    let self.min_hour = hour - diff / 2 + 1
    let self.max_hour = self.min_hour + diff
    call b:calendar.move_day(a:action =~# 'head' ? -wnum : 0)
  elseif a:action ==# 'command_enter' && mode() ==# 'c' && getcmdtype() ==# ':'
    let cmd = calendar#util#getcmdline()
    if cmd =~# '^\s*\d\+\s*$'
      let c = max([min([matchstr(cmd, '\d\+') * 1, 23]), 0])
      call b:calendar.move_hour(c - hour)
      return calendar#util#update_keys()
    endif
  endif
endfunction

let s:super_constructor = calendar#constructor#view#new(s:instance)

let &cpo = s:save_cpo
unlet s:save_cpo