function! targets#target#new(sl, sc, el, ec, error)
    return {
        \ 'error': a:error,
        \ 'sl': a:sl,
        \ 'sc': a:sc,
        \ 'el': a:el,
        \ 'ec': a:ec,
        \ 'linewise': 0,
        \
        \ 'copy': function('targets#target#copy'),
        \ 'setS': function('targets#target#setS'),
        \ 'setE': function('targets#target#setE'),
        \ 's': function('targets#target#s'),
        \ 'e': function('targets#target#e'),
        \ 'searchposS': function('targets#target#searchposS'),
        \ 'searchposE': function('targets#target#searchposE'),
        \ 'getcharS': function('targets#target#getcharS'),
        \ 'getcharE': function('targets#target#getcharE'),
        \ 'getposS': function('targets#target#getposS'),
        \ 'getposE': function('targets#target#getposE'),
        \ 'cursorS': function('targets#target#cursorS'),
        \ 'cursorE': function('targets#target#cursorE'),
        \ 'state': function('targets#target#state'),
        \ 'range': function('targets#target#range'),
        \ 'select': function('targets#target#select'),
        \ 'string': function('targets#target#string')
        \ }
endfunction

function! targets#target#fromValues(sl, sc, el, ec)
    if a:sl == 0 || a:sc == 0 || a:el == 0 || a:ec == 0
        return targets#target#withError("zero found")
    endif
    return targets#target#new(a:sl, a:sc, a:el, a:ec, '')
endfunction

function! targets#target#fromVisualSelection()
    let [sl, sc] = getpos("'<")[1:2]
    let [el, ec] = getpos("'>")[1:2]
    return targets#target#fromValues(sl, sc, el, ec)
endfunction

function! targets#target#withError(error)
    return targets#target#new(0, 0, 0, 0, a:error)
endfunction

function! targets#target#copy() dict
    return targets#target#fromValues(self.sl, self.sc, self.el, self.ec)
endfunction

function! targets#target#setS(line, column) dict
    let [self.sl, self.sc] = [a:line, a:column]
endfunction

function! targets#target#setE(line, column) dict
    let [self.el, self.ec] = [a:line, a:column]
endfunction

function! targets#target#s() dict
    return [self.sl, self.sc]
endfunction

function! targets#target#e() dict
    return [self.el, self.ec]
endfunction

function! targets#target#searchposS(...) dict
    let pattern = a:1
    let flags = a:0 > 1 ? a:2 : ''
    let stopline = a:0 > 2 ? a:3 : 0
    let [self.sl, self.sc] = searchpos(pattern, flags, stopline)
endfunction

function! targets#target#searchposE(...) dict
    let pattern = a:1
    let flags = a:0 > 1 ? a:2 : ''
    let stopline = a:0 > 2 ? a:3 : 0
    let [self.el, self.ec] = searchpos(pattern, flags, stopline)
endfunction

function! targets#target#getcharS() dict
    return getline(self.sl)[self.sc-1]
endfunction

function! targets#target#getcharE() dict
    return getline(self.el)[self.ec-1]
endfunction

" args (mark = '.')
function! targets#target#getposS(...) dict
    let mark = a:0 > 0 ? a:1 : '.'
    let [self.sl, self.sc] = getpos(mark)[1:2]
endfunction

" args (mark = '.')
function! targets#target#getposE(...) dict
    let mark = a:0 > 0 ? a:1 : '.'
    let [self.el, self.ec] = getpos(mark)[1:2]
endfunction

function! targets#target#cursorS() dict
    call cursor(self.s())
endfunction

function! targets#target#cursorE() dict
    call cursor(self.e())
endfunction

function! targets#target#state() dict
    if self.error != ''
        return targets#state#invalid()
    endif
    if self.sl == 0 || self.el == 0
        return targets#state#invalid()
    elseif self.sl < self.el
        return targets#state#nonempty()
    elseif self.sl > self.el
        return targets#state#invalid()
    elseif self.sc == self.ec + 1
        return targets#state#empty()
    elseif self.sc > self.ec
        return targets#state#invalid()
    else
        return targets#state#nonempty()
    endif
endfunction

function! targets#target#range(cursor, min, max) dict
    if self.error != ''
        return ''
    endif

    let positionS = s:position(self.sl, self.sc, a:cursor, a:min, a:max, 'c')
    let positionE = s:position(self.el, self.ec, a:cursor, a:min, a:max, 'c')
    return positionS . positionE
endfunction

function! s:position(line, column, cursor, min, max, tie)
    let cursorLine = a:cursor[1]

    if a:line == cursorLine " cursor line
        let cursorColumn = a:cursor[2]
        if a:column == cursorColumn " same column
            return a:tie
        elseif a:column < cursorColumn " left of cursor
            return 'l'
        else " a:column > cursorColumn " right of cursor
            return 'r'
        endif

    elseif a:line < cursorLine
        if a:line >= a:min " above on screen
            return 'a'
        else " above off screen
            return 'A'
        endif

    else " a:line > cursorLine
        if a:line <= a:max " below on screen
            return 'b'
        else " below off screen
            return 'B'
        endif
    endif
endfunction

" visually select the target
function! targets#target#select() dict
    call cursor(self.s())

    if self.linewise
        silent! normal! V
    else
        silent! normal! v
    endif

    call cursor(self.e())
endfunction

function! targets#target#string() dict
    if self.error != ''
        return '[err:' . self.error . ']'
    endif

    return '[' . self.sl . ' ' . self.sc . '; ' . self.el . ' ' . self.ec . ']'
endfunction