local M = {}

function M:new(args)
  args = args or {}
  for index, arg in pairs(args) do
    self[index] = arg
  end
  setmetatable(args, self)
  self.__index = self
  return args
end

local function close_pipe(pipe)
  if pipe ~= nil and not pipe:is_closing() then
      pipe:close()
  end
end

function M.close_all()
  close_pipe(M.stdin)
  close_pipe(M.stderr)
  close_pipe(M.stdout)
  close_pipe(M.handle)
end

function M.init_options()
  local options = {}
  local args  = vim.fn.split(M.cmd, ' ')
  M.stdin = vim.loop.new_pipe(false)
  M.stdout = vim.loop.new_pipe(false)
  M.stderr = vim.loop.new_pipe(false)
  options.command = table.remove(args, 1)
  options.args = args
  options.stdio = {
    M.stdin,
    M.stdout,
    M.stderr
  }
  if M.cwd then
    options.cwd = M.cwd
  end
  if M.env then
    options.env = M.env
  end
  if M.detach then
    options.detach = M.detach
  end
  return options
end

function M.start()
  local options = M.init_options()
  M.handle = vim.loop.spawn(options.command, options, vim.schedule_wrap(M.stop))
  if M.on_stdout then
      M.stdout:read_start(vim.schedule_wrap(M.on_stdout))
  end
  if M.on_stderr then
      M.stderr:read_start(vim.schedule_wrap(M.on_stderr))
  end
end

function M.stop(code, signal)
  if M.on_exit then
    M.on_exit(code, signal)
  end
  if M.on_stdout then
      M.stdout:read_stop()
  end
  if M.on_stderr then
      M.stderr:read_stop()
  end
  M.close_all()
end

return M