-----------------------------------------------------------------------------
-- Unified SMTP/FTP subsystem
-- LuaSocket toolkit.
-- Author: Diego Nehab
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local string = require("string")
local socket = require("socket")
local ltn12 = require("ltn12")
socket.tp = {}
local _M = socket.tp
-----------------------------------------------------------------------------
-- Program constants
-----------------------------------------------------------------------------
_M.TIMEOUT = 60

-----------------------------------------------------------------------------
-- Implementation
-----------------------------------------------------------------------------
-- gets server reply (works for SMTP and FTP)
local function get_reply(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 -- reply is multiline
		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		
		-- reply ends with same code
		until code == current and sep == " "
	end

	return code, reply
end

-- metatable for sock object
local metat = {__index = {}}

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
		else
			if string.find(code, ok) then
				return base.tonumber(code), reply
			else
				return nil, reply
			end
		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 = 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

-- closes the underlying c
function metat.__index:close()
	self.c:close()
	return 1
end

-- connect with server and return c object
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