if not modules then modules = { } end modules ['util-tar'] = {
    version   = 1.001,
    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"
}

local type, tonumber = type, tonumber
local gsub, escapedpattern = string.gsub, string.escapedpattern
local nameonly, dirname, makedirs = file.nameonly, file.dirname, dir.makedirs
local savedata = io.savedata
local newreader = io.newreader

local report = logs.reporter("tar")

local types = {
    ["0"]  = "file",
    ["\0"] = "regular",
    ["1"]  = "link",
    ["2"]  = "symbolic",     -- reserved
    ["3"]  = "character",
    ["4"]  = "block",
    ["5"]  = "directory",
    ["6"]  = "fifo",
    ["7"]  = "continuation", -- reserved
    ["x"]  = "extended",     -- header
}

local function asstring(str)
    return str and gsub(str,"[\0 ]+$","") or nil
end

local function asnumber(str)
    str = gsub(str,"\0$","")
    return tonumber(str,8)
end

local function opentar(whatever,filename)
    local f = newreader(filename,whatever)
    if f then
        f.metadata = {
            nofpaths = 0,
            noffiles = 0,
            noflinks = 0,
            nofbytes = 0,
        }
        return f
    end
end

local function readheader(t)
    -- checksum
    local p = t:getposition()
    local h = t:readbytetable(512)
    t:setposition(p)
    for i=149,156 do -- nasty, one less
        h[i] = 0
    end
    local c = 256
    for i=1,512 do
        c = c + h[i]
    end
    --
    local header = {
        name     = asstring(t:readstring(100)),    --   0
        mode     = asnumber(t:readstring(  8)), -- 100 -- when we write: 0775 octal
        uid      = asnumber(t:readstring(  8)), -- 108
        gid      = asnumber(t:readstring(  8)), -- 116
        size     = asnumber(t:readstring( 12)), -- 124
        mtime    = asnumber(t:readstring( 12)), -- 136
        checksum = asnumber(t:readstring(  6)), -- 148 -- actually 6 with space and \0
        dummy    =          t:skip        (2) ,
        typeflag =          t:readstring(  1) , -- 156
        linkname = asstring(t:readstring(100)), -- 157
     -- magic    = asstring(t:readstring(  6)), -- 257 -- ustar\0
     -- version  =                         2    -- 263
     -- uname    =                        32    -- 265
     -- gname    =                        32    -- 297
     -- devmajor =                         8    -- 329
     -- devminor =                         8    -- 337
     -- prefix   =                       155    -- 345
        padding  =          t:skip      (255) , -- 500
    }
    local typeflag = header.typeflag
    if typeflag then
        header.filetype = types[typeflag]
        if c == header.checksum then
            return header
        end
    end
end

local readers = {

    directory = function(t,h)
        local metadata = t.metadata
        local filename = h.name
        if metadata.verbose then
            report("%8s   %s","",filename)
        end
        metadata.nofpaths = metadata.nofpaths + 1
        return true
    end,

    file = function(t,h)
        local metadata = t.metadata
        local filename = h.name
        local filesize = h.size
        local pathname = dirname(filename)
        if metadata.verbose then
            report("% 8i : %s",filesize,filename)
        end
        if makedirs(pathname) then
            savedata(filename,t:readstring(filesize))
        else
            t.skip(filesize)
        end
        local position = t:getposition()
        local target   = position + (512 - position % 512) % 512
        t:setposition(target)
        metadata.noffiles = metadata.noffiles + 1
        metadata.nofbytes = metadata.nofbytes + filesize
        return true
    end,

    symbolic = function(t,h)
        local metadata = t.metadata
        local filename = h.name
        local linkname = h.linkname
        if metadata.verbose then
            report("%8s   %s => %s","",linkname,filename)
        end
        metadata.noflinks = metadata.noflinks + 1
        return true
    end,

}

local skippers = {

    directory = function(t,h)
        return true
    end,

    file = function(t,h)
        local filesize   = h.size
        local fileoffset = t:getposition()
        local position   = filesize + fileoffset
        local target     = position + (512 - position % 512) % 512
        t:setposition(target)
        return fileoffset
    end,

    symbolic = function(t,h)
        return true
    end,

}

local writers = {
    -- nothing here (yet)
}

local function saveheader(t,h)
    local filetype = h.filetype
    local reader   = readers[filetype]
    if reader then
        return filetype, reader(t,h)
    else
        report("no reader for %s",filetype)
    end
end

local function skipheader(t,h)
    local filetype = h.filetype
    local skipper  = skippers[filetype]
    if skipper then
        return filetype, skipper(t,h)
    else
        report("no skipper for %s",filetype)
    end
end

local function unpacktar(whatever,filename,verbose)
    local t = opentar(whatever,filename)
    if t then
        local metadata = t.metadata
        statistics.starttiming(metadata)
        if verbose then
            if whatever == "string" then
                report("unpacking: %i bytes",#filename)
            else
                report("unpacking: %s",filename)
            end
            report("")
            metadata.verbose = verbose
        end
        while true do
            local h = readheader(t)
            if not h then
                break
            else
                local filetype, saved = saveheader(t,h)
                if not saved then
                    break
                end
            end
        end
        statistics.stoptiming(metadata)
        metadata.runtime = statistics.elapsed(metadata)
        if verbose then
            report("")
            report("number of paths : %i",metadata.nofpaths)
            report("number of files : %i",metadata.noffiles)
            report("number of links : %i",metadata.noflinks)
            report("number of bytes : %i",metadata.nofbytes)
            report("")
            report("runtime needed  : %s",statistics.elapsedseconds(metadata))
            report("")
        end
        t.close()
        return metadata
    end
end

local function listtar(whatever,filename,onlyfiles)
    local t = opentar(whatever,filename)
    if t then
        local list, n = { }, 0
        while true do
            local h = readheader(t)
            if not h then
                break
            else
                local filetype, offset = skipheader(t,h)
                if not offset then
                    break
                elseif filetype == "file" then
                    n = n + 1 ; list[n] = { filetype, h.name, h.size }
                elseif filetype == "link" then
                    n = n + 1 ; list[n] = { filetype, h.name, h.linkfile }
                elseif not onlyfiles then
                    n = n + 1 ; list[n] = { filetype, h.name }
                end
            end
        end
        t.close()
        -- can be an option
        table.sort(list,function(a,b) return a[2] < b[2] end)
        return list
    end
end

local function hashtar(whatever,filename,strip)
    local t = opentar(whatever,filename)
    if t then
        local list = { }
        if strip then
            strip = "^" .. escapedpattern(nameonly(nameonly(strip))) .. "/"
        end
        while true do
            local h = readheader(t)
            if not h then
                break
            else
                local filetype, offset = skipheader(t,h)
                if not offset then
                    break
                else
                    local name = h.name
                    if strip then
                        name = gsub(name,strip,"")
                    end
                    if filetype == "file" then
                        list[name] = { offset, h.size }
                    elseif filetype == "link" then
                        list[name] = h.linkname
                    end
                end
            end
        end
        t.close()
        return list
    end
end

-- weak table ?

local function fetchtar(whatever,archive,filename,list)
    if not list then
        list = hashtar(whatever,archive)
    end
    if list then
        local what = list[filename]
        if type(what) == "string" then
            what = list[what] -- a link
        end
        if what then
            local t = opentar(whatever,archive)
            if t then
                t:setposition(what[1])
                return t:readstring(what[2])
            end
        end
    end
end

local function packtar(whatever,filename,verbose)
    report("packing will be implemented when we need it")
end

local tar = {
    files = {
        unpack = function(...) return unpacktar("file",  ...) end,
        pack   = function(...) return packtar  ("file",  ...) end,
        list   = function(...) return listtar  ("file",  ...) end,
        hash   = function(...) return hashtar  ("file",  ...) end,
        fetch  = function(...) return fetchtar ("file",  ...) end,
    },
    strings = {
        unpack = function(...) return unpacktar("string",...) end,
        pack   = function(...) return packtar  ("string",...) end,
        list   = function(...) return listtar  ("string",...) end,
        hash   = function(...) return hashtar  ("string",...) end,
        fetch  = function(...) return fetchtar ("string",...) end,
    },
    streams = {
        unpack = function(...) return unpacktar("stream",...) end,
        pack   = function(...) return packtar  ("stream",...) end,
        list   = function(...) return listtar  ("stream",...) end,
        hash   = function(...) return hashtar  ("stream",...) end,
        fetch  = function(...) return fetchtar ("stream",...) end,
    },
}

utilities.tar = tar

-- tar.files  .unpack("e:/luatex/luametatex-source.tar",true)
-- tar.streams.unpack("e:/luatex/luametatex-source.tar",true)
-- tar.strings.unpack(io.loaddata("e:/luatex/luametatex-source.tar"),true)

-- inspect(tar.files  .unpack("e:/luatex/luametatex-source.tar"))
-- inspect(tar.streams.unpack("e:/luatex/luametatex-source.tar"))
-- inspect(tar.strings.unpack(io.loaddata("e:/luatex/luametatex-source.tar")))

-- inspect(tar.files  .list("e:/luatex/luametatex-source.tar",true))
-- inspect(tar.streams.list("e:/luatex/luametatex-source.tar",true))
-- inspect(tar.strings.list(io.loaddata("e:/luatex/luametatex-source.tar"),true))

-- local c = os.clock()
-- local l = tar.files.hash("e:/luatex/luametatex-source.tar")
-- for i=1,500 do
--     local s = tar.files.fetch("e:/luatex/luametatex-source.tar", "luametatex-source/source/tex/texbuildpage.c", l)
--     local s = tar.files.fetch( "e:/luatex/luametatex-source.tar","luametatex-source/source/lua/lmtlibrary.c",   l)
-- end
-- print(os.clock()-c)

return tar
