

if has('pythonx')
	let s:py = 'pythonx'
	let s:pyeval = function('pyxeval')
elseif has('python3')
	let s:py = 'python3'
	let s:pyeval = function('py3eval')
else
	let s:py = 'python'
	let s:pyeval = function('pyeval')
endif

func! neovim_rpc#serveraddr()
	if exists('g:_neovim_rpc_nvim_server')
		return g:_neovim_rpc_nvim_server
	endif

    " must be utf-8
    if &encoding !=? "utf-8"
        throw '[vim-hug-neovim-rpc] requires `:set encoding=utf-8`'
    endif

    try
        execute s:py . ' import neovim'
    catch
        call neovim_rpc#_error("failed executing: " . s:py . " import neovim")
        call neovim_rpc#_error(v:exception)
        throw '[vim-hug-neovim-rpc] requires `:' . s:py . ' import neovim` command to work'
    endtry

	execute s:py . ' import neovim_rpc_server'
	let l:servers = s:pyeval('neovim_rpc_server.start()')

	let g:_neovim_rpc_nvim_server     = l:servers[0]
	let g:_neovim_rpc_vim_server = l:servers[1]

	let g:_neovim_rpc_main_channel = ch_open(g:_neovim_rpc_vim_server)

	" close channel before vim exit
	" au VimLeavePre *  let s:leaving = 1 | execute s:py . ' neovim_rpc_server.stop()'

	" identify myself
	call ch_sendexpr(g:_neovim_rpc_main_channel,'neovim_rpc_setup')

	return g:_neovim_rpc_nvim_server
endfunc

" elegant python function call wrapper
func! neovim_rpc#pyxcall(func,...)
	execute s:py . ' import vim, json'
    let g:neovim_rpc#_tmp_args = copy(a:000)
	let l:ret = s:pyeval(a:func . '(*vim.vars["neovim_rpc#_tmp_args"])')
    unlet g:neovim_rpc#_tmp_args
    return l:ret
endfunc

" supported opt keys:
" - on_stdout
" - on_stderr
" - on_exit
" - detach
func! neovim_rpc#jobstart(cmd,...)

	let l:opts = {}
	if len(a:000)
		let l:opts = a:1
	endif

	let l:real_opts = {'mode': 'raw'}
	if has_key(l:opts,'detach') && l:opts['detach']
		let l:real_opts['stoponexit'] = ''
	endif

	if has_key(l:opts,'on_stdout')
		let l:real_opts['out_cb'] = function('neovim_rpc#_on_stdout')
	endif
	if has_key(l:opts,'on_stderr')
		let l:real_opts['err_cb'] = function('neovim_rpc#_on_stderr')
	endif
    let l:real_opts['exit_cb'] = function('neovim_rpc#_on_exit')

	let l:job   = job_start(a:cmd, l:real_opts)
	let l:jobid = ch_info(l:job)['id']

	let g:_neovim_rpc_jobs[l:jobid] = {'cmd':a:cmd, 'opts': l:opts, 'job': l:job}

	return l:jobid
endfunc

func! neovim_rpc#jobstop(jobid)
	let l:job = g:_neovim_rpc_jobs[a:jobid]['job']
	return job_stop(l:job)
endfunc

func! neovim_rpc#rpcnotify(channel,event,...)
	call neovim_rpc#pyxcall('neovim_rpc_server.rpcnotify',a:channel,a:event,a:000)
endfunc

let s:rspid = 1
func! neovim_rpc#rpcrequest(channel, event, ...)
    let s:rspid = s:rspid + 1

    " a unique key for storing response
    let rspid = '' . s:rspid

    " neovim's rpcrequest doesn't have timeout
    let opt = {'timeout': 24 * 60 * 60 * 1000}
    let args = ['rpcrequest', a:channel, a:event, a:000, rspid]
    call ch_evalexpr(g:_neovim_rpc_main_channel, args, opt)

    let expr = 'json.dumps(neovim_rpc_server.responses.pop("' . rspid . '"))'

	execute s:py ' import neovim_rpc_server, json'
    let [err, result] = json_decode(s:pyeval(expr))
    if err
        if type(err) == type('')
            throw err
        endif
        throw err[1]
    endif
    return result
endfunc

func! neovim_rpc#_on_stdout(job,data)
	let l:jobid = ch_info(a:job)['id']
	let l:opts = g:_neovim_rpc_jobs[l:jobid]['opts']
	" convert to neovim style function call
	call call(l:opts['on_stdout'],[l:jobid,split(a:data,"\n",1),'stdout'],l:opts)
endfunc

func! neovim_rpc#_on_stderr(job,data)
	let l:jobid = ch_info(a:job)['id']
	let l:opts = g:_neovim_rpc_jobs[l:jobid]['opts']
	" convert to neovim style function call
	call call(l:opts['on_stderr'],[l:jobid,split(a:data,"\n",1),'stderr'],l:opts)
endfunc

func! neovim_rpc#_on_exit(job,status)
	let l:jobid = ch_info(a:job)['id']
	let l:opts = g:_neovim_rpc_jobs[l:jobid]['opts']
	unlet g:_neovim_rpc_jobs[l:jobid]
    if has_key(l:opts, 'on_exit')
        " convert to neovim style function call
        call call(l:opts['on_exit'],[l:jobid,a:status,'exit'],l:opts)
    endif
endfunc

func! neovim_rpc#_callback()
	execute s:py . ' neovim_rpc_server.process_pending_requests()'
endfunc

let g:_neovim_rpc_main_channel = -1
let g:_neovim_rpc_jobs = {}

let s:leaving = 0

func! neovim_rpc#_error(msg)
    if mode() == 'i'
        " NOTE: side effect, sorry, but this is necessary
        set nosmd
    endif
    echohl ErrorMsg
    echom '[vim-hug-neovim-rpc] ' . a:msg
    echohl None
endfunc

func! neovim_rpc#_nvim_err_write(msg)
    if mode() == 'i'
        " NOTE: side effect, sorry, but this is necessary
        set nosmd
    endif
    echohl ErrorMsg
    let g:error = a:msg
    echom a:msg
    echohl None
endfunc

func! neovim_rpc#_nvim_out_write(msg)
    echom a:msg
endfunc
