-----------------------------------------------------------------------------
-- LuaSocket helper module
-- Author: Diego Nehab
-- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $
-----------------------------------------------------------------------------

-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local string = require("string")
local math = require("math")
local socket = require("socket.core")
local M = {}

-----------------------------------------------------------------------------
-- Exported auxiliar functions
-----------------------------------------------------------------------------
function M.connect(address, port, laddress, lport)
    local sock, err = socket.tcp()
    if not sock then return nil, err end
    if laddress then
        local res, berr = sock:bind(laddress, lport, -1)
        if not res then return nil, berr end
    end
    local res, cerr = sock:connect(address, port)
    if not res then return nil, cerr end
    return sock
end

function M.bind(host, port, backlog)
    local sock, err = socket.tcp()
    if not sock then return nil, err end
    sock:setoption("reuseaddr", true)
    local res, berr = sock:bind(host, port)
    if not res then return nil, berr end
    res, berr = sock:listen(backlog)
    if not res then return nil, berr end
    return sock
end

M.try = socket.newtry()

function M.choose(table)
    return function(name, opt1, opt2)
        if base.type(name) ~= "string" then
            name, opt1, opt2 = "default", name, opt1
        end
        local f = table[name or "nil"]
        if not f then
            base.error("unknown key (" .. base.tostring(name) .. ")", 3)
        else
            return f(opt1, opt2)
        end
    end
end

-----------------------------------------------------------------------------
-- Socket sources and sinks, conforming to LTN12
-----------------------------------------------------------------------------
-- create namespaces inside LuaSocket namespace
M.sourcet = {}
M.sinkt = {}

M.BLOCKSIZE = 2048

M.sinkt["close-when-done"] = function(sock)
    return base.setmetatable({
        getfd = function() return sock:getfd() end,
        dirty = function() return sock:dirty() end
    }, {
        __call = function(_, chunk, _)
            if not chunk then
                sock:close()
                return 1
            else
                return sock:send(chunk)
            end
        end
    })
end

M.sinkt["keep-open"] = function(sock)
    return base.setmetatable({
        getfd = function() return sock:getfd() end,
        dirty = function() return sock:dirty() end
    }, {
        __call = function(_, chunk, _)
            if chunk then
                return sock:send(chunk)
            else
                return 1
            end
        end
    })
end

M.sinkt["default"] = M.sinkt["keep-open"]

M.sink = M.choose(M.sinkt)

M.sourcet["by-length"] = function(sock, length)
    return base.setmetatable({
        getfd = function() return sock:getfd() end,
        dirty = function() return sock:dirty() end
    }, {
        __call = function()
            if length <= 0 then return nil end
            local size = math.min(socket.BLOCKSIZE, length)
            local chunk, err = sock:receive(size)
            if err then return nil, err end
            length = length - string.len(chunk)
            return chunk
        end
    })
end

M.sourcet["until-closed"] = function(sock)
    local done
    return base.setmetatable({
        getfd = function() return sock:getfd() end,
        dirty = function() return sock:dirty() end
    }, {
        __call = function()
            if done then return nil end
            local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
            if not err then
                return chunk
            elseif err == "closed" then
                sock:close()
                done = 1
                return partial
            else
                return nil, err
            end
        end
    })
end


M.sourcet["default"] = M.sourcet["until-closed"]

M.source = M.choose(M.sourcet)

return M