if not modules then modules = { } end modules ['l-os'] = {
    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"
}

-- This file deals with some operating system issues. Please don't bother me
-- with the pros and cons of operating systems as they all have their flaws
-- and benefits. Bashing one of them won't help solving problems and fixing
-- bugs faster and is a waste of time and energy.
--
-- path separators: / or \ ... we can use / everywhere
-- suffixes       : dll so exe <none> ... no big deal
-- quotes         : we can use "" in most cases
-- expansion      : unless "" are used * might give side effects
-- piping/threads : somewhat different for each os
-- locations      : specific user file locations and settings can change over time
--
-- os.type     : windows | unix (new, we already guessed os.platform)
-- os.name     : windows | msdos | linux | macosx | solaris | .. | generic (new)
-- os.platform : extended os.name with architecture

-- os.sleep() => socket.sleep()
-- math.randomseed(tonumber(string.sub(string.reverse(tostring(math.floor(socket.gettime()*10000))),1,6)))

local os = os
local date, time = os.date, os.time
local find, format, gsub, upper, gmatch = string.find, string.format, string.gsub, string.upper, string.gmatch
local concat = table.concat
local random, ceil, randomseed = math.random, math.ceil, math.randomseed
local rawget, rawset, type, getmetatable, setmetatable, tonumber, tostring = rawget, rawset, type, getmetatable, setmetatable, tonumber, tostring

-- The following code permits traversing the environment table, at least
-- in luatex. Internally all environment names are uppercase.

-- The randomseed in Lua is not that random, although this depends on the operating system as well
-- as the binary (Luatex is normally okay). But to be sure we set the seed anyway.

math.initialseed = tonumber(string.sub(string.reverse(tostring(ceil(socket and socket.gettime()*10000 or time()))),1,6))

randomseed(math.initialseed)

if not os.__getenv__ then

    os.__getenv__ = os.getenv
    os.__setenv__ = os.setenv

    if os.env then

        local osgetenv  = os.getenv
        local ossetenv  = os.setenv
        local osenv     = os.env      local _ = osenv.PATH -- initialize the table

        function os.setenv(k,v)
            if v == nil then
                v = ""
            end
            local K = upper(k)
            osenv[K] = v
            if type(v) == "table" then
                v = concat(v,";") -- path
            end
            ossetenv(K,v)
        end

        function os.getenv(k)
            local K = upper(k)
            local v = osenv[K] or osenv[k] or osgetenv(K) or osgetenv(k)
            if v == "" then
                return nil
            else
                return v
            end
        end

    else

        local ossetenv  = os.setenv
        local osgetenv  = os.getenv
        local osenv     = { }

        function os.setenv(k,v)
            if v == nil then
                v = ""
            end
            local K = upper(k)
            osenv[K] = v
        end

        function os.getenv(k)
            local K = upper(k)
            local v = osenv[K] or osgetenv(K) or osgetenv(k)
            if v == "" then
                return nil
            else
                return v
            end
        end

        local function __index(t,k)
            return os.getenv(k)
        end
        local function __newindex(t,k,v)
            os.setenv(k,v)
        end

        os.env = { }

        setmetatable(os.env, { __index = __index, __newindex = __newindex } )

    end

end

-- end of environment hack

local execute = os.execute
local iopopen = io.popen

function os.resultof(command)
    local handle = iopopen(command,"r") -- already has flush
    if handle then
        local result = handle:read("*all") or ""
        handle:close()
        return result
    else
        return ""
    end
end

if not io.fileseparator then
    if find(os.getenv("PATH"),";",1,true) then
        io.fileseparator, io.pathseparator, os.type = "\\", ";", os.type or "mswin"
    else
        io.fileseparator, io.pathseparator, os.type = "/" , ":", os.type or "unix"
    end
end

os.type = os.type or (io.pathseparator == ";"       and "windows") or "unix"
os.name = os.name or (os.type          == "windows" and "mswin"  ) or "linux"

if os.type == "windows" then
    os.libsuffix, os.binsuffix, os.binsuffixes = 'dll', 'exe', { 'exe', 'cmd', 'bat' }
else
    os.libsuffix, os.binsuffix, os.binsuffixes = 'so', '', { '' }
end

local launchers = {
    windows = "start %s",
    macosx  = "open %s",
    unix    = "$BROWSER %s &> /dev/null &",
}

function os.launch(str)
    execute(format(launchers[os.name] or launchers.unix,str))
end

if not os.times then -- ?
    -- utime  = user time
    -- stime  = system time
    -- cutime = children user time
    -- cstime = children system time
    function os.times()
        return {
            utime  = os.gettimeofday(), -- user
            stime  = 0,                 -- system
            cutime = 0,                 -- children user
            cstime = 0,                 -- children system
        }
    end
end


local gettimeofday = os.gettimeofday or os.clock
os.gettimeofday    = gettimeofday

local startuptime = gettimeofday()

function os.runtime()
    return gettimeofday() - startuptime
end

-- print(os.gettimeofday()-os.time())
-- os.sleep(1.234)
-- print (">>",os.runtime())
-- print(os.date("%H:%M:%S",os.gettimeofday()))
-- print(os.date("%H:%M:%S",os.time()))

-- no need for function anymore as we have more clever code and helpers now
-- this metatable trickery might as well disappear

local resolvers = os.resolvers or { }
os.resolvers    = resolvers

setmetatable(os, { __index = function(t,k)
    local r = resolvers[k]
    return r and r(t,k) or nil -- no memoize
end })

-- we can use HOSTTYPE on some platforms

local name, platform = os.name or "linux", os.getenv("MTX_PLATFORM") or ""

local function guess()
    local architecture = os.resultof("uname -m") or ""
    if architecture ~= "" then
        return architecture
    end
    architecture = os.getenv("HOSTTYPE") or ""
    if architecture ~= "" then
        return architecture
    end
    return os.resultof("echo $HOSTTYPE") or ""
end

-- os.bits = 32 | 64

if platform ~= "" then

    os.platform = platform

elseif os.type == "windows" then

    -- we could set the variable directly, no function needed here

    -- PROCESSOR_ARCHITECTURE : binary platform
    -- PROCESSOR_ARCHITEW6432 : OS platform

    function resolvers.platform(t,k)
        local platform, architecture = "", os.getenv("PROCESSOR_ARCHITECTURE") or ""
        if find(architecture,"AMD64",1,true) then
         -- platform = "mswin-64"
            platform = "win64"
        else
            platform = "mswin"
        end
        os.setenv("MTX_PLATFORM",platform)
        os.platform = platform
        return platform
    end

elseif name == "linux" then

    function resolvers.platform(t,k)
        -- we sometimes have HOSTTYPE set so let's check that first
        local platform, architecture = "", os.getenv("HOSTTYPE") or os.resultof("uname -m") or ""
        if find(architecture,"x86_64",1,true) then
            platform = "linux-64"
        elseif find(architecture,"ppc",1,true) then
            platform = "linux-ppc"
        else
            platform = "linux"
        end
        os.setenv("MTX_PLATFORM",platform)
        os.platform = platform
        return platform
    end

elseif name == "macosx" then

    --[[
        Identifying the architecture of OSX is quite a mess and this
        is the best we can come up with. For some reason $HOSTTYPE is
        a kind of pseudo environment variable, not known to the current
        environment. And yes, uname cannot be trusted either, so there
        is a change that you end up with a 32 bit run on a 64 bit system.
        Also, some proper 64 bit intel macs are too cheap (low-end) and
        therefore not permitted to run the 64 bit kernel.
      ]]--

    function resolvers.platform(t,k)
     -- local platform, architecture = "", os.getenv("HOSTTYPE") or ""
     -- if architecture == "" then
     --     architecture = os.resultof("echo $HOSTTYPE") or ""
     -- end
        local platform, architecture = "", os.resultof("echo $HOSTTYPE") or ""
        if architecture == "" then
         -- print("\nI have no clue what kind of OSX you're running so let's assume an 32 bit intel.\n")
            platform = "osx-intel"
        elseif find(architecture,"i386",1,true) then
            platform = "osx-intel"
        elseif find(architecture,"x86_64",1,true) then
            platform = "osx-64"
        else
            platform = "osx-ppc"
        end
        os.setenv("MTX_PLATFORM",platform)
        os.platform = platform
        return platform
    end

elseif name == "sunos" then

    function resolvers.platform(t,k)
        local platform, architecture = "", os.resultof("uname -m") or ""
        if find(architecture,"sparc",1,true) then
            platform = "solaris-sparc"
        else -- if architecture == 'i86pc'
            platform = "solaris-intel"
        end
        os.setenv("MTX_PLATFORM",platform)
        os.platform = platform
        return platform
    end

elseif name == "freebsd" then

    function resolvers.platform(t,k)
        local platform, architecture = "", os.resultof("uname -m") or ""
        if find(architecture,"amd64",1,true) then
            platform = "freebsd-amd64"
        else
            platform = "freebsd"
        end
        os.setenv("MTX_PLATFORM",platform)
        os.platform = platform
        return platform
    end

elseif name == "kfreebsd" then

    function resolvers.platform(t,k)
        -- we sometimes have HOSTTYPE set so let's check that first
        local platform, architecture = "", os.getenv("HOSTTYPE") or os.resultof("uname -m") or ""
        if find(architecture,"x86_64",1,true) then
            platform = "kfreebsd-amd64"
        else
            platform = "kfreebsd-i386"
        end
        os.setenv("MTX_PLATFORM",platform)
        os.platform = platform
        return platform
    end

else

    -- platform = "linux"
    -- os.setenv("MTX_PLATFORM",platform)
    -- os.platform = platform

    function resolvers.platform(t,k)
        local platform = "linux"
        os.setenv("MTX_PLATFORM",platform)
        os.platform = platform
        return platform
    end

end

os.newline = name == "windows" and "\013\010" or "\010" -- crlf or lf

function resolvers.bits(t,k)
    local bits = find(os.platform,"64",1,true) and 64 or 32
    os.bits = bits
    return bits
end

-- beware, we set the randomseed

-- from wikipedia: Version 4 UUIDs use a scheme relying only on random numbers. This algorithm sets the
-- version number as well as two reserved bits. All other bits are set using a random or pseudorandom
-- data source. Version 4 UUIDs have the form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx with hexadecimal
-- digits x and hexadecimal digits 8, 9, A, or B for y. e.g. f47ac10b-58cc-4372-a567-0e02b2c3d479.
--
-- as we don't call this function too often there is not so much risk on repetition

local t = { 8, 9, "a", "b" }

function os.uuid()
    return format("%04x%04x-4%03x-%s%03x-%04x-%04x%04x%04x",
        random(0xFFFF),random(0xFFFF),
        random(0x0FFF),
        t[ceil(random(4))] or 8,random(0x0FFF),
        random(0xFFFF),
        random(0xFFFF),random(0xFFFF),random(0xFFFF)
    )
end

local d

function os.timezone(delta)
    d = d or tonumber(tonumber(date("%H")-date("!%H")))
    if delta then
        if d > 0 then
            return format("+%02i:00",d)
        else
            return format("-%02i:00",-d)
        end
    else
        return 1
    end
end

local timeformat = format("%%s%s",os.timezone(true))
local dateformat = "!%Y-%m-%d %H:%M:%S"
local lasttime   = nil
local lastdate   = nil

function os.fulltime(t,default)
    t = t and tonumber(t) or 0
    if t > 0 then
        -- valid time
    elseif default then
        return default
    else
        t = time()
    end
    if t ~= lasttime then
        lasttime = t
        lastdate = format(timeformat,date(dateformat))
    end
    return lastdate
end

local dateformat = "%Y-%m-%d %H:%M:%S"
local lasttime   = nil
local lastdate   = nil

function os.localtime(t,default)
    t = t and tonumber(t) or 0
    if t > 0 then
        -- valid time
    elseif default then
        return default
    else
        t = time()
    end
    if t ~= lasttime then
        lasttime = t
        lastdate = date(dateformat,t)
    end
    return lastdate
end

function os.converttime(t,default)
    local t = tonumber(t)
    if t and t > 0 then
        return date(dateformat,t)
    else
        return default or "-"
    end
end

local memory = { }

local function which(filename)
    local fullname = memory[filename]
    if fullname == nil then
        local suffix = file.suffix(filename)
        local suffixes = suffix == "" and os.binsuffixes or { suffix }
        for directory in gmatch(os.getenv("PATH"),"[^" .. io.pathseparator .."]+") do
            local df = file.join(directory,filename)
            for i=1,#suffixes do
                local dfs = file.addsuffix(df,suffixes[i])
                if io.exists(dfs) then
                    fullname = dfs
                    break
                end
            end
        end
        if not fullname then
            fullname = false
        end
        memory[filename] = fullname
    end
    return fullname
end

os.which = which
os.where = which

function os.today()
    return date("!*t") -- table with values
end

function os.now()
    return date("!%Y-%m-%d %H:%M:%S") -- 2011-12-04 14:59:12
end

-- if not os.sleep and socket then
--     os.sleep = socket.sleep
-- end

if not os.sleep then
    local socket = socket
    function os.sleep(n)
        if not socket then
            -- so we delay ... if os.sleep is really needed then one should also
            -- be sure that socket can be found
            socket = require("socket")
        end
        socket.sleep(n)
    end
end

-- print(os.which("inkscape.exe"))
-- print(os.which("inkscape"))
-- print(os.which("gs.exe"))
-- print(os.which("ps2pdf"))

-- These are moved from core-con.lua (as I needed them elsewhere).

local function isleapyear(year)
    return (year % 400 == 0) or ((year % 100 ~= 0) and (year % 4 == 0))
end

os.isleapyear = isleapyear

-- nicer:
--
-- local days = {
--     [false] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
--     [true]  = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
-- }
--
-- local function nofdays(year,month)
--     return days[isleapyear(year)][month]
--     return month == 2 and isleapyear(year) and 29 or days[month]
-- end
--
-- more efficient:

local days = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

local function nofdays(year,month)
    if not month then
        return isleapyear(year) and 365 or 364
    else
        return month == 2 and isleapyear(year) and 29 or days[month]
    end
end

os.nofdays = nofdays

function os.weekday(day,month,year)
    return date("%w",time { year = year, month = month, day = day }) + 1
end

function os.validdate(year,month,day)
    -- we assume that all three values are set
    -- year is always ok, even if lua has a 1970 time limit
    if month < 1 then
        month = 1
    elseif month > 12 then
        month = 12
    end
    if day < 1 then
        day = 1
    else
        local max = nofdays(year,month)
        if day > max then
            day = max
        end
    end
    return year, month, day
end
