local uv = require "lluv"
local ut = require "lluv.utils"
local zt = require "lzmq.timer"

local server = arg[1] or "testsrv"
local client = arg[1] or "utestcli"

local function spawn(file, args, line_cb)
  local stdout_buffer = ut.Buffer.new()
  local stderr_buffer = ut.Buffer.new()

  local line_mode = "*l"
  if type(line_cb) == 'table' then
    line_mode, line_cb = line_cb[1], line_cb[2]
  end

  local stdout = line_cb and uv.pipe()
  local stderr = line_cb and uv.pipe()

  local function P(pipe, read)
    return {
      stream = pipe,
      flags = uv.CREATE_PIPE + 
              (read and uv.READABLE_PIPE or uv.WRITABLE_PIPE) +
              uv.PROCESS_DETACHED
    }
  end

  local opt = {file = file, args = args or {}}
  if line_cb then
    opt.stdio = {{}, P(stdout, false), P(stderr, false)}
  else
    opt.stdio = {{}, {}, {}}
  end

  local exit_code, run_error
  local proc, pid = uv.spawn(opt, function(handle, err, status, signal)
    if err then
      run_error = err
      print(err)
    end
    exit_code = status
    uv.stop()
  end)

  if line_cb then
    local buffers = {
      [stdout] = stdout_buffer;
      [stderr] = stderr_buffer;
    }

    local function on_data(self, err, data)
      buffer = buffers[self]

      if err then
        local line = buffer:read_all()
        if line then line_cb(line) end
        if err:name() ~= 'EOF' then
          print(err)
          uv.stop()
        end
        return
      end

      buffer:append(data)
      while true do
        local line = buffer:read(line_mode)
        if not line then break end
        if line_cb(line) then uv.stop() end
      end
    end

    stdout:start_read(on_data)
    stderr:start_read(on_data)
  else
    uv.timer():start(5000, function()
      uv.stop()
    end)
  end

  uv.run()
  uv.handles(function(h) h:close() end)
  uv.run()

  if run_error then return nil, run_error end

  return pid, exit_code
end

local function kill(pid)
  uv.kill(pid)
  uv.run()
end

local function socat()
  local port1, port2
  spawn('socat', {'-d', '-d', 'PTY', 'PTY'}, function(line)
    local port = line:match('PTY is%s*([%S]+)')
    if port then
      if not port1 then port1 = port else port2 = port end
    end
    return not not port2
  end)

  return port1, port2
end

local spawn_lua do
  local i = 0 while arg[i-1] do i = i - 1 end
  local LUA = arg[i]

  spawn_lua = function(...)
    return spawn(LUA, ...)
  end
end

local function run_server(...)
  print("Server:")
  local started
  local pid = spawn_lua({...}, function(line)
    print(line)
    started = not not line:find('Server started')
    return started
  end)
  print("----------------------------")

  if not started then
    print('Fail start server')
    if pid then kill(pid) end
    os.exit(-1)
  end

  return pid
end

local control_port_1, control_port_2 = assert(socat())
local data_port_1, data_port_2 = assert(socat())

print(control_port_1, "<=>", control_port_2)
print(data_port_1, "<=>", data_port_2)

local pid = run_server("testsrv.lua", control_port_1, data_port_1)

local _, status1 = spawn_lua({"utestcli.lua", control_port_2, data_port_2}, {nil, function(line)
  io.write(line)
end})

kill(pid)

zt.sleep(5000)

local pid = run_server("testsrv_rs232.lua", control_port_1, data_port_1)

local _, status2 = spawn_lua({"utestcli_rs232.lua", control_port_2, data_port_2}, {nil, function(line)
  io.write(line)
end})

kill(pid)

io.write('test_gc\n')
local _, status3 = spawn_lua({'test_gc.lua', control_port_1}, {nil, function(line)
  io.write(line)
end})

if status1 ~= 0 or status2 ~= 0 then
  os.exit(-1)
end
