local base = _G
local string = require("string")
local socket = require("socket")
local ltn12 = require("ltn12")
socket.tp = {}
local _M = socket.tp
_M.TIMEOUT = 60
local get_reply = function(c)
  local code, current, sep
  local line, err = c:receive()
  local reply = line
  if err then
    return nil, err
  end
  code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)"))
  if not code then
    return nil, "invalid server reply"
  end
  if sep == "-" then
    repeat
      line, err = c:receive()
      if err then
        return nil, err
      end
      current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)"))
      reply = reply .. "\n" .. line
    until code == current and sep == " "
  end
  return code, reply
end
local metat = {
  __index = {}
}

function metat.__index:getpeername()
  return self.c:getpeername()
end

function metat.__index:getsockname()
  return self.c:getpeername()
end

function metat.__index:check(ok)
  local code, reply = get_reply(self.c)
  if not code then
    return nil, reply
  end
  if base.type(ok) ~= "function" then
    if base.type(ok) == "table" then
      for i, v in base.ipairs(ok) do
        if string.find(code, v) then
          return base.tonumber(code), reply
        end
      end
      return nil, reply
    elseif string.find(code, ok) then
      return base.tonumber(code), reply
    else
      return nil, reply
    end
  else
    return ok(base.tonumber(code), reply)
  end
end

function metat.__index:command(cmd, arg)
  cmd = string.upper(cmd)
  if arg then
    return self.c:send(cmd .. " " .. arg .. "\r\n")
  else
    return self.c:send(cmd .. "\r\n")
  end
end

function metat.__index:sink(snk, pat)
  local chunk, err = self.c:receive(pat)
  return snk(chunk, err)
end

function metat.__index:send(data)
  return self.c:send(data)
end

function metat.__index:receive(pat)
  return self.c:receive(pat)
end

function metat.__index:getfd()
  return self.c:getfd()
end

function metat.__index:dirty()
  return self.c:dirty()
end

function metat.__index:getcontrol()
  return self.c
end

function metat.__index:source(source, step)
  local sink = socket.sink("keep-open", self.c)
  local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step)
  return ret, err
end

function metat.__index:close()
  self.c:close()
  return 1
end

function _M.connect(host, port, timeout, create)
  local c, e = (create or socket.tcp)()
  if not c then
    return nil, e
  end
  c:settimeout(timeout or _M.TIMEOUT)
  local r, e = c:connect(host, port)
  if not r then
    c:close()
    return nil, e
  end
  return base.setmetatable({c = c}, metat)
end

return _M
