" Description: Handle errors for cppcheck.

function! ale#handlers#cppcheck#GetCwd(buffer) abort
    let [l:dir, l:json_path] = ale#c#FindCompileCommands(a:buffer)

    return !empty(l:dir) ? l:dir : ''
endfunction

function! ale#handlers#cppcheck#GetBufferPathIncludeOptions(buffer) abort
    let l:buffer_path_include = ''

    " Get path to this buffer so we can include it into cppcheck with -I
    " This could be expanded to get more -I directives from the compile
    " command in compile_commands.json, if it's found.
    let l:buffer_path = fnamemodify(bufname(a:buffer), ':p:h')
    let l:buffer_path_include = ' -I' . ale#Escape(l:buffer_path)

    return l:buffer_path_include
endfunction

function! ale#handlers#cppcheck#GetCompileCommandsOptions(buffer) abort
    " The compile_commands.json doesn't apply to headers and cppheck will
    " bail out if it cannot find a file matching the filter, below. Skip out
    " now, for headers. Also, suppress FPs; cppcheck is not meant to
    " process lone header files.
    let b:buffer_name = bufname(a:buffer)
    let b:file_extension = fnamemodify(b:buffer_name, ':e')

    if b:file_extension is# 'h' || b:file_extension is# 'hpp'
        return ale#handlers#cppcheck#GetBufferPathIncludeOptions(a:buffer)
        \   . ' --suppress=unusedStructMember'
    endif

    " If the current buffer is modified, using compile_commands.json does no
    " good, so include the file's directory instead. It's not quite as good as
    " using --project, but is at least equivalent to running cppcheck on this
    " file manually from the file's directory.
    let l:modified = getbufvar(a:buffer, '&modified')

    if l:modified
        return ''
    endif

    " Search upwards from the file for compile_commands.json.
    "
    " If we find it, we'll `cd` to where the compile_commands.json file is,
    " then use the file to set up import paths, etc.
    let [l:dir, l:json_path] = ale#c#FindCompileCommands(a:buffer)

    " By default, cppcheck processes every config in compile_commands.json.
    " Use --file-filter to limit to just the buffer file.
    return !empty(l:json_path)
    \   ? '--project=' . ale#Escape(l:json_path[len(l:dir) + 1: ]) . ' --file-filter=' . ale#Escape(bufname(a:buffer))
    \   : ''
endfunction

function! ale#handlers#cppcheck#HandleCppCheckFormat(buffer, lines) abort
    " Look for lines like the following.
    "
    "test.cpp:974:6: error:inconclusive Array 'n[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\
    "    n[3]=3;
    "     ^
    "" OR if cppcheck doesn't support {column} or {inconclusive:text}:
    "test.cpp:974:{column}: error:{inconclusive:inconclusive} Array 'n[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\
    "    n[3]=3;
    "     ^
    "
    "" OR if using the misra addon:
    "test.c:1:16: style: misra violation (use --rule-texts=<file> to get proper output) [misra-c2012-2.7]\'
    "void test( int parm ) {}
    "               ^
    let l:pattern = '\v(\f+):(\d+):(\d+|\{column\}): (\w+):(\{inconclusive:inconclusive\})? ?(.*) \[(%(\w[-.]?)+)\]\'
    let l:output = []

    for l:match in ale#util#GetMatches(a:lines, l:pattern)
        if ale#path#IsBufferPath(a:buffer, l:match[1])
            call add(l:output, {
            \   'lnum':     str2nr(l:match[2]),
            \   'col':      match(l:match[3],'{column}') >= 0 ? 1 : str2nr(l:match[3]),
            \   'type':     l:match[4] is# 'error' ? 'E' : 'W',
            \   'sub_type': l:match[4] is# 'style' ? 'style' : '',
            \   'text':     l:match[6],
            \   'code':     l:match[7]
            \})
        endif
    endfor

    return l:output
endfunction
