function! scope#pair(head, tail)
  let obj = {}
  let obj.head = a:head
  let obj.tail = a:tail
  let obj.head_line_number = obj.head[0]
  let obj.tail_line_number = obj.tail[0]

  if len(obj.head[1]) == 0
    let obj.head_string = ''
  elseif join(obj.head[1][1:]) == ''
    let obj.head_string = obj.head[1][0]
  else
    let obj.head_string = string#trim(join(obj.head[1][1:], ' '))
  endif

  if len(obj.tail[1]) == 0
    let obj.tail_string = ''
  elseif join(obj.tail[1][1:]) == ''
    let obj.tail_string = obj.tail[1][0]
  else
    let obj.tail_string = join(obj.tail[1][1:], ' ')
  endif

  return obj
endfunction

function! scope#inspect(head_pattern, tail_pattern)
  let obj = {}
  let obj.head_search_pattern  = substitute(a:head_pattern, '\\(', '\\%(', 'g')
  let obj.head_collect_pattern = a:head_pattern
  let obj.tail_search_pattern  = substitute(a:tail_pattern, '\\(', '\\%(', 'g')
  if obj.tail_search_pattern !~ '\\z[es]' && obj.tail_search_pattern !~ '\$$'
    let obj.tail_search_pattern = obj.tail_search_pattern . '\zs'
  endif
  let obj.tail_collect_pattern = a:tail_pattern

  func obj.init()
    let self.stack = []
  endfunc

  func obj.push(head, tail)
    call add(self.stack, scope#pair([a:head, matchlist(getline(a:head), self.head_collect_pattern)], [a:tail, matchlist(getline(a:tail), self.tail_collect_pattern)]))
  endfunc

  func obj.find_outer_tail()
    let self.outer_tail = searchpair(self.head_search_pattern, '', self.tail_search_pattern, 'rcnW')
  endfunc

  func obj.find_outer_head()
    let self.outer_head = searchpair(self.head_search_pattern, '', self.tail_search_pattern, 'rbcnW')
  endfunc

  func obj.find_current_head()
    let self.current_head = searchpair(self.head_search_pattern, '', self.tail_search_pattern, 'bW')
  endfunc

  func obj.find_current_tail()
    let self.current_tail = searchpair(self.head_search_pattern, '', self.tail_search_pattern, 'nW')
  endfunc

  func obj.scope()
    let self.stack = reverse(list#lrotate(self.stack))
    return self
  endfunc

  func obj.get_scope()
    call self.init()
    call self.find_outer_tail()
    if self.outer_tail == 0
      return self
    endif

    let cur_pos = getpos('.')

    call self.find_outer_head()
    if self.outer_head == 0
      return self
    endif
    call self.push(self.outer_head, self.outer_tail)
    call self.find_current_head()
    while self.current_head > self.outer_head
      call self.find_current_tail()
      call self.push(self.current_head, self.current_tail)
      call self.find_current_head()
    endwhile

    call setpos('.', cur_pos)
    return self.scope()
  endfunction

  return obj.get_scope()
endfunction