if not modules then modules = { } end modules ['util-sac'] = {
    version   = 1.001,
    optimize  = true,
    comment   = "companion to luat-lib.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- experimental string access (some 3 times faster than file access when messing
-- with bytes)

local byte, sub = string.byte, string.sub
local tonumber = tonumber

utilities         = utilities or { }
local streams     = { }
utilities.streams = streams

function streams.open(filename,zerobased)
    local f = filename and io.loaddata(filename)
    if f then
        return { f, 1, #f, zerobased or false }
    end
end

function streams.openstring(f,zerobased)
    if f then
        return { f, 1, #f, zerobased or false }
    end
end

function streams.getstring(f)
    if f then
        return f[1]
    end
end

function streams.close()
    -- dummy
end

function streams.size(f)
    return f and f[3] or 0
end

streams.getsize = streams.size

function streams.setposition(f,i)
    if f[4] then
        -- zerobased
        if i <= 0 then
            f[2] = 1
        else
            f[2] = i + 1
        end
    else
        if i <= 1 then
            f[2] = 1
        else
            f[2] = i
        end
    end
end

function streams.getposition(f)
    if f[4] then
        -- zerobased
        return f[2] - 1
    else
        return f[2]
    end
end

function streams.look(f,n,chars)
    local b = f[2]
    local e = b + n - 1
    if chars then
        return sub(f[1],b,e)
    else
        return byte(f[1],b,e)
    end
end

function streams.skip(f,n)
    f[2] = f[2] + n
end

function streams.readbyte(f)
    local i = f[2]
    f[2] = i + 1
    return byte(f[1],i)
end

function streams.readbytes(f,n)
    local i = f[2]
    local j = i + n
    f[2] = j
    return byte(f[1],i,j-1)
end

function streams.readbytetable(f,n)
    local i = f[2]
    local j = i + n
    f[2] = j
    return { byte(f[1],i,j-1) }
end

function streams.skipbytes(f,n)
    f[2] = f[2] + n
end

function streams.readchar(f)
    local i = f[2]
    f[2] = i + 1
    return sub(f[1],i,i)
end

function streams.readstring(f,n)
    local i = f[2]
    local j = i + n
    f[2] = j
    return sub(f[1],i,j-1)
end

function streams.readinteger1(f)  -- one byte
    local i = f[2]
    f[2] = i + 1
    local n = byte(f[1],i)
    if n  >= 0x80 then
        return n - 0x100
    else
        return n
    end
end

streams.readcardinal1 = streams.readbyte  -- one byte
streams.readcardinal  = streams.readcardinal1
streams.readinteger   = streams.readinteger1

function streams.readcardinal2(f)
    local i = f[2]
    local j = i + 1
    f[2] = j + 1
    local a, b = byte(f[1],i,j)
    return 0x100 * a + b
end

function streams.readcardinal2le(f)
    local i = f[2]
    local j = i + 1
    f[2] = j + 1
    local b, a = byte(f[1],i,j)
    return 0x100 * a + b
end

function streams.readinteger2(f)
    local i = f[2]
    local j = i + 1
    f[2] = j + 1
    local a, b = byte(f[1],i,j)
    if a >= 0x80 then
        return 0x100 * a + b - 0x10000
    else
        return 0x100 * a + b
    end
end

function streams.readinteger2le(f)
    local i = f[2]
    local j = i + 1
    f[2] = j + 1
    local b, a = byte(f[1],i,j)
    if a >= 0x80 then
        return 0x100 * a + b - 0x10000
    else
        return 0x100 * a + b
    end
end

function streams.readcardinal3(f)
    local i = f[2]
    local j = i + 2
    f[2] = j + 1
    local a, b, c = byte(f[1],i,j)
    return 0x10000 * a + 0x100 * b + c
end

function streams.readcardinal3le(f)
    local i = f[2]
    local j = i + 2
    f[2] = j + 1
    local c, b, a = byte(f[1],i,j)
    return 0x10000 * a + 0x100 * b + c
end

function streams.readinteger3(f)
    local i = f[2]
    local j = i + 3
    f[2] = j + 1
    local a, b, c = byte(f[1],i,j)
    if a >= 0x80 then
        return 0x10000 * a + 0x100 * b + c - 0x1000000
    else
        return 0x10000 * a + 0x100 * b + c
    end
end

function streams.readinteger3le(f)
    local i = f[2]
    local j = i + 3
    f[2] = j + 1
    local c, b, a = byte(f[1],i,j)
    if a >= 0x80 then
        return 0x10000 * a + 0x100 * b + c - 0x1000000
    else
        return 0x10000 * a + 0x100 * b + c
    end
end

function streams.readcardinal4(f)
    local i = f[2]
    local j = i + 3
    f[2] = j + 1
    local a, b, c, d = byte(f[1],i,j)
    return 0x1000000 * a + 0x10000 * b + 0x100 * c + d
end

function streams.readcardinal4le(f)
    local i = f[2]
    local j = i + 3
    f[2] = j + 1
    local d, c, b, a = byte(f[1],i,j)
    return 0x1000000 * a + 0x10000 * b + 0x100 * c + d
end

function streams.readinteger4(f)
    local i = f[2]
    local j = i + 3
    f[2] = j + 1
    local a, b, c, d = byte(f[1],i,j)
    if a >= 0x80 then
        return 0x1000000 * a + 0x10000 * b + 0x100 * c + d - 0x100000000
    else
        return 0x1000000 * a + 0x10000 * b + 0x100 * c + d
    end
end

function streams.readinteger4le(f)
    local i = f[2]
    local j = i + 3
    f[2] = j + 1
    local d, c, b, a = byte(f[1],i,j)
    if a >= 0x80 then
        return 0x1000000 * a + 0x10000 * b + 0x100 * c + d - 0x100000000
    else
        return 0x1000000 * a + 0x10000 * b + 0x100 * c + d
    end
end

function streams.readfixed2(f)
    local i = f[2]
    local j = i + 1
    f[2] = j + 1
    local n1, n2 = byte(f[1],i,j)
    if n1 >= 0x80 then
        n1 = n1 - 0x100
    end
    return n1 + n2/0xFF
end

function streams.readfixed4(f)
    local i = f[2]
    local j = i + 3
    f[2] = j + 1
    local a, b, c, d = byte(f[1],i,j)
    local n1 = 0x100 * a + b
    local n2 = 0x100 * c + d
    if n1 >= 0x8000 then
        n1 = n1 - 0x10000
    end
    return n1 + n2/0xFFFF
end

if bit32 then

    local extract = bit32.extract
    local band    = bit32.band

    function streams.read2dot14(f)
        local i = f[2]
        local j = i + 1
        f[2] = j + 1
        local a, b = byte(f[1],i,j)
        if a >= 0x80 then
            local n = -(0x100 * a + b)
            return - (extract(n,14,2) + (band(n,0x3FFF) / 16384.0))
        else
            local n =   0x100 * a + b
            return   (extract(n,14,2) + (band(n,0x3FFF) / 16384.0))
        end
    end

end

function streams.skipshort(f,n)
    f[2] = f[2] + 2*(n or 1)
end

function streams.skiplong(f,n)
    f[2] = f[2] + 4*(n or 1)
end

if sio and sio.readcardinal2 then

    local readcardinal1  = sio.readcardinal1
    local readcardinal2  = sio.readcardinal2
    local readcardinal3  = sio.readcardinal3
    local readcardinal4  = sio.readcardinal4
    local readinteger1   = sio.readinteger1
    local readinteger2   = sio.readinteger2
    local readinteger3   = sio.readinteger3
    local readinteger4   = sio.readinteger4
    local readfixed2     = sio.readfixed2
    local readfixed4     = sio.readfixed4
    local read2dot14     = sio.read2dot14
    local readbytes      = sio.readbytes
    local readbytetable  = sio.readbytetable

    function streams.readcardinal1(f)
        local i = f[2]
        f[2] = i + 1
        return readcardinal1(f[1],i)
    end
    function streams.readcardinal2(f)
        local i = f[2]
        f[2] = i + 2
        return readcardinal2(f[1],i)
    end
    function streams.readcardinal3(f)
        local i = f[2]
        f[2] = i + 3
        return readcardinal3(f[1],i)
    end
    function streams.readcardinal4(f)
        local i = f[2]
        f[2] = i + 4
        return readcardinal4(f[1],i)
    end
    function streams.readinteger1(f)
        local i = f[2]
        f[2] = i + 1
        return readinteger1(f[1],i)
    end
    function streams.readinteger2(f)
        local i = f[2]
        f[2] = i + 2
        return readinteger2(f[1],i)
    end
    function streams.readinteger3(f)
        local i = f[2]
        f[2] = i + 3
        return readinteger3(f[1],i)
    end
    function streams.readinteger4(f)
        local i = f[2]
        f[2] = i + 4
        return readinteger4(f[1],i)
    end
    function streams.readfixed2(f) -- needs recent luatex
        local i = f[2]
        f[2] = i + 2
        return readfixed2(f[1],i)
    end
    function streams.readfixed4(f) -- needs recent luatex
        local i = f[2]
        f[2] = i + 4
        return readfixed4(f[1],i)
    end
    function streams.read2dot14(f)
        local i = f[2]
        f[2] = i + 2
        return read2dot14(f[1],i)
    end
    function streams.readbytes(f,n)
        local i = f[2]
        local s = f[3]
        local p = i + n
        if p > s then
            f[2] = s + 1
        else
            f[2] = p
        end
        return readbytes(f[1],i,n)
    end
    function streams.readbytetable(f,n)
        local i = f[2]
        local s = f[3]
        local p = i + n
        if p > s then
            f[2] = s + 1
        else
            f[2] = p
        end
        return readbytetable(f[1],i,n)
    end

    streams.readbyte       = streams.readcardinal1
    streams.readsignedbyte = streams.readinteger1
    streams.readcardinal   = streams.readcardinal1
    streams.readinteger    = streams.readinteger1

end

if sio and sio.readcardinaltable then

    local readcardinaltable = sio.readcardinaltable
    local readintegertable  = sio.readintegertable

    function utilities.streams.readcardinaltable(f,n,b)
        local i = f[2]
        local s = f[3]
        local p = i + n * b
        if p > s then
            f[2] = s + 1
        else
            f[2] = p
        end
        return readcardinaltable(f[1],i,n,b)
    end

    function utilities.streams.readintegertable(f,n,b)
        local i = f[2]
        local s = f[3]
        local p = i +  n * b
        if p > s then
            f[2] = s + 1
        else
            f[2] = p
        end
        return readintegertable(f[1],i,n,b)
    end

else

    local readcardinal1 = streams.readcardinal1
    local readcardinal2 = streams.readcardinal2
    local readcardinal3 = streams.readcardinal3
    local readcardinal4 = streams.readcardinal4

    function streams.readcardinaltable(f,n,b)
        local i = f[2]
        local s = f[3]
        local p = i + n * b
        if p > s then
            f[2] = s + 1
        else
            f[2] = p
        end
        local t = { }
            if b == 1 then for i=1,n do t[i] = readcardinal1(f[1],i) end
        elseif b == 2 then for i=1,n do t[i] = readcardinal2(f[1],i) end
        elseif b == 3 then for i=1,n do t[i] = readcardinal3(f[1],i) end
        elseif b == 4 then for i=1,n do t[i] = readcardinal4(f[1],i) end end
        return t
    end

    local readinteger1 = streams.readinteger1
    local readinteger2 = streams.readinteger2
    local readinteger3 = streams.readinteger3
    local readinteger4 = streams.readinteger4

    function streams.readintegertable(f,n,b)
        local i = f[2]
        local s = f[3]
        local p = i + n * b
        if p > s then
            f[2] = s + 1
        else
            f[2] = p
        end
        local t = { }
            if b == 1 then for i=1,n do t[i] = readinteger1(f[1],i) end
        elseif b == 2 then for i=1,n do t[i] = readinteger2(f[1],i) end
        elseif b == 3 then for i=1,n do t[i] = readinteger3(f[1],i) end
        elseif b == 4 then for i=1,n do t[i] = readinteger4(f[1],i) end end
        return t
    end

end

-- For practical reasons we put this here. It's less efficient but ok when we don't
-- have much access.

do

    local files = utilities.files

    if files then

        local openfile     = files.open
        local openstream   = streams.open
        local openstring   = streams.openstring

        local setmetatable = setmetatable

        function io.newreader(str,method)
            local f, m
            if method == "string" then
                f = openstring(str,true)
                m = streams
            elseif method == "stream" then
                f = openstream(str,true)
                m = streams
            else
                f = openfile(str,"rb")
                m = files
            end
            if f then
                local t = { }
                setmetatable(t, {
                    __index = function(t,k)
                        local r = m[k]
                        if k == "close" then
                            -- maybe use __toclose
                            if f then
                                m.close(f)
                                f = nil
                            end
                            return function() end
                        elseif r then
                            local v = function(_,a,b) return r(f,a,b) end
                            t[k] = v
                            return v
                        else
                            print("unknown key",k)
                        end
                    end
                } )
                return t
            end
        end

    end

end

if bit32 and not streams.tocardinal1 then

    local extract = bit32.extract
    local char    = string.char

             streams.tocardinal1           = char
    function streams.tocardinal2(n)   return char(extract( 8,8),extract( 0,8)) end
    function streams.tocardinal3(n)   return char(extract(16,8),extract( 8,8),extract(0,8)) end
    function streams.tocardinal4(n)   return char(extract(24,8),extract(16,8),extract(8,8),extract(0,8)) end

             streams.tocardinal1le         = char
    function streams.tocardinal2le(n) return char(extract(0,8),extract(8,8)) end
    function streams.tocardinal3le(n) return char(extract(0,8),extract(8,8),extract(16,8)) end
    function streams.tocardinal4le(n) return char(extract(0,8),extract(8,8),extract(16,8),extract(24,8)) end

end

if not streams.readcstring then

    local readchar = streams.readchar
    local concat   = table.concat

    function streams.readcstring(f)
        local t = { }
        while true do
            local c = readchar(f)
            if c and c ~= "\0" then
                t[#t+1] = c
            else
                return concat(t)
            end
        end
    end

end
